import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { makeErrorResponse } from '../../common/error/makeErrorResponse'; import { isVerifyError, verify } from '../../common/jwt'; import { getPublicKey } from '../../common/jwt/jwt'; import { makePassword } from '../../common/password/password'; import { SortDirection, TaskListSortableAttribute, isSortDirection, isTaskListSortableAttribute, } from '../../common/types/sort'; import { AdB2cService, ConflictError, isConflictError, } from '../../gateways/adb2c/adb2c.service'; import { SendGridService } from '../../gateways/sendgrid/sendgrid.service'; import { SortCriteriaRepositoryService } from '../../repositories/sort_criteria/sort_criteria.repository.service'; import { User as EntityUser, newUser, } from '../../repositories/users/entity/user.entity'; import { UsersRepositoryService } from '../../repositories/users/users.repository.service'; import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service'; import { GetRelationsResponse, User } from './types/types'; import { AuthorIdAlreadyExistsError, EmailAlreadyVerifiedError, EncryptionPasswordNeedError, InvalidRoleChangeError, UpdateTermsVersionNotSetError, UserNotFoundError, } from '../../repositories/users/errors/types'; import { ADB2C_SIGN_IN_TYPE, LICENSE_EXPIRATION_THRESHOLD_DAYS, MANUAL_RECOVERY_REQUIRED, OPTION_ITEM_VALUE_TYPE_NUMBER, USER_AUDIO_FORMAT, USER_LICENSE_STATUS, USER_ROLES, } from '../../constants'; import { DateWithZeroTime } from '../licenses/types/types'; import { Context } from '../../common/log'; import { UserRoles } from '../../common/types/role'; import { LicenseAlreadyDeallocatedError, LicenseExpiredError, LicenseUnavailableError, } from '../../repositories/licenses/errors/types'; import { AccountNotFoundError } from '../../repositories/accounts/errors/types'; @Injectable() export class UsersService { private readonly logger = new Logger(UsersService.name); private readonly mailFrom: string; private readonly appDomain: string; constructor( private readonly usersRepository: UsersRepositoryService, private readonly licensesRepository: LicensesRepositoryService, private readonly sortCriteriaRepository: SortCriteriaRepositoryService, private readonly adB2cService: AdB2cService, private readonly configService: ConfigService, private readonly sendgridService: SendGridService, ) { this.mailFrom = this.configService.getOrThrow('MAIL_FROM'); this.appDomain = this.configService.getOrThrow('APP_DOMAIN'); } /** * Confirms user * @param token ユーザ仮登録時に払いだされるトークン */ async confirmUser(context: Context, token: string): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${this.confirmUser.name}`, ); const pubKey = getPublicKey(this.configService); const decodedToken = verify<{ accountId: number; userId: number; email: string; }>(token, pubKey); if (isVerifyError(decodedToken)) { throw new HttpException( makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST, ); } try { // トランザクションで取得と更新をまとめる const userId = decodedToken.userId; await this.usersRepository.updateUserVerifiedAndCreateTrialLicense( userId, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case EmailAlreadyVerifiedError: throw new HttpException( makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } /** * Creates user * @param accessToken * @param name * @param role * @param email * @param autoRenew * @param licenseAlert * @param notification * @param [authorId] * @param [encryption] * @param [encryptionPassword] * @param [prompt] * @returns void */ async createUser( context: Context, externalId: string, name: string, role: UserRoles, email: string, autoRenew: boolean, licenseAlert: boolean, notification: boolean, authorId?: string | undefined, encryption?: boolean | undefined, encryptionPassword?: string | undefined, prompt?: boolean | undefined, ): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${this.createUser.name} | params: { ` + `externalId: ${externalId}, ` + `role: ${role}, ` + `autoRenew: ${autoRenew}, ` + `licenseAlert: ${licenseAlert}, ` + `notification: ${notification}, ` + `authorId: ${authorId}, ` + `encryption: ${encryption}, ` + `prompt: ${prompt} };`, ); //DBよりアクセス者の所属するアカウントIDを取得する let adminUser: EntityUser; try { adminUser = await this.usersRepository.findUserByExternalId(externalId); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } const accountId = adminUser.account_id; //authorIdが重複していないかチェックする if (authorId) { let isAuthorIdDuplicated = false; try { isAuthorIdDuplicated = await this.usersRepository.existsAuthorId( accountId, authorId, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } if (isAuthorIdDuplicated) { throw new HttpException( makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST, ); } } // ランダムなパスワードを生成する const ramdomPassword = makePassword(); //Azure AD B2Cにユーザーを新規登録する let externalUser: { sub: string } | ConflictError; try { // idpにユーザーを作成 externalUser = await this.adB2cService.createUser( context, email, ramdomPassword, name, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error( `[${context.getTrackingId()}] create externalUser failed`, ); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } // メールアドレス重複エラー if (isConflictError(externalUser)) { throw new HttpException( makeErrorResponse('E010301'), HttpStatus.BAD_REQUEST, ); } //Azure AD B2Cに登録したユーザー情報のID(sub)と受け取った情報を使ってDBにユーザーを登録する let newUser: EntityUser; try { //roleに応じてユーザー情報を作成する const newUserInfo = this.createNewUserInfo( context, role, accountId, externalUser.sub, autoRenew, licenseAlert, notification, authorId, encryption, encryptionPassword, prompt, ); // ユーザ作成 newUser = await this.usersRepository.createNormalUser(newUserInfo); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}]create user failed`); //リカバリー処理 //Azure AD B2Cに登録したユーザー情報を削除する await this.deleteB2cUser(externalUser.sub, context); switch (e.code) { case 'ER_DUP_ENTRY': //AuthorID重複エラー throw new HttpException( makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } //Email送信用のコンテンツを作成する try { // メールの内容を構成 const { subject, text, html } = await this.sendgridService.createMailContentFromEmailConfirmForNormalUser( context, accountId, newUser.id, email, ); //SendGridAPIを呼び出してメールを送信する await this.sendgridService.sendMail( context, email, this.mailFrom, subject, text, html, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); this.logger.error(`[${context.getTrackingId()}] create user failed`); //リカバリー処理 //Azure AD B2Cに登録したユーザー情報を削除する await this.deleteB2cUser(externalUser.sub, context); // DBからユーザーを削除する await this.deleteUser(newUser.id, context); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.createUser.name}`, ); return; } // Azure AD B2Cに登録したユーザー情報を削除する // TODO 「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補 private async deleteB2cUser(externalUserId: string, context: Context) { try { await this.adB2cService.deleteUser(externalUserId, context); this.logger.log( `[${context.getTrackingId()}] delete externalUser: ${externalUserId}`, ); } catch (error) { this.logger.error(`[${context.getTrackingId()}] error=${error}`); this.logger.error( `${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`, ); } } // DBに登録したユーザー情報を削除する private async deleteUser(userId: number, context: Context) { try { await this.usersRepository.deleteNormalUser(userId); this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`); } catch (error) { this.logger.error(`[${context.getTrackingId()}] error=${error}`); this.logger.error( `${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user: ${userId}`, ); } } // roleを受け取って、roleに応じたnewUserを作成して返却する private createNewUserInfo( context: Context, role: UserRoles, accountId: number, externalId: string, autoRenew: boolean, licenseAlert: boolean, notification: boolean, authorId?: string | undefined, encryption?: boolean | undefined, encryptionPassword?: string | undefined, prompt?: boolean | undefined, ): newUser { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.createNewUserInfo.name } | params: { ` + `role: ${role}, ` + `accountId: ${accountId}, ` + `authorId: ${authorId}, ` + `externalId: ${externalId}, ` + `autoRenew: ${autoRenew}, ` + `licenseAlert: ${licenseAlert}, ` + `notification: ${notification}, ` + `authorId: ${authorId}, ` + `encryption: ${encryption}, ` + `prompt: ${prompt} };`, ); switch (role) { case USER_ROLES.NONE: case USER_ROLES.TYPIST: return { account_id: accountId, external_id: externalId, auto_renew: autoRenew, license_alert: licenseAlert, notification, role, accepted_dpa_version: null, accepted_eula_version: null, encryption: false, encryption_password: null, prompt: false, author_id: null, }; case USER_ROLES.AUTHOR: return { account_id: accountId, external_id: externalId, auto_renew: autoRenew, license_alert: licenseAlert, notification, role, author_id: authorId ?? null, encryption: encryption ?? false, encryption_password: encryptionPassword ?? null, prompt: prompt ?? false, accepted_dpa_version: null, accepted_eula_version: null, }; default: //不正なroleが指定された場合はログを出力してエラーを返す this.logger.error( `[${context.getTrackingId()}] [NOT IMPLEMENT] [RECOVER] role: ${role}`, ); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } /** * confirm User And Init Password * @param token ユーザ仮登録時に払いだされるトークン */ async confirmUserAndInitPassword( context: Context, token: string, ): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.confirmUserAndInitPassword.name }`, ); const pubKey = getPublicKey(this.configService); const decodedToken = verify<{ accountId: number; userId: number; email: string; }>(token, pubKey); if (isVerifyError(decodedToken)) { throw new HttpException( makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST, ); } // ランダムなパスワードを生成する const ramdomPassword = makePassword(); const { userId, email } = decodedToken; try { // ユーザー情報からAzure AD B2CのIDを特定する const user = await this.usersRepository.findUserById(userId); const extarnalId = user.external_id; // パスワードを変更する await this.adB2cService.changePassword( context, extarnalId, ramdomPassword, ); // ユーザを認証済みにする await this.usersRepository.updateUserVerified(userId); // TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする const subject = 'A temporary password has been issued.'; const text = 'temporary password: ' + ramdomPassword; const html = `

OMDS TOP PAGE URL.

${this.appDomain}
temporary password: ${ramdomPassword}`; // メールを送信 await this.sendgridService.sendMail( context, email, this.mailFrom, subject, text, html, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case EmailAlreadyVerifiedError: throw new HttpException( makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } } } /** * Get Users * @param accessToken * @returns users */ async getUsers(context: Context, externalId: string): Promise { this.logger.log(`[IN] [${context.getTrackingId()}] ${this.getUsers.name}`); try { // DBから同一アカウントのユーザ一覧を取得する const dbUsers = await this.usersRepository.findSameAccountUsers( externalId, ); // DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する const externalIds = dbUsers.map((x) => x.external_id); const trackingId = new Context(context.trackingId); const adb2cUsers = await this.adB2cService.getUsers( // TODO: 外部連携以外のログ強化時に、ContollerからContextを取得するように修正する trackingId, externalIds, ); // DBから取得した各ユーザーをもとにADB2C情報をマージしライセンス情報を算出 const users = dbUsers.map((dbUser): User => { // ユーザーの所属グループ名を取得する const userGroupMembers = dbUser.userGroupMembers !== null ? dbUser.userGroupMembers : []; //所属グループ名の配列にする const groupNames = userGroupMembers.flatMap((userGroupMember) => userGroupMember.userGroup ? [userGroupMember.userGroup.name] : [], ); const adb2cUser = adb2cUsers.find( (user) => user.id === dbUser.external_id, ); // メールアドレスを取得する const mail = adb2cUser?.identities?.find( (identity) => identity.signInType === ADB2C_SIGN_IN_TYPE.EMAILADDRESS, )?.issuerAssignedId; //メールアドレスが取得できない場合はエラー if (!mail) { throw new Error('mail not found.'); } let status = USER_LICENSE_STATUS.NORMAL; // ライセンスの有効期限と残日数は、ライセンスが存在する場合のみ算出する // ライセンスが存在しない場合は、undefinedのままとする let expiration: string | undefined = undefined; let remaining: number | undefined = undefined; if (dbUser.license) { // 有効期限日付 YYYY/MM/DD const expiry_date = dbUser.license.expiry_date ?? undefined; expiration = expiry_date !== undefined ? `${expiry_date.getFullYear()}/${ expiry_date.getMonth() + 1 }/${expiry_date.getDate()}` : undefined; const currentDate = new DateWithZeroTime(); // 有効期限までの日数 remaining = expiry_date !== undefined ? Math.floor( (expiry_date.getTime() - currentDate.getTime()) / (1000 * 60 * 60 * 24), ) : undefined; if ( remaining !== undefined && remaining <= LICENSE_EXPIRATION_THRESHOLD_DAYS ) { status = dbUser.auto_renew ? USER_LICENSE_STATUS.RENEW : USER_LICENSE_STATUS.ALERT; } } else { status = USER_LICENSE_STATUS.NO_LICENSE; } return { id: dbUser.id, name: adb2cUser.displayName, role: dbUser.role, authorId: dbUser.author_id ?? undefined, typistGroupName: groupNames, email: mail, emailVerified: dbUser.email_verified, autoRenew: dbUser.auto_renew, licenseAlert: dbUser.license_alert, notification: dbUser.notification, encryption: dbUser.encryption, prompt: dbUser.prompt, expiration: expiration, remaining: remaining, licenseStatus: status, }; }); return users; } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.NOT_FOUND, ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.getUsers.name}`, ); } } /** * Updates sort criteria * @param paramName * @param direction * @param token * @returns sort criteria */ async updateSortCriteria( context: Context, paramName: TaskListSortableAttribute, direction: SortDirection, externalId: string, ): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.updateSortCriteria.name } | params: { paramName: ${paramName}, direction: ${direction}, externalId: ${externalId} };`, ); let user: EntityUser; try { // ユーザー情報を取得 user = await this.usersRepository.findUserByExternalId(externalId); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } try { // ユーザーのソート条件を更新 await this.sortCriteriaRepository.updateSortCriteria( user.id, paramName, direction, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.updateSortCriteria.name}`, ); } } /** * Gets sort criteria * @param token * @returns sort criteria */ async getSortCriteria( context: Context, externalId: string, ): Promise<{ paramName: TaskListSortableAttribute; direction: SortDirection; }> { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.getSortCriteria.name } | params: { externalId: ${externalId} };`, ); let user: EntityUser; try { // ユーザー情報を取得 user = await this.usersRepository.findUserByExternalId(externalId); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } try { // ユーザーのソート条件を取得 const sortCriteria = await this.sortCriteriaRepository.getSortCriteria( user.id, ); const { direction, parameter } = sortCriteria; //型チェック if ( !isTaskListSortableAttribute(parameter) || !isSortDirection(direction) ) { throw new Error('The value stored in the DB is invalid.'); } return { direction, paramName: parameter }; } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.getSortCriteria.name}`, ); } } /** * 指定したユーザーの文字起こし業務に関連する情報を取得します * @param userId * @returns relations */ async getRelations( context: Context, userId: string, ): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.getRelations.name } | params: { userId: ${userId} };`, ); try { const { id } = await this.usersRepository.findUserByExternalId(userId); // ユーザー関連情報を取得 const { user, authors, worktypes, activeWorktype } = await this.usersRepository.getUserRelations(id); // AuthorIDのリストを作成 const authorIds = authors.flatMap((author) => author.author_id ? [author.author_id] : [], ); const workTypeList = worktypes?.map((worktype) => { return { workTypeId: worktype.custom_worktype_id, optionItemList: worktype.option_items.map((optionItem) => { const initialValueType = OPTION_ITEM_VALUE_TYPE_NUMBER.find( (x) => x.type === optionItem.default_value_type, )?.value; if (!initialValueType) { throw new Error( `invalid default_value_type ${optionItem.default_value_type}`, ); } return { label: optionItem.item_label, initialValueType, defaultValue: optionItem.initial_value, }; }), }; }); return { authorId: user.author_id ?? undefined, authorIdList: authorIds, workTypeList, isEncrypted: user.encryption, encryptionPassword: user.encryption_password ?? undefined, activeWorktype: activeWorktype?.custom_worktype_id ?? '', audioFormat: USER_AUDIO_FORMAT, prompt: user.prompt, }; } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case UserNotFoundError: throw new HttpException( makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.getRelations.name}`, ); } } /** * 指定したユーザーの情報を更新します * @param context * @param extarnalId * @param id * @param role * @param authorId * @param autoRenew * @param licenseAlart * @param notification * @param encryption * @param encryptionPassword * @param prompt * @returns user */ async updateUser( context: Context, extarnalId: string, id: number, role: string, authorId: string | undefined, autoRenew: boolean, licenseAlart: boolean, notification: boolean, encryption: boolean | undefined, encryptionPassword: string | undefined, prompt: boolean | undefined, ): Promise { try { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.updateUser.name } | params: { ` + `extarnalId: ${extarnalId}, ` + `id: ${id}, ` + `role: ${role}, ` + `authorId: ${authorId}, ` + `autoRenew: ${autoRenew}, ` + `licenseAlart: ${licenseAlart}, ` + `notification: ${notification}, ` + `encryption: ${encryption}, ` + `prompt: ${prompt} }`, ); // 実行ユーザーのアカウントIDを取得 const accountId = ( await this.usersRepository.findUserByExternalId(extarnalId) ).account_id; // ユーザー情報を更新 await this.usersRepository.update( accountId, id, role, authorId, autoRenew, licenseAlart, notification, encryption, encryptionPassword, prompt, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case UserNotFoundError: throw new HttpException( makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST, ); case AuthorIdAlreadyExistsError: throw new HttpException( makeErrorResponse('E010302'), HttpStatus.BAD_REQUEST, ); case InvalidRoleChangeError: throw new HttpException( makeErrorResponse('E010207'), HttpStatus.BAD_REQUEST, ); case EncryptionPasswordNeedError: throw new HttpException( makeErrorResponse('E010208'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.updateUser.name}`, ); } } /** * ライセンスをユーザーに割り当てます * @param context * @param userId * @param newLicenseId */ async allocateLicense( context: Context, userId: number, newLicenseId: number, ): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.allocateLicense.name } | params: { ` + `userId: ${userId}, ` + `newLicenseId: ${newLicenseId}, };`, ); try { const accountId = (await this.usersRepository.findUserById(userId)) .account_id; await this.licensesRepository.allocateLicense( userId, newLicenseId, accountId, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case LicenseExpiredError: throw new HttpException( makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST, ); case LicenseUnavailableError: throw new HttpException( makeErrorResponse('E010806'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.allocateLicense.name}`, ); } } /** * ユーザーに割り当てられているライセンスを解除します * @param context * @param userId */ async deallocateLicense(context: Context, userId: number): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.deallocateLicense.name } | params: { ` + `userId: ${userId}, };`, ); try { const accountId = (await this.usersRepository.findUserById(userId)) .account_id; await this.licensesRepository.deallocateLicense(userId, accountId); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case LicenseAlreadyDeallocatedError: throw new HttpException( makeErrorResponse('E010807'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.deallocateLicense.name}`, ); } } /** * 同意済み利用規約バージョンを更新する * @param context * @param idToken * @param eulaVersion * @param dpaVersion */ async updateAcceptedVersion( context: Context, externalId: string, eulaVersion: string, dpaVersion?: string, ): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.updateAcceptedVersion.name } | params: { ` + `externalId: ${externalId}, ` + `eulaVersion: ${eulaVersion}, ` + `dpaVersion: ${dpaVersion}, };`, ); try { await this.usersRepository.updateAcceptedTermsVersion( externalId, eulaVersion, dpaVersion, ); } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case UserNotFoundError: throw new HttpException( makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST, ); case AccountNotFoundError: throw new HttpException( makeErrorResponse('E010501'), HttpStatus.BAD_REQUEST, ); case UpdateTermsVersionNotSetError: throw new HttpException( makeErrorResponse('E010001'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.updateAcceptedVersion.name}`, ); } } /** * Azure AD B2Cからユーザー名を取得する * @param context * @param externalId */ async getUserName(context: Context, externalId: string): Promise { this.logger.log( `[IN] [${context.getTrackingId()}] ${ this.getUserName.name } | params: { externalId: ${externalId} };`, ); try { // extarnalIdの存在チェックを行う await this.usersRepository.findUserByExternalId(externalId); // ADB2Cからユーザー名を取得する const adb2cUser = await this.adB2cService.getUser(context, externalId); return adb2cUser.displayName; } catch (e) { this.logger.error(`[${context.getTrackingId()}] error=${e}`); if (e instanceof Error) { switch (e.constructor) { case UserNotFoundError: throw new HttpException( makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST, ); default: throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } } throw new HttpException( makeErrorResponse('E009999'), HttpStatus.INTERNAL_SERVER_ERROR, ); } finally { this.logger.log( `[OUT] [${context.getTrackingId()}] ${this.getUserName.name}`, ); } } }