diff --git a/dictation_client/src/components/auth/constants.ts b/dictation_client/src/components/auth/constants.ts index cf2b972..ef98041 100644 --- a/dictation_client/src/components/auth/constants.ts +++ b/dictation_client/src/components/auth/constants.ts @@ -39,6 +39,12 @@ export const UNAUTHORIZED_TO_CONTINUE_ERROR_CODES = [ "E010501", ]; +/** + * ローカルストレージに残すキー類 + * @const {string[]} + */ +export const KEYS_TO_PRESERVE = ["accessToken", "refreshToken", "displayInfo"]; + /** * アクセストークンを更新する基準の秒数 * @const {number} diff --git a/dictation_client/src/features/login/operations.ts b/dictation_client/src/features/login/operations.ts index cc7c65c..08a3a06 100644 --- a/dictation_client/src/features/login/operations.ts +++ b/dictation_client/src/features/login/operations.ts @@ -1,6 +1,7 @@ import { createAsyncThunk } from "@reduxjs/toolkit"; import type { RootState } from "app/store"; import { setToken } from "features/auth"; +import { KEYS_TO_PRESERVE } from "components/auth/constants"; import { AuthApi, UsersApi, @@ -42,7 +43,18 @@ export const loginAsync = createAsyncThunk< refreshToken: data.refreshToken, }) ); + // ローカルストレージに残すキー + const keysToPreserve = KEYS_TO_PRESERVE; + // すべてのローカルストレージキーを取得 + const allKeys = Object.keys(localStorage); + + // 特定のキーを除外して削除 + allKeys.forEach((key) => { + if (!keysToPreserve.includes(key)) { + localStorage.removeItem(key); + } + }); return data; } catch (e) { // e ⇒ errorObjectに変換" diff --git a/dictation_server/src/common/cache/constants.ts b/dictation_server/src/common/cache/constants.ts index b79dd78..9f588f6 100644 --- a/dictation_server/src/common/cache/constants.ts +++ b/dictation_server/src/common/cache/constants.ts @@ -1 +1,3 @@ export const ADB2C_PREFIX = 'adb2c-external-id:'; + +export const IDTOKEN_PREFIX = 'id-token:'; diff --git a/dictation_server/src/common/cache/index.ts b/dictation_server/src/common/cache/index.ts index 3355a54..4985492 100644 --- a/dictation_server/src/common/cache/index.ts +++ b/dictation_server/src/common/cache/index.ts @@ -1,4 +1,4 @@ -import { ADB2C_PREFIX } from './constants'; +import { ADB2C_PREFIX, IDTOKEN_PREFIX } from './constants'; /** * ADB2Cのユーザー格納用のキーを生成する @@ -17,3 +17,12 @@ export const makeADB2CKey = (externalId: string): string => { export const restoreAdB2cID = (key: string): string => { return key.replace(ADB2C_PREFIX, ''); }; + +/** + * ADB2CのIDトークン格納用のキーを生成する + * @param idToken IDトークン + * @returns キャッシュのキー + */ +export const makeIDTokenKey = (idToken: string): string => { + return `${IDTOKEN_PREFIX}${idToken}`; +}; diff --git a/dictation_server/src/common/test/modules.ts b/dictation_server/src/common/test/modules.ts index 9c3d578..61c0396 100644 --- a/dictation_server/src/common/test/modules.ts +++ b/dictation_server/src/common/test/modules.ts @@ -38,6 +38,8 @@ import { TermsService } from '../../features/terms/terms.service'; import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; import { TermsModule } from '../../features/terms/terms.module'; import { CacheModule } from '@nestjs/common'; +import { RedisModule } from '../../gateways/redis/redis.module'; +import { RedisService } from '../../gateways/redis/redis.service'; export const makeTestingModule = async ( datasource: DataSource, @@ -77,6 +79,7 @@ export const makeTestingModule = async ( SortCriteriaRepositoryModule, WorktypesRepositoryModule, TermsRepositoryModule, + RedisModule, CacheModule.register({ isGlobal: true }), ], providers: [ @@ -90,6 +93,7 @@ export const makeTestingModule = async ( TemplatesService, WorkflowsService, TermsService, + RedisService, ], }) .useMocker(async (token) => { diff --git a/dictation_server/src/features/auth/auth.controller.ts b/dictation_server/src/features/auth/auth.controller.ts index a07b666..32d7697 100644 --- a/dictation_server/src/features/auth/auth.controller.ts +++ b/dictation_server/src/features/auth/auth.controller.ts @@ -33,6 +33,8 @@ import { RoleGuard } from '../../common/guards/role/roleguards'; import { ADMIN_ROLES, TIERS } from '../../constants'; import jwt from 'jsonwebtoken'; import { AccessToken, RefreshToken } from '../../common/token'; +import { makeIDTokenKey } from '../../common/cache'; +import { RedisService } from '../../gateways/redis/redis.service'; @ApiTags('auth') @Controller('auth') @@ -41,6 +43,7 @@ export class AuthController { // TODO「タスク 1828: IDトークンを一度しか使えないようにする」で使用する予定 // private readonly redisService: RedisService, private readonly authService: AuthService, + private readonly redisService: RedisService, ) {} @Post('token') @@ -77,6 +80,18 @@ export class AuthController { const context = makeContext(uuidv4()); + const key = makeIDTokenKey(body.idToken); + const isTokenExists = await this.redisService.get(key); + if (!isTokenExists) { + // IDトークンがキャッシュに存在しない場合(idTokenの有効期限をADB2Cの有効期限と合わせる(300秒)) + await this.redisService.set(key, true, 300); + } else { + // IDトークンがキャッシュに存在する場合エラー + throw new HttpException( + makeErrorResponse('E000106'), + HttpStatus.UNAUTHORIZED, + ); + } // 同意済み利用規約バージョンが最新かチェック const isAcceptedLatestVersion = await this.authService.isAcceptedLatestVersion(context, idToken); diff --git a/dictation_server/src/features/auth/auth.module.ts b/dictation_server/src/features/auth/auth.module.ts index 2e3dc4c..d86bf30 100644 --- a/dictation_server/src/features/auth/auth.module.ts +++ b/dictation_server/src/features/auth/auth.module.ts @@ -4,15 +4,10 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module'; import { UsersRepositoryModule } from '../../repositories/users/users.repository.module'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; -import { TermsRepositoryModule } from '../../repositories/terms/terms.repository.module'; +import { RedisService } from '../../gateways/redis/redis.service'; @Module({ - imports: [ - ConfigModule, - AdB2cModule, - UsersRepositoryModule, - TermsRepositoryModule, - ], + imports: [ConfigModule, AdB2cModule, UsersRepositoryModule], controllers: [AuthController], - providers: [AuthService], + providers: [AuthService, RedisService], }) export class AuthModule {}