Merged PR 530: API実装(代行操作用トークン生成API)
## 概要 [Task2905: API実装(代行操作用トークン生成API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2905) - 代行操作用トークン生成APIとテストを実装しました。 ## レビューポイント - リポジトリの処理は適切か - アカウントの取得⇒管理者ユーザ取得としているためUsersリポジトリ配下に配置していますが構成として問題ないでしょうか。 - テストケースは適切か - アクセストークン生成は既存と別に代行操作用のメソッドを用意していますが想定とあっていますでしょうか。 ## UIの変更 - なし ## 動作確認状況 - ローカルで確認
This commit is contained in:
parent
b314fe4b46
commit
e6da791406
@ -38,6 +38,7 @@ export const ErrorCodes = [
|
||||
'E010401', // PONumber重複エラー
|
||||
'E010501', // アカウント不在エラー
|
||||
'E010502', // アカウント情報変更不可エラー
|
||||
'E010503', // 代行操作不許可エラー
|
||||
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
|
||||
'E010602', // タスク変更権限不足エラー
|
||||
'E010603', // タスク不在エラー
|
||||
|
||||
@ -27,6 +27,7 @@ export const errors: Errors = {
|
||||
E010401: 'This PoNumber already used Error',
|
||||
E010501: 'Account not Found Error.',
|
||||
E010502: 'Account information cannot be changed Error.',
|
||||
E010503: 'Delegation not allowed Error.',
|
||||
E010601: 'Task is not Editable Error',
|
||||
E010602: 'No task edit permissions Error',
|
||||
E010603: 'Task not found Error.',
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
import { Context } from './types';
|
||||
|
||||
export const makeContext = (externalId: string): Context => {
|
||||
export const makeContext = (
|
||||
externalId: string,
|
||||
delegationId?: string,
|
||||
): Context => {
|
||||
return {
|
||||
trackingId: externalId,
|
||||
delegationId: delegationId,
|
||||
};
|
||||
};
|
||||
|
||||
@ -3,4 +3,8 @@ export class Context {
|
||||
* APIの操作ユーザーを追跡するためのID
|
||||
*/
|
||||
trackingId: string;
|
||||
/**
|
||||
* APIの代行操作ユーザーを追跡するためのID
|
||||
*/
|
||||
delegationId?: string | undefined;
|
||||
}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
export type RefreshToken = {
|
||||
/**
|
||||
* 外部認証サービスの識別子(代行者)
|
||||
*/
|
||||
delegateUserId?: string | undefined;
|
||||
/**
|
||||
* 外部認証サービスの識別子
|
||||
*/
|
||||
@ -14,6 +18,10 @@ export type RefreshToken = {
|
||||
};
|
||||
|
||||
export type AccessToken = {
|
||||
/**
|
||||
* 外部認証サービスの識別子(代行者)
|
||||
*/
|
||||
delegateUserId?: string | undefined;
|
||||
/**
|
||||
* 外部認証サービスの識別子
|
||||
*/
|
||||
|
||||
@ -31,6 +31,8 @@ import { Request } from 'express';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
import { RoleGuard } from '../../common/guards/role/roleguards';
|
||||
import { ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { AccessToken } from '../../common/token';
|
||||
|
||||
@ApiTags('auth')
|
||||
@Controller('auth')
|
||||
@ -183,18 +185,35 @@ export class AuthController {
|
||||
@Body() body: DelegationTokenRequest,
|
||||
): Promise<DelegationTokenResponse> {
|
||||
const { delegatedAccountId } = body;
|
||||
const refreshToken = retrieveAuthorizationToken(req);
|
||||
const token = retrieveAuthorizationToken(req);
|
||||
|
||||
if (!refreshToken) {
|
||||
if (!token) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const decodedAccessToken = jwt.decode(token, { json: true });
|
||||
if (!decodedAccessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
const { userId } = decodedAccessToken as AccessToken;
|
||||
|
||||
const context = makeContext(uuidv4());
|
||||
const context = makeContext(userId);
|
||||
const refreshToken = await this.authService.generateDelegationRefreshToken(
|
||||
context,
|
||||
userId,
|
||||
delegatedAccountId,
|
||||
);
|
||||
const accessToken = await this.authService.generateDelegationAccessToken(
|
||||
context,
|
||||
refreshToken,
|
||||
);
|
||||
|
||||
return { accessToken: '', refreshToken: '' };
|
||||
return { accessToken, refreshToken };
|
||||
}
|
||||
|
||||
@Post('delegation/access-token')
|
||||
|
||||
@ -13,6 +13,9 @@ import { makeTestAccount } from '../../common/test/utility';
|
||||
import { AuthService } from './auth.service';
|
||||
import { createTermInfo } 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 () => {
|
||||
@ -276,6 +279,239 @@ describe('checkIsAcceptedLatestVersion', () => {
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const idTokenPayload = {
|
||||
exp: 9000000000,
|
||||
nbf: 1000000000,
|
||||
|
||||
@ -19,9 +19,14 @@ import {
|
||||
} from '../../common/token';
|
||||
import { ADMIN_ROLES, TIERS, 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';
|
||||
import { Context } from '../../common/log';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
AdminUserNotFoundError,
|
||||
} from '../../repositories/accounts/errors/types';
|
||||
import { DelegationNotAllowedError } from '../../repositories/users/errors/types';
|
||||
import { RoleUnexpectedError, TierUnexpectedError } from './errors/types';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@ -76,39 +81,23 @@ export class AuthService {
|
||||
`[IN] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
|
||||
let user: User;
|
||||
// ユーザー情報とユーザーが属しているアカウント情報を取得
|
||||
try {
|
||||
user = await this.usersRepository.findUserByExternalId(idToken.sub);
|
||||
const user = await this.usersRepository.findUserByExternalId(idToken.sub);
|
||||
if (!user.account) {
|
||||
throw new Error('Account information not found');
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
// Tierのチェック
|
||||
const minTier = 1;
|
||||
const maxTier = 5;
|
||||
const userTier = user.account.tier;
|
||||
if (userTier < minTier || userTier > maxTier) {
|
||||
this.logger.error(
|
||||
throw new TierUnexpectedError(
|
||||
`Tier from DB is unexpected value. tier=${user.account.tier}`,
|
||||
);
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010206'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
// 要求された環境用トークンの寿命を決定
|
||||
const refreshTokenLifetime =
|
||||
type === 'web'
|
||||
@ -117,26 +106,7 @@ export class AuthService {
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
// ユーザーのロールを設定
|
||||
// 万一不正なRoleが登録されていた場合、そのままDBの値を使用すると不正なロールのリフレッシュトークンが発行されるため、
|
||||
// ロールの設定値はDBに保存したRoleの値を直接トークンに入れないように定数で設定する
|
||||
// ※none/author/typist以外はロールに設定されない
|
||||
let role = '';
|
||||
if (user.role === USER_ROLES.NONE) {
|
||||
role = USER_ROLES.NONE;
|
||||
} else if (user.role === USER_ROLES.AUTHOR) {
|
||||
role = USER_ROLES.AUTHOR;
|
||||
} else if (user.role === USER_ROLES.TYPIST) {
|
||||
role = USER_ROLES.TYPIST;
|
||||
} else {
|
||||
this.logger.error(`Role from DB is unexpected value. role=${user.role}`);
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010205'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
const role = this.getUserRole(user.role);
|
||||
|
||||
const token = sign<RefreshToken>(
|
||||
{
|
||||
@ -154,10 +124,34 @@ export class AuthService {
|
||||
privateKey,
|
||||
);
|
||||
|
||||
return token;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case TierUnexpectedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010206'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
case RoleUnexpectedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010205'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateRefreshToken.name}`,
|
||||
);
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -203,6 +197,137 @@ export class AuthService {
|
||||
);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 代行操作用のリフレッシュトークンを生成します
|
||||
* @param context
|
||||
* @param delegateUserExternalId 代行操作者の外部認証サービスの識別子
|
||||
* @param originAccountId 代行操作対象アカウントのID
|
||||
* @returns delegation refresh token
|
||||
*/
|
||||
async generateDelegationRefreshToken(
|
||||
context: Context,
|
||||
delegateUserExternalId: string,
|
||||
originAccountId: number,
|
||||
): Promise<string> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.generateDelegationRefreshToken.name} | params: { ` +
|
||||
`delegateUserExternalId: ${delegateUserExternalId}, ` +
|
||||
`originAccountId: ${originAccountId}, };`,
|
||||
);
|
||||
|
||||
// ユーザー情報とユーザーが属しているアカウント情報を取得
|
||||
try {
|
||||
const user = await this.usersRepository.findUserByExternalId(
|
||||
delegateUserExternalId,
|
||||
);
|
||||
|
||||
// 代行操作対象アカウントの管理者ユーザーを取得
|
||||
const adminUser = await this.usersRepository.findDelegateUser(
|
||||
user.account_id,
|
||||
originAccountId,
|
||||
);
|
||||
|
||||
// 要求された環境用トークンの寿命を決定
|
||||
const refreshTokenLifetime = this.refreshTokenLifetimeWeb;
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
|
||||
// ユーザーのロールを設定
|
||||
const role = this.getUserRole(adminUser.role);
|
||||
|
||||
const token = sign<RefreshToken>(
|
||||
{
|
||||
role: `${role} ${ADMIN_ROLES.ADMIN}`,
|
||||
tier: TIERS.TIER5,
|
||||
userId: adminUser.external_id,
|
||||
delegateUserId: delegateUserExternalId,
|
||||
},
|
||||
refreshTokenLifetime,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
return token;
|
||||
} catch (e) {
|
||||
this.logger.error(`error=${e}`);
|
||||
if (e instanceof Error) {
|
||||
switch (e.constructor) {
|
||||
case AccountNotFoundError:
|
||||
case AdminUserNotFoundError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010501'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case DelegationNotAllowedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010503'),
|
||||
HttpStatus.BAD_REQUEST,
|
||||
);
|
||||
case RoleUnexpectedError:
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E010205'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E009999'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
} finally {
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateDelegationRefreshToken.name}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代行操作アクセストークンの更新
|
||||
* @param context
|
||||
* @param refreshToken
|
||||
* @returns delegation access token
|
||||
*/
|
||||
async generateDelegationAccessToken(
|
||||
context: Context,
|
||||
refreshToken: string,
|
||||
): Promise<string> {
|
||||
this.logger.log(
|
||||
`[IN] [${context.trackingId}] ${this.generateDelegationAccessToken.name}`,
|
||||
);
|
||||
|
||||
const privateKey = getPrivateKey(this.configService);
|
||||
const pubkey = getPublicKey(this.configService);
|
||||
|
||||
const token = verify<RefreshToken>(refreshToken, pubkey);
|
||||
if (isVerifyError(token)) {
|
||||
this.logger.error(`${token.reason} | ${token.message}`);
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateDelegationAccessToken.name}`,
|
||||
);
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000101'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const accessToken = sign<AccessToken>(
|
||||
{
|
||||
role: token.role,
|
||||
tier: token.tier,
|
||||
userId: token.userId,
|
||||
delegateUserId: token.delegateUserId,
|
||||
},
|
||||
this.accessTokenlifetime,
|
||||
privateKey,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`[OUT] [${context.trackingId}] ${this.generateDelegationAccessToken.name}`,
|
||||
);
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets id token
|
||||
* @param token
|
||||
@ -340,6 +465,26 @@ export class AuthService {
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
};
|
||||
/**
|
||||
* トークンに設定するユーザーのロールを取得
|
||||
*/
|
||||
getUserRole = (role: string): string => {
|
||||
// ユーザーのロールを設定
|
||||
// 万一不正なRoleが登録されていた場合、そのままDBの値を使用すると不正なロールのリフレッシュトークンが発行されるため、
|
||||
// ロールの設定値はDBに保存したRoleの値を直接トークンに入れないように定数で設定する
|
||||
// ※none/author/typist以外はロールに設定されない
|
||||
if (role === USER_ROLES.NONE) {
|
||||
return USER_ROLES.NONE;
|
||||
} else if (role === USER_ROLES.AUTHOR) {
|
||||
return USER_ROLES.AUTHOR;
|
||||
} else if (role === USER_ROLES.TYPIST) {
|
||||
return USER_ROLES.TYPIST;
|
||||
} else {
|
||||
throw new RoleUnexpectedError(
|
||||
`Role from DB is unexpected value. role=${role}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 同意済み利用規約バージョンが最新かチェック
|
||||
|
||||
4
dictation_server/src/features/auth/errors/types.ts
Normal file
4
dictation_server/src/features/auth/errors/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
// Role文字列想定外エラー
|
||||
export class RoleUnexpectedError extends Error {}
|
||||
// Tier範囲想定外エラー
|
||||
export class TierUnexpectedError extends Error {}
|
||||
@ -1,4 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsInt } from 'class-validator';
|
||||
|
||||
export class TokenRequest {
|
||||
@ApiProperty()
|
||||
@ -28,6 +29,7 @@ export type TermsCheckInfo = {
|
||||
|
||||
export class DelegationTokenRequest {
|
||||
@ApiProperty({ description: '代行操作対象のアカウントID' })
|
||||
@IsInt()
|
||||
delegatedAccountId: number;
|
||||
}
|
||||
export class DelegationTokenResponse {
|
||||
|
||||
@ -12,3 +12,5 @@ export class EncryptionPasswordNeedError extends Error {}
|
||||
export class TermInfoNotFoundError extends Error {}
|
||||
// 利用規約バージョンパラメータ不在エラー
|
||||
export class UpdateTermsVersionNotSetError extends Error {}
|
||||
// 代行操作不許可エラー
|
||||
export class DelegationNotAllowedError extends Error {}
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
EncryptionPasswordNeedError,
|
||||
TermInfoNotFoundError,
|
||||
UpdateTermsVersionNotSetError,
|
||||
DelegationNotAllowedError,
|
||||
} from './errors/types';
|
||||
import {
|
||||
LICENSE_ALLOCATED_STATUS,
|
||||
@ -27,7 +28,11 @@ import { License } from '../licenses/entity/license.entity';
|
||||
import { NewTrialLicenseExpirationDate } from '../../features/licenses/types/types';
|
||||
import { Term } from '../terms/entity/term.entity';
|
||||
import { TermsCheckInfo } from '../../features/auth/types/types';
|
||||
import { AccountNotFoundError } from '../accounts/errors/types';
|
||||
import {
|
||||
AccountNotFoundError,
|
||||
AdminUserNotFoundError,
|
||||
} from '../accounts/errors/types';
|
||||
import { Account } from '../accounts/entity/account.entity';
|
||||
|
||||
@Injectable()
|
||||
export class UsersRepositoryService {
|
||||
@ -533,4 +538,67 @@ export class UsersRepositoryService {
|
||||
await userRepo.update({ id: user.id }, user);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 代行操作対象のユーザー情報を取得する
|
||||
* @param delegateAccountId 代行操作者のアカウントID
|
||||
* @param originAccountId 代行操作対象のアカウントID
|
||||
* @returns delegate accounts
|
||||
*/
|
||||
async findDelegateUser(
|
||||
delegateAccountId: number,
|
||||
originAccountId: number,
|
||||
): Promise<User> {
|
||||
return await this.dataSource.transaction(async (entityManager) => {
|
||||
const accountRepo = entityManager.getRepository(Account);
|
||||
|
||||
// 代行操作対象のアカウントを取得 ※親アカウントが代行操作者のアカウントIDと一致すること
|
||||
const account = await accountRepo.findOne({
|
||||
where: {
|
||||
id: originAccountId,
|
||||
parent_account_id: delegateAccountId,
|
||||
tier: TIERS.TIER5,
|
||||
},
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new AccountNotFoundError(
|
||||
`Account is not found. originAccountId: ${originAccountId}, delegateAccountId: ${delegateAccountId}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 代行操作が許可されていない場合はエラー
|
||||
if (!account.delegation_permission) {
|
||||
throw new DelegationNotAllowedError(
|
||||
`Delegation is not allowed. id: ${originAccountId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const adminUserId = account.primary_admin_user_id;
|
||||
|
||||
// 運用上、代行操作対象アカウントの管理者ユーザーがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
|
||||
if (!adminUserId) {
|
||||
throw new Error(`Admin user is not found. id: ${originAccountId}`);
|
||||
}
|
||||
|
||||
// 代行操作対象のアカウントの管理者ユーザーを取得
|
||||
const userRepo = entityManager.getRepository(User);
|
||||
const primaryUser = await userRepo.findOne({
|
||||
where: {
|
||||
account_id: originAccountId,
|
||||
id: adminUserId,
|
||||
},
|
||||
relations: {
|
||||
account: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 運用上、代行操作対象アカウントの管理者ユーザーがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
|
||||
if (!primaryUser) {
|
||||
throw new Error(`Admin user is not found. id: ${originAccountId}`);
|
||||
}
|
||||
|
||||
return primaryUser;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user