From 731c63318926d3e01711da50eecd0cf940cf7c26 Mon Sep 17 00:00:00 2001 From: "makabe.t" Date: Fri, 31 Mar 2023 07:46:56 +0000 Subject: [PATCH] =?UTF-8?q?Merged=20PR=2057:=20=E3=83=AD=E3=82=B0=E3=82=A4?= =?UTF-8?q?=E3=83=B3API=E3=82=92=E4=BF=AE=E6=AD=A3=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 概要 [Task1504: ログインAPIを修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1504) - ログイン時にIDトークンをもとにメールアドレスで認証済みのユーザかをDBから取得して判定する処理を追加しました ## レビューポイント - IDトークンのSubをもとにUserテーブルの`external_id`でユーザを特定しているが利用法として適切か - DBからのクエリに問題はないか - ※アカウント登録の処理が含まれていますが、以下の変更についてご確認をお願いします。 - feature/auth/* - repositories/users/* - common/error ## UIの変更 - なし ## 動作確認状況 - ローカルで確認 --- dictation_server/.env.local.example | 2 +- dictation_server/src/common/error/code.ts | 1 + dictation_server/src/common/error/message.ts | 1 + .../src/features/auth/auth.controller.ts | 9 +++++++ .../src/features/auth/auth.module.ts | 3 ++- .../src/features/auth/auth.service.ts | 24 +++++++++++++++++++ .../users/users.repository.service.ts | 14 +++++++++++ 7 files changed, 52 insertions(+), 2 deletions(-) diff --git a/dictation_server/.env.local.example b/dictation_server/.env.local.example index 59fb71a..5140b0c 100644 --- a/dictation_server/.env.local.example +++ b/dictation_server/.env.local.example @@ -8,4 +8,4 @@ KEY_VAULT_NAME=kv-odms-secret-dev JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5IZZNgDew9eGmuFTezwdHYLSaJvUPPIKYoiOeVLD1paWNI51\n7Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3yCTR6wcWR3PfFJrl9vh5SOo79koZ\noJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbWFJXnDe0DVXYXpJLb4LAlF2XAyYX0\nSYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qSfiL9zWk9dvHoKzSnfSDzDFoFcEoV\nchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//mBNNaDHv83Yuw3mGShT73iJ0JQdk\nTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GOOQIDAQABAoIBADrwp7u097+dK/tw\nWD61n3DIGAqg/lmFt8X4IH8MKLSE/FKr16CS1bqwOEuIM3ZdUtDeXd9Xs7IsyEPE\n5ZwuXK7DSF0M4+Mj8Ip49Q0Aww9aUoLQU9HGfgN/r4599GTrt31clZXA/6Mlighq\ncOZgCcEfdItz8OMu5SQuOIW4CKkCuaWnPOP26UqZocaXNZfpZH0iFLATMMH/TT8x\nay9ToHTQYE17ijdQ/EOLSwoeDV1CU1CIE3P4YfLJjvpKptly5dTevriHEzBi70Jx\n/KEPUn9Jj2gZafrUxRVhmMbm1zkeYxL3gsqRuTzRjEeeILuZhSJyCkQZyUNARxsg\nQY4DZfECgYEA+YLKUtmYTx60FS6DJ4s31TAsXY8kwhq/lB9E3GBZKDd0DPayXEeK\n4UWRQDTT6MI6fedW69FOZJ5sFLp8HQpcssb4Weq9PCpDhNTx8MCbdH3Um5QR3vfW\naKq/1XM8MDUnx5XcNYd87Aw3azvJAvOPr69as8IPnj6sKaRR9uQjbYUCgYEA6nfV\n5j0qmn0EJXZJblk4mvvjLLoWSs17j9YlrZJlJxXMDFRYtgnelv73xMxOMvcGoxn5\nifs7dpaM2x5EmA6jVU5sYaB/beZGEPWqPYGyjIwXPvUGAAv8Gbnvpp+xlSco/Dum\nIq0w+43ry5/xWh6CjfrvKV0J2bDOiJwPEdu/8iUCgYEAnBBSvL+dpN9vhFAzeOh7\nY71eAqcmNsLEUcG9MJqTKbSFwhYMOewF0iHRWHeylEPokhfBJn8kqYrtz4lVWFTC\n5o/Nh3BsLNXCpbMMIapXkeWiti1HgE9ErPMgSkJpwz18RDpYIqM8X+jEQS6D7HSr\nyxfDg+w+GJza0rEVE3hfMIECgYBw+KZ2VfhmEWBjEHhXE+QjQMR3s320MwebCUqE\nNCpKx8TWF/naVC0MwfLtvqbbBY0MHyLN6d//xpA9r3rLbRojqzKrY2KiuDYAS+3n\nzssRzxoQOozWju+8EYu30/ADdqfXyIHG6X3VZs87AGiQzGyJLmP3oR1y5y7MQa09\nJI16hQKBgHK5uwJhGa281Oo5/FwQ3uYLymbNwSGrsOJXiEu2XwJEXwVi2ELOKh4/\n03pBk3Kva3fIwEK+vCzDNnxShIQqBE76/2I1K1whOfoUehhYvKHGaXl2j70Zz9Ks\nrkGW1cx7p+yDqATDrwHBHTHFh5bUTTn8dN40n0e0W/llurpbBkJM\n-----END RSA PRIVATE KEY-----\n" JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd\nHYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3\nyCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW\nFJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS\nfiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//\nmBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO\nOQIDAQAB\n-----END PUBLIC KEY-----\n" SENDGRID_API_KEY=xxxxxxxxxxxxxxxx -MAIL_FROM=xxxxx@xxxxx.xxxx \ No newline at end of file +MAIL_FROM=xxxxx@xxxxx.xxxx diff --git a/dictation_server/src/common/error/code.ts b/dictation_server/src/common/error/code.ts index bf4eb59..56c25b6 100644 --- a/dictation_server/src/common/error/code.ts +++ b/dictation_server/src/common/error/code.ts @@ -19,4 +19,5 @@ export const ErrorCodes = [ 'E000104', // トークン署名エラー 'E000105', // トークン発行元エラー 'E000106', // トークンアルゴリズムエラー + 'E010201', // 未認証ユーザエラー ] as const; diff --git a/dictation_server/src/common/error/message.ts b/dictation_server/src/common/error/message.ts index 085b6c2..bdb49d1 100644 --- a/dictation_server/src/common/error/message.ts +++ b/dictation_server/src/common/error/message.ts @@ -9,4 +9,5 @@ export const errors: Errors = { E000104: 'Token invalid signature Error.', E000105: 'Token invalid issuer Error.', E000106: 'Token invalid algorithm Error.', + E010201: 'Email not verified user Error.', }; diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index 854bed0..245f5de 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -46,6 +46,15 @@ export class AuthController { async token(@Body() body: TokenRequest): Promise { console.log(body); const idToken = await this.authService.getVerifiedIdToken(body.idToken); + + const isVerified = await this.authService.isVerifiedUser(idToken); + if (!isVerified) { + throw new HttpException( + makeErrorResponse('E010201'), + HttpStatus.BAD_REQUEST, + ); + } + const refreshToken = await this.authService.generateRefreshToken( idToken, body.type, diff --git a/dictation_server/src/features/auth/auth.module.ts b/dictation_server/src/features/auth/auth.module.ts index a1a4135..25d0e08 100644 --- a/dictation_server/src/features/auth/auth.module.ts +++ b/dictation_server/src/features/auth/auth.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; +import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; import { AdB2cModule } from '../../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], + imports: [CryptoModule, AdB2cModule, UsersRepositoryModule], controllers: [AuthController], providers: [AuthService], }) diff --git a/dictation_server/src/features/auth/auth.service.ts b/dictation_server/src/features/auth/auth.service.ts index 4f4b3fe..8cd5651 100644 --- a/dictation_server/src/features/auth/auth.service.ts +++ b/dictation_server/src/features/auth/auth.service.ts @@ -1,6 +1,7 @@ import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import jwt from 'jsonwebtoken'; +import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { isVerifyError, sign, verify } from '../../common/jwt'; import { @@ -18,8 +19,31 @@ export class AuthService { private readonly cryptoService: CryptoService, private readonly adB2cService: AdB2cService, private readonly configService: ConfigService, + private readonly usersRepository: UsersRepositoryService, ) {} private readonly logger = new Logger(AuthService.name); + /** + * Determines whether verified user is + * @param idToken AzureAD B2Cにより発行されたIDトークン + * @returns verified user + */ + async isVerifiedUser(idToken: IDToken): Promise { + this.logger.log(`[IN] ${this.isVerifiedUser.name}`); + try { + // IDトークンのユーザーがDBに登録されていてメール認証が完了しているユーザーか検証 + const user = await this.usersRepository.findVerifiedUser(idToken.sub); + + return user !== undefined; + } catch (e) { + this.logger.error(`error=${e}`); + throw new HttpException( + makeErrorResponse('E009999'), + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } finally { + this.logger.log(`[OUT] ${this.isVerifiedUser.name}`); + } + } /** * Generates refresh token diff --git a/dictation_server/src/repositories/users/users.repository.service.ts b/dictation_server/src/repositories/users/users.repository.service.ts index 296b35a..302fad2 100644 --- a/dictation_server/src/repositories/users/users.repository.service.ts +++ b/dictation_server/src/repositories/users/users.repository.service.ts @@ -30,4 +30,18 @@ export class UsersRepositoryService { ); return createdEntity; } + + async findVerifiedUser(sub: string): Promise { + const user = await this.dataSource.getRepository(User).findOne({ + where: { + external_id: sub, + email_verified: true, + }, + }); + + if (!user) { + return undefined; + } + return user; + } }