diff --git a/dictation_server/.env b/dictation_server/.env index d9f662d..1fd4c98 100644 --- a/dictation_server/.env +++ b/dictation_server/.env @@ -6,5 +6,8 @@ DB_ROOT_PASS=omdsdbpass DB_USERNAME=omdsdbuser DB_PASSWORD=omdsdbpass NO_COLOR=TRUE +ACCESS_TOKEN_LIFETIME_WEB=1600 +REFRESH_TOKEN_LIFETIME_WEB=86400 +REFRESH_TOKEN_LIFETIME_DEFAULT=2592000 TENANT_NAME=adb2codmsdev SIGNIN_FLOW_NAME=b2c_1_signin_dev \ No newline at end of file diff --git a/dictation_server/.env.local.example b/dictation_server/.env.local.example index 357e702..6990f4e 100644 --- a/dictation_server/.env.local.example +++ b/dictation_server/.env.local.example @@ -1,3 +1,7 @@ STAGE=local CORS=TRUE PORT=8081 +AZURE_TENANT_ID=xxxxxxxx +AZURE_CLIENT_ID=xxxxxxxx +AZURE_CLIENT_SECRET=xxxxxxxx +KEY_VAULT_NAME=kv-odms-secret-dev \ No newline at end of file diff --git a/dictation_server/package-lock.json b/dictation_server/package-lock.json index dfe438d..d389b38 100644 --- a/dictation_server/package-lock.json +++ b/dictation_server/package-lock.json @@ -9,6 +9,9 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { + "@azure/identity": "^3.1.3", + "@azure/keyvault-secrets": "^4.6.0", + "@nestjs/axios": "^0.1.0", "@nestjs/common": "^8.0.0", "@nestjs/config": "^2.2.0", "@nestjs/core": "^8.0.0", @@ -285,6 +288,251 @@ "openapi-types": ">=7" } }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", + "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.2.tgz", + "integrity": "sha512-ye5554gnVnXdfZ64hptUtETgacXoRWxYv1JF5MctoAzTSH5dXhDPZd9gOjDPyWMcLIk58pnP5+p5vGX6PYn1ag==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "dependencies": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.1.tgz", + "integrity": "sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz", + "integrity": "sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", + "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/identity": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.1.3.tgz", + "integrity": "sha512-y0jFjSfHsVPwXSwi3KaSPtOZtJZqhiqAhWUXfFYBUd/+twUBovZRXspBwLrF5rJe0r5NyvmScpQjL+TYDTQVvw==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.32.2", + "@azure/msal-common": "^9.0.2", + "@azure/msal-node": "^1.14.6", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@azure/identity/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@azure/identity/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@azure/keyvault-secrets": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.6.0.tgz", + "integrity": "sha512-MDqsyODCGC2srqLKmO6MFw9WdgLrbPsfCNxgbekHXEd6XKM6KKyBlup5joj96EmdfZnXDFriecAIpj0Dtu81RQ==", + "dependencies": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", + "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/msal-browser": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.33.0.tgz", + "integrity": "sha512-c7CVh1tfUfxiWkEIhoIb11hL4PGo4hz0M+gMy34ATagAKdLK7qyEu/5AXJWAf5lz5eE+vQhm7+LKiuETrcXXGw==", + "dependencies": { + "@azure/msal-common": "^10.0.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-browser/node_modules/@azure/msal-common": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-10.0.0.tgz", + "integrity": "sha512-/LghpT93jsZLy55QzTsRZWMx6R1Mjc1Aktwps8sKSGE3WbrGwbSsh2uhDlpl6FMcKChYjJ0ochThWwwOodrQNg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-9.1.1.tgz", + "integrity": "sha512-we9xR8lvu47fF0h+J8KyXoRy9+G/fPzm3QEa2TrdR3jaVS3LKAyE2qyMuUkNdbVkvzl8Zr9f7l+IUSP22HeqXw==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.15.0.tgz", + "integrity": "sha512-fwC5M0c8pxOAzmScPbpx7j28YVTDebUaizlVF7bR0xvlU0r3VWW5OobCcr9ybqKS6wGyO7u4EhXJS9rjRWAuwA==", + "dependencies": { + "@azure/msal-common": "^10.0.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": "10 || 12 || 14 || 16 || 18" + } + }, + "node_modules/@azure/msal-node/node_modules/@azure/msal-common": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-10.0.0.tgz", + "integrity": "sha512-/LghpT93jsZLy55QzTsRZWMx6R1Mjc1Aktwps8sKSGE3WbrGwbSsh2uhDlpl6FMcKChYjJ0ochThWwwOodrQNg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -1466,6 +1714,29 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "node_modules/@nestjs/axios": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.1.tgz", + "integrity": "sha512-rLEq6yfho2CZyOcxP+P4Q3FjkNuiiHDyzj3Cr9i4Kdn3Ng09ygtOB4++jjXPREc6650pOFfzNtw18QH7bfLnQA==", + "dependencies": { + "axios": "1.2.1" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^6.0.0 || ^7.0.0" + } + }, + "node_modules/@nestjs/axios/node_modules/axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@nestjs/cli": { "version": "8.2.8", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.8.tgz", @@ -1516,15 +1787,6 @@ "node": ">=8" } }, - "node_modules/@nestjs/cli/node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/@nestjs/cli/node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -2342,6 +2604,14 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -3073,6 +3343,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -4292,6 +4573,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "engines": { + "node": ">=8" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4840,6 +5129,14 @@ "node": ">= 0.6" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -5691,6 +5988,31 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5872,6 +6194,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5964,6 +6300,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -7877,6 +8224,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openapi-types": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", @@ -9103,6 +9466,15 @@ "node": ">= 0.8" } }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -10313,16 +10685,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -10774,6 +11136,206 @@ "call-me-maybe": "^1.0.1" } }, + "@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-auth": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz", + "integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-client": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.2.tgz", + "integrity": "sha512-ye5554gnVnXdfZ64hptUtETgacXoRWxYv1JF5MctoAzTSH5dXhDPZd9gOjDPyWMcLIk58pnP5+p5vGX6PYn1ag==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "requires": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + } + }, + "@azure/core-lro": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.1.tgz", + "integrity": "sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-rest-pipeline": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz", + "integrity": "sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + } + }, + "@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.1.1.tgz", + "integrity": "sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/identity": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-3.1.3.tgz", + "integrity": "sha512-y0jFjSfHsVPwXSwi3KaSPtOZtJZqhiqAhWUXfFYBUd/+twUBovZRXspBwLrF5rJe0r5NyvmScpQjL+TYDTQVvw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.32.2", + "@azure/msal-common": "^9.0.2", + "@azure/msal-node": "^1.14.6", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "@azure/keyvault-secrets": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@azure/keyvault-secrets/-/keyvault-secrets-4.6.0.tgz", + "integrity": "sha512-MDqsyODCGC2srqLKmO6MFw9WdgLrbPsfCNxgbekHXEd6XKM6KKyBlup5joj96EmdfZnXDFriecAIpj0Dtu81RQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/logger": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.3.tgz", + "integrity": "sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/msal-browser": { + "version": "2.33.0", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.33.0.tgz", + "integrity": "sha512-c7CVh1tfUfxiWkEIhoIb11hL4PGo4hz0M+gMy34ATagAKdLK7qyEu/5AXJWAf5lz5eE+vQhm7+LKiuETrcXXGw==", + "requires": { + "@azure/msal-common": "^10.0.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-10.0.0.tgz", + "integrity": "sha512-/LghpT93jsZLy55QzTsRZWMx6R1Mjc1Aktwps8sKSGE3WbrGwbSsh2uhDlpl6FMcKChYjJ0ochThWwwOodrQNg==" + } + } + }, + "@azure/msal-common": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-9.1.1.tgz", + "integrity": "sha512-we9xR8lvu47fF0h+J8KyXoRy9+G/fPzm3QEa2TrdR3jaVS3LKAyE2qyMuUkNdbVkvzl8Zr9f7l+IUSP22HeqXw==" + }, + "@azure/msal-node": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.15.0.tgz", + "integrity": "sha512-fwC5M0c8pxOAzmScPbpx7j28YVTDebUaizlVF7bR0xvlU0r3VWW5OobCcr9ybqKS6wGyO7u4EhXJS9rjRWAuwA==", + "requires": { + "@azure/msal-common": "^10.0.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-10.0.0.tgz", + "integrity": "sha512-/LghpT93jsZLy55QzTsRZWMx6R1Mjc1Aktwps8sKSGE3WbrGwbSsh2uhDlpl6FMcKChYjJ0ochThWwwOodrQNg==" + } + } + }, "@babel/code-frame": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", @@ -11695,6 +12257,26 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "dev": true }, + "@nestjs/axios": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.1.tgz", + "integrity": "sha512-rLEq6yfho2CZyOcxP+P4Q3FjkNuiiHDyzj3Cr9i4Kdn3Ng09ygtOB4++jjXPREc6650pOFfzNtw18QH7bfLnQA==", + "requires": { + "axios": "1.2.1" + }, + "dependencies": { + "axios": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", + "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + } + } + }, "@nestjs/cli": { "version": "8.2.8", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.8.tgz", @@ -11735,12 +12317,6 @@ "supports-color": "^7.1.0" } }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, "json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -12304,6 +12880,11 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -12930,6 +13511,14 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "devOptional": true }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, "ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -13824,6 +14413,11 @@ "clone": "^1.0.2" } }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -14231,6 +14825,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -14888,6 +15487,25 @@ "toidentifier": "1.0.1" } }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -15026,6 +15644,11 @@ "has": "^1.0.3" } }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -15085,6 +15708,14 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -16585,6 +17216,16 @@ "mimic-fn": "^2.1.0" } }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, "openapi-types": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.0.tgz", @@ -17526,6 +18167,11 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" + }, "streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -18299,15 +18945,6 @@ "terser-webpack-plugin": "^5.1.3", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" - }, - "dependencies": { - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true - } } }, "webpack-node-externals": { diff --git a/dictation_server/package.json b/dictation_server/package.json index 8f9d3c9..ecd4fae 100644 --- a/dictation_server/package.json +++ b/dictation_server/package.json @@ -25,6 +25,9 @@ "swgbundle:sentinel": "swagger-cli bundle -o openapi/build/bundle.yml -t yaml openapi/sentinel_ems/sentinel_ems.yml" }, "dependencies": { + "@azure/identity": "^3.1.3", + "@azure/keyvault-secrets": "^4.6.0", + "@nestjs/axios": "^0.1.0", "@nestjs/common": "^8.0.0", "@nestjs/config": "^2.2.0", "@nestjs/core": "^8.0.0", diff --git a/dictation_server/src/common/jwt/index.ts b/dictation_server/src/common/jwt/index.ts new file mode 100644 index 0000000..aea44b5 --- /dev/null +++ b/dictation_server/src/common/jwt/index.ts @@ -0,0 +1,3 @@ +import { isVerifyError, sign, verify } from './jwt'; + +export { isVerifyError, sign, verify }; diff --git a/dictation_server/src/common/jwt/jwt.spec.ts b/dictation_server/src/common/jwt/jwt.spec.ts new file mode 100644 index 0000000..169949f --- /dev/null +++ b/dictation_server/src/common/jwt/jwt.spec.ts @@ -0,0 +1,250 @@ +import { sign, verify, isVerifyError } from './jwt'; +import base64url from 'base64url'; + +test('success sign and verify', () => { + const token = sign({ value: 'testvalue' }, 5 * 60, privateKey); + const payload = verify<{ value: 'testvalue' }>(token, publicKey); + if (isVerifyError(payload)) { + throw new Error(`${payload.reason} | ${payload.message}`); + } + + expect(payload.value).toBe('testvalue'); +}); + +test('failed sign and verify (jwt expired)', () => { + // 有効期限を0秒にすることで、検証を行った時点で有効期限切れにする + const token = sign({ value: 'testvalue' }, 0, privateKey); + const payload = verify<{ value: 'testvalue' }>(token, publicKey); + if (!isVerifyError(payload)) { + throw new Error(JSON.stringify(payload)); + } + expect(payload.reason).toBe('ExpiredError'); +}); + +test('failed sign and verify (invalid key pair)', () => { + const token = sign({ value: 'testvalue' }, 5 * 60, privateKey); + // 秘密鍵と対ではない公開鍵を使用して検証する + const payload = verify<{ value: 'testvalue' }>(token, anotherPublicKey); + if (!isVerifyError(payload)) { + throw new Error(JSON.stringify(payload)); + } + expect(payload.reason).toBe('InvalidToken'); + expect(payload.message).toBe('invalid signature'); +}); + +test('failed sign and verify (invalid public key)', () => { + const token = sign({ value: 'testvalue' }, 5 * 60, privateKey); + // 公開鍵の形式になっていない文字列を使用して検証する + const payload = verify<{ value: 'testvalue' }>(token, fakePublicKey); + if (!isVerifyError(payload)) { + throw new Error(JSON.stringify(payload)); + } + expect(payload.reason).toBe('InvalidToken'); + expect(payload.message).toBe( + 'secretOrPublicKey must be an asymmetric key when using RS256', + ); +}); + +test('failed sign (invalid private key)', () => { + expect(() => { + // 不正な秘密鍵で署名しようとする場合はエラーがthrowされる + sign({ value: 'testvalue' }, 5 * 60, fakePrivateKey); + }).toThrowError(); +}); + +test('success rewrite-token verify (as is)', () => { + const token = sign({ value: 'testvalue' }, 5 * 60, privateKey); + const { header, payload, verifySignature } = splitToken(token); + + { + // 何も操作せずに構築しなおした場合、成功する + const validToken = rebuildToken(header, payload, verifySignature); + + const value = verify<{ value: string }>(validToken, publicKey); + if (isVerifyError(value)) { + throw new Error(`${value.reason} | ${value.message}`); + } + + expect(value.value).toBe('testvalue'); + } +}); + +test('failed rewrite-token verify (override algorithm)', () => { + const token = sign({ value: 'testvalue' }, 5 * 60, privateKey); + const { payload, verifySignature } = splitToken(token); + + { + // 検証アルゴリズムを「検証なし」に書き換える + const headerObject = { alg: 'none' }; + const payloadObject = JSON.parse(payload) as { + value: string; + iat: number; + exp: number; + }; + + // 内容を操作して構築しなおした場合、失敗する + const customToken = rebuildToken( + JSON.stringify(headerObject), + JSON.stringify(payloadObject), + verifySignature, + ); + + const value = verify<{ value: string }>(customToken, publicKey); + if (!isVerifyError(value)) { + throw new Error(JSON.stringify(payload)); + } + expect(value.reason).toBe('InvalidToken'); + expect(value.message).toBe('invalid algorithm'); + } +}); + +test('failed rewrite-token verify (override expire)', () => { + const token = sign({ value: 'testvalue' }, 5 * 60, privateKey); + const { header, payload, verifySignature } = splitToken(token); + + { + // expの値を操作する + const payloadObject = JSON.parse(payload) as { + value: string; + iat: number; + exp: number; + }; + payloadObject.exp = payloadObject.exp + 100000; + + // 内容を操作して構築しなおした場合、失敗する + const customToken = rebuildToken( + header, + JSON.stringify(payloadObject), + verifySignature, + ); + + const value = verify<{ value: string }>(customToken, publicKey); + if (!isVerifyError(value)) { + throw new Error(JSON.stringify(payload)); + } + expect(value.reason).toBe('InvalidToken'); + expect(value.message).toBe('invalid signature'); + } +}); + +// JWT改竄テスト用ユーティリティ +const splitToken = ( + token: string, +): { header: string; payload: string; verifySignature: string } => { + const splited = token.split('.'); + + const header = base64url.decode(splited[0]); + const payload = base64url.decode(splited[1]); + const verifySignature = splited[2]; + return { header, payload, verifySignature }; +}; + +// JWT改竄テスト用ユーティリティ +const rebuildToken = ( + header: string, + payload: string, + verifySignature: string, +): string => { + const rebuild_header = base64url.encode(header); + const rebuild_payload = base64url.encode(payload); + return `${rebuild_header}.${rebuild_payload}.${verifySignature}`; +}; + +// テスト用に生成した秘密鍵 +const privateKey = [ + '-----BEGIN RSA PRIVATE KEY-----', + 'MIIEpAIBAAKCAQEAsTVLNpW0/FzVCU7qo1DDjOkYWx6s/jE56YOOc3UzaaG/zb1F', + 'GyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23tL7p3PS0ZCsLeggcyLctbJAzLy/a', + 'fF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc57Tli2x8NiOZr5dafs3vMuIIKNsBa', + 'FAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxDalw5DCd0mmdY0vrbRsgkbej0Zzzq', + 'zukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn21AIcn8P3rKZmDyPH+9KNfWC8+ubF', + '+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3aIQIDAQABAoIBAQCk7fkmwIdGKhCN', + 'LUns3opiZ8AnbpGLs702vR6kDvze35BoqDPdZl4RPwkjvMGBCLmRLly/+ATPnwcq', + 'L5Y2iz4jl1yKLaaHZBi2Zz6DARnh5QP+cwdiojQw4qb7xcfXrSltVZjBbBWPnWz0', + 'WAH3yAz94V9Emc47EFpz/CF/J0YOokxY8GlR4cwfK6NE0goAjzmatwV3IVFeR/eE', + 'x6JZAmd/0HMfOn3k/NumAMCJXKnZMQBAMQ3AduTO2lbZm+29yBqymtzTGFjrj0gm', + '+E/ibD8vVzh0toPvUfPIqetdRT8vkUJ5UHhAkz9Vzvqhr6BhYhc2ft0x/z7HpaiX', + 'cDqnaRLBAoGBAODdPEktK1VOVXhOuikZBUHXU25iQdQRbM4kCtWiE8lBZ/f+6OPc', + 'BN+OedYMDhpFe/oFqGU4t610SPO1CdVRPnWHhMSabjh9G3gqOZjSW5tEAgT2wi+H', + 'IOVXnsos1qCMFdXWgVZw6F8wNcui9VabGic/EOqMRihEeSOjcradTSQFAoGBAMm+', + 'y2wZ8usanIDzADgTJnA4kBZzhIxK6qcPf3tPVXKuFUOFWwzGiDXeXTwM0sWN7kGb', + 'iymqhTWlYETQ3C6jPXTJiyOSco1rw45wO+xSHeQvUzXpk+9whbVAlhTcoVGiKz+9', + 'BS7+3+lKtBzXDNADxQfSGjiGb+ceilBGLV+WurRtAoGAPxn2a/aP/X1hAMTe+t95', + 'mTNqx0Qtguxs4yA8Jh04fjarjW1sP10jxPR/fjCd2IN9OflSey1CZhuGyVUZcFI/', + 'O84O1PkdSx7YkY0P4rHNYTHhezEf5yR9d75x4fxZMm59RifO3coLe4LU5dNSE76s', + 'xSyue5NnsK8ea4DXlSVpW10CgYAfHz3GWWJt/lbyVYpNHDcrzK39qKhj9BKq3ust', + 'nJlz7YL+PY5ENERC+yCq6NeC/lgo6tPXA6U1F2P4ebfdwfTzFTxPqoHdayhpysqT', + 'tD9EOkC96mCV6WfXBDWi1j5Ul43QcVphW5QzKwEKCerCFDLK+BBvc93Da6SuqYTK', + 'YDhBKQKBgQDKtNe8CjHRvkWoyKErMMpv5D0ce/yWq+oAaoqW1QKwngPyaiDeDwqM', + 'iOJzQxtvK4YqMYQdkgj5VLfWzeazd28RLODZua6phe776zuUv93LHTvYq/8RZfhk', + 'JIQJ7GETBnHmoTemwmJiSdVDsjJdtsyR4XRjIDNR5bGe7NNbZJpCUw==', + '-----END RSA PRIVATE KEY-----', +].join('\n'); + +// テスト用に生成した公開鍵 +const publicKey = [ + '-----BEGIN PUBLIC KEY-----', + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsTVLNpW0/FzVCU7qo1DD', + 'jOkYWx6s/jE56YOOc3UzaaG/zb1FGyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23', + 'tL7p3PS0ZCsLeggcyLctbJAzLy/afF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc5', + '7Tli2x8NiOZr5dafs3vMuIIKNsBaFAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxD', + 'alw5DCd0mmdY0vrbRsgkbej0ZzzqzukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn2', + '1AIcn8P3rKZmDyPH+9KNfWC8+ubF+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3a', + 'IQIDAQAB', + '-----END PUBLIC KEY-----', +].join('\n'); + +// テスト用に作成した、違う秘密鍵から生成した公開鍵 +const anotherPublicKey = [ + '-----BEGIN PUBLIC KEY-----', + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt1WsgrjpjsEfRa7vqlR3', + '2mGxErXpvC+uRQnFtSXdP4tEYicPb1cNFUcu5xW6attTyzKHKMzwJrvmKEKVYGig', + 'n43rM+UyW79DNOQWQQblCHAc3hMolLWC+Tkw7xL4JhzZLH0rm5DF52YNYSicV1S9', + 'RpxYEeyHUa+ExV82lT47ySWAwg+yPwtDeDPMbOxHXqyw1wdqR2WVuxsQBaIRQgMk', + 'EL/qObQjA4e5jOOwERRvVLxzjhnldUZcG0cYGDfjPTewRYfCeXzMx2YM4Uo0vx0x', + '2ZIY+im061GvfugX4/31xB5YEi+62qIwuSL5UpKjMv5yx1cvIqO76Ro3XNwsR+81', + 'KQIDAQAB', + '-----END PUBLIC KEY-----', +].join('\n'); + +// 秘密鍵のように見えるが想定する形式と違う +const fakePrivateKey = [ + '-----BAGIN RSA PRIVATE KEY-----', + 'MIIEpAIBAAKCAQEAsTVLNpW0/FzVCU7qo1DDjOkYWx6s/jE56YOOc3UzaaG/zb1F', + 'GyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23tL7p3PS0ZCsLeggcyLctbJAzLy/a', + 'fF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc57Tli2x8NiOZr5dafs3vMuIIKNsBa', + 'FAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxDalw5DCd0mmdY0vrbRsgkbej0Zzzq', + 'zukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn21AIcn8P3rKZmDyPH+9KNfWC8+ubF', + '+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3aIQIDAQABAoIBAQCk7fkmwIdGKhCN', + 'LUns3opiZ8AnbpGLs702vR6kDvze35BoqDPdZl4RPwkjvMGBCLmRLly/+ATPnwcq', + 'L5Y2iz4jl1yKLaaHZBi2Zz6DARnh5QP+cwdiojQw4qb7xcfXrSltVZjBbBWPnWz0', + 'WAH3yAz94V9Emc47EFpz/CF/J0YOokxY8GlR4cwfK6NE0goAjzmatwV3IVFeR/eE', + 'x6JZAmd/0HMfOn3k/NumAMCJXKnZMQBAMQ3AduTO2lbZm+29yBqymtzTGFjrj0gm', + '+E/ibD8vVzh0toPvUfPIqetdRT8vkUJ5UHhAkz9Vzvqhr6BhYhc2ft0x/z7HpaiX', + 'cDqnaRLBAoGBAODdPEktK1VOVXhOuikZBUHXU25iQdQRbM4kCtWiE8lBZ/f+6OPc', + 'BN+OedYMDhpFe/oFqGU4t610SPO1CdVRPnWHhMSabjh9G3gqOZjSW5tEAgT2wi+H', + 'IOVXnsos1qCMFdXWgVZw6F8wNcui9VabGic/EOqMRihEeSOjcradTSQFAoGBAMm+', + 'y2wZ8usanIDzADgTJnA4kBZzhIxK6qcPf3tPVXKuFUOFWwzGiDXeXTwM0sWN7kGb', + 'iymqhTWlYETQ3C6jPXTJiyOSco1rw45wO+xSHeQvUzXpk+9whbVAlhTcoVGiKz+9', + 'BS7+3+lKtBzXDNADxQfSGjiGb+ceilBGLV+WurRtAoGAPxn2a/aP/X1hAMTe+t95', + 'mTNqx0Qtguxs4yA8Jh04fjarjW1sP10jxPR/fjCd2IN9OflSey1CZhuGyVUZcFI/', + 'O84O1PkdSx7YkY0P4rHNYTHhezEf5yR9d75x4fxZMm59RifO3coLe4LU5dNSE76s', + 'xSyue5NnsK8ea4DXlSVpW10CgYAfHz3GWWJt/lbyVYpNHDcrzK39qKhj9BKq3ust', + 'nJlz7YL+PY5ENERC+yCq6NeC/lgo6tPXA6U1F2P4ebfdwfTzFTxPqoHdayhpysqT', + 'tD9EOkC96mCV6WfXBDWi1j5Ul43QcVphW5QzKwEKCerCFDLK+BBvc93Da6SuqYTK', + 'YDhBKQKBgQDKtNe8CjHRvkWoyKErMMpv5D0ce/yWq+oAaoqW1QKwngPyaiDeDwqM', + 'iOJzQxtvK4YqMYQdkgj5VLfWzeazd28RLODZua6phe776zuUv93LHTvYq/8RZfhk', + 'JIQJ7GETBnHmoTemwmJiSdVDsjJdtsyR4XRjIDNR5bGe7NNbZJpCUw==', + '-----END RSA PRIVATE KEY-----', +].join('\n'); + +// 公開鍵のように見えるが想定する形式と違う +const fakePublicKey = [ + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt1WsgrjpjsEfRa7vqlR3', + '2mGxErXpvC+uRQnFtSXdP4tEYicPb1cNFUcu5xW6attTyzKHKMzwJrvmKEKVYGig', + 'n43rM+UyW79DNOQWQQblCHAc3hMolLWC+Tkw7xL4JhzZLH0rm5DF52YNYSicV1S9', + 'RpxYEeyHUa+ExV82lT47ySWAwg+yPwtDeDPMbOxHXqyw1wdqR2WVuxsQBaIRQgMk', + 'EL/qObQjA4e5jOOwERRvVLxzjhnldUZcG0cYGDfjPTewRYfCeXzMx2YM4Uo0vx0x', + '2ZIY+im061GvfugX4/31xB5YEi+62qIwuSL5UpKjMv5yx1cvIqO76Ro3XNwsR+81', + 'KQIDAQAB', +].join('\n'); diff --git a/dictation_server/src/common/jwt/jwt.ts b/dictation_server/src/common/jwt/jwt.ts new file mode 100644 index 0000000..4625f89 --- /dev/null +++ b/dictation_server/src/common/jwt/jwt.ts @@ -0,0 +1,90 @@ +import * as jwt from 'jsonwebtoken'; + +export type VerifyError = { + reason: 'ExpiredError' | 'InvalidToken' | 'InvalidTimeStamp' | 'Unknown'; + message: string; +}; + +export const isVerifyError = (arg: unknown): arg is VerifyError => { + const value = arg as VerifyError; + if (value.message === undefined) { + return false; + } + + if (value.reason === undefined) { + return false; + } + switch (value.reason) { + case 'ExpiredError': + case 'InvalidTimeStamp': + case 'InvalidToken': + case 'Unknown': + return true; + default: + return false; + } +}; + +/** + * Payloadと秘密鍵を使用して署名されたJWTを生成します + * @param {T} payload payloadの型 + * @param {number} expirationSeconds トークンの有効期限(秒) + * @param {string} privateKey 署名に使用する秘密鍵 + * @return {string} 署名済みトークン + * @throws {Error} 秘密鍵の形式が間違っている等の理由が格納されたErrorオブジェクト + */ +export const sign = ( + payload: T, + expirationSeconds: number, + privateKey: string, +): string => { + try { + const token = jwt.sign(payload, privateKey, { + expiresIn: expirationSeconds, + algorithm: 'RS256', + }); + return token; + } catch (e) { + throw e; + } +}; + +/** + * tokenと公開鍵を使用して検証済みJWTのpayloadを取得します + * @param {string} token JWT + * @param {string} publicKey 検証に使用する公開鍵 + * @return {T | VerifyError} Payload または 検証エラーの内容を表すオブジェクト + */ +export const verify = ( + token: string, + publicKey: string, +): T | VerifyError => { + try { + const payload = jwt.verify(token, publicKey, { + algorithms: ['RS256'], + }) as T; + return payload; + } catch (e) { + if (e instanceof jwt.TokenExpiredError) { + return { + reason: 'ExpiredError', + message: e.message, + }; + } else if (e instanceof jwt.NotBeforeError) { + return { + reason: 'InvalidTimeStamp', + message: e.message, + }; + } else if (e instanceof jwt.JsonWebTokenError) { + return { + reason: 'InvalidToken', + message: e.message, + }; + } else { + return { + reason: 'Unknown', + message: e.message, + }; + } + } +}; diff --git a/dictation_server/src/common/token/index.ts b/dictation_server/src/common/token/index.ts index 3ccff00..7c1b5ef 100644 --- a/dictation_server/src/common/token/index.ts +++ b/dictation_server/src/common/token/index.ts @@ -1,20 +1,11 @@ -export type RefreshToken = { - scope: string; -}; +import type { + AccessToken, + B2cMetadata, + IDToken, + JwkSignKey, + RefreshToken, +} from './types'; +import { isIDToken } from './typeguard'; -export type AccessToken = { - scope: string; -}; - -export type B2cMetadata = { - issuer: string; -}; - -export type JwkSignKey = { - kid: string; - nbf: number; - use: string; - kty: string; - e: string; - n: string; -}; +export type { AccessToken, B2cMetadata, IDToken, JwkSignKey, RefreshToken }; +export { isIDToken }; diff --git a/dictation_server/src/common/token/typeguard.ts b/dictation_server/src/common/token/typeguard.ts new file mode 100644 index 0000000..ae2076a --- /dev/null +++ b/dictation_server/src/common/token/typeguard.ts @@ -0,0 +1,18 @@ +import jwt from 'jsonwebtoken'; +import { IDToken } from './types'; + +export const isIDToken = ( + payload: jwt.JwtPayload | string, +): payload is IDToken => { + if (typeof payload === 'string') return false; + const token = payload as IDToken; + if (token.sub === undefined) return false; + if (token.emails === undefined) return false; + if (token.exp === undefined) return false; + if (token.iat === undefined) return false; + if (token.family_name === undefined) return false; + if (token.given_name === undefined) return false; + if (token.nonce === undefined) return false; + + return true; +}; diff --git a/dictation_server/src/common/token/types.ts b/dictation_server/src/common/token/types.ts new file mode 100644 index 0000000..3e7d69b --- /dev/null +++ b/dictation_server/src/common/token/types.ts @@ -0,0 +1,32 @@ +export type RefreshToken = { + userId: string; + scope: string; +}; + +export type AccessToken = { + userId: string; + scope: string; +}; + +export type IDToken = { + emails: string[]; + family_name: string; + given_name: string; + nonce: string; + sub: string; // AzureAD B2C ID + exp: number; // 有効期限 + iat: number; // 発行日時 +}; + +export type B2cMetadata = { + issuer: string; +}; + +export type JwkSignKey = { + kid: string; + nbf: number; + use: string; + kty: string; + e: string; + n: string; +}; diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index cd0c062..854bed0 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -1,10 +1,18 @@ -import { Body, Controller, Headers, HttpStatus, Post } from '@nestjs/common'; +import { + Body, + Controller, + Headers, + HttpException, + HttpStatus, + Post, +} from '@nestjs/common'; import { ApiResponse, ApiOperation, ApiBearerAuth, ApiTags, } from '@nestjs/swagger'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { ErrorResponse } from '../../common/error/types/types'; import { AuthService } from './auth.service'; import { @@ -36,12 +44,19 @@ export class AuthController { }) @ApiOperation({ operationId: 'token' }) async token(@Body() body: TokenRequest): Promise { - const payload = await this.authService.getVerifiedIdToken(body.idToken); - console.log(payload); + console.log(body); + const idToken = await this.authService.getVerifiedIdToken(body.idToken); + const refreshToken = await this.authService.generateRefreshToken( + idToken, + body.type, + ); + const accessToken = await this.authService.generateAccessToken( + refreshToken, + ); return { - accessToken: '', - refreshToken: '', + accessToken, + refreshToken, }; } @@ -65,15 +80,20 @@ export class AuthController { @ApiOperation({ operationId: 'accessToken' }) async accessToken(@Headers() headers): Promise { console.log(headers['authorization']); + const header = headers['authorization']; + if (typeof header === 'string') { + if (header.startsWith('Bearer ')) { + const refreshToken = header.substring('Bearer '.length, header.length); + const accessToken = await this.authService.generateAccessToken( + refreshToken, + ); - const authHeader = headers['authorization']; - - let refreshToken = ''; - if (typeof authHeader === 'string') { - refreshToken = authHeader.replace('Bearer ', ''); + return { accessToken }; + } } - console.log(refreshToken); - - return { accessToken: '' }; + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.UNAUTHORIZED, + ); } } diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index 6684355..4f4b3fe 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -1,18 +1,86 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import jwt from 'jsonwebtoken'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { isVerifyError, sign, verify } from '../../common/jwt'; +import { + AccessToken, + IDToken, + isIDToken, + RefreshToken, +} from '../../common/token'; import { AdB2cService } from '../../gateways/adb2c/adb2c.service'; import { CryptoService } from '../../gateways/crypto/crypto.service'; @Injectable() export class AuthService { constructor( - private adB2cService: AdB2cService, - private cryptoService: CryptoService, + private readonly cryptoService: CryptoService, + private readonly adB2cService: AdB2cService, + private readonly configService: ConfigService, ) {} private readonly logger = new Logger(AuthService.name); - async getVerifiedIdToken(token: string): Promise { + /** + * Generates refresh token + * @param idToken AzureAD B2Cにより発行されたIDトークン + * @param type 環境を表す文字列(web/desktop/mobile) + * @returns refresh token + */ + async generateRefreshToken(idToken: IDToken, type: string): Promise { + const lifetimeWeb = this.configService.get('REFRESH_TOKEN_LIFETIME_WEB'); + const lifetimeDefault = this.configService.get( + 'REFRESH_TOKEN_LIFETIME_DEFAULT', + ); + + // 要求された環境用トークンの寿命を決定 + const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault; + + const privateKey = await this.cryptoService.getPrivateKey(); + const token = sign( + { + scope: 'test', + userId: idToken.sub, + }, + refreshTokenLifetime, + privateKey, + ); + return token; + } + + /** + * Generates access token + * @param refreshToken リフレッシュトークン(jwt) + * @returns access token(jwt) + */ + async generateAccessToken(refreshToken: string): Promise { + const lifetime = this.configService.get('ACCESS_TOKEN_LIFETIME_WEB'); + + const privateKey = await this.cryptoService.getPrivateKey(); + const pubkey = await this.cryptoService.getPublicKey(); + + const token = verify(refreshToken, pubkey); + if (isVerifyError(token)) { + throw new Error(`${token.reason} | ${token.message}`); + } + + const accessToken = sign( + { + scope: token.scope, + userId: token.userId, + }, + lifetime, + privateKey, + ); + + return accessToken; + } + /** + * Gets id token + * @param token + * @returns id token + */ + async getVerifiedIdToken(token: string): Promise { this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`); let kid = ''; @@ -48,6 +116,9 @@ export class AuthService { issuer: [issuer], }); + if (!isIDToken(verifiedToken)) { + throw new Error('invalid format ID token'); + } return verifiedToken; } catch (e) { if (e instanceof Error) { diff --git a/dictation_server/src/gateways/crypto/crypto.service.ts b/dictation_server/src/gateways/crypto/crypto.service.ts index 58bea44..96ee432 100644 --- a/dictation_server/src/gateways/crypto/crypto.service.ts +++ b/dictation_server/src/gateways/crypto/crypto.service.ts @@ -1,69 +1,66 @@ +import { DefaultAzureCredential } from '@azure/identity'; +import { SecretClient } from '@azure/keyvault-secrets'; import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import jwkToPem from 'jwk-to-pem'; -import { JwkSignKey } from 'src/common/token'; +import { JwkSignKey } from '../../common/token'; @Injectable() export class CryptoService { - constructor(private readonly configService: ConfigService) {} private readonly logger = new Logger(CryptoService.name); + private readonly credential: DefaultAzureCredential; + private readonly url: string; + constructor(private readonly configService: ConfigService) { + // DefaultAzureCredentialが内部で直接的に環境変数を読むので、明示的にセットする + process.env.AZURE_TENANT_ID = configService.get('AZURE_TENANT_ID'); + process.env.AZURE_CLIENT_ID = configService.get('AZURE_CLIENT_ID'); + process.env.AZURE_CLIENT_SECRET = configService.get('AZURE_CLIENT_SECRET'); + + // If you're using MSI, DefaultAzureCredential should "just work". + // Otherwise, DefaultAzureCredential expects the following three environment variables: + // - AZURE_TENANT_ID: The tenant ID in Azure Active Directory + // - AZURE_CLIENT_ID: The application (client) ID registered in the AAD tenant + // - AZURE_CLIENT_SECRET: The client secret for the registered application + this.credential = new DefaultAzureCredential(); + this.url = + 'https://' + configService.get('KEY_VAULT_NAME') + '.vault.azure.net'; + } + + /** + * Gets private key + * @returns private key(PEM) + */ async getPrivateKey(): Promise { try { - // XXX 動作確認用 - const privateKey = [ - '-----BEGIN RSA PRIVATE KEY-----', - 'MIIEpAIBAAKCAQEAsTVLNpW0/FzVCU7qo1DDjOkYWx6s/jE56YOOc3UzaaG/zb1F', - 'GyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23tL7p3PS0ZCsLeggcyLctbJAzLy/a', - 'fF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc57Tli2x8NiOZr5dafs3vMuIIKNsBa', - 'FAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxDalw5DCd0mmdY0vrbRsgkbej0Zzzq', - 'zukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn21AIcn8P3rKZmDyPH+9KNfWC8+ubF', - '+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3aIQIDAQABAoIBAQCk7fkmwIdGKhCN', - 'LUns3opiZ8AnbpGLs702vR6kDvze35BoqDPdZl4RPwkjvMGBCLmRLly/+ATPnwcq', - 'L5Y2iz4jl1yKLaaHZBi2Zz6DARnh5QP+cwdiojQw4qb7xcfXrSltVZjBbBWPnWz0', - 'WAH3yAz94V9Emc47EFpz/CF/J0YOokxY8GlR4cwfK6NE0goAjzmatwV3IVFeR/eE', - 'x6JZAmd/0HMfOn3k/NumAMCJXKnZMQBAMQ3AduTO2lbZm+29yBqymtzTGFjrj0gm', - '+E/ibD8vVzh0toPvUfPIqetdRT8vkUJ5UHhAkz9Vzvqhr6BhYhc2ft0x/z7HpaiX', - 'cDqnaRLBAoGBAODdPEktK1VOVXhOuikZBUHXU25iQdQRbM4kCtWiE8lBZ/f+6OPc', - 'BN+OedYMDhpFe/oFqGU4t610SPO1CdVRPnWHhMSabjh9G3gqOZjSW5tEAgT2wi+H', - 'IOVXnsos1qCMFdXWgVZw6F8wNcui9VabGic/EOqMRihEeSOjcradTSQFAoGBAMm+', - 'y2wZ8usanIDzADgTJnA4kBZzhIxK6qcPf3tPVXKuFUOFWwzGiDXeXTwM0sWN7kGb', - 'iymqhTWlYETQ3C6jPXTJiyOSco1rw45wO+xSHeQvUzXpk+9whbVAlhTcoVGiKz+9', - 'BS7+3+lKtBzXDNADxQfSGjiGb+ceilBGLV+WurRtAoGAPxn2a/aP/X1hAMTe+t95', - 'mTNqx0Qtguxs4yA8Jh04fjarjW1sP10jxPR/fjCd2IN9OflSey1CZhuGyVUZcFI/', - 'O84O1PkdSx7YkY0P4rHNYTHhezEf5yR9d75x4fxZMm59RifO3coLe4LU5dNSE76s', - 'xSyue5NnsK8ea4DXlSVpW10CgYAfHz3GWWJt/lbyVYpNHDcrzK39qKhj9BKq3ust', - 'nJlz7YL+PY5ENERC+yCq6NeC/lgo6tPXA6U1F2P4ebfdwfTzFTxPqoHdayhpysqT', - 'tD9EOkC96mCV6WfXBDWi1j5Ul43QcVphW5QzKwEKCerCFDLK+BBvc93Da6SuqYTK', - 'YDhBKQKBgQDKtNe8CjHRvkWoyKErMMpv5D0ce/yWq+oAaoqW1QKwngPyaiDeDwqM', - 'iOJzQxtvK4YqMYQdkgj5VLfWzeazd28RLODZua6phe776zuUv93LHTvYq/8RZfhk', - 'JIQJ7GETBnHmoTemwmJiSdVDsjJdtsyR4XRjIDNR5bGe7NNbZJpCUw==', - '-----END RSA PRIVATE KEY-----', - ].join('\n'); - return privateKey; + const client = new SecretClient(this.url, this.credential); + const secret = await client.getSecret('token-private-key'); + if (secret.value) { + return secret.value; + } else { + throw new Error('private key was empty'); + } } catch (e) { this.logger.error(`error=${e}`); throw e; } finally { - this.logger.log(`[OUT] ${this.getPublicKey.name}`); + this.logger.log(`[OUT] ${this.getPrivateKey.name}`); } } + /** + * Gets public key + * @returns public key(PEM) + */ async getPublicKey(): Promise { try { - // XXX 動作確認用 - const publicKey = [ - '-----BEGIN PUBLIC KEY-----', - 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsTVLNpW0/FzVCU7qo1DD', - 'jOkYWx6s/jE56YOOc3UzaaG/zb1FGyfRoUUgS4DnQxPNz9oM63RpQlhvG6UCwx23', - 'tL7p3PS0ZCsLeggcyLctbJAzLy/afF9ABoreorqp/AaEs+Vdwbykb+M+nB2Sxsc5', - '7Tli2x8NiOZr5dafs3vMuIIKNsBaFAugFrd2ApxXR04jBRAorZRRFPtECE7D+hxD', - 'alw5DCd0mmdY0vrbRsgkbej0ZzzqzukJVXTMjy1YScqi3I9gLx2hLVmpK76Gtxn2', - '1AIcn8P3rKZmDyPH+9KNfWC8+ubF+VuY6nItlCgiSyTKErAp6M9pyRHKbPpdUM3a', - 'IQIDAQAB', - '-----END PUBLIC KEY-----', - ].join('\n'); - return publicKey; + const client = new SecretClient(this.url, this.credential); + const secret = await client.getSecret('token-public-key'); + if (secret.value) { + return secret.value; + } else { + throw new Error('public key was empty'); + } } catch (e) { this.logger.error(`error=${e}`); throw e; diff --git a/dictation_server/src/main.ts b/dictation_server/src/main.ts index 3c685c5..c5363c6 100644 --- a/dictation_server/src/main.ts +++ b/dictation_server/src/main.ts @@ -13,10 +13,7 @@ async function bootstrap() { // バリデーター(+型の自動変換機能)を適用 app.useGlobalPipes( - new ValidationPipe({ - transform: true, - forbidUnknownValues: false, - }), + new ValidationPipe({ transform: true, forbidUnknownValues: false }), ); if (process.env.STAGE === 'local') {