From 41f0213fe99aace0fe825ab5d1d17ec50281be30 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Tue, 7 Mar 2023 01:30:13 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=201:=20=E3=82=BF=E3=82=B9=E3=82=AF?= =?UTF-8?q?=201359:=20API=E5=AE=9F=E8=A3=85=EF=BC=88=E8=AA=8D=E8=A8=BC/ID?= =?UTF-8?q?=E3=83=88=E3=83=BC=E3=82=AF=E3=83=B3=E6=A4=9C=E8=A8=BC=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [タスク 1359: API実装(認証/IDトークン検証)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_workitems/edit/1359) - IDトークンを受け取って内容を検証し、デコードしたペイロードを返すサービスを実装しました。 ## レビューポイント - サービスの処理の流れが認識とあっているか。 - ADB2CのAPI呼び出しを別サービスにしているが問題ないか - 公開鍵の変換処理を別サービスに切り出しているが構成に問題はないか。 - トークンの検証をエラーごとに処理しているがエラー内容は認識通りか ## UIの変更 - なし ## 動作確認状況 - テストが通ることを確認 # 備考 - IDトークンを検証して中身を返すまでの実装です。 --- dictation_server/.env | 4 +- dictation_server/package-lock.json | 334 ++++++++++++++++-- dictation_server/package.json | 8 +- dictation_server/src/app.module.ts | 7 +- dictation_server/src/common/error/code.ts | 20 +- dictation_server/src/common/error/message.ts | 8 +- dictation_server/src/common/token/index.ts | 20 ++ .../src/features/auth/auth.controller.spec.ts | 10 +- .../src/features/auth/auth.controller.ts | 15 +- .../src/features/auth/auth.module.ts | 3 + .../src/features/auth/auth.service.spec.ts | 138 +++++++- .../src/features/auth/auth.service.ts | 123 ++++++- .../features/auth/test/auth.service.mock.ts | 100 ++++++ .../src/gateways/adb2c/adb2c.module.ts | 10 + .../src/gateways/adb2c/adb2c.service.ts | 60 ++++ .../src/gateways/crypto/crypto.module.ts | 10 + .../src/gateways/crypto/crypto.service.ts | 92 +++++ dictation_server/src/main.ts | 9 +- dictation_server/tsconfig.json | 3 +- 19 files changed, 915 insertions(+), 59 deletions(-) create mode 100644 dictation_server/src/common/token/index.ts create mode 100644 dictation_server/src/features/auth/test/auth.service.mock.ts create mode 100644 dictation_server/src/gateways/adb2c/adb2c.module.ts create mode 100644 dictation_server/src/gateways/adb2c/adb2c.service.ts create mode 100644 dictation_server/src/gateways/crypto/crypto.module.ts create mode 100644 dictation_server/src/gateways/crypto/crypto.service.ts diff --git a/dictation_server/.env b/dictation_server/.env index b2baf36..d9f662d 100644 --- a/dictation_server/.env +++ b/dictation_server/.env @@ -5,4 +5,6 @@ DB_NAME=omds DB_ROOT_PASS=omdsdbpass DB_USERNAME=omdsdbuser DB_PASSWORD=omdsdbpass -NO_COLOR=TRUE \ No newline at end of file +NO_COLOR=TRUE +TENANT_NAME=adb2codmsdev +SIGNIN_FLOW_NAME=b2c_1_signin_dev \ No newline at end of file diff --git a/dictation_server/package-lock.json b/dictation_server/package-lock.json index 5808b82..dfe438d 100644 --- a/dictation_server/package-lock.json +++ b/dictation_server/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.1", "license": "UNLICENSED", "dependencies": { - "@nestjs/axios": "^0.1.0", "@nestjs/common": "^8.0.0", "@nestjs/config": "^2.2.0", "@nestjs/core": "^8.0.0", @@ -17,12 +16,16 @@ "@nestjs/serve-static": "^2.2.2", "@nestjs/typeorm": "^9.0.1", "@openapitools/openapi-generator-cli": "^2.5.1", + "@types/jsonwebtoken": "^9.0.1", "@types/uuid": "^8.3.4", + "axios": "^1.3.4", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "connect-redis": "^6.1.3", "cookie-parser": "^1.4.6", "express-session": "^1.17.3", + "jsonwebtoken": "^9.0.0", + "jwk-to-pem": "^2.0.5", "mysql2": "^2.3.3", "redis": "^4.2.0", "reflect-metadata": "^0.1.13", @@ -40,10 +43,13 @@ "@types/express": "^4.17.13", "@types/express-session": "^1.17.5", "@types/jest": "27.5.0", + "@types/jsonwebtoken": "^9.0.1", + "@types/jwk-to-pem": "^2.0.1", "@types/node": "^16.0.0", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "base64url": "^3.0.1", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", @@ -1460,19 +1466,6 @@ "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/cli": { "version": "8.2.8", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.8.tgz", @@ -2561,6 +2554,21 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jwk-to-pem": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/jwk-to-pem/-/jwk-to-pem-2.0.1.tgz", + "integrity": "sha512-QXmRPhR/LPzvXBHTPfG2BBfMTkNLUD7NyRcPft8m5xFCeANa1BZyLgT0Gw+OxdWx6i1WCpT27EqyggP4UUHMrA==", + "dev": true + }, "node_modules/@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -3243,15 +3251,26 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", - "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -3373,6 +3392,15 @@ } ] }, + "node_modules/base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3428,6 +3456,11 @@ "node": ">= 6" } }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "node_modules/body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -3485,6 +3518,11 @@ "node": ">=8" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, "node_modules/browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -3534,6 +3572,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -4367,6 +4410,14 @@ "wcwidth": ">=1.0.1" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4378,6 +4429,20 @@ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", @@ -5563,6 +5628,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -5580,6 +5654,16 @@ "node": "*" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -7038,6 +7122,50 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwk-to-pem": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz", + "integrity": "sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==", + "dependencies": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.4", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7419,6 +7547,16 @@ "node": ">=6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8624,7 +8762,6 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -8639,7 +8776,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -8650,8 +8786,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/send": { "version": "0.18.0", @@ -11560,14 +11695,6 @@ "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" - } - }, "@nestjs/cli": { "version": "8.2.8", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-8.2.8.tgz", @@ -12389,6 +12516,21 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/jwk-to-pem": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/jwk-to-pem/-/jwk-to-pem-2.0.1.tgz", + "integrity": "sha512-QXmRPhR/LPzvXBHTPfG2BBfMTkNLUD7NyRcPft8m5xFCeANa1BZyLgT0Gw+OxdWx6i1WCpT27EqyggP4UUHMrA==", + "dev": true + }, "@types/mime": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", @@ -12913,15 +13055,26 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", - "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -13008,6 +13161,12 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -13045,6 +13204,11 @@ } } }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, "body-parser": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", @@ -13097,6 +13261,11 @@ "fill-range": "^7.0.1" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, "browserslist": { "version": "4.21.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", @@ -13127,6 +13296,11 @@ "node-int64": "^0.4.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -13734,6 +13908,14 @@ "wcwidth": ">=1.0.1" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13745,6 +13927,20 @@ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==", "dev": true }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "emittery": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", @@ -14638,6 +14834,15 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -14649,6 +14854,16 @@ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -15770,6 +15985,46 @@ "universalify": "^2.0.0" } }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jwk-to-pem": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jwk-to-pem/-/jwk-to-pem-2.0.5.tgz", + "integrity": "sha512-L90jwellhO8jRKYwbssU9ifaMVqajzj3fpRjDKcsDzrslU9syRbFqfkXtT4B89HYAap+xsxNcxgBSB09ig+a7A==", + "requires": { + "asn1.js": "^5.3.0", + "elliptic": "^6.5.4", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -16065,6 +16320,16 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -16968,7 +17233,6 @@ "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, "requires": { "lru-cache": "^6.0.0" }, @@ -16977,7 +17241,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -16985,8 +17248,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" } } }, diff --git a/dictation_server/package.json b/dictation_server/package.json index 42bf69a..8f9d3c9 100644 --- a/dictation_server/package.json +++ b/dictation_server/package.json @@ -25,7 +25,6 @@ "swgbundle:sentinel": "swagger-cli bundle -o openapi/build/bundle.yml -t yaml openapi/sentinel_ems/sentinel_ems.yml" }, "dependencies": { - "@nestjs/axios": "^0.1.0", "@nestjs/common": "^8.0.0", "@nestjs/config": "^2.2.0", "@nestjs/core": "^8.0.0", @@ -33,12 +32,16 @@ "@nestjs/serve-static": "^2.2.2", "@nestjs/typeorm": "^9.0.1", "@openapitools/openapi-generator-cli": "^2.5.1", + "@types/jsonwebtoken": "^9.0.1", "@types/uuid": "^8.3.4", + "axios": "^1.3.4", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "connect-redis": "^6.1.3", "cookie-parser": "^1.4.6", "express-session": "^1.17.3", + "jsonwebtoken": "^9.0.0", + "jwk-to-pem": "^2.0.5", "mysql2": "^2.3.3", "redis": "^4.2.0", "reflect-metadata": "^0.1.13", @@ -56,10 +59,13 @@ "@types/express": "^4.17.13", "@types/express-session": "^1.17.5", "@types/jest": "27.5.0", + "@types/jsonwebtoken": "^9.0.1", + "@types/jwk-to-pem": "^2.0.1", "@types/node": "^16.0.0", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "base64url": "^3.0.1", "eslint": "^8.0.1", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", diff --git a/dictation_server/src/app.module.ts b/dictation_server/src/app.module.ts index eed0928..a4da90b 100644 --- a/dictation_server/src/app.module.ts +++ b/dictation_server/src/app.module.ts @@ -6,6 +6,9 @@ import { join } from 'path'; import { LoggerMiddleware } from './common/loggerMiddleware'; import { AuthModule } from './features/auth/auth.module'; import { AuthController } from './features/auth/auth.controller'; +import { AuthService } from './features/auth/auth.service'; +import { CryptoModule } from './gateways/crypto/crypto.module'; +import { AdB2cModule } from './gateways/adb2c/adb2c.module'; @Module({ imports: [ @@ -17,6 +20,8 @@ import { AuthController } from './features/auth/auth.controller'; isGlobal: true, }), AuthModule, + CryptoModule, + AdB2cModule, // TypeOrmModule.forRootAsync({ // imports: [ConfigModule], // useFactory: async (configService: ConfigService) => ({ @@ -33,7 +38,7 @@ import { AuthController } from './features/auth/auth.controller'; // }), ], controllers: [HealthController, AuthController], - providers: [], + providers: [AuthService], }) export class AppModule { configure(consumer: MiddlewareConsumer) { diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index 75bd4d4..bf4eb59 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -1,4 +1,22 @@ -//TODO 仮のエラーコード作成 +/* +エラーコード作成方針 +E+6桁(数字)で構成する。 +- 1~2桁目の値は種類(業務エラー、システムエラー...) +- 3~4桁目の値は原因箇所(トークン、DB、...) +- 5~6桁目の値は任意の重複しない値 +ex) +E00XXXX : システムエラー(通信エラーやDB接続失敗など) +E01XXXX : 業務エラー +EXX00XX : 内部エラー(内部プログラムのエラー) +EXX01XX : トークンエラー(トークン認証関連) +EXX02XX : DBエラー(DB関連) +*/ export const ErrorCodes = [ 'E009999', // 汎用エラー + 'E000101', // トークン形式不正エラー + 'E000102', // トークン有効期限切れエラー + 'E000103', // トークン非アクティブエラー + 'E000104', // トークン署名エラー + 'E000105', // トークン発行元エラー + 'E000106', // トークンアルゴリズムエラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index e2cbc14..085b6c2 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -2,5 +2,11 @@ import { Errors } from './types/types'; // エラーコードとメッセージ対応表 export const errors: Errors = { - E009999: 'Internal Server Error', + E009999: 'Internal Server Error.', + E000101: 'Token invalid format Error.', + E000102: 'Token expired Error.', + E000103: 'Token not before Error', + E000104: 'Token invalid signature Error.', + E000105: 'Token invalid issuer Error.', + E000106: 'Token invalid algorithm Error.', }; diff --git a/dictation_server/src/common/token/index.ts b/dictation_server/src/common/token/index.ts new file mode 100644 index 0000000..3ccff00 --- /dev/null +++ b/dictation_server/src/common/token/index.ts @@ -0,0 +1,20 @@ +export type RefreshToken = { + scope: string; +}; + +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; +}; diff --git a/dictation_server/src/features/auth/auth.controller.spec.ts b/dictation_server/src/features/auth/auth.controller.spec.ts index 27a31e6..b6e162a 100644 --- a/dictation_server/src/features/auth/auth.controller.spec.ts +++ b/dictation_server/src/features/auth/auth.controller.spec.ts @@ -1,5 +1,10 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { + makeAdB2cServiceMock, + makeDefaultAdB2cMockValue, +} from './test/auth.service.mock'; describe('AuthController', () => { let controller: AuthController; @@ -7,7 +12,10 @@ describe('AuthController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [AuthController], - }).compile(); + providers: [AuthService], + }) + .useMocker(() => makeAdB2cServiceMock(makeDefaultAdB2cMockValue())) + .compile(); controller = module.get(AuthController); }); diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index bbc1f5a..cd0c062 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -6,14 +6,18 @@ import { ApiTags, } from '@nestjs/swagger'; import { ErrorResponse } from '../../common/error/types/types'; +import { AuthService } from './auth.service'; import { AccessTokenResponse, TokenRequest, TokenResponse, } from './types/types'; + @ApiTags('auth') @Controller('auth') export class AuthController { + constructor(private readonly authService: AuthService) {} + @Post('token') @ApiResponse({ status: HttpStatus.OK, @@ -32,7 +36,8 @@ export class AuthController { }) @ApiOperation({ operationId: 'token' }) async token(@Body() body: TokenRequest): Promise { - console.log(body); + const payload = await this.authService.getVerifiedIdToken(body.idToken); + console.log(payload); return { accessToken: '', @@ -61,6 +66,14 @@ export class AuthController { async accessToken(@Headers() headers): Promise { console.log(headers['authorization']); + const authHeader = headers['authorization']; + + let refreshToken = ''; + if (typeof authHeader === 'string') { + refreshToken = authHeader.replace('Bearer ', ''); + } + console.log(refreshToken); + return { accessToken: '' }; } } diff --git a/dictation_server/src/features/auth/auth.module.ts b/dictation_server/src/features/auth/auth.module.ts index a7d9fbc..b50902d 100644 --- a/dictation_server/src/features/auth/auth.module.ts +++ b/dictation_server/src/features/auth/auth.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; +import { AdB2cModule } from 'src/gateways/adb2c/adb2c.module'; +import { CryptoModule } from '../../gateways/crypto/crypto.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; @Module({ + imports: [CryptoModule, AdB2cModule], controllers: [AuthController], providers: [AuthService], }) diff --git a/dictation_server/src/features/auth/auth.service.spec.ts b/dictation_server/src/features/auth/auth.service.spec.ts index 800ab66..2a153db 100644 --- a/dictation_server/src/features/auth/auth.service.spec.ts +++ b/dictation_server/src/features/auth/auth.service.spec.ts @@ -1,18 +1,134 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthService } from './auth.service'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { + makeAdB2cServiceMock, + makeAuthServiceMock, + makeDefaultAdB2cMockValue, + makeDefaultCryptoMockValue, +} from './test/auth.service.mock'; describe('AuthService', () => { - let service: AuthService; + it('IDトークンの検証とペイロードの取得に成功する', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaXNzdXNlciJ9.dURTqFfUc94qYNFS65xMqMT3tJi-uH8weJ5GU74F-UvhLKJmqVoNIbXCm-ASg2mZ5SxeX683q2MMrlzxNcNel-QIFTj0zWem4eLKuarsYRMTU8x27jMss0b_JkFtiy5KE2052_fLeW2aZBb4AjieIW34-c0Ol1rrih-W9Pk6ZEOwf14Ju0IAgdUw66-HuLOUprZjavvFp2rwuAC7gqEujg3wtjO7VR82NNoVv2dxByTdrzG_eOk8QKTJYBoM50ztT6WFs-qWetZIoQDxMJRA9ObfEVyJ4Bq2b4qelfuhAqbQWRNfCvqhoRPMJI6Y7-R94FuO1zg4d4Gm3J0qHj7r8Q'; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], - }).compile(); - - service = module.get(AuthService); + expect(await service.getVerifiedIdToken(token)).toEqual(idTokenPayload); }); - it('should be defined', () => { - expect(service).toBeDefined(); + it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = 'invalid.id.token'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException(makeErrorResponse('E000101'), HttpStatus.UNAUTHORIZED), + ); + }); + + it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaXNzdWVyIn0.pk9-POXmo3ie7wfqizRr2NSX7YGEJed3krdMImzKp5qeRfsEJwM6zTSzjL_gbLQyMEDg3IoyWzVVUha4sdPcummtt8gJ6N25s_H9taFY-xJjx_-wmttGQvXD44apyUF_c_Xg0l7MzNQEqpnzsLdDcBITSN-Rk-bT_n9U4dxhYaWukOPfAPf7DzSxT9TCpTaCTPIAKp2JXVU41J9uv5WklhNjMSv9L5jWT-IstEC71-DO6jD6yFpAOLIG6Aq-C7rQolt_Cny9qKugu6hd7_Aj-YrW9773khUOyFotJ7Qs9g-qSEMy7M9ljvai2eu5Otfja3hlS5wF1VH_ENsXye6C0A'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException(makeErrorResponse('E000102'), HttpStatus.UNAUTHORIZED), + ); + }); + + it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwiaXNzIjoiaXNzdWVyIn0.pSk3U8Wn_XTm6KxvdQpqQuGROjanLxLGsnRGg35jO4iPVysHKVWya7Leik3aTtK8lGCSoapodQoV4EXbP5CzBf5O83l9tKaF2pwEO_C9QePlLJIJMaCmqRFrr1ozcVFQNwYAr81KNmXBuEEG5duT36Fk2A9-PLDtwg816J4nMEnd1umgCSRTKOdxh265ybDxX1Pe8KDtzlCi8Wjj-lhFwxskpKD3xlFl2plhW9_P7eq-DJXtm1fvLk27zxwOq8uaaxu6sfXoeu1D4qi4aZ0MF-5eaOezc8KH-aYgU9o5yQCZH6tJJDrZvzi7GAFTd-c952V9jgPZpifEoDMRJXS5zA'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException(makeErrorResponse('E000103'), HttpStatus.UNAUTHORIZED), + ); + }); + + it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaXNzdWVyIn0.sign'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException(makeErrorResponse('E000104'), HttpStatus.UNAUTHORIZED), + ); + }); + + it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaW52YWxpZCJ9.jmwBBc2r55YOr88W_4aRzFccmtmQfQRLob7YGM4bOPMGK-ExXJQG24CiMpODZEvVTtQKucEd2cBB6zzLYGCiKRHig8iM_EV57klMwKrczdL2ov8Hc3IyA407idDxDi0KyyveEctxEUdl3PgbY54CVc6URhUAzGdcQ2mKMRVe-Zxb7CrFYqwiHUCmCnSRFUYj_9kkI3epkdsXPsLyDApDEbLEeD1ztnoyYrbfmdce6flpf-h1r2VK4dMz8m4GMOLQHM9gWSzRUkUsN3hGOKTofUxCKC4CArfSP1vZ5k9FZrjqOZn1m6bxKalCox2n96GqLtuFV3-sOTnCqgHj0fQEJA'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException(makeErrorResponse('E000105'), HttpStatus.UNAUTHORIZED), + ); + }); + + it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(メタデータ)', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + adb2cParam.getMetaData = new Error('failed get metadata'); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaXNzdXNlciJ9.dURTqFfUc94qYNFS65xMqMT3tJi-uH8weJ5GU74F-UvhLKJmqVoNIbXCm-ASg2mZ5SxeX683q2MMrlzxNcNel-QIFTj0zWem4eLKuarsYRMTU8x27jMss0b_JkFtiy5KE2052_fLeW2aZBb4AjieIW34-c0Ol1rrih-W9Pk6ZEOwf14Ju0IAgdUw66-HuLOUprZjavvFp2rwuAC7gqEujg3wtjO7VR82NNoVv2dxByTdrzG_eOk8QKTJYBoM50ztT6WFs-qWetZIoQDxMJRA9ObfEVyJ4Bq2b4qelfuhAqbQWRNfCvqhoRPMJI6Y7-R94FuO1zg4d4Gm3J0qHj7r8Q'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(キーセット)', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + adb2cParam.getSignKeySets = new Error('failed get keyset'); + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaXNzdXNlciJ9.dURTqFfUc94qYNFS65xMqMT3tJi-uH8weJ5GU74F-UvhLKJmqVoNIbXCm-ASg2mZ5SxeX683q2MMrlzxNcNel-QIFTj0zWem4eLKuarsYRMTU8x27jMss0b_JkFtiy5KE2052_fLeW2aZBb4AjieIW34-c0Ol1rrih-W9Pk6ZEOwf14Ju0IAgdUw66-HuLOUprZjavvFp2rwuAC7gqEujg3wtjO7VR82NNoVv2dxByTdrzG_eOk8QKTJYBoM50ztT6WFs-qWetZIoQDxMJRA9ObfEVyJ4Bq2b4qelfuhAqbQWRNfCvqhoRPMJI6Y7-R94FuO1zg4d4Gm3J0qHj7r8Q'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); + }); + + it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => { + const adb2cParam = makeDefaultAdB2cMockValue(); + adb2cParam.getSignKeySets = [ + { kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' }, + ]; + const cryptoParam = makeDefaultCryptoMockValue(); + const service = await makeAuthServiceMock(adb2cParam, cryptoParam); + const token = + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwiaXNzIjoiaXNzdXNlciJ9.dURTqFfUc94qYNFS65xMqMT3tJi-uH8weJ5GU74F-UvhLKJmqVoNIbXCm-ASg2mZ5SxeX683q2MMrlzxNcNel-QIFTj0zWem4eLKuarsYRMTU8x27jMss0b_JkFtiy5KE2052_fLeW2aZBb4AjieIW34-c0Ol1rrih-W9Pk6ZEOwf14Ju0IAgdUw66-HuLOUprZjavvFp2rwuAC7gqEujg3wtjO7VR82NNoVv2dxByTdrzG_eOk8QKTJYBoM50ztT6WFs-qWetZIoQDxMJRA9ObfEVyJ4Bq2b4qelfuhAqbQWRNfCvqhoRPMJI6Y7-R94FuO1zg4d4Gm3J0qHj7r8Q'; + + await expect(service.getVerifiedIdToken(token)).rejects.toEqual( + new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ), + ); }); }); + +const idTokenPayload = { + exp: 9000000000, + nbf: 1000000000, + iss: 'issuser', +}; diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index a41c649..6684355 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -1,4 +1,123 @@ -import { Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; +import jwt from 'jsonwebtoken'; +import { makeErrorResponse } from '../../common/error/makeErrorResponse'; +import { AdB2cService } from '../../gateways/adb2c/adb2c.service'; +import { CryptoService } from '../../gateways/crypto/crypto.service'; @Injectable() -export class AuthService {} +export class AuthService { + constructor( + private adB2cService: AdB2cService, + private cryptoService: CryptoService, + ) {} + private readonly logger = new Logger(AuthService.name); + + async getVerifiedIdToken(token: string): Promise { + this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`); + + let kid = ''; + try { + // JWTトークンのヘッダを見るため一度デコードする + const decodedToken = jwt.decode(token, { complete: true }); + kid = decodedToken.header.kid; + } catch (e) { + this.logger.error(`error=${e}`); + throw new HttpException( + makeErrorResponse('E000101'), + HttpStatus.UNAUTHORIZED, + ); + } + + let issuer = ''; + try { + const metadata = await this.adB2cService.getMetaData(); + const keySets = await this.adB2cService.getSignKeySets(); + + issuer = metadata.issuer; + + const jwkKey = keySets.find((x) => x.kid === kid); + + if (!jwkKey) { + throw new Error('Public Key Not Found.'); + } + + const publicKey = await this.cryptoService.getPublicKeyFromJwk(jwkKey); + + const verifiedToken = jwt.verify(token, publicKey, { + algorithms: ['RS256'], + issuer: [issuer], + }); + + return verifiedToken; + } catch (e) { + if (e instanceof Error) { + const { name, message } = e; + this.logger.error(`error=${name}: ${message}`); + + switch (e.constructor) { + case jwt.TokenExpiredError: + throw new HttpException( + makeErrorResponse('E000102'), + HttpStatus.UNAUTHORIZED, + ); + case jwt.NotBeforeError: + throw new HttpException( + makeErrorResponse('E000103'), + HttpStatus.UNAUTHORIZED, + ); + case jwt.JsonWebTokenError: + // メッセージごとにエラーを判定しHTTPエラーを生成 + throw this.makeHttpErrorFromJsonWebTokenErrorMessage( + message, + issuer, + ); + default: + break; + } + } else { + this.logger.error(`error=${e}`); + } + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log(`[OUT] ${this.getVerifiedIdToken.name}`); + } + } + + /** + * JWT検証時のError、JsonWebTokenErrorをメッセージごとに仕分けてHTTPエラーを生成 + */ + makeHttpErrorFromJsonWebTokenErrorMessage = ( + message: string, + issuer: string, + ): Error => { + // 署名が不正 + if (message === 'invalid signature') { + return new HttpException( + makeErrorResponse('E000104'), + HttpStatus.UNAUTHORIZED, + ); + } + // 想定発行元と異なる + if (message === `jwt issuer invalid. expected: ${issuer}`) { + return new HttpException( + makeErrorResponse('E000105'), + HttpStatus.UNAUTHORIZED, + ); + } + // アルゴリズムが想定と異なる + if (message === 'invalid algorithm') { + return new HttpException( + makeErrorResponse('E000106'), + HttpStatus.UNAUTHORIZED, + ); + } + // トークンの形式が不正 + return new HttpException( + makeErrorResponse('E000101'), + HttpStatus.UNAUTHORIZED, + ); + }; +} diff --git a/dictation_server/src/features/auth/test/auth.service.mock.ts b/dictation_server/src/features/auth/test/auth.service.mock.ts new file mode 100644 index 0000000..c7006f8 --- /dev/null +++ b/dictation_server/src/features/auth/test/auth.service.mock.ts @@ -0,0 +1,100 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AdB2cService } from '../../../gateways/adb2c/adb2c.service'; +import { CryptoService } from '../../../gateways/crypto/crypto.service'; +import { JwkSignKey, B2cMetadata } from '../../../common/token'; +import { AuthService } from '../auth.service'; + +export type AdB2cMockValue = { + getMetaData: B2cMetadata | Error; + getSignKeySets: JwkSignKey[] | Error; +}; + +export type CryptoMockValue = { + getPublicKeyFromJwk: string | Error; +}; + +export const makeAuthServiceMock = async ( + adB2cMockValue: AdB2cMockValue, + cryptoMockValue: CryptoMockValue, +): Promise => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AuthService], + }) + .useMocker((token) => { + switch (token) { + case AdB2cService: + return makeAdB2cServiceMock(adB2cMockValue); + case CryptoService: + return makeCryptoServiceMock(cryptoMockValue); + } + }) + .compile(); + + return module.get(AuthService); +}; + +export const makeDefaultAdB2cMockValue = (): AdB2cMockValue => { + return { + getMetaData: { + issuer: 'issuser', + }, + getSignKeySets: [ + { + kid: 'kid', + nbf: 1111111111, + use: 'sig', + kty: 'RSA', + e: 'e', + n: 'n', + }, + ], + }; +}; + +export const makeAdB2cServiceMock = (value: AdB2cMockValue) => { + const { getMetaData, getSignKeySets } = value; + + return { + getMetaData: + getMetaData instanceof Error + ? jest.fn, []>().mockRejectedValue(getMetaData) + : jest.fn, []>().mockResolvedValue(getMetaData), + getSignKeySets: + getSignKeySets instanceof Error + ? jest.fn, []>().mockRejectedValue(getSignKeySets) + : jest + .fn, []>() + .mockResolvedValue(getSignKeySets), + }; +}; + +export const makeDefaultCryptoMockValue = (): CryptoMockValue => { + return { + getPublicKeyFromJwk: [ + '-----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'), + }; +}; + +export const makeCryptoServiceMock = (value: CryptoMockValue) => { + const { getPublicKeyFromJwk } = value; + + return { + getPublicKeyFromJwk: + getPublicKeyFromJwk instanceof Error + ? jest + .fn, [JwkSignKey]>() + .mockRejectedValue(getPublicKeyFromJwk) + : jest + .fn, [JwkSignKey]>() + .mockResolvedValue(getPublicKeyFromJwk), + }; +}; diff --git a/dictation_server/src/gateways/adb2c/adb2c.module.ts b/dictation_server/src/gateways/adb2c/adb2c.module.ts new file mode 100644 index 0000000..95d40fb --- /dev/null +++ b/dictation_server/src/gateways/adb2c/adb2c.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { AdB2cService } from './adb2c.service'; + +@Module({ + imports: [ConfigModule], + exports: [AdB2cService], + providers: [AdB2cService], +}) +export class AdB2cModule {} diff --git a/dictation_server/src/gateways/adb2c/adb2c.service.ts b/dictation_server/src/gateways/adb2c/adb2c.service.ts new file mode 100644 index 0000000..8344754 --- /dev/null +++ b/dictation_server/src/gateways/adb2c/adb2c.service.ts @@ -0,0 +1,60 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import axios from 'axios'; +import { JwkSignKey, B2cMetadata } from '../../common/token'; + +@Injectable() +export class AdB2cService { + constructor(private readonly configService: ConfigService) {} + private readonly logger = new Logger(AdB2cService.name); + private readonly tenantName = this.configService.get('TENANT_NAME'); + private readonly flowName = + this.configService.get('SIGNIN_FLOW_NAME'); + + /** + * ADB2Cのメタデータを取得する + * @returns meta data + */ + async getMetaData(): Promise { + this.logger.log(`[IN] ${this.getMetaData.name}`); + try { + // Azure AD B2Cのメタデータを取得する。 以下のURLから取得できる。 + // https://<テナント名>.b2clogin.com/<テナント名>.onmicrosoft.com/<ユーザーフロー名>/v2.0/.well-known/openid-configuration + const metaData = await axios + .get( + `https://${this.tenantName}.b2clogin.com/${this.tenantName}.onmicrosoft.com/${this.flowName}/v2.0/.well-known/openid-configuration`, + ) + .then((res) => res.data); + + return metaData; + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + this.logger.log(`[OUT] ${this.getMetaData.name}`); + } + } + + /** + * IDトークンの署名キーセットを取得する + * @returns sign key sets + */ + async getSignKeySets(): Promise { + this.logger.log(`[IN] ${this.getSignKeySets.name}`); + try { + // 署名キーのキーセット配列を取得する。 以下のURLから取得できる。 + const keySets = await axios + .get( + `https://${this.tenantName}.b2clogin.com/${this.tenantName}.onmicrosoft.com/${this.flowName}/discovery/v2.0/keys`, + ) + .then((res) => res.data.keys); + + return keySets; + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + this.logger.log(`[OUT] ${this.getSignKeySets.name}`); + } + } +} diff --git a/dictation_server/src/gateways/crypto/crypto.module.ts b/dictation_server/src/gateways/crypto/crypto.module.ts new file mode 100644 index 0000000..2d98065 --- /dev/null +++ b/dictation_server/src/gateways/crypto/crypto.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CryptoService } from './crypto.service'; + +@Module({ + imports: [ConfigModule], + exports: [CryptoService], + providers: [CryptoService], +}) +export class CryptoModule {} diff --git a/dictation_server/src/gateways/crypto/crypto.service.ts b/dictation_server/src/gateways/crypto/crypto.service.ts new file mode 100644 index 0000000..58bea44 --- /dev/null +++ b/dictation_server/src/gateways/crypto/crypto.service.ts @@ -0,0 +1,92 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import jwkToPem from 'jwk-to-pem'; +import { JwkSignKey } from 'src/common/token'; + +@Injectable() +export class CryptoService { + constructor(private readonly configService: ConfigService) {} + private readonly logger = new Logger(CryptoService.name); + + 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; + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + this.logger.log(`[OUT] ${this.getPublicKey.name}`); + } + } + + 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; + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + this.logger.log(`[OUT] ${this.getPublicKey.name}`); + } + } + + async getPublicKeyFromJwk(jwkKey: JwkSignKey): Promise { + try { + // JWK形式のJSONなのでJWTの公開鍵として使えるようにPEM形式に変換 + const publicKey = jwkToPem({ + kty: 'RSA', + n: jwkKey.n, + e: jwkKey.e, + }); + + return publicKey; + } catch (e) { + this.logger.error(`error=${e}`); + throw e; + } finally { + this.logger.log(`[OUT] ${this.getPublicKey.name}`); + } + } +} diff --git a/dictation_server/src/main.ts b/dictation_server/src/main.ts index 5619f07..3c685c5 100644 --- a/dictation_server/src/main.ts +++ b/dictation_server/src/main.ts @@ -1,5 +1,5 @@ import { NestFactory } from '@nestjs/core'; -import * as cookieParser from 'cookie-parser'; +import cookieParser from 'cookie-parser'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; @@ -12,7 +12,12 @@ async function bootstrap() { app.use(new LoggerMiddleware(), cookieParser()); // バリデーター(+型の自動変換機能)を適用 - app.useGlobalPipes(new ValidationPipe({ transform: true })); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + forbidUnknownValues: false, + }), + ); if (process.env.STAGE === 'local') { const options = new DocumentBuilder() diff --git a/dictation_server/tsconfig.json b/dictation_server/tsconfig.json index adb614c..0c28902 100644 --- a/dictation_server/tsconfig.json +++ b/dictation_server/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "esModuleInterop": true } }