Merged PR 138: SendGridService内のprivateキーの取得方法を修正する
## 概要 [Task1736: SendGridService内のprivateキーの取得方法を修正する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1736) - CryptoServiceの削除 - トークン発行・認証に使用するKeyを環境変数から取得するように修正 - 既存テストの修正 ## レビューポイント - Keyの取得関数の配置場所は妥当か ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - ローカルでテストが通ることを確認 - keyを使う処理(ユーザー追加)を実行して、成功することを確認 ## 補足 - 修正した箇所のロールチェックはレビュー対象外 - 「タスク 1830: 認証・認可を宣言的に扱える仕組みを既存処理に適用する」で調整してもらう想定
This commit is contained in:
parent
25072f558f
commit
0907bd28af
@ -7,7 +7,6 @@ import { LoggerMiddleware } from './common/loggerMiddleware';
|
||||
import { AuthModule } from './features/auth/auth.module';
|
||||
import { AuthController } from './features/auth/auth.controller';
|
||||
import { AuthService } from './features/auth/auth.service';
|
||||
import { CryptoModule } from './gateways/crypto/crypto.module';
|
||||
import { AdB2cModule } from './gateways/adb2c/adb2c.module';
|
||||
import { AccountsController } from './features/accounts/accounts.controller';
|
||||
import { AccountsService } from './features/accounts/accounts.service';
|
||||
@ -49,7 +48,6 @@ import { SortCriteriaRepositoryModule } from './repositories/sort_criteria/sort_
|
||||
isGlobal: true,
|
||||
}),
|
||||
AuthModule,
|
||||
CryptoModule,
|
||||
AdB2cModule,
|
||||
AccountsModule,
|
||||
UsersModule,
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AuthGuard } from './authguards';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [ConfigModule],
|
||||
controllers: [],
|
||||
providers: [AuthGuard],
|
||||
})
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Request } from 'express';
|
||||
import { getPublicKey } from '../../../common/jwt/jwt';
|
||||
import { makeErrorResponse } from '../../error/makeErrorResponse';
|
||||
import { retrieveAuthorizationToken } from '../../http/helper';
|
||||
import { isVerifyError, verify } from '../../jwt';
|
||||
import { AccessToken } from '../../token';
|
||||
import { retrieveAuthorizationToken } from '../../http/helper';
|
||||
import { makeErrorResponse } from '../../error/makeErrorResponse';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
|
||||
const pubkey = this.configService
|
||||
.getOrThrow<string>('JWT_PUBLIC_KEY')
|
||||
.replace('\\n', '\n');
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
const req = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
// XXX: decodeがうまく使えないことがあるので応急対応 バージョン9以降だとなる?
|
||||
import { decode as jwtDecode } from 'jsonwebtoken';
|
||||
@ -126,3 +127,19 @@ export const decode = <T extends object>(token: string): T | VerifyError => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getPrivateKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.get<string>('JWT_PRIVATE_KEY')?.replace('\\n', '\n') ?? ''
|
||||
);
|
||||
};
|
||||
|
||||
export const getPublicKey = (configService: ConfigService): string => {
|
||||
return (
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
configService.get<string>('JWT_PUBLIC_KEY')?.replace('\\n', '\n') ?? ''
|
||||
);
|
||||
};
|
||||
|
||||
@ -64,6 +64,7 @@ export class AuthController {
|
||||
idToken,
|
||||
body.type,
|
||||
);
|
||||
|
||||
const accessToken = await this.authService.generateAccessToken(
|
||||
refreshToken,
|
||||
);
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { CryptoModule } from '../../gateways/crypto/crypto.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Module({
|
||||
imports: [CryptoModule, AdB2cModule, UsersRepositoryModule],
|
||||
imports: [ConfigModule, AdB2cModule, UsersRepositoryModule],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
})
|
||||
|
||||
@ -3,14 +3,15 @@ import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import {
|
||||
makeAuthServiceMock,
|
||||
makeDefaultAdB2cMockValue,
|
||||
makeDefaultCryptoMockValue,
|
||||
makeDefaultGetPublicKeyFromJwk,
|
||||
} from './test/auth.service.mock';
|
||||
|
||||
describe('AuthService', () => {
|
||||
it('IDトークンの検証とペイロードの取得に成功する', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -19,8 +20,7 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const token = 'invalid.id.token';
|
||||
|
||||
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
|
||||
@ -30,8 +30,9 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.r9x61Mf1S2qFgU_QDKB6tRFBmTQXyOEtpoacOlL_bQzFz1t3GsxMy6SJIvQQ-LtDgylQ1UCdMFiRuy4V8nyLuME0fR-9IkKsboGvwllHB_Isai3XFoja0jpDHMVby1m0B3Z9xOTb7YsaQGyEH-qs1TtnRm6Ny98h4Po80nK8HGefQZHBOlfQN_B1LiHwI3nLXV18NL-4olKXj2NloNRYtnWM0PaqDQcGvZFaSNvtrSYpo9ddD906QWDGVOQ7WvGSUgdNCoxX8Lb3r2-VSj6n84jpb-Y1Fz-GhLluNglAsBhasnJfUIvCIO3iG5pRyTYjHFAVHmzjr8xMOmhS3s41Jw';
|
||||
|
||||
@ -42,8 +43,9 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.fX2Gbd7fDPNE3Lw-xbum_5CVqQYqEmMhv_v5u8A-U81pmPD2P5rsJEJx66ns1taFLVaE3j9_OzotxrqjqqQqbACkagGcN5wvA3_ZIxyqmhrKYFJc53ZcO7d0pFWiQlluNBI_pnFNDlSMB2Ut8Th5aiPy2uamBM9wC99bcjo7HkHvTKBf6ljU6rPKoD51qGDWqNxjoH-hdSJ29wprvyxyk_yX6dp-cxXUj5DIgXYQuIZF71rdiPtGlAiyTBns8rS2QlEEXapZVlvYrK4mkpUXVDA7ifD8q6gAC2BStqHeys7CGp2MbV4ZwKCVbAUbMs6Tboh8rADZvQhuTEq7qlhZ-w';
|
||||
|
||||
@ -54,8 +56,7 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
|
||||
|
||||
@ -66,8 +67,9 @@ describe('AuthService', () => {
|
||||
|
||||
it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
|
||||
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaW52bGlkX2lzc3VlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.0bp3e1mDG78PX3lo8zgOLXGenIqG_Vi6kw7CbwauAQM-cnUZ_aVCoJ_dAv_QmPElOQKcCkRrAvAZ91FwuHDlBGuuDqx8OwqN0VaD-4NPouoAswj-9HNvBm8gUn-pGaXkvWt_72UdCJavZJjDj_RHur8y8kFt5Qeab3mUP2x-uNcV2Q2x3M_IIfcRiIZkRZm_azKfiVIy7tzoUFLDss97y938aR8imMVxazoSQvj7RWIWylgeRr9yVt7qYl18cnEVL0IGtslFbqhfNsiEmRCMsttm5kXs7E9B0bhhUe_xbJW9VumQ6G7dgMrswevp_jRgbpWJoZsgErtqIRl9Tc9ikA';
|
||||
|
||||
@ -79,8 +81,7 @@ describe('AuthService', () => {
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(メタデータ)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
adb2cParam.getMetaData = new Error('failed get metadata');
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -94,8 +95,7 @@ describe('AuthService', () => {
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。(キーセット)', async () => {
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
adb2cParam.getSignKeySets = new Error('failed get keyset');
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
@ -112,8 +112,7 @@ describe('AuthService', () => {
|
||||
adb2cParam.getSignKeySets = [
|
||||
{ kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' },
|
||||
];
|
||||
const cryptoParam = makeDefaultCryptoMockValue();
|
||||
const service = await makeAuthServiceMock(adb2cParam, cryptoParam);
|
||||
const service = await makeAuthServiceMock(adb2cParam);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
|
||||
|
||||
|
||||
@ -1,24 +1,30 @@
|
||||
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||
import jwkToPem from 'jwk-to-pem';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { isVerifyError, sign, verify } from '../../common/jwt';
|
||||
import { sign } from '../../common/jwt';
|
||||
import {
|
||||
getPrivateKey,
|
||||
getPublicKey,
|
||||
isVerifyError,
|
||||
verify,
|
||||
} from '../../common/jwt/jwt';
|
||||
import {
|
||||
AccessToken,
|
||||
IDToken,
|
||||
isIDToken,
|
||||
JwkSignKey,
|
||||
RefreshToken,
|
||||
isIDToken,
|
||||
} from '../../common/token';
|
||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
import { ADMIN_ROLES, USER_ROLES } from '../../constants';
|
||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||
import { User } from '../../repositories/users/entity/user.entity';
|
||||
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly cryptoService: CryptoService,
|
||||
private readonly adB2cService: AdB2cService,
|
||||
private readonly configService: ConfigService,
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
@ -87,8 +93,7 @@ export class AuthService {
|
||||
}
|
||||
// 要求された環境用トークンの寿命を決定
|
||||
const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault;
|
||||
|
||||
const privateKey = await this.cryptoService.getPrivateKey();
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
// ユーザーのロールを設定
|
||||
// 万一不正なRoleが登録されていた場合、そのままDBの値を使用すると不正なロールのリフレッシュトークンが発行されるため、
|
||||
@ -135,8 +140,8 @@ export class AuthService {
|
||||
async generateAccessToken(refreshToken: string): Promise<string> {
|
||||
const lifetime = this.configService.get('ACCESS_TOKEN_LIFETIME_WEB');
|
||||
|
||||
const privateKey = await this.cryptoService.getPrivateKey();
|
||||
const pubkey = await this.cryptoService.getPublicKey();
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
|
||||
const token = verify<RefreshToken>(refreshToken, pubkey);
|
||||
if (isVerifyError(token)) {
|
||||
@ -189,7 +194,7 @@ export class AuthService {
|
||||
throw new Error('Public Key Not Found.');
|
||||
}
|
||||
|
||||
const publicKey = await this.cryptoService.getPublicKeyFromJwk(jwkKey);
|
||||
const publicKey = this.getPublicKeyFromJwk(jwkKey);
|
||||
|
||||
const verifiedToken = jwt.verify(token, publicKey, {
|
||||
algorithms: ['RS256'],
|
||||
@ -237,6 +242,24 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
getPublicKeyFromJwk(jwkKey: JwkSignKey): string {
|
||||
try {
|
||||
// JWK形式のJSONなのでJWTの公開鍵として使えるようにPEM形式に変換
|
||||
const publicKey = jwkToPem({
|
||||
kty: 'RSA',
|
||||
n: jwkKey.n,
|
||||
e: jwkKey.e,
|
||||
});
|
||||
|
||||
return publicKey;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(`[OUT] ${this.getPublicKeyFromJwk.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT検証時のError、JsonWebTokenErrorをメッセージごとに仕分けてHTTPエラーを生成
|
||||
*/
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AdB2cService } from '../../../gateways/adb2c/adb2c.service';
|
||||
import { CryptoService } from '../../../gateways/crypto/crypto.service';
|
||||
import { JwkSignKey, B2cMetadata } from '../../../common/token';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
@ -11,13 +10,8 @@ export type AdB2cMockValue = {
|
||||
getSignKeySets: JwkSignKey[] | Error;
|
||||
};
|
||||
|
||||
export type CryptoMockValue = {
|
||||
getPublicKeyFromJwk: string | Error;
|
||||
};
|
||||
|
||||
export const makeAuthServiceMock = async (
|
||||
adB2cMockValue: AdB2cMockValue,
|
||||
cryptoMockValue: CryptoMockValue,
|
||||
): Promise<AuthService> => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
@ -26,8 +20,6 @@ export const makeAuthServiceMock = async (
|
||||
switch (token) {
|
||||
case AdB2cService:
|
||||
return makeAdB2cServiceMock(adB2cMockValue);
|
||||
case CryptoService:
|
||||
return makeCryptoServiceMock(cryptoMockValue);
|
||||
case ConfigService:
|
||||
return {};
|
||||
case UsersRepositoryService:
|
||||
@ -74,33 +66,17 @@ export const makeAdB2cServiceMock = (value: AdB2cMockValue) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultCryptoMockValue = (): CryptoMockValue => {
|
||||
return {
|
||||
getPublicKeyFromJwk: [
|
||||
'-----BEGIN PUBLIC KEY-----',
|
||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd',
|
||||
'HYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3',
|
||||
'yCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW',
|
||||
'FJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS',
|
||||
'fiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//',
|
||||
'mBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO',
|
||||
'OQIDAQAB',
|
||||
'-----END PUBLIC KEY-----',
|
||||
].join('\n'),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeCryptoServiceMock = (value: CryptoMockValue) => {
|
||||
const { getPublicKeyFromJwk } = value;
|
||||
|
||||
return {
|
||||
getPublicKeyFromJwk:
|
||||
getPublicKeyFromJwk instanceof Error
|
||||
? jest
|
||||
.fn<Promise<void>, [JwkSignKey]>()
|
||||
.mockRejectedValue(getPublicKeyFromJwk)
|
||||
: jest
|
||||
.fn<Promise<string>, [JwkSignKey]>()
|
||||
.mockResolvedValue(getPublicKeyFromJwk),
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const makeDefaultGetPublicKeyFromJwk = (jwkKey: JwkSignKey): string => {
|
||||
return [
|
||||
'-----BEGIN PUBLIC KEY-----',
|
||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd',
|
||||
'HYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3',
|
||||
'yCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW',
|
||||
'FJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS',
|
||||
'fiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//',
|
||||
'mBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO',
|
||||
'OQIDAQAB',
|
||||
'-----END PUBLIC KEY-----',
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
@ -22,6 +22,7 @@ describe('FilesService', () => {
|
||||
await service.publishUploadSas({
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
).toEqual('https://blob-storage?sas-token');
|
||||
});
|
||||
@ -43,6 +44,7 @@ describe('FilesService', () => {
|
||||
await service.publishUploadSas({
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
).toEqual('https://blob-storage?sas-token');
|
||||
});
|
||||
@ -63,6 +65,7 @@ describe('FilesService', () => {
|
||||
service.publishUploadSas({
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
||||
@ -86,6 +89,7 @@ describe('FilesService', () => {
|
||||
service.publishUploadSas({
|
||||
userId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
|
||||
role: 'Author',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { LicensesController } from './licenses.controller';
|
||||
import { LicensesService } from './licenses.service';
|
||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
describe('LicensesController', () => {
|
||||
@ -17,7 +16,7 @@ describe('LicensesController', () => {
|
||||
}),
|
||||
],
|
||||
controllers: [LicensesController],
|
||||
providers: [LicensesService, CryptoService],
|
||||
providers: [LicensesService],
|
||||
})
|
||||
.overrideProvider(LicensesService)
|
||||
.useValue(mockLicensesService)
|
||||
|
||||
@ -1,38 +1,33 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
HttpException,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiBearerAuth,
|
||||
} from '@nestjs/swagger';
|
||||
import { ErrorResponse } from '../../common/error/types/types';
|
||||
import { LicensesService } from './licenses.service';
|
||||
import { CreateOrdersResponse, CreateOrdersRequest } from './types/types';
|
||||
import { Request } from 'express';
|
||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { confirmPermission } from '../../common/auth/auth';
|
||||
import { decode } from 'jsonwebtoken';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { isVerifyError, verify } from '../../common/jwt';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import { ErrorResponse } from '../../common/error/types/types';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import { LicensesService } from './licenses.service';
|
||||
import { CreateOrdersRequest, CreateOrdersResponse } from './types/types';
|
||||
|
||||
@ApiTags('licenses')
|
||||
@Controller('licenses')
|
||||
export class LicensesController {
|
||||
constructor(
|
||||
private readonly licensesService: LicensesService,
|
||||
private readonly cryptoService: CryptoService,
|
||||
) {}
|
||||
|
||||
constructor(private readonly licensesService: LicensesService) {}
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: CreateOrdersResponse,
|
||||
@ -55,8 +50,8 @@ export class LicensesController {
|
||||
})
|
||||
@ApiOperation({ operationId: 'createOrders' })
|
||||
@ApiBearerAuth()
|
||||
// @UseGuards(AuthGuard)
|
||||
// @UseGuards(RoleGuard.requireds({ roles: ['admin', 'author'] }))
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: ['admin', 'author'] }))
|
||||
@Post('/orders')
|
||||
async createOrders(
|
||||
@Req() req: Request,
|
||||
@ -65,8 +60,6 @@ export class LicensesController {
|
||||
console.log(req.header('Authorization'));
|
||||
console.log(body);
|
||||
|
||||
// アクセストークンにより権限を確認する
|
||||
const pubKey = await this.cryptoService.getPublicKey();
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
|
||||
//アクセストークンが存在しない場合のエラー
|
||||
@ -76,26 +69,13 @@ export class LicensesController {
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const payload = verify<AccessToken>(accessToken, pubKey);
|
||||
|
||||
//アクセストークン形式エラー
|
||||
if (isVerifyError(payload)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
//アクセストークンの権限不足エラー
|
||||
if (!confirmPermission(payload.role)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000108'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedToken = decode(accessToken, {
|
||||
json: true,
|
||||
}) as AccessToken;
|
||||
|
||||
// ライセンス注文処理
|
||||
await this.licensesService.licenseOrders(
|
||||
payload,
|
||||
decodedToken,
|
||||
body.poNumber,
|
||||
body.orderCount,
|
||||
);
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { LicensesController } from './licenses.controller';
|
||||
import { LicensesService } from './licenses.service';
|
||||
import { CryptoModule } from '../../gateways/crypto/crypto.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { AccountsRepositoryModule } from '../../repositories/accounts/accounts.repository.module';
|
||||
import { LicensesRepositoryModule } from '../../repositories/licenses/licenses.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CryptoModule,
|
||||
UsersRepositoryModule,
|
||||
AccountsRepositoryModule,
|
||||
LicensesRepositoryModule,
|
||||
|
||||
@ -23,7 +23,7 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
expect(
|
||||
@ -45,7 +45,7 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '', role: '' };
|
||||
const token: AccessToken = { userId: '', role: '', tier: 5 };
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
@ -72,7 +72,7 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
@ -97,7 +97,7 @@ describe('LicensesService', () => {
|
||||
accountsRepositoryMockValue,
|
||||
);
|
||||
const body = new CreateOrdersRequest();
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
body.orderCount = 1000;
|
||||
body.poNumber = '1';
|
||||
await expect(
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
AdB2cService,
|
||||
ConflictError,
|
||||
} from '../../../gateways/adb2c/adb2c.service';
|
||||
import { CryptoService } from '../../../gateways/crypto/crypto.service';
|
||||
import { SendGridService } from '../../../gateways/sendgrid/sendgrid.service';
|
||||
import { User } from '../../../repositories/users/entity/user.entity';
|
||||
import { UsersRepositoryService } from '../../../repositories/users/users.repository.service';
|
||||
@ -56,7 +55,6 @@ export type SendGridMockValue = {
|
||||
};
|
||||
|
||||
export const makeUsersServiceMock = async (
|
||||
cryptoMockValue: CryptoMockValue,
|
||||
usersRepositoryMockValue: UsersRepositoryMockValue,
|
||||
adB2cMockValue: AdB2cMockValue,
|
||||
sendGridMockValue: SendGridMockValue,
|
||||
@ -67,15 +65,12 @@ export const makeUsersServiceMock = async (
|
||||
providers: [UsersService],
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
ignoreEnvFile: true,
|
||||
ignoreEnvVars: true,
|
||||
envFilePath: ['.env.local', '.env'],
|
||||
}),
|
||||
],
|
||||
})
|
||||
.useMocker((token) => {
|
||||
switch (token) {
|
||||
case CryptoService:
|
||||
return makeCryptoServiceMock(cryptoMockValue);
|
||||
case UsersRepositoryService:
|
||||
return makeUsersRepositoryMock(usersRepositoryMockValue);
|
||||
case AdB2cService:
|
||||
@ -277,22 +272,6 @@ export const makeConfigMock = (value: ConfigMockValue) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultCryptoMockValue = (): CryptoMockValue => {
|
||||
return {
|
||||
getPublicKey: [
|
||||
'-----BEGIN PUBLIC KEY-----',
|
||||
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd',
|
||||
'HYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3',
|
||||
'yCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW',
|
||||
'FJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS',
|
||||
'fiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//',
|
||||
'mBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO',
|
||||
'OQIDAQAB',
|
||||
'-----END PUBLIC KEY-----',
|
||||
].join('\n'),
|
||||
};
|
||||
};
|
||||
|
||||
export const makeDefaultSendGridlValue = (): SendGridMockValue => {
|
||||
return {
|
||||
sendMail: undefined,
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
describe('UsersController', () => {
|
||||
let controller: UsersController;
|
||||
const mockUserService = {};
|
||||
const mockCryptoService = {};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -18,12 +16,10 @@ describe('UsersController', () => {
|
||||
}),
|
||||
],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService, CryptoService],
|
||||
providers: [UsersService],
|
||||
})
|
||||
.overrideProvider(UsersService)
|
||||
.useValue(mockUserService)
|
||||
.overrideProvider(CryptoService)
|
||||
.useValue(mockCryptoService)
|
||||
.compile();
|
||||
|
||||
controller = module.get<UsersController>(UsersController);
|
||||
|
||||
@ -2,12 +2,12 @@ import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
HttpException,
|
||||
UseGuards,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
@ -16,39 +16,35 @@ import {
|
||||
ApiTags,
|
||||
} from '@nestjs/swagger';
|
||||
import { Request } from 'express';
|
||||
import { confirmPermission } from '../../common/auth/auth';
|
||||
import { decode } from 'jsonwebtoken';
|
||||
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
|
||||
import { ErrorResponse } from '../../common/error/types/types';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { isVerifyError, verify } from '../../common/jwt/jwt';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||
import {
|
||||
ConfirmRequest,
|
||||
ConfirmResponse,
|
||||
GetRelationsResponse,
|
||||
GetUsersResponse,
|
||||
SignupRequest,
|
||||
SignupResponse,
|
||||
PostSortCriteriaRequest,
|
||||
PostSortCriteriaResponse,
|
||||
GetSortCriteriaRequest,
|
||||
GetSortCriteriaResponse,
|
||||
} from './types/types';
|
||||
import { UsersService } from './users.service';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { retrieveAuthorizationToken } from '../../common/http/helper';
|
||||
import { AccessToken } from '../../common/token';
|
||||
import {
|
||||
isSortDirection,
|
||||
isTaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
import {
|
||||
ConfirmRequest,
|
||||
ConfirmResponse,
|
||||
GetRelationsResponse,
|
||||
GetSortCriteriaRequest,
|
||||
GetSortCriteriaResponse,
|
||||
GetUsersResponse,
|
||||
PostSortCriteriaRequest,
|
||||
PostSortCriteriaResponse,
|
||||
SignupRequest,
|
||||
SignupResponse,
|
||||
} from './types/types';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
@ApiTags('users')
|
||||
@Controller('users')
|
||||
export class UsersController {
|
||||
constructor(
|
||||
private readonly usersService: UsersService,
|
||||
private readonly cryptoService: CryptoService,
|
||||
) {}
|
||||
constructor(private readonly usersService: UsersService) {}
|
||||
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
@ -114,14 +110,12 @@ export class UsersController {
|
||||
})
|
||||
@ApiOperation({ operationId: 'getUsers' })
|
||||
@ApiBearerAuth()
|
||||
// @UseGuards(AuthGuard)
|
||||
// @UseGuards(RoleGuard.requireds({ roles: ['admin', 'author'] }))
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: ['admin', 'author'] }))
|
||||
@Get()
|
||||
async getUsers(@Req() req: Request): Promise<GetUsersResponse> {
|
||||
console.log(req.header('Authorization'));
|
||||
|
||||
// アクセストークンにより権限を確認する
|
||||
const pubKey = await this.cryptoService.getPublicKey();
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
|
||||
// アクセストークンが存在しない場合のエラー
|
||||
@ -131,25 +125,9 @@ export class UsersController {
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const payload = verify<AccessToken>(accessToken, pubKey);
|
||||
const decodedToken = decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
// アクセストークン形式エラー
|
||||
if (isVerifyError(payload)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
// アクセストークンの権限不足エラー
|
||||
if (!confirmPermission(payload.role)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000108'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const users = await this.usersService.getUsers(accessToken);
|
||||
const users = await this.usersService.getUsers(decodedToken);
|
||||
return { users };
|
||||
}
|
||||
|
||||
@ -175,6 +153,8 @@ export class UsersController {
|
||||
})
|
||||
@ApiOperation({ operationId: 'signup' })
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(RoleGuard.requireds({ roles: ['admin', 'author'] }))
|
||||
@Post('/signup')
|
||||
async signup(
|
||||
@Req() req: Request,
|
||||
@ -191,37 +171,20 @@ export class UsersController {
|
||||
typistGroupId,
|
||||
} = body;
|
||||
|
||||
// アクセストークンにより権限を確認する
|
||||
const pubKey = await this.cryptoService.getPublicKey();
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
|
||||
//アクセストークンが存在しない場合のエラー
|
||||
// アクセストークンが存在しない場合のエラー
|
||||
if (accessToken == undefined) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const payload = verify<AccessToken>(accessToken, pubKey);
|
||||
|
||||
//アクセストークン形式エラー
|
||||
if (isVerifyError(payload)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
//アクセストークンの権限不足エラー
|
||||
if (!confirmPermission(payload.role)) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000108'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedToken = decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
//ユーザ作成処理
|
||||
await this.usersService.createUser(
|
||||
payload,
|
||||
decodedToken,
|
||||
name,
|
||||
role,
|
||||
email,
|
||||
@ -313,7 +276,7 @@ export class UsersController {
|
||||
): Promise<PostSortCriteriaResponse> {
|
||||
const { direction, paramName } = body;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const decodedToken = decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
//型チェック
|
||||
if (
|
||||
@ -361,7 +324,7 @@ export class UsersController {
|
||||
): Promise<GetSortCriteriaResponse> {
|
||||
const {} = query;
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
const decodedToken = jwt.decode(accessToken, { json: true }) as AccessToken;
|
||||
const decodedToken = decode(accessToken, { json: true }) as AccessToken;
|
||||
|
||||
const { direction, paramName } = await this.usersService.getSortCriteria(
|
||||
decodedToken,
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { AdB2cModule } from '../../gateways/adb2c/adb2c.module';
|
||||
import { CryptoModule } from '../../gateways/crypto/crypto.module';
|
||||
import { SendGridModule } from '../../gateways/sendgrid/sendgrid.module';
|
||||
import { SortCriteriaRepositoryModule } from '../../repositories/sort_criteria/sort_criteria.repository.module';
|
||||
import { UsersRepositoryModule } from '../../repositories/users/users.repository.module';
|
||||
import { UsersController } from './users.controller';
|
||||
import { UsersService } from './users.service';
|
||||
import { SortCriteriaRepositoryModule } from '../../repositories/sort_criteria/sort_criteria.repository.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CryptoModule,
|
||||
UsersRepositoryModule,
|
||||
SortCriteriaRepositoryModule,
|
||||
AdB2cModule,
|
||||
|
||||
@ -6,7 +6,6 @@ import { EmailAlreadyVerifiedError } from '../../repositories/users/users.reposi
|
||||
import {
|
||||
makeDefaultAdB2cMockValue,
|
||||
makeDefaultConfigValue,
|
||||
makeDefaultCryptoMockValue,
|
||||
makeDefaultSendGridlValue,
|
||||
makeDefaultSortCriteriaRepositoryMockValue,
|
||||
makeDefaultUsersRepositoryMockValue,
|
||||
@ -16,7 +15,6 @@ import { User } from './types/types';
|
||||
|
||||
describe('UsersService', () => {
|
||||
it('ユーザの仮登録時に払い出されるトークンにより、未認証のユーザが認証済みになる', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendGridMockValue = makeDefaultSendGridlValue();
|
||||
@ -24,7 +22,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendGridMockValue,
|
||||
@ -37,7 +34,6 @@ describe('UsersService', () => {
|
||||
});
|
||||
|
||||
it('ユーザーが発行されたパスワードでログインできるようにする', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
usersRepositoryMockValue.findUserById = {
|
||||
id: 1,
|
||||
@ -60,7 +56,6 @@ describe('UsersService', () => {
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const sendGridMockValue = makeDefaultSendGridlValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendGridMockValue,
|
||||
@ -74,7 +69,6 @@ describe('UsersService', () => {
|
||||
});
|
||||
|
||||
it('トークンの形式が不正な場合、形式不正エラーとなる。', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -82,7 +76,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -96,7 +89,6 @@ describe('UsersService', () => {
|
||||
});
|
||||
|
||||
it('トークンの形式が不正な場合、形式不正エラーとなる。(メール認証API)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
usersRepositoryMockValue.findUserById = {
|
||||
id: 1,
|
||||
@ -119,7 +111,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendGridMockValue,
|
||||
@ -132,7 +123,6 @@ describe('UsersService', () => {
|
||||
);
|
||||
});
|
||||
it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -144,7 +134,6 @@ describe('UsersService', () => {
|
||||
new EmailAlreadyVerifiedError();
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -158,7 +147,6 @@ describe('UsersService', () => {
|
||||
);
|
||||
});
|
||||
it('ユーザが既に認証済みだった場合、認証済みユーザエラーとなる。(メール認証API)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
usersRepositoryMockValue.findUserById = {
|
||||
id: 1,
|
||||
@ -184,7 +172,6 @@ describe('UsersService', () => {
|
||||
new EmailAlreadyVerifiedError();
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendGridMockValue,
|
||||
@ -198,7 +185,6 @@ describe('UsersService', () => {
|
||||
);
|
||||
});
|
||||
it('DBネットワークエラーとなる場合、エラーとなる。', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -208,7 +194,6 @@ describe('UsersService', () => {
|
||||
usersRepositoryMockValue.updateUserVerified = new Error('DB error');
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -225,7 +210,6 @@ describe('UsersService', () => {
|
||||
);
|
||||
});
|
||||
it('DBネットワークエラーとなる場合、エラーとなる。(メール認証API)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
usersRepositoryMockValue.findUserById = {
|
||||
id: 1,
|
||||
@ -249,7 +233,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendGridMockValue,
|
||||
@ -266,7 +249,6 @@ describe('UsersService', () => {
|
||||
);
|
||||
});
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:None)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -274,7 +256,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -287,7 +268,7 @@ describe('UsersService', () => {
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
expect(
|
||||
await service.createUser(
|
||||
token,
|
||||
@ -304,7 +285,6 @@ describe('UsersService', () => {
|
||||
|
||||
describe('UsersService', () => {
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Author)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -312,7 +292,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -326,7 +305,7 @@ describe('UsersService', () => {
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const authorId = 'testID';
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
expect(
|
||||
await service.createUser(
|
||||
token,
|
||||
@ -344,7 +323,6 @@ describe('UsersService', () => {
|
||||
|
||||
describe('UsersService', () => {
|
||||
it('管理者権限のあるアクセストークンを使用して、新規ユーザが追加される(role:Transcriptioninst)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -352,7 +330,6 @@ describe('UsersService', () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -366,7 +343,7 @@ describe('UsersService', () => {
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const typistGroupId = 111;
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
expect(
|
||||
await service.createUser(
|
||||
token,
|
||||
@ -384,7 +361,6 @@ describe('UsersService', () => {
|
||||
});
|
||||
|
||||
it('DBネットワークエラーとなる場合、エラーとなる。', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -393,7 +369,6 @@ it('DBネットワークエラーとなる場合、エラーとなる。', async
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
usersRepositoryMockValue.createNormalUser = new Error('DB error');
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -406,7 +381,7 @@ it('DBネットワークエラーとなる場合、エラーとなる。', async
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
await expect(
|
||||
service.createUser(
|
||||
token,
|
||||
@ -425,7 +400,6 @@ it('DBネットワークエラーとなる場合、エラーとなる。', async
|
||||
);
|
||||
});
|
||||
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
adb2cParam.createUser = new Error();
|
||||
@ -434,7 +408,6 @@ it('Azure ADB2Cでネットワークエラーとなる場合、エラーとな
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -447,7 +420,7 @@ it('Azure ADB2Cでネットワークエラーとなる場合、エラーとな
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
await expect(
|
||||
service.createUser(
|
||||
token,
|
||||
@ -466,7 +439,6 @@ it('Azure ADB2Cでネットワークエラーとなる場合、エラーとな
|
||||
);
|
||||
});
|
||||
it('メールアドレスが重複している場合、エラーとなる。', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
adb2cParam.createUser = { reason: 'email', message: 'ObjectConflict' };
|
||||
@ -475,7 +447,6 @@ it('メールアドレスが重複している場合、エラーとなる。', a
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -488,7 +459,7 @@ it('メールアドレスが重複している場合、エラーとなる。', a
|
||||
const autoRenew = true;
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
await expect(
|
||||
service.createUser(
|
||||
token,
|
||||
@ -504,7 +475,6 @@ it('メールアドレスが重複している場合、エラーとなる。', a
|
||||
);
|
||||
});
|
||||
it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複チェックでエラー)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -515,7 +485,6 @@ it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複
|
||||
usersRepositoryMockValue.existsAuthorId = true;
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -529,7 +498,7 @@ it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const authorId = 'testID';
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
await expect(
|
||||
service.createUser(
|
||||
token,
|
||||
@ -546,7 +515,6 @@ it('AuthorIDが重複している場合、エラーとなる。(AuthorID重複
|
||||
);
|
||||
});
|
||||
it('AuthorIDが重複している場合、エラーとなる。(insert失敗)', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -557,7 +525,6 @@ it('AuthorIDが重複している場合、エラーとなる。(insert失敗)',
|
||||
usersRepositoryMockValue.createNormalUser.name = 'ER_DUP_ENTRY';
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -571,7 +538,7 @@ it('AuthorIDが重複している場合、エラーとなる。(insert失敗)',
|
||||
const licenseAlert = true;
|
||||
const notification = true;
|
||||
const authorId = 'testID';
|
||||
const token: AccessToken = { userId: '0001', role: '' };
|
||||
const token: AccessToken = { userId: '0001', role: '', tier: 5 };
|
||||
await expect(
|
||||
service.createUser(
|
||||
token,
|
||||
@ -589,7 +556,6 @@ it('AuthorIDが重複している場合、エラーとなる。(insert失敗)',
|
||||
});
|
||||
|
||||
it('ユーザの一覧を取得する', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -597,17 +563,16 @@ it('ユーザの一覧を取得する', async () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
configMockValue,
|
||||
sortCriteriaRepositoryMockValue,
|
||||
);
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
|
||||
|
||||
expect(await service.getUsers(token)).toEqual(expectedUsers);
|
||||
expect(
|
||||
await service.getUsers({ role: 'Admin', userId: 'XXXXXX', tier: 5 }),
|
||||
).toEqual(expectedUsers);
|
||||
});
|
||||
|
||||
const expectedUsers = [
|
||||
@ -636,7 +601,6 @@ const expectedUsers = [
|
||||
];
|
||||
|
||||
it('ユーザの一覧を取得に失敗する', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -648,7 +612,6 @@ it('ユーザの一覧を取得に失敗する', async () => {
|
||||
);
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -656,16 +619,14 @@ it('ユーザの一覧を取得に失敗する', async () => {
|
||||
sortCriteriaRepositoryMockValue,
|
||||
);
|
||||
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
|
||||
|
||||
await expect(service.getUsers(token)).rejects.toEqual(
|
||||
await expect(
|
||||
service.getUsers({ role: 'Admin', userId: 'XXXXXX', tier: 5 }),
|
||||
).rejects.toEqual(
|
||||
new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND),
|
||||
);
|
||||
});
|
||||
|
||||
it('ユーザの一覧を0件取得する', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -678,7 +639,6 @@ it('ユーザの一覧を0件取得する', async () => {
|
||||
usersRepositoryMockValue.findSameAccountUsers = noDbUsers;
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -686,15 +646,13 @@ it('ユーザの一覧を0件取得する', async () => {
|
||||
sortCriteriaRepositoryMockValue,
|
||||
);
|
||||
|
||||
const token =
|
||||
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
|
||||
|
||||
const emptyMergedUsers: User[] = [];
|
||||
expect(await service.getUsers(token)).toEqual(emptyMergedUsers);
|
||||
expect(
|
||||
await service.getUsers({ role: 'Admin', userId: 'XXXXXX', tier: 5 }),
|
||||
).toEqual(emptyMergedUsers);
|
||||
});
|
||||
|
||||
it('ソート条件を変更できる', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -702,7 +660,6 @@ it('ソート条件を変更できる', async () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -714,12 +671,12 @@ it('ソート条件を変更できる', async () => {
|
||||
await service.updateSortCriteria('AUTHOR_ID', 'ASC', {
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('ユーザー情報が存在せず、ソート条件を変更できない', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -730,7 +687,6 @@ it('ユーザー情報が存在せず、ソート条件を変更できない', a
|
||||
usersRepositoryMockValue.findUserByExternalId = new Error('user not found');
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -742,6 +698,7 @@ it('ユーザー情報が存在せず、ソート条件を変更できない', a
|
||||
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
@ -752,7 +709,6 @@ it('ユーザー情報が存在せず、ソート条件を変更できない', a
|
||||
});
|
||||
|
||||
it('ソート条件が存在せず、ソート条件を変更できない', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -764,7 +720,6 @@ it('ソート条件が存在せず、ソート条件を変更できない', asyn
|
||||
);
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -776,6 +731,7 @@ it('ソート条件が存在せず、ソート条件を変更できない', asyn
|
||||
service.updateSortCriteria('AUTHOR_ID', 'ASC', {
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
@ -786,7 +742,6 @@ it('ソート条件が存在せず、ソート条件を変更できない', asyn
|
||||
});
|
||||
|
||||
it('ソート条件を取得できる', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -794,7 +749,6 @@ it('ソート条件を取得できる', async () => {
|
||||
const sortCriteriaRepositoryMockValue =
|
||||
makeDefaultSortCriteriaRepositoryMockValue();
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -806,12 +760,12 @@ it('ソート条件を取得できる', async () => {
|
||||
await service.getSortCriteria({
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).toEqual({ direction: 'ASC', paramName: 'JOB_NUMBER' });
|
||||
});
|
||||
|
||||
it('ソート条件が存在せず、ソート条件を取得できない', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -824,7 +778,6 @@ it('ソート条件が存在せず、ソート条件を取得できない', asyn
|
||||
);
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -836,6 +789,7 @@ it('ソート条件が存在せず、ソート条件を取得できない', asyn
|
||||
service.getSortCriteria({
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
@ -846,7 +800,6 @@ it('ソート条件が存在せず、ソート条件を取得できない', asyn
|
||||
});
|
||||
|
||||
it('DBから取得した値が不正だった場合、エラーとなる', async () => {
|
||||
const cryptoMockValue = makeDefaultCryptoMockValue();
|
||||
const usersRepositoryMockValue = makeDefaultUsersRepositoryMockValue();
|
||||
const adb2cParam = makeDefaultAdB2cMockValue();
|
||||
const sendgridMockValue = makeDefaultSendGridlValue();
|
||||
@ -861,7 +814,6 @@ it('DBから取得した値が不正だった場合、エラーとなる', async
|
||||
};
|
||||
|
||||
const service = await makeUsersServiceMock(
|
||||
cryptoMockValue,
|
||||
usersRepositoryMockValue,
|
||||
adb2cParam,
|
||||
sendgridMockValue,
|
||||
@ -873,6 +825,7 @@ it('DBから取得した値が不正だった場合、エラーとなる', async
|
||||
service.getSortCriteria({
|
||||
role: 'none admin',
|
||||
userId: 'xxxxxxxxxxxx',
|
||||
tier: 5,
|
||||
}),
|
||||
).rejects.toEqual(
|
||||
new HttpException(
|
||||
|
||||
@ -2,20 +2,20 @@ 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 { AccessToken } from '../../common/token';
|
||||
import {
|
||||
TaskListSortableAttribute,
|
||||
SortDirection,
|
||||
isTaskListSortableAttribute,
|
||||
TaskListSortableAttribute,
|
||||
isSortDirection,
|
||||
isTaskListSortableAttribute,
|
||||
} from '../../common/types/sort';
|
||||
import {
|
||||
AdB2cService,
|
||||
ConflictError,
|
||||
isConflictError,
|
||||
} from '../../gateways/adb2c/adb2c.service';
|
||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||
import { SendGridService } from '../../gateways/sendgrid/sendgrid.service';
|
||||
import { SortCriteriaRepositoryService } from '../../repositories/sort_criteria/sort_criteria.repository.service';
|
||||
import { User as EntityUser } from '../../repositories/users/entity/user.entity';
|
||||
@ -28,7 +28,6 @@ import { User } from './types/types';
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(
|
||||
private readonly cryptoService: CryptoService,
|
||||
private readonly usersRepository: UsersRepositoryService,
|
||||
private readonly sortCriteriaRepository: SortCriteriaRepositoryService,
|
||||
private readonly adB2cService: AdB2cService,
|
||||
@ -43,8 +42,7 @@ export class UsersService {
|
||||
*/
|
||||
async confirmUser(token: string): Promise<void> {
|
||||
this.logger.log(`[IN] ${this.confirmUser.name}`);
|
||||
|
||||
const pubKey = await this.cryptoService.getPublicKey();
|
||||
const pubKey = getPublicKey(this.configService);
|
||||
|
||||
const decodedToken = verify<{
|
||||
accountId: number;
|
||||
@ -236,7 +234,8 @@ export class UsersService {
|
||||
async confirmUserAndInitPassword(token: string): Promise<void> {
|
||||
this.logger.log(`[IN] ${this.confirmUserAndInitPassword.name}`);
|
||||
|
||||
const pubKey = await this.cryptoService.getPublicKey();
|
||||
const pubKey = getPublicKey(this.configService);
|
||||
|
||||
const decodedToken = verify<{
|
||||
accountId: number;
|
||||
userId: number;
|
||||
@ -295,20 +294,13 @@ export class UsersService {
|
||||
* @param accessToken
|
||||
* @returns users
|
||||
*/
|
||||
async getUsers(accessToken: string): Promise<User[]> {
|
||||
async getUsers(accessToken: AccessToken): Promise<User[]> {
|
||||
this.logger.log(`[IN] ${this.getUsers.name}`);
|
||||
|
||||
try {
|
||||
// DBよりアクセス者の所属するアカウントを取得する
|
||||
const pubKey = await this.cryptoService.getPublicKey();
|
||||
const payload = verify<AccessToken>(accessToken, pubKey);
|
||||
if (isVerifyError(payload)) {
|
||||
throw new Error(`${payload.reason} | ${payload.message}`);
|
||||
}
|
||||
|
||||
// DBから同一アカウントのユーザ一覧を取得する
|
||||
const dbUsers = await this.usersRepository.findSameAccountUsers(
|
||||
payload.userId,
|
||||
accessToken.userId,
|
||||
);
|
||||
|
||||
// 値をマージして定義されたレスポンス通りに返す
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { CryptoService } from './crypto.service';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
exports: [CryptoService],
|
||||
providers: [CryptoService],
|
||||
})
|
||||
export class CryptoModule {}
|
||||
@ -1,74 +0,0 @@
|
||||
import { DefaultAzureCredential } from '@azure/identity';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import jwkToPem from 'jwk-to-pem';
|
||||
import { JwkSignKey } from '../../common/token';
|
||||
|
||||
@Injectable()
|
||||
export class CryptoService {
|
||||
private readonly logger = new Logger(CryptoService.name);
|
||||
private readonly credential: DefaultAzureCredential;
|
||||
private readonly url: string;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
/**
|
||||
* Gets private key
|
||||
* @returns private key(PEM)
|
||||
*/
|
||||
async getPrivateKey(): Promise<string> {
|
||||
try {
|
||||
const key = this.configService.get<string>('JWT_PRIVATE_KEY');
|
||||
if (key) {
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
return key.replace('\\n', '\n');
|
||||
}
|
||||
throw new Error(`JWT_PRIVATE_KEY not found.`);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(`[OUT] ${this.getPrivateKey.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets public key
|
||||
* @returns public key(PEM)
|
||||
*/
|
||||
async getPublicKey(): Promise<string> {
|
||||
try {
|
||||
const key = this.configService.get<string>('JWT_PUBLIC_KEY');
|
||||
if (key) {
|
||||
// 開発環境用に改行コードを置換する
|
||||
// 本番環境では\\nが含まれないため、置換が行われない想定
|
||||
return key.replace('\\n', '\n');
|
||||
}
|
||||
throw new Error(`JWT_PUBLIC_KEY not found.`);
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(`[OUT] ${this.getPublicKey.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getPublicKeyFromJwk(jwkKey: JwkSignKey): Promise<string> {
|
||||
try {
|
||||
// JWK形式のJSONなのでJWTの公開鍵として使えるようにPEM形式に変換
|
||||
const publicKey = jwkToPem({
|
||||
kty: 'RSA',
|
||||
n: jwkKey.n,
|
||||
e: jwkKey.e,
|
||||
});
|
||||
|
||||
return publicKey;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
throw e;
|
||||
} finally {
|
||||
this.logger.log(`[OUT] ${this.getPublicKey.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { sign } from '../../common/jwt';
|
||||
import sendgrid from '@sendgrid/mail';
|
||||
import { getPrivateKey } from '../../common/jwt/jwt';
|
||||
|
||||
@Injectable()
|
||||
export class SendGridService {
|
||||
@ -25,9 +26,7 @@ export class SendGridService {
|
||||
): Promise<{ subject: string; text: string; html: string }> {
|
||||
const lifetime =
|
||||
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
|
||||
const privateKey =
|
||||
this.configService.get<string>('JWT_PRIVATE_KEY')?.replace('\\n', '\n') ??
|
||||
'';
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
const token = sign<{ accountId: number; userId: number; email: string }>(
|
||||
{
|
||||
accountId,
|
||||
@ -62,9 +61,9 @@ export class SendGridService {
|
||||
): Promise<{ subject: string; text: string; html: string }> {
|
||||
const lifetime =
|
||||
this.configService.get<number>('EMAIL_CONFIRM_LIFETIME') ?? 0;
|
||||
const privateKey =
|
||||
this.configService.get<string>('JWT_PRIVATE_KEY')?.replace('\\n', '\n') ??
|
||||
'';
|
||||
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
const token = sign<{ accountId: number; userId: number; email: string }>(
|
||||
{
|
||||
accountId,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user