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 { TermsModule } from './features/terms/terms.module';
|
||||||
import { RedisModule } from './gateways/redis/redis.module';
|
import { RedisModule } from './gateways/redis/redis.module';
|
||||||
import * as redisStore from 'cache-manager-redis-store';
|
import * as redisStore from 'cache-manager-redis-store';
|
||||||
|
import { SystemAccessGuardsModule } from './common/guards/system/accessguards.module';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ServeStaticModule.forRootAsync({
|
ServeStaticModule.forRootAsync({
|
||||||
@ -133,6 +134,7 @@ import * as redisStore from 'cache-manager-redis-store';
|
|||||||
NotificationhubModule,
|
NotificationhubModule,
|
||||||
BlobstorageModule,
|
BlobstorageModule,
|
||||||
AuthGuardsModule,
|
AuthGuardsModule,
|
||||||
|
SystemAccessGuardsModule,
|
||||||
SortCriteriaRepositoryModule,
|
SortCriteriaRepositoryModule,
|
||||||
WorktypesRepositoryModule,
|
WorktypesRepositoryModule,
|
||||||
TermsModule,
|
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;
|
tier: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// システムの内部で発行し、外部に公開しないトークン
|
||||||
|
// システム間通信用(例: Azure Functions→AppService)に使用する
|
||||||
|
export type SystemAccessToken = {
|
||||||
|
/**
|
||||||
|
* トークンの発行者名(ログ記録用)
|
||||||
|
*/
|
||||||
|
systemName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 付加情報を 文字情報として格納できる
|
||||||
|
*/
|
||||||
|
context?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type IDToken = {
|
export type IDToken = {
|
||||||
emails: string[];
|
emails: string[];
|
||||||
nonce?: string | undefined;
|
nonce?: string | undefined;
|
||||||
|
|||||||
@ -1,11 +1,16 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import {
|
import {
|
||||||
|
IsArray,
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsEmail,
|
IsEmail,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsInt,
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
|
IsString,
|
||||||
MaxLength,
|
MaxLength,
|
||||||
|
ValidateIf,
|
||||||
|
ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import {
|
import {
|
||||||
TASK_LIST_SORTABLE_ATTRIBUTES,
|
TASK_LIST_SORTABLE_ATTRIBUTES,
|
||||||
@ -264,6 +269,103 @@ export class PostDeleteUserRequest {
|
|||||||
|
|
||||||
export class PostDeleteUserResponse {}
|
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 {
|
export class AllocateLicenseRequest {
|
||||||
@ApiProperty({ description: 'ユーザーID' })
|
@ApiProperty({ description: 'ユーザーID' })
|
||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
|
|||||||
@ -3,6 +3,12 @@ import { UsersController } from './users.controller';
|
|||||||
import { UsersService } from './users.service';
|
import { UsersService } from './users.service';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { AuthService } from '../auth/auth.service';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
import {
|
||||||
|
PostMultipleImportsCompleteRequest,
|
||||||
|
PostMultipleImportsRequest,
|
||||||
|
} from './types/types';
|
||||||
|
import { validate } from 'class-validator';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
|
|
||||||
describe('UsersController', () => {
|
describe('UsersController', () => {
|
||||||
let controller: UsersController;
|
let controller: UsersController;
|
||||||
@ -32,4 +38,440 @@ describe('UsersController', () => {
|
|||||||
it('should be defined', () => {
|
it('should be defined', () => {
|
||||||
expect(controller).toBeDefined();
|
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,
|
GetMyUserResponse,
|
||||||
PostDeleteUserRequest,
|
PostDeleteUserRequest,
|
||||||
PostDeleteUserResponse,
|
PostDeleteUserResponse,
|
||||||
|
PostMultipleImportsRequest,
|
||||||
|
PostMultipleImportsResponse,
|
||||||
|
PostMultipleImportsCompleteRequest,
|
||||||
|
PostMultipleImportsCompleteResponse,
|
||||||
} from './types/types';
|
} from './types/types';
|
||||||
import { UsersService } from './users.service';
|
import { UsersService } from './users.service';
|
||||||
import { AuthService } from '../auth/auth.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 { RoleGuard } from '../../common/guards/role/roleguards';
|
||||||
import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
|
import { makeContext, retrieveRequestId, retrieveIp } from '../../common/log';
|
||||||
import { UserRoles } from '../../common/types/role';
|
import { UserRoles } from '../../common/types/role';
|
||||||
|
import { SystemAccessGuard } from '../../common/guards/system/accessguards';
|
||||||
|
import { SystemAccessToken } from '../../common/token/types';
|
||||||
|
|
||||||
@ApiTags('users')
|
@ApiTags('users')
|
||||||
@Controller('users')
|
@Controller('users')
|
||||||
@ -992,4 +998,149 @@ export class UsersController {
|
|||||||
await this.usersService.deleteUser(context, body.userId, now);
|
await this.usersService.deleteUser(context, body.userId, now);
|
||||||
return {};
|
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