OMDSCloud/dictation_server/src/features/auth/auth.service.spec.ts
saito.k f1583cf783 Merged PR 611: 操作を特定できる文字列を追跡用のIDに追加する(IPアドレスもログに出力する)
## 概要
[Task3265: IPアドレスを追跡用のIDに追加する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3265)

- MiddlewareでUUIDを発行しリクエストのヘッダに追加する
- 各コントローラーではヘッダからUUIDとIPアドレスを取得する
  - 取得したUUIDとADB2Cの外部IDでトラッキングIDを作成する
  - 作成したトラッキングIDとIPアドレスの繋がりをログに出力する。

## レビューポイント
- ADB2Cの外部IDがない場合にUnauthorized Userという文字列を入れているがほかの表現のほうが良いか
  - 外部IDもオプショナルにして入れなくてもよくする?
-

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2023-12-12 04:11:36 +00:00

812 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import {
makeAuthServiceMock,
makeDefaultAdB2cMockValue,
makeDefaultConfigValue,
makeDefaultGetPublicKeyFromJwk,
} from './test/auth.service.mock';
import { DataSource } from 'typeorm';
import { makeContext } from '../../common/log';
import { makeTestingModule } from '../../common/test/modules';
import { getAccount, makeTestAccount } from '../../common/test/utility';
import { AuthService } from './auth.service';
import {
createTermInfo,
deleteAccount,
updateAccountDelegationPermission,
} from './test/utility';
import { v4 as uuidv4 } from 'uuid';
import { TIERS, USER_ROLES } from '../../constants';
import { decode, isVerifyError } from '../../common/jwt';
import { RefreshToken, AccessToken } from '../../common/token';
describe('AuthService', () => {
it('IDトークンの検証とペイロードの取得に成功する', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
expect(await service.getVerifiedIdToken(context, token)).toEqual(
idTokenPayload,
);
});
it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = 'invalid.id.token';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.UNAUTHORIZED),
);
});
it('IDトークンの有効期限が切れている場合、有効期限切れエラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.r9x61Mf1S2qFgU_QDKB6tRFBmTQXyOEtpoacOlL_bQzFz1t3GsxMy6SJIvQQ-LtDgylQ1UCdMFiRuy4V8nyLuME0fR-9IkKsboGvwllHB_Isai3XFoja0jpDHMVby1m0B3Z9xOTb7YsaQGyEH-qs1TtnRm6Ny98h4Po80nK8HGefQZHBOlfQN_B1LiHwI3nLXV18NL-4olKXj2NloNRYtnWM0PaqDQcGvZFaSNvtrSYpo9ddD906QWDGVOQ7WvGSUgdNCoxX8Lb3r2-VSj6n84jpb-Y1Fz-GhLluNglAsBhasnJfUIvCIO3iG5pRyTYjHFAVHmzjr8xMOmhS3s41Jw';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000102'), HttpStatus.UNAUTHORIZED),
);
});
it('IDトークンが開始日より前の場合、開始前エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//JWKの生成→PEM変換を自力で表現することが厳しいためMockで代替
service.getPublicKeyFromJwk = makeDefaultGetPublicKeyFromJwk;
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.fX2Gbd7fDPNE3Lw-xbum_5CVqQYqEmMhv_v5u8A-U81pmPD2P5rsJEJx66ns1taFLVaE3j9_OzotxrqjqqQqbACkagGcN5wvA3_ZIxyqmhrKYFJc53ZcO7d0pFWiQlluNBI_pnFNDlSMB2Ut8Th5aiPy2uamBM9wC99bcjo7HkHvTKBf6ljU6rPKoD51qGDWqNxjoH-hdSJ29wprvyxyk_yX6dp-cxXUj5DIgXYQuIZF71rdiPtGlAiyTBns8rS2QlEEXapZVlvYrK4mkpUXVDA7ifD8q6gAC2BStqHeys7CGp2MbV4ZwKCVbAUbMs6Tboh8rADZvQhuTEq7qlhZ-w';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000103'), HttpStatus.UNAUTHORIZED),
);
});
it('IDトークンの署名が不正な場合、署名不正エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000104'), HttpStatus.UNAUTHORIZED),
);
});
it('IDトークンの発行元が想定と異なる場合、発行元不正エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
//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';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000105'), HttpStatus.UNAUTHORIZED),
);
});
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。メタデータ', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
adb2cParam.getMetaData = new Error('failed get metadata');
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
it('Azure ADB2Cでネットワークエラーとなる場合、エラーとなる。キーセット', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
adb2cParam.getSignKeySets = new Error('failed get keyset');
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
it('Azure ADB2Cから取得した鍵が一致しない場合、エラーとなる。', async () => {
const adb2cParam = makeDefaultAdB2cMockValue();
const configMockValue = makeDefaultConfigValue();
adb2cParam.getSignKeySets = [
{ kid: 'invalid', kty: 'RSA', nbf: 0, use: 'sig', e: '', n: '' },
];
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
const context = makeContext(`uuidv4`, 'xxx.xxx.xxx.xxx', 'requestId');
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
});
});
describe('checkIsAcceptedLatestVersion', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第五)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, {
tier: 5,
});
const context = makeContext(uuidv4(), 'xxx.xxx.xxx.xxx', 'requestId');
const idToken = {
emails: [],
sub: admin.external_id,
exp: 0,
iat: 0,
};
await createTermInfo(source, 'EULA', '1.0');
await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(true);
});
it('同意済み利用規約バージョンが最新のときにチェックが通ること(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, {
tier: 4,
});
const context = makeContext(uuidv4(), 'xxx.xxx.xxx.xxx', 'requestId');
const idToken = {
emails: [],
sub: admin.external_id,
exp: 0,
iat: 0,
};
await createTermInfo(source, 'EULA', '1.0');
await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(true);
});
it('同意済み利用規約バージョンが最新でないときにチェックが通らないこと(第五)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, {
tier: 5,
});
const context = makeContext(uuidv4(), 'xxx.xxx.xxx.xxx', 'requestId');
const idToken = {
emails: [],
sub: admin.external_id,
exp: 0,
iat: 0,
};
await createTermInfo(source, 'EULA', '1.1');
await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
});
it('同意済み利用規約EULAバージョンが最新でないときにチェックが通らないこと第一第四', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, {
tier: 4,
});
const context = makeContext(uuidv4(), 'xxx.xxx.xxx.xxx', 'requestId');
const idToken = {
emails: [],
sub: admin.external_id,
exp: 0,
iat: 0,
};
await createTermInfo(source, 'EULA', '1.1');
await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
});
it('同意済み利用規約バージョンDPAが最新でないときにチェックが通らないこと第一第四', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, {
tier: 4,
});
const context = makeContext(uuidv4(), 'xxx.xxx.xxx.xxx', 'requestId');
const idToken = {
emails: [],
sub: admin.external_id,
exp: 0,
iat: 0,
};
await createTermInfo(source, 'EULA', '1.0');
await createTermInfo(source, 'PrivacyNotice', '1.0');
await createTermInfo(source, 'DPA', '1.1');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
});
it('同意済みプライバシーポリシーが最新でないときにチェックが通らないこと(第一~第四)', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin } = await makeTestAccount(source, {
tier: 4,
});
const context = makeContext(uuidv4(), 'xxx.xxx.xxx.xxx', 'requestId');
const idToken = {
emails: [],
sub: admin.external_id,
exp: 0,
iat: 0,
};
await createTermInfo(source, 'EULA', '1.0');
await createTermInfo(source, 'PrivacyNotice', '1.1');
await createTermInfo(source, 'DPA', '1.0');
const result = await service.isAcceptedLatestVersion(context, idToken);
expect(result).toBe(false);
});
});
describe('generateDelegationRefreshToken', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('代行操作が許可されたパートナーの代行操作用リフレッシュトークンを取得できること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
const { admin: partnerAdmin, account: partnerAccount } =
await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: true,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
const delegationRefreshToken = await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
partnerAccount.id,
);
// 取得できた代行操作用リフレッシュトークンをデコード
const decodeToken = decode<RefreshToken>(delegationRefreshToken);
if (isVerifyError(decodeToken)) {
fail();
}
expect(decodeToken.role).toBe('none admin');
expect(decodeToken.tier).toBe(TIERS.TIER5);
expect(decodeToken.userId).toBe(partnerAdmin.external_id);
expect(decodeToken.delegateUserId).toBe(parentAdmin.external_id);
});
it('代行操作が許可されていない場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
const { account: partnerAccount } = await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: false,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
try {
await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
partnerAccount.id,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010503'));
} else {
fail();
}
}
});
it('代行操作対象が存在しない場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: false,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
try {
await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
9999,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
} else {
fail();
}
}
});
});
describe('generateDelegationAccessToken', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('代行操作用リフレッシュトークンから代行操作用アクセストークンを取得できること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
const { admin: partnerAdmin, account: partnerAccount } =
await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: true,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
const delegationRefreshToken = await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
partnerAccount.id,
);
// 取得できた代行操作用リフレッシュトークンをデコード
const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
if (isVerifyError(decodeRefreshToken)) {
fail();
}
expect(decodeRefreshToken.role).toBe('none admin');
expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);
const delegationAccessToken = await service.generateDelegationAccessToken(
context,
delegationRefreshToken,
);
// 取得できた代行操作用アクセストークンをデコード
const decodeAccessToken = decode<AccessToken>(delegationAccessToken);
if (isVerifyError(decodeAccessToken)) {
fail();
}
expect(decodeAccessToken.role).toBe('none admin');
expect(decodeAccessToken.tier).toBe(TIERS.TIER5);
expect(decodeAccessToken.userId).toBe(partnerAdmin.external_id);
expect(decodeAccessToken.delegateUserId).toBe(parentAdmin.external_id);
});
it('代行操作用リフレッシュトークンの形式が不正な場合、エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin } = await makeTestAccount(source, {
tier: 4,
});
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
try {
await service.generateDelegationAccessToken(context, 'invalid token');
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.UNAUTHORIZED);
expect(e.getResponse()).toEqual(makeErrorResponse('E000101'));
} else {
fail();
}
}
});
});
describe('updateDelegationAccessToken', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('代行操作用リフレッシュトークンから代行操作用アクセストークンを更新できること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
const { admin: partnerAdmin, account: partnerAccount } =
await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: true,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
const delegationRefreshToken = await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
partnerAccount.id,
);
// 取得できた代行操作用リフレッシュトークンをデコード
const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
if (isVerifyError(decodeRefreshToken)) {
fail();
}
expect(decodeRefreshToken.role).toBe('none admin');
expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);
const token = await service.updateDelegationAccessToken(
context,
decodeRefreshToken.delegateUserId,
decodeRefreshToken.userId,
delegationRefreshToken,
);
// 取得できた代行操作用リフレッシュトークンをデコード
const decodeAccessToken = decode<RefreshToken>(token);
if (isVerifyError(decodeAccessToken)) {
fail();
}
expect(decodeAccessToken.role).toBe('none admin');
expect(decodeAccessToken.tier).toBe(TIERS.TIER5);
expect(decodeAccessToken.userId).toBe(partnerAdmin.external_id);
expect(decodeAccessToken.delegateUserId).toBe(parentAdmin.external_id);
});
it('代行操作対象アカウントの代行操作が許可されていない場合、エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
const { admin: partnerAdmin, account: partnerAccount } =
await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: true,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
const delegationRefreshToken = await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
partnerAccount.id,
);
// 取得できた代行操作用リフレッシュトークンをデコード
const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
if (isVerifyError(decodeRefreshToken)) {
fail();
}
expect(decodeRefreshToken.role).toBe('none admin');
expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);
if (decodeRefreshToken.delegateUserId === undefined) {
fail();
}
// 代行操作対象アカウントの代行操作を許可しないように変更
await updateAccountDelegationPermission(source, partnerAccount.id, false);
const account = await getAccount(source, partnerAccount.id);
expect(account?.delegation_permission ?? true).toBeFalsy();
try {
await service.updateDelegationAccessToken(
context,
decodeRefreshToken.delegateUserId,
decodeRefreshToken.userId,
delegationRefreshToken,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.UNAUTHORIZED);
expect(e.getResponse()).toEqual(makeErrorResponse('E010503'));
} else {
fail();
}
}
});
it('代行操作対象アカウントが存在しない場合、エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AuthService>(AuthService);
const { admin: parentAdmin, account: parentAccount } =
await makeTestAccount(source, {
tier: 4,
});
const { admin: partnerAdmin, account: partnerAccount } =
await makeTestAccount(
source,
{
tier: 5,
parent_account_id: parentAccount.id,
delegation_permission: true,
},
{ role: USER_ROLES.NONE },
);
const context = makeContext(
parentAdmin.external_id,
'xxx.xxx.xxx.xxx',
'requestId',
);
const delegationRefreshToken = await service.generateDelegationRefreshToken(
context,
parentAdmin.external_id,
partnerAccount.id,
);
// 取得できた代行操作用リフレッシュトークンをデコード
const decodeRefreshToken = decode<RefreshToken>(delegationRefreshToken);
if (isVerifyError(decodeRefreshToken)) {
fail();
}
expect(decodeRefreshToken.role).toBe('none admin');
expect(decodeRefreshToken.tier).toBe(TIERS.TIER5);
expect(decodeRefreshToken.userId).toBe(partnerAdmin.external_id);
expect(decodeRefreshToken.delegateUserId).toBe(parentAdmin.external_id);
if (decodeRefreshToken.delegateUserId === undefined) {
fail();
}
// 代行操作対象アカウントを削除
deleteAccount(source, partnerAccount.id);
try {
await service.updateDelegationAccessToken(
context,
decodeRefreshToken.delegateUserId,
partnerAdmin.external_id,
delegationRefreshToken,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.UNAUTHORIZED);
expect(e.getResponse()).toEqual(makeErrorResponse('E010501'));
} else {
fail();
}
}
});
});
const idTokenPayload = {
exp: 9000000000,
nbf: 1000000000,
ver: '1.0',
iss: 'issuer',
sub: 'sub',
aud: 'aud',
nonce: 'defaultNonce',
iat: 1000000000,
auth_time: 1000000000,
emails: ['xxx@xx.com'],
tfp: 'signin_userflow',
};