Kostal API auth and call
This flow shows how to auth at a KOSTAL Api!
Change your IP (openAPI-URL) and Password (edit "Prepare Auth Finish Payload")
Hope this helps someone...
[{"id":"inject_start_auth","type":"inject","z":"f05a7cbda577023d","name":"Start Auth Request","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":310,"y":140,"wires":[["prepare_auth_payload"]]},{"id":"prepare_auth_payload","type":"function","z":"f05a7cbda577023d","name":"Prepare Auth Start Payload","func":"// Importiere crypto-js aus dem globalen Kontext\nconst crypto = global.get('cryptojs');\n\n// Erstelle ein zufälliges 12-Byte nonce und wandle es in Base64 um\nconst nonce = Buffer.from(crypto.lib.WordArray.random(12).toString(crypto.enc.Base64)).toString('base64');\n\n// Bereite den Payload für den /auth/start Request vor\nmsg.payload = {\n payload: {\n username: \"user\",\n nonce: nonce\n }\n};\n\n// Speichere nonce temporär, um es später zu nutzen\nglobal.set('clientNonce', nonce);\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":560,"y":320,"wires":[["13afbbee146e4b5b"]]},{"id":"process_auth_response","type":"function","z":"f05a7cbda577023d","name":"Process Auth Start Response","func":"global.set('transactionId', msg.payload.transactionId);\nglobal.set('serverNonce', msg.payload.nonce);\nglobal.set('salt', msg.payload.salt);\nglobal.set('rounds', msg.payload.rounds);\nreturn msg;","outputs":1,"noerr":0,"x":1140,"y":320,"wires":[["prepare_auth_finish_payload"]]},{"id":"prepare_auth_finish_payload","type":"function","z":"f05a7cbda577023d","name":"Prepare Auth Finish Payload","func":"// Hole die notwendigen Module aus dem globalen Kontext\nconst crypto = global.get('cryptojs'); \nconst nodeCrypto = global.get('nodeCrypto');\n\n// Werte aus globalen Variablen abrufen\nconst clientNonce = global.get('clientNonce');\nconst serverNonce = global.get('serverNonce');\nconst transactionId = global.get('transactionId');\nconst salt = global.get('salt');\nconst rounds = global.get('rounds');\n\n// Passwort (muss ersetzt werden durch das echte Passwort)\nconst password = \"123456\"; // Beispiel-Passwort (ersetzen!)\n\n\n// Berechnung des PBKDF2 Hashes mit den empfangenen Werten\nconst saltBytes = Buffer.from(salt, 'base64'); // Salt dekodieren\nconst key = nodeCrypto.pbkdf2Sync(password, saltBytes, rounds, 32, 'sha256'); // PBKDF2 mit SHA-256 und 32-Byte Key\n\n// Berechnung des Client Key und Server Key\nconst clientKey = nodeCrypto.createHmac('sha256', key).update(\"Client Key\").digest();\nconst serverKey = nodeCrypto.createHmac('sha256', key).update(\"Server Key\").digest();\n\n\n// Berechnung des Stored Key (Hash des Client Keys)\nconst storedKey = nodeCrypto.createHash('sha256').update(clientKey).digest();\n\n\n// Erstelle den Auth-String basierend auf den erhaltenen Werten\nconst authMessage = `n=user,r=${clientNonce},r=${serverNonce},s=${salt},i=${rounds},c=biws,r=${serverNonce}`;\n\n\n// Berechne Client Signature (Stored Key + Auth-String)\nconst clientSignature = nodeCrypto.createHmac('sha256', storedKey).update(authMessage).digest();\n\n\n// Berechnung des `proof` durch XOR des Client Keys und Client Signature\nconst proofBytes = Buffer.alloc(clientKey.length);\nfor (let i = 0; i < clientKey.length; i++) {\n proofBytes[i] = clientKey[i] ^ clientSignature[i];\n}\nconst proof = proofBytes.toString('base64');\n\n\n// Berechnung des Protocol Keys (Session Key) basierend auf dem Stored Key, Auth-Message und Client Key\nconst sessionKeyHmac = nodeCrypto.createHmac('sha256', storedKey).update(\"Session Key\");\nsessionKeyHmac.update(authMessage); // Füge `authMessage` zur Berechnung hinzu\nsessionKeyHmac.update(clientKey); // Füge `clientKey` zur Berechnung hinzu\nconst protocolKey = sessionKeyHmac.digest();\n\n\n// Speichere den Protocol Key für den nächsten Schritt (auth/create_session)\nglobal.set('protocolKey', protocolKey);\n\n// Erstelle den Payload für den /auth/finish Request\nmsg.payload = {\n payload: {\n transactionId: transactionId,\n proof: proof\n }\n};\n\n// Gebe die berechneten Werte in Debug-Nodes aus\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":560,"y":380,"wires":[["6649fbbd23f5d4db"]]},{"id":"process_auth_finish_response","type":"function","z":"f05a7cbda577023d","name":"Process Auth Finish Response","func":"// Speichere den token und signature in globalen Variablen\nglobal.set('sessionToken', msg.payload.token);\nglobal.set('serverSignature', msg.payload.signature);\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":1150,"y":380,"wires":[["prepare_create_session_payload"]]},{"id":"13afbbee146e4b5b","type":"openApi-red","z":"f05a7cbda577023d","name":"","configUrlNode":"1f08ba83891ebaa9","apiTag":"auth","keepAuth":false,"operationData":{"id":"post_auth_start","hasOperationId":true,"path":"/auth/start","method":"post"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"payload body":{"name":"payload","isActive":true,"required":true,"type":"editor:payload","collapsed":false,"parameters":{"username":{"name":"username","isActive":true,"required":true,"type":"msg","value":"payload.payload.username"},"nonce":{"name":"nonce","isActive":true,"required":true,"type":"msg","value":"payload.payload.nonce"}},"in":"body","selectedSchema":{"name":"payload"}}},"requestContentType":"application/json","responseContentType":"application/json","outputs":4,"responseOutputLabels":[{"code":"200","text":"Success"},{"code":"400","text":"Invalid user"},{"code":"403","text":"User is locked"},{"code":"503","text":"Internal communication error"}],"responseAsPayload":false,"debugMode":false,"headers":[],"_version":"","x":820,"y":300,"wires":[["process_auth_response"],[],[],[]]},{"id":"6649fbbd23f5d4db","type":"openApi-red","z":"f05a7cbda577023d","name":"","configUrlNode":"1f08ba83891ebaa9","apiTag":"auth","keepAuth":false,"operationData":{"id":"post_auth_finish","hasOperationId":true,"path":"/auth/finish","method":"post"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"payload body":{"name":"payload","isActive":true,"required":true,"type":"editor:payload","collapsed":false,"parameters":{"transactionId":{"name":"transactionId","isActive":true,"required":true,"type":"msg","value":"payload.payload.transactionId"},"proof":{"name":"proof","isActive":true,"required":true,"type":"msg","value":"payload.payload.proof"}},"in":"body","selectedSchema":{"name":"payload"}}},"requestContentType":"application/json","responseContentType":"application/json","outputs":2,"responseOutputLabels":[{"code":"200","text":"Success"},{"code":"400"}],"responseAsPayload":false,"debugMode":false,"headers":[],"_version":"","x":820,"y":380,"wires":[["process_auth_finish_response"],[]]},{"id":"prepare_create_session_payload","type":"function","z":"f05a7cbda577023d","name":"Prepare Auth Create Session Payload","func":"// Hole die notwendigen Module aus dem globalen Kontext\nconst nodeCrypto = global.get('nodeCrypto');\n\n// Werte aus globalen Variablen abrufen\nconst transactionId = global.get('transactionId');\nconst sessionToken = global.get('sessionToken');\nconst protocolKey = global.get('protocolKey');\n\n// Prüfe, ob der protocolKey verfügbar ist\nif (!protocolKey) {\n node.error(\"Protocol Key ist nicht definiert. Stelle sicher, dass dieser korrekt berechnet wurde.\", msg);\n return null;\n}\n\n// Generiere ein zufälliges IV (Initialisierungsvektor) mit 16 Byte\nconst iv = nodeCrypto.randomBytes(16);\n\n\ntry {\n // Verschlüssele den `sessionToken` mit `aes-256-gcm`\n const cipher = nodeCrypto.createCipheriv('aes-256-gcm', protocolKey, iv);\n const encrypted = Buffer.concat([cipher.update(sessionToken, 'utf8'), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n // Erstelle den Payload für den /auth/create_session Request mit der erwarteten Struktur\n msg.payload = {\n payload: {\n transactionId: transactionId,\n iv: iv.toString('base64'),\n tag: authTag.toString('base64'),\n payload: encrypted.toString('base64') // Verschlüsseltes Token als Base64-String\n }\n };\n\n // Debug-Ausgaben zur Überprüfung der verschlüsselten Werte\n\n\n} catch (err) {\n node.error(`Fehler bei der Verschlüsselung: ${err.message}`, msg);\n return null;\n}\n\nreturn msg;\n","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":440,"wires":[["5172c965ff1b6ef5"]]},{"id":"process_create_session_response","type":"function","z":"f05a7cbda577023d","name":"Process Create Session Response","func":"// Speichere die sessionId in einer globalen Variablen\nglobal.set('sessionId', msg.payload.sessionId);\n\nmsg.headers = {\n \"Authorization\": \"Session \" + global.get('sessionId') // Nutze die global gespeicherte sessionId\n};\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":540,"y":500,"wires":[["811f54e88ede26d8"]]},{"id":"5172c965ff1b6ef5","type":"openApi-red","z":"f05a7cbda577023d","name":"","configUrlNode":"1f08ba83891ebaa9","apiTag":"auth","keepAuth":false,"operationData":{"id":"post_auth_create_session","hasOperationId":true,"path":"/auth/create_session","method":"post"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{"payload body":{"name":"payload","isActive":true,"required":true,"type":"editor:payload","collapsed":false,"parameters":{"transactionId":{"name":"transactionId","isActive":true,"required":true,"type":"msg","value":"payload.payload.transactionId"},"iv":{"name":"iv","isActive":true,"required":true,"type":"msg","value":"payload.payload.iv"},"tag":{"name":"tag","isActive":true,"required":true,"type":"msg","value":"payload.payload.tag"},"payload":{"name":"payload","isActive":true,"required":true,"type":"msg","value":"payload.payload.payload"}},"in":"body"}},"requestContentType":"application/json","responseContentType":"application/json","outputs":2,"responseOutputLabels":[{"code":"200","text":"Success"},{"code":"400","text":"Authentication failed"}],"responseAsPayload":false,"debugMode":false,"headers":[],"_version":"","x":850,"y":440,"wires":[["process_create_session_response"],[]]},{"id":"811f54e88ede26d8","type":"openApi-red","z":"f05a7cbda577023d","name":"","configUrlNode":"1f08ba83891ebaa9","apiTag":"modules","keepAuth":false,"operationData":{"id":"get_module_list","hasOperationId":true,"path":"/modules","method":"get"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{},"requestContentType":"application/json","responseContentType":"application/json","outputs":2,"responseOutputLabels":[{"code":"200","text":"Success"},{"code":"503","text":"Internal communication error"}],"responseAsPayload":false,"debugMode":false,"headers":[{"key":"Authorization","valueType":"msg","value":"headers"}],"_version":"","x":820,"y":500,"wires":[["695343e1e804da32","2acfcfe1c226244c"],[]]},{"id":"695343e1e804da32","type":"debug","z":"f05a7cbda577023d","name":"API Response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1280,"y":540,"wires":[]},{"id":"1175fd15e4f8e00d","type":"openApi-red","z":"f05a7cbda577023d","name":"","configUrlNode":"1f08ba83891ebaa9","apiTag":"auth","keepAuth":false,"operationData":{"id":"post_logout","hasOperationId":true,"path":"/auth/logout","method":"post"},"outputStyle":"each response","errorHandling":"throw exception","internalErrors":{"text":"","source":""},"parameters":{},"requestContentType":"application/json","responseContentType":"application/json","outputs":1,"responseOutputLabels":[{"code":"200","text":"Success"}],"responseAsPayload":false,"debugMode":false,"headers":[],"_version":"","x":810,"y":560,"wires":[[]]},{"id":"2acfcfe1c226244c","type":"function","z":"f05a7cbda577023d","name":"Session Header","func":"msg.headers = {\n \"Authorization\": \"Session \" + global.get('sessionId') // Nutze die global gespeicherte sessionId\n};\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":560,"wires":[["1175fd15e4f8e00d"]]},{"id":"1f08ba83891ebaa9","type":"openApi-red-url","name":"","url":"http://192.168.1.150/api/v1/swagger.json","urlType":"str","server":"http://192.168.1.150/api/v1","serverType":"custom","devMode":false,"headers":[],"_version":"2.1.0"}]