Merged PR 493: API作成(アカウント情報取得(未認証時最小アクセス)API)
## 概要 [Task2807: API作成(アカウント情報取得(未認証時最小アクセス)API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2807) - 未ログインユーザーについて、IDトークンを受け取ってユーザの所属するアカウントの階層情報を返却するAPIを実装しました。 ## レビューポイント - ContorollerでIDトークンをデコードしているが問題ないか? - ※ログインAPIを参考にしています。 - テストケースは適切か ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
685a8f6c3e
commit
69ff6f3432
@ -2,10 +2,12 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AccountsController } from './accounts.controller';
|
||||
import { AccountsService } from './accounts.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
describe('AccountsController', () => {
|
||||
let controller: AccountsController;
|
||||
const mockAccountService = {};
|
||||
const mockAuthService = {};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -16,10 +18,12 @@ describe('AccountsController', () => {
|
||||
}),
|
||||
],
|
||||
controllers: [AccountsController],
|
||||
providers: [AccountsService],
|
||||
providers: [AccountsService, AuthService],
|
||||
})
|
||||
.overrideProvider(AccountsService)
|
||||
.useValue(mockAccountService)
|
||||
.overrideProvider(AuthService)
|
||||
.useValue(mockAuthService)
|
||||
.compile();
|
||||
|
||||
controller = module.get<AccountsController>(AccountsController);
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
UseGuards,
|
||||
Param,
|
||||
Query,
|
||||
HttpException,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiOperation,
|
||||
@ -74,12 +75,15 @@ import { AccessToken } from '../../common/token';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { makeContext } from '../../common/log';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
|
||||
@ApiTags('accounts')
|
||||
@Controller('accounts')
|
||||
export class AccountsController {
|
||||
constructor(
|
||||
private readonly accountService: AccountsService, //private readonly cryptoService: CryptoService,
|
||||
private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@ -1116,11 +1120,22 @@ export class AccountsController {
|
||||
async getAccountInfoMinimalAccess(
|
||||
@Body() body: GetAccountInfoMinimalAccessRequest,
|
||||
): Promise<GetAccountInfoMinimalAccessResponse> {
|
||||
const context = makeContext(uuidv4());
|
||||
// IDトークンの検証
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO 仮実装。API実装タスクで本実装する。
|
||||
// const idToken = await this.authService.getVerifiedIdToken(body.idToken);
|
||||
// await this.accountService.getAccountInfoMinimalAccess(context, idToken);
|
||||
return;
|
||||
const context = makeContext(idToken.sub);
|
||||
|
||||
const tier = await this.accountService.getAccountInfoMinimalAccess(
|
||||
context,
|
||||
idToken.sub,
|
||||
);
|
||||
return { tier };
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { UserGroupsRepositoryModule } from '../../repositories/user_groups/user_groups.repository.module';
|
||||
import { BlobstorageModule } from '../../gateways/blobstorage/blobstorage.module';
|
||||
import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktypes.repository.module';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -22,6 +23,6 @@ import { WorktypesRepositoryModule } from '../../repositories/worktypes/worktype
|
||||
BlobstorageModule,
|
||||
],
|
||||
controllers: [AccountsController],
|
||||
providers: [AccountsService],
|
||||
providers: [AccountsService, AuthService],
|
||||
})
|
||||
export class AccountsModule {}
|
||||
|
||||
@ -5595,3 +5595,127 @@ describe('deleteAccountAndData', () => {
|
||||
expect(userRecord).toBe(null);
|
||||
});
|
||||
});
|
||||
describe('getAccountInfoMinimalAccess', () => {
|
||||
let source: DataSource = null;
|
||||
beforeEach(async () => {
|
||||
source = new DataSource({
|
||||
type: 'sqlite',
|
||||
database: ':memory:',
|
||||
logging: false,
|
||||
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
|
||||
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
|
||||
});
|
||||
return source.initialize();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await source.destroy();
|
||||
source = null;
|
||||
});
|
||||
it('IDトークンのsub情報からアカウントの階層情報を取得できること(第五階層)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
// 第五階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, {
|
||||
tier: 5,
|
||||
});
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const tier5Account = await getAccount(source, account.id);
|
||||
expect(tier5Account.tier).toBe(5);
|
||||
}
|
||||
|
||||
const tier = await service.getAccountInfoMinimalAccess(
|
||||
context,
|
||||
admin.external_id,
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
expect(tier).toBe(5);
|
||||
});
|
||||
it('IDトークンのSub情報からアカウントの階層情報を取得できること(第四階層)', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
// 第四階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const tier5Account = await getAccount(source, account.id);
|
||||
expect(tier5Account.tier).toBe(4);
|
||||
}
|
||||
|
||||
const tier = await service.getAccountInfoMinimalAccess(
|
||||
context,
|
||||
admin.external_id,
|
||||
);
|
||||
|
||||
//実行結果を確認
|
||||
expect(tier).toBe(4);
|
||||
});
|
||||
it('対象のユーザーが存在しない場合、400エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
// 第四階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const tier5Account = await getAccount(source, account.id);
|
||||
expect(tier5Account.tier).toBe(4);
|
||||
}
|
||||
|
||||
try {
|
||||
await service.getAccountInfoMinimalAccess(context, 'fail_external_id');
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
it('DBアクセスに失敗した場合、500エラーとなること', async () => {
|
||||
const module = await makeTestingModule(source);
|
||||
const service = module.get<AccountsService>(AccountsService);
|
||||
// 第四階層のアカウント作成
|
||||
const { account, admin } = await makeTestAccount(source, {
|
||||
tier: 4,
|
||||
});
|
||||
const context = makeContext(admin.external_id);
|
||||
|
||||
// 作成したデータを確認
|
||||
{
|
||||
const tier5Account = await getAccount(source, account.id);
|
||||
expect(tier5Account.tier).toBe(4);
|
||||
}
|
||||
|
||||
//DBアクセスに失敗するようにする
|
||||
const usersRepositoryService = module.get<UsersRepositoryService>(
|
||||
UsersRepositoryService,
|
||||
);
|
||||
usersRepositoryService.findUserByExternalId = jest
|
||||
.fn()
|
||||
.mockRejectedValue('DB failed');
|
||||
|
||||
try {
|
||||
await service.getAccountInfoMinimalAccess(context, admin.external_id);
|
||||
} catch (e) {
|
||||
if (e instanceof HttpException) {
|
||||
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -1843,4 +1843,61 @@ export class AccountsService {
|
||||
`[OUT] [${context.trackingId}] ${this.deleteAccountAndData.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* IDトークンのsubからアカウントの階層情報を取得します
|
||||
* @param context
|
||||
* @param externalId
|
||||
* @returns account info minimal access
|
||||
*/
|
||||
async getAccountInfoMinimalAccess(
|
||||
context: Context,
|
||||
externalId: string,
|
||||
): Promise<number> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name} | params: { externalId: ${externalId} };`,
|
||||
);
|
||||
|
||||
try {
|
||||
const { account } = await this.usersRepository.findUserByExternalId(
|
||||
externalId,
|
||||
);
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError(
|
||||
`Account not found. externalId: ${externalId}`,
|
||||
);
|
||||
}
|
||||
|
||||
return account.tier;
|
||||
} catch (e) {
|
||||
this.logger.error(`[${context.trackingId}] 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,
|
||||
);
|
||||
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.trackingId}] ${this.getAccountInfoMinimalAccess.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user