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); 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); 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); 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); 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); 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); 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); 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(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); 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); 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); 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(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(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); 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); 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(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(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); 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(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); 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(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', };