Merged PR 766: API I/F & system権限Token実装
## 概要 [Task3764: API I/F & system権限Token実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3764) - システムが発行したトークンの型定義を追加 - AuthGuardと同等の、システムが発行したTokenである `SystemAccessToken` を検証する `SystemAccessGuard` を追加 - API I/Fを実装 ## レビューポイント - バリデーターは適切か - システムが発行したトークンの型定義は適切か - API I/Fの型は問題ないか ## 動作確認状況 - ローカルでswagger UI上で確認
This commit is contained in:
parent
13d421c2bc
commit
c1f370faaf
@ -52,6 +52,7 @@ import { WorkflowsRepositoryModule } from './repositories/workflows/workflows.re
|
||||
import { TermsModule } from './features/terms/terms.module';
|
||||
import { RedisModule } from './gateways/redis/redis.module';
|
||||
import * as redisStore from 'cache-manager-redis-store';
|
||||
import { SystemAccessGuardsModule } from './common/guards/system/accessguards.module';
|
||||
@Module({
|
||||
imports: [
|
||||
ServeStaticModule.forRootAsync({
|
||||
@ -133,6 +134,7 @@ import * as redisStore from 'cache-manager-redis-store';
|
||||
NotificationhubModule,
|
||||
BlobstorageModule,
|
||||
AuthGuardsModule,
|
||||
SystemAccessGuardsModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
WorktypesRepositoryModule,
|
||||
TermsModule,
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { SystemAccessGuard } from './accessguards';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
controllers: [],
|
||||
providers: [SystemAccessGuard],
|
||||
})
|
||||
export class SystemAccessGuardsModule {}
|
||||
43
dictation_server/src/common/guards/system/accessguards.ts
Normal file
43
dictation_server/src/common/guards/system/accessguards.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { isVerifyError, decode, verify } from '../../jwt';
|
||||
import { Request } from 'express';
|
||||
import { retrieveAuthorizationToken } from '../../../common/http/helper';
|
||||
import { makeErrorResponse } from '../../../common/error/makeErrorResponse';
|
||||
import { SystemAccessToken } from '../../token/types';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { getPublicKey } from '../../jwt/jwt';
|
||||
/**
|
||||
* システム間通信用のトークンを検証するガード
|
||||
**/
|
||||
@Injectable()
|
||||
export class SystemAccessGuard implements CanActivate {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
const req = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
if (!token) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const payload = verify<SystemAccessToken>(token, pubkey);
|
||||
if (isVerifyError(payload)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -36,6 +36,20 @@ export type AccessToken = {
|
||||
tier: number;
|
||||
};
|
||||
|
||||
// システムの内部で発行し、外部に公開しないトークン
|
||||
// システム間通信用(例: Azure Functions→AppService)に使用する
|
||||
export type SystemAccessToken = {
|
||||
/**
|
||||
* トークンの発行者名(ログ記録用)
|
||||
*/
|
||||
systemName: string;
|
||||
|
||||
/**
|
||||
* 付加情報を 文字情報として格納できる
|
||||
*/
|
||||
context?: string;
|
||||
};
|
||||
|
||||
export type IDToken = {
|
||||
emails: string[];
|
||||
nonce?: string | undefined;
|
||||
|
||||
@ -1,11 +1,16 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsEmail,
|
||||
IsIn,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
ValidateIf,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import {
|
||||
TASK_LIST_SORTABLE_ATTRIBUTES,
|
||||
@ -264,6 +269,103 @@ export class PostDeleteUserRequest {
|
||||
|
||||
export class PostDeleteUserResponse {}
|
||||
|
||||
export class MultipleImportUser {
|
||||
@ApiProperty({ description: 'ユーザー名' })
|
||||
@IsString()
|
||||
@MaxLength(256) // AzureAdB2Cの仕様上、256文字まで[https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/user-profile-attributes]
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ description: 'メールアドレス' })
|
||||
@IsEmail({ blacklisted_chars: '*' })
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ description: '0(none)/1(author)/2(typist)' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsIn([0, 1, 2])
|
||||
role: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsAuthorIdValid()
|
||||
@ValidateIf((o) => o.role === 1) // roleがauthorの場合のみバリデーションを実施
|
||||
authorId?: string;
|
||||
|
||||
@ApiProperty({ description: '0(false)/1(true)' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsIn([0, 1])
|
||||
autoRenew: number;
|
||||
|
||||
@ApiProperty({ description: '0(false)/1(true)' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsIn([0, 1])
|
||||
notification: number;
|
||||
|
||||
@ApiProperty({ required: false, description: '0(false)/1(true)' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsIn([0, 1])
|
||||
@ValidateIf((o) => o.role === 1) // roleがauthorの場合のみバリデーションを実施
|
||||
encryption?: number;
|
||||
|
||||
@ApiProperty({ required: false })
|
||||
@IsPasswordvalid()
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@ValidateIf((o) => o.role === 1 && o.encryption === 1) // roleがauthorかつencryptionがtrueの場合のみバリデーションを実施
|
||||
encryptionPassword?: string;
|
||||
|
||||
@ApiProperty({ required: false, description: '0(false)/1(true)' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsIn([0, 1])
|
||||
@ValidateIf((o) => o.role === 1) // roleがauthorの場合のみバリデーションを実施
|
||||
prompt?: number;
|
||||
}
|
||||
|
||||
export class PostMultipleImportsRequest {
|
||||
@ApiProperty({ type: [MultipleImportUser] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => MultipleImportUser)
|
||||
users: MultipleImportUser[];
|
||||
}
|
||||
export class PostMultipleImportsResponse {}
|
||||
|
||||
export class MultipleImportErrors {
|
||||
@ApiProperty({ description: 'ユーザー名' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ description: 'メールアドレス' })
|
||||
@IsEmail({ blacklisted_chars: '*' })
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ description: 'エラーコード' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
errorCode: string;
|
||||
}
|
||||
|
||||
export class PostMultipleImportsCompleteRequest {
|
||||
@ApiProperty({ description: 'アカウントID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
accountId: number;
|
||||
|
||||
@ApiProperty({ type: [MultipleImportErrors] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => MultipleImportErrors)
|
||||
errors: MultipleImportErrors[];
|
||||
}
|
||||
export class PostMultipleImportsCompleteResponse {}
|
||||
|
||||
export class AllocateLicenseRequest {
|
||||
@ApiProperty({ description: 'ユーザーID' })
|
||||
@Type(() => Number)
|
||||
|
||||
@ -3,6 +3,12 @@ import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
import {
|
||||
PostMultipleImportsCompleteRequest,
|
||||
PostMultipleImportsRequest,
|
||||
} from './types/types';
|
||||
import { validate } from 'class-validator';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
|
||||
describe('UsersController', () => {
|
||||
let controller: UsersController;
|
||||
@ -32,4 +38,440 @@ describe('UsersController', () => {
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
describe('valdation PostMultipleImportsRequest', () => {
|
||||
it('role:noneの最低限の有効なリクエストが成功する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 0,
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('role:authorの最低限の有効なリクエストが成功する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 0,
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('emailがメールアドレスではない場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge',
|
||||
role: 0,
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('AuthorなのにAuthorIDがない場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 0,
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('Authorなのにencryptionがない場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('Authorなのにpromptがない場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('Authorでencryption:trueなのに、encryptionPasswordがない場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 1,
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('Authorでencryption:trueでencryptionPasswordが正常であれば成功する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 1,
|
||||
encryptionPassword: 'abcd',
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('encryptionPasswordが要件外(短い)の場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 1,
|
||||
encryptionPassword: 'abc',
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('encryptionPasswordが要件外(長い)の場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 1,
|
||||
encryptionPassword: 'abcxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('encryptionPasswordが要件外(全角が含まれる)の場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'AUTHOR',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 1,
|
||||
encryptionPassword: 'abcあいうえお',
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('AuthorIDが要件外(小文字)の場合、バリデーションエラーが発生する', async () => {
|
||||
const request = new PostMultipleImportsRequest();
|
||||
request.users = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
role: 1,
|
||||
authorId: 'author',
|
||||
autoRenew: 0,
|
||||
notification: 0,
|
||||
encryption: 1,
|
||||
encryptionPassword: 'abc',
|
||||
prompt: 0,
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(PostMultipleImportsRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('valdation PostMultipleImportsCompleteRequest', () => {
|
||||
it('最低限の有効なリクエストが成功する', async () => {
|
||||
const request = new PostMultipleImportsCompleteRequest();
|
||||
request.accountId = 1;
|
||||
request.errors = [];
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('エラーが存在するリクエストが成功する', async () => {
|
||||
const request = new PostMultipleImportsCompleteRequest();
|
||||
request.accountId = 1;
|
||||
request.errors = [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
];
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('名前が足りないエラーがある場合、バリデーションエラーが発生する', async () => {
|
||||
const request = {
|
||||
accountId: 1,
|
||||
errors: [
|
||||
{
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('emailが足りないエラーがある場合、バリデーションエラーが発生する', async () => {
|
||||
const request = {
|
||||
accountId: 1,
|
||||
errors: [
|
||||
{
|
||||
name: 'namae',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('errorCodeが足りないエラーがある場合、バリデーションエラーが発生する', async () => {
|
||||
const request = {
|
||||
accountId: 1,
|
||||
errors: [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('名前が空のエラーがある場合、バリデーションエラーが発生する', async () => {
|
||||
const request = {
|
||||
accountId: 1,
|
||||
errors: [
|
||||
{
|
||||
name: '',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('emailが空のエラーがある場合、バリデーションエラーが発生する', async () => {
|
||||
const request = {
|
||||
accountId: 1,
|
||||
errors: [
|
||||
{
|
||||
name: 'namae',
|
||||
email: '',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
it('emailが空のエラーがある場合、バリデーションエラーが発生する', async () => {
|
||||
const request = {
|
||||
accountId: 1,
|
||||
errors: [
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: '',
|
||||
},
|
||||
{
|
||||
name: 'namae',
|
||||
email: 'hogehoge@example.com',
|
||||
errorCode: 'E1101',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(
|
||||
PostMultipleImportsCompleteRequest,
|
||||
request,
|
||||
);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -44,6 +44,10 @@ import {
|
||||
GetMyUserResponse,
|
||||
PostDeleteUserRequest,
|
||||
PostDeleteUserResponse,
|
||||
PostMultipleImportsRequest,
|
||||
PostMultipleImportsResponse,
|
||||
PostMultipleImportsCompleteRequest,
|
||||
PostMultipleImportsCompleteResponse,
|
||||
} from './types/types';
|
||||
import { UsersService } from './users.service';
|
||||
import { AuthService } from '../auth/auth.service';
|
||||
@ -57,6 +61,8 @@ import { ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
|
||||
import { UserRoles } from '../../common/types/role';
|
||||
import { SystemAccessGuard } from '../../common/guards/system/accessguards';
|
||||
import { SystemAccessToken } from '../../common/token/types';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users')
|
||||
@ -992,4 +998,149 @@ export class UsersController {
|
||||
await this.usersService.deleteUser(context, body.userId, now);
|
||||
return {};
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: PostMultipleImportsResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '不正なパラメータ',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'multipleImports',
|
||||
description: 'ユーザーを一括登録します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], delegation: true }),
|
||||
)
|
||||
@Post('multiple-imports')
|
||||
async multipleImports(
|
||||
@Body() body: PostMultipleImportsRequest,
|
||||
@Req() req: Request,
|
||||
): Promise<PostMultipleImportsResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const ip = retrieveIp(req);
|
||||
if (!ip) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000401'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const requestId = retrieveRequestId(req);
|
||||
if (!requestId) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000501'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const decodedToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId, delegateUserId } = decodedToken as AccessToken;
|
||||
const context = makeContext(userId, requestId, delegateUserId);
|
||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||
|
||||
// TODO: 処理を実装
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: PostMultipleImportsCompleteResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: '不正なパラメータ',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'multipleImportsComplate',
|
||||
description: 'ユーザー一括登録の完了を通知します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(SystemAccessGuard)
|
||||
@Post('multiple-imports/complete')
|
||||
async multipleImportsComplate(
|
||||
@Body() body: PostMultipleImportsCompleteRequest,
|
||||
@Req() req: Request,
|
||||
): Promise<PostMultipleImportsCompleteResponse> {
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const ip = retrieveIp(req);
|
||||
if (!ip) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000401'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const requestId = retrieveRequestId(req);
|
||||
if (!requestId) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000501'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const decodedToken = jwt.decode(accessToken, { json: true });
|
||||
if (!decodedToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { systemName } = decodedToken as SystemAccessToken;
|
||||
const context = makeContext(systemName, requestId);
|
||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||
|
||||
// TODO: 処理を実装
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user