Merged PR 93: [WIP]API実装
## 概要 [Task1630: API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1630) - アップロード先取得APIの実行権限をチェックするためにアクセストークンに権限情報とロール情報を追加する - ログイン時に発行しているトークンにパラメータを追加 - role - ユーザーのrole情報(typist/author) - scope - 管理者権限があるか (admin/空文字) - トークン発行前にDBからユーザーの情報を取得する処理を追加 - ユーザーを取得するときにユーザーが属しているアカウントの情報も取得するようにentitiyを修正 - `findUserByExternalId`で実行されるSQL ``` SELECT `User`.`id` AS `User_id`, `User`.`external_id` AS `User_external_id`, `User`.`account_id` AS `User_account_id`, `User`.`role` AS `User_role`, `User`.`author_id` AS `User_author_id`, `User`.`accepted_terms_version` AS `User_accepted_terms_version`, `User`.`email_verified` AS `User_email_verified`, `User`.`deleted_at` AS `User_deleted_at`, `User`.`created_by` AS `User_created_by`, `User`.`created_at` AS `User_created_at`, `User`.`updated_by` AS `User_updated_by`, `User`.`updated_at` AS `User_updated_at`, `User__User_account`.`id` AS `User__User_account_id`, `User__User_account`.`parent_account_id` AS `User__User_account_parent_account_id`, `User__User_account`.`tier` AS `User__User_account_tier`, `User__User_account`.`country` AS `User__User_account_country`, `User__User_account`.`delegation_permission` AS `User__User_account_delegation_permission`, `User__User_account`.`locked` AS `User__User_account_locked`, `User__User_account`.`company_name` AS `User__User_account_company_name`, `User__User_account`.`verified` AS `User__User_account_verified`, `User__User_account`.`primary_admin_user_id` AS `User__User_account_primary_admin_user_id`, `User__User_account`.`secondary_admin_user_id` AS `User__User_account_secondary_admin_user_id`, `User__User_account`.`deleted_at` AS `User__User_account_deleted_at`, `User__User_account`.`created_by` AS `User__User_account_created_by`, `User__User_account`.`created_at` AS `User__User_account_created_at`, `User__User_account`.`updated_by` AS `User__User_account_updated_by`, `User__User_account`.`updated_at` AS `User__User_account_updated_at` FROM `users` `User` LEFT JOIN `accounts` `User__User_account` ON `User__User_account`.`id` = `User`.`account_id` WHERE ((`User`.`external_id` = ?)) AND (`User`.`id` IN (?)) -- PARAMETERS: ["B2CのID","2"] ``` ## レビューポイント - 管理者権限の有無とロールは別の概念であるため、別のパラメータとして用意したが問題なさそうか - 他の案としてscopeの中に`typist , admin`のようにして、一つのパラメータで権限チェックする? - DBから取得するデータとしてユーザーが属しているアカウント情報のすべてのカラムを取得するようにしているが、必要なカラムのみにしたほうが良いか? ## UIの変更 - Before/Afterのスクショなど - スクショ置き場 ## 動作確認状況 - 実行SQLを確認、JWTの内容を確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
3f7a9ed11a
commit
b91d260015
@ -2,7 +2,7 @@
|
|||||||
// TODO トークンの型は仮
|
// TODO トークンの型は仮
|
||||||
export interface Token {
|
export interface Token {
|
||||||
userId: string;
|
userId: string;
|
||||||
scope: string;
|
role: string;
|
||||||
exp: number;
|
exp: number;
|
||||||
iat: number;
|
iat: number;
|
||||||
}
|
}
|
||||||
@ -13,7 +13,7 @@ export const isToken = (arg: any): arg is Token => {
|
|||||||
if (arg.userId === undefined) {
|
if (arg.userId === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (arg.scope === undefined) {
|
if (arg.role === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (arg.exp === undefined) {
|
if (arg.exp === undefined) {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
export type RefreshToken = {
|
export type RefreshToken = {
|
||||||
userId: string;
|
userId: string;
|
||||||
scope: string;
|
role: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AccessToken = {
|
export type AccessToken = {
|
||||||
userId: string;
|
userId: string;
|
||||||
scope: string;
|
role: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IDToken = {
|
export type IDToken = {
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
} from '../../common/token';
|
} from '../../common/token';
|
||||||
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
import { AdB2cService } from '../../gateways/adb2c/adb2c.service';
|
||||||
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
import { CryptoService } from '../../gateways/crypto/crypto.service';
|
||||||
|
import { User } from '../../repositories/users/entity/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
@ -56,6 +57,20 @@ export class AuthService {
|
|||||||
const lifetimeDefault = this.configService.get(
|
const lifetimeDefault = this.configService.get(
|
||||||
'REFRESH_TOKEN_LIFETIME_DEFAULT',
|
'REFRESH_TOKEN_LIFETIME_DEFAULT',
|
||||||
);
|
);
|
||||||
|
let user: User;
|
||||||
|
// ユーザー情報とユーザーが属しているアカウント情報を取得
|
||||||
|
try {
|
||||||
|
user = await this.usersRepository.findUserByExternalId(idToken.sub);
|
||||||
|
if (!user.account) {
|
||||||
|
throw new Error('Account information not found');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`error=${e}`);
|
||||||
|
throw new HttpException(
|
||||||
|
makeErrorResponse('E009999'),
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 要求された環境用トークンの寿命を決定
|
// 要求された環境用トークンの寿命を決定
|
||||||
const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault;
|
const refreshTokenLifetime = type === 'web' ? lifetimeWeb : lifetimeDefault;
|
||||||
@ -63,7 +78,12 @@ export class AuthService {
|
|||||||
const privateKey = await this.cryptoService.getPrivateKey();
|
const privateKey = await this.cryptoService.getPrivateKey();
|
||||||
const token = sign<RefreshToken>(
|
const token = sign<RefreshToken>(
|
||||||
{
|
{
|
||||||
scope: 'test',
|
role: `${user.role} ${
|
||||||
|
user.account.primary_admin_user_id === user.id ||
|
||||||
|
user.account.secondary_admin_user_id === user.id
|
||||||
|
? 'admin'
|
||||||
|
: 'standard'
|
||||||
|
}`,
|
||||||
userId: idToken.sub,
|
userId: idToken.sub,
|
||||||
},
|
},
|
||||||
refreshTokenLifetime,
|
refreshTokenLifetime,
|
||||||
@ -90,7 +110,7 @@ export class AuthService {
|
|||||||
|
|
||||||
const accessToken = sign<AccessToken>(
|
const accessToken = sign<AccessToken>(
|
||||||
{
|
{
|
||||||
scope: token.scope,
|
role: token.role,
|
||||||
userId: token.userId,
|
userId: token.userId,
|
||||||
},
|
},
|
||||||
lifetime,
|
lifetime,
|
||||||
|
|||||||
@ -129,7 +129,7 @@ export class UsersController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// アクセストークンの権限不足エラー
|
// アクセストークンの権限不足エラー
|
||||||
if (!confirmPermission(payload.scope)) {
|
if (!confirmPermission(payload.role)) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
makeErrorResponse('E000108'),
|
makeErrorResponse('E000108'),
|
||||||
HttpStatus.UNAUTHORIZED,
|
HttpStatus.UNAUTHORIZED,
|
||||||
@ -199,7 +199,7 @@ export class UsersController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
//アクセストークンの権限不足エラー
|
//アクセストークンの権限不足エラー
|
||||||
if (!confirmPermission(payload.scope)) {
|
if (!confirmPermission(payload.role)) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
makeErrorResponse('E000108'),
|
makeErrorResponse('E000108'),
|
||||||
HttpStatus.UNAUTHORIZED,
|
HttpStatus.UNAUTHORIZED,
|
||||||
|
|||||||
@ -259,7 +259,7 @@ describe('UsersService', () => {
|
|||||||
const autoRenew = true;
|
const autoRenew = true;
|
||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
expect(
|
expect(
|
||||||
await service.createUser(
|
await service.createUser(
|
||||||
token,
|
token,
|
||||||
@ -295,7 +295,7 @@ describe('UsersService', () => {
|
|||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const authorId = 'testID';
|
const authorId = 'testID';
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
expect(
|
expect(
|
||||||
await service.createUser(
|
await service.createUser(
|
||||||
token,
|
token,
|
||||||
@ -332,7 +332,7 @@ describe('UsersService', () => {
|
|||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const typistGroupId = 111;
|
const typistGroupId = 111;
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
expect(
|
expect(
|
||||||
await service.createUser(
|
await service.createUser(
|
||||||
token,
|
token,
|
||||||
@ -369,7 +369,7 @@ it('DBネットワークエラーとなる場合、エラーとなる。', async
|
|||||||
const autoRenew = true;
|
const autoRenew = true;
|
||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
await expect(
|
await expect(
|
||||||
service.createUser(
|
service.createUser(
|
||||||
token,
|
token,
|
||||||
@ -407,7 +407,7 @@ it('Azure ADB2Cでネットワークエラーとなる場合、エラーとな
|
|||||||
const autoRenew = true;
|
const autoRenew = true;
|
||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
await expect(
|
await expect(
|
||||||
service.createUser(
|
service.createUser(
|
||||||
token,
|
token,
|
||||||
@ -445,7 +445,7 @@ it('メールアドレスが重複している場合、エラーとなる。', a
|
|||||||
const autoRenew = true;
|
const autoRenew = true;
|
||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
await expect(
|
await expect(
|
||||||
service.createUser(
|
service.createUser(
|
||||||
token,
|
token,
|
||||||
@ -483,7 +483,7 @@ it('AuthorIDが重複している場合、エラーとなる。', async () => {
|
|||||||
const licenseAlert = true;
|
const licenseAlert = true;
|
||||||
const notification = true;
|
const notification = true;
|
||||||
const authorId = 'testID';
|
const authorId = 'testID';
|
||||||
const token: AccessToken = { userId: '0001', scope: '' };
|
const token: AccessToken = { userId: '0001', role: '' };
|
||||||
await expect(
|
await expect(
|
||||||
service.createUser(
|
service.createUser(
|
||||||
token,
|
token,
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { User } from '../../../repositories/users/entity/user.entity';
|
||||||
import {
|
import {
|
||||||
Entity,
|
Entity,
|
||||||
Column,
|
Column,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
|
OneToMany,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'accounts' })
|
@Entity({ name: 'accounts' })
|
||||||
@ -52,4 +54,7 @@ export class Account {
|
|||||||
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
|
|
||||||
|
@OneToMany(() => User, (user) => user.id)
|
||||||
|
user?: User[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
|
import { Account } from '../../../repositories/accounts/entity/account.entity';
|
||||||
import {
|
import {
|
||||||
Entity,
|
Entity,
|
||||||
Column,
|
Column,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryGeneratedColumn,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
UpdateDateColumn,
|
UpdateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
@Entity({ name: 'users' })
|
@Entity({ name: 'users' })
|
||||||
@ -52,4 +55,8 @@ export class User {
|
|||||||
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
|
|
||||||
|
@ManyToOne(() => Account, (account) => account.user)
|
||||||
|
@JoinColumn({ name: 'account_id' })
|
||||||
|
account?: Account;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,6 +93,22 @@ export class UsersRepositoryService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findUserByExternalId(sub: string): Promise<User | undefined> {
|
||||||
|
const user = await this.dataSource.getRepository(User).findOne({
|
||||||
|
where: {
|
||||||
|
external_id: sub,
|
||||||
|
},
|
||||||
|
relations: {
|
||||||
|
account: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
async findUserById(id: number): Promise<User | undefined> {
|
async findUserById(id: number): Promise<User | undefined> {
|
||||||
const user = await this.dataSource.getRepository(User).findOne({
|
const user = await this.dataSource.getRepository(User).findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user