Skip to content

Commit

Permalink
feat: apply auth entity changes
Browse files Browse the repository at this point in the history
  • Loading branch information
skgndi12 committed Jan 22, 2024
1 parent c4dfada commit 19d8569
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 82 deletions.
19 changes: 11 additions & 8 deletions api/src/core/services/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { AccessLevel, Idp } from '@prisma/client';
import { randomUUID } from 'crypto';

import { AppIdToken } from '@src/core/entities/auth.entity';
import { User } from '@src/core/entities/user.entity';
import {
generateUserNickname,
Expand Down Expand Up @@ -124,14 +125,16 @@ export class AuthService {
IsolationLevel.READ_COMMITTED
)) as User;

const appIdToken = this.jwtHandler.signAppIdToken({
userId: user.id,
nickname: user.nickname,
tag: user.tag,
idp: user.idp,
email: user.email,
accessLevel: user.accessLevel
});
const appIdToken = this.jwtHandler.signAppIdToken(
new AppIdToken(
user.id,
user.nickname,
user.tag,
user.idp,
user.email,
user.accessLevel
)
);

const { referrer } = JSON.parse(stateTokenString) as OauthState;
if (referrer) {
Expand Down
6 changes: 2 additions & 4 deletions api/src/core/services/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AccessLevel } from '@prisma/client';

import { validateUserAuthorization } from '@src/core/auth.manager';
import { AppIdToken } from '@src/core/entities/auth.entity';
import { User } from '@src/core/entities/user.entity';
import { UserRepository } from '@src/core/ports/user.repository';
Expand All @@ -15,16 +14,15 @@ export class UserService {
requestedUserId: string
): Promise<User> => {
if (
!validateUserAuthorization(
!requesterIdToken.isAccessLevelAndUserIdAuthorized(
new AccessLevelEnum(AccessLevel.DEVELOPER),
requesterIdToken,
requestedUserId
)
) {
throw new CustomError({
code: AppErrorCode.PERMISSIION_DENIED,
message: 'insufficient access level to fetch user information',
context: { accessLevel: requesterIdToken.accessLevel }
context: { accessLevel: requesterIdToken.accessLevel.get() }
});
}

Expand Down
16 changes: 8 additions & 8 deletions api/src/jwt/payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ export function createAppPayload(
}

export function convertToAppIdToken(appPayload: AppPayload): AppIdToken {
return {
userId: appPayload.userId,
nickname: appPayload.nickname,
tag: appPayload.tag,
idp: new IdpEnum(appPayload.idp),
email: appPayload.email,
accessLevel: new AccessLevelEnum(appPayload.accessLevel)
};
return new AppIdToken(
appPayload.userId,
appPayload.nickname,
appPayload.tag,
new IdpEnum(appPayload.idp),
appPayload.email,
new AccessLevelEnum(appPayload.accessLevel)
);
}

export function isAppPayload(payload: unknown): payload is AppPayload {
Expand Down
33 changes: 17 additions & 16 deletions api/test/core/services/auth/auth.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { TransactionManager } from '@src/core/ports/transaction.manager';
import { UserRepository } from '@src/core/ports/user.repository';
import { AuthService } from '@src/core/services/auth/auth.service';
import { AuthConfig } from '@src/core/services/auth/types';
import { AppErrorCode, CustomError } from '@src/error/errors';
import { AccessLevelEnum, IdpEnum } from '@src/core/types';
import { AppErrorCode, CustomError } from '@src/error/errors';

jest.mock('crypto');

Expand Down Expand Up @@ -87,7 +87,6 @@ describe('Test auth service', () => {
txManager,
googleHandler
);
const givenReferrer = 'http://127.0.0.1/home';
const expectedQueyObject = {
client_id: oauthClientId,
response_type,
Expand Down Expand Up @@ -200,14 +199,16 @@ describe('Test auth service', () => {
);

expect(jwtHandler.signAppIdToken).toBeCalledTimes(1);
expect(jwtHandler.signAppIdToken).toBeCalledWith({
userId: upsertUser.id,
nickname: upsertUser.nickname,
tag: upsertUser.tag,
idp: upsertUser.idp,
email: upsertUser.email,
accessLevel: upsertUser.accessLevel
});
expect(jwtHandler.signAppIdToken).toBeCalledWith(
expect.objectContaining({
userId: upsertUser.id,
nickname: upsertUser.nickname,
tag: upsertUser.tag,
idp: upsertUser.idp,
email: upsertUser.email,
accessLevel: upsertUser.accessLevel
})
);
expect(txManager.runInTransaction).toBeCalledTimes(1);
});

Expand All @@ -224,10 +225,10 @@ describe('Test auth service', () => {
jwtHandler,
txManager,
googleHandler
).finalizeGoogleSignIn(baseUrl, state, authCode)
).finalizeGoogleSignIn(baseUrl, state, authCode);
} catch (error: unknown) {
expect(error).toBeInstanceOf(CustomError)
expect(error).toHaveProperty('code', AppErrorCode.BAD_REQUEST)
expect(error).toBeInstanceOf(CustomError);
expect(error).toHaveProperty('code', AppErrorCode.BAD_REQUEST);
}

expect(keyValueRepository.getThenDelete).toBeCalledTimes(1);
Expand Down Expand Up @@ -269,10 +270,10 @@ describe('Test auth service', () => {
jwtHandler,
txManager,
googleHandler
).finalizeGoogleSignIn(baseUrl, state, authCode)
).finalizeGoogleSignIn(baseUrl, state, authCode);
} catch (error: unknown) {
expect(error).toBeInstanceOf(CustomError)
expect(error).toHaveProperty('code', AppErrorCode.INTERNAL_ERROR)
expect(error).toBeInstanceOf(CustomError);
expect(error).toHaveProperty('code', AppErrorCode.INTERNAL_ERROR);
}

expect(keyValueRepository.getThenDelete).toBeCalledTimes(1);
Expand Down
26 changes: 18 additions & 8 deletions api/test/core/services/user/user.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,21 @@ describe('Test user service', () => {
});

describe('Test get user', () => {
const requesterIdToken = {
userId: requesterUserId,
accessLevel: new AccessLevelEnum(AccessLevel.DEVELOPER)
} as AppIdToken;

beforeAll(() => {
userRepository.findById = jest.fn(() => Promise.resolve(user));
});

it('should success when valid', async () => {
const givenRequesterIdToken = new AppIdToken(
requesterUserId,
'nickname',
'#TAGG',
new IdpEnum(Idp.GOOGLE),
'[email protected]',
new AccessLevelEnum(AccessLevel.DEVELOPER)
);
const actualResult = await new UserService(userRepository).getUser(
requesterIdToken,
givenRequesterIdToken,
requestedUserId
);

Expand All @@ -60,9 +63,16 @@ describe('Test user service', () => {

it('should failure when user authorization is not valid', async () => {
try {
requesterIdToken.accessLevel = new AccessLevelEnum(AccessLevel.USER);
const givenRequesterIdToken = new AppIdToken(
requesterUserId,
'nickname',
'#TAGG',
new IdpEnum(Idp.GOOGLE),
'[email protected]',
new AccessLevelEnum(AccessLevel.USER)
);
await new UserService(userRepository).getUser(
requesterIdToken,
givenRequesterIdToken,
requestedUserId
);
} catch (error: unknown) {
Expand Down
73 changes: 35 additions & 38 deletions api/test/jwt/jwt.client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,37 +36,30 @@ describe('Test jwt client', () => {
});

it('should verify app id token', () => {
const givenToken: AppIdToken = {
userId: randomUUID(),
nickname: '신비로운 시네필 황금 사자',
tag: '#MQ3B',
idp: new IdpEnum(Idp.GOOGLE),
email: '[email protected]',
accessLevel: new AccessLevelEnum(AccessLevel.USER)
};
const givenToken = new AppIdToken(
randomUUID(),
'신비로운 시네필 황금 사자',
'#MQ3B',
new IdpEnum(Idp.GOOGLE),
'[email protected]',
new AccessLevelEnum(AccessLevel.USER)
);

const tokenString = client.signAppIdToken(givenToken);
const verifiedToken = client.verifyAppIdToken(tokenString);

expect(verifiedToken.userId).toEqual(givenToken.userId);
expect(verifiedToken.nickname).toEqual(givenToken.nickname);
expect(verifiedToken.tag).toEqual(givenToken.tag);
expect(verifiedToken.idp.get()).toEqual(givenToken.idp.get());
expect(verifiedToken.email).toEqual(givenToken.email);
expect(verifiedToken.accessLevel.get()).toEqual(
givenToken.accessLevel.get()
);
expect(JSON.stringify(verifiedToken)).toEqual(JSON.stringify(givenToken));
});

it('should throw error when verify malicious app id token', () => {
const givenToken: AppIdToken = {
userId: randomUUID(),
nickname: '신비로운 시네필 황금 사자',
tag: '#MQ3B',
idp: new IdpEnum(Idp.GOOGLE),
email: '[email protected]',
accessLevel: new AccessLevelEnum(AccessLevel.USER)
};
const givenToken = new AppIdToken(
randomUUID(),
'신비로운 시네필 황금 사자',
'#MQ3B',
new IdpEnum(Idp.GOOGLE),
'[email protected]',
new AccessLevelEnum(AccessLevel.USER)
);

const tokenString = client.signAppIdToken(givenToken);
const [encodedHeader, encodedPayload, signature] = tokenString.split('.');
Expand All @@ -85,24 +78,28 @@ describe('Test jwt client', () => {
});

it('should successfully decode a token without verification', () => {
const givenToken: AppIdToken = {
userId: randomUUID(),
nickname: '신비로운 시네필 황금 사자',
tag: '#MQ3B',
idp: new IdpEnum(Idp.GOOGLE),
email: '[email protected]',
accessLevel: new AccessLevelEnum(AccessLevel.USER)
};
const givenToken = new AppIdToken(
randomUUID(),
'신비로운 시네필 황금 사자',
'#MQ3B',
new IdpEnum(Idp.GOOGLE),
'[email protected]',
new AccessLevelEnum(AccessLevel.USER)
);

const tokenString = client.signAppIdToken(givenToken);
const decodedPayload = client.decodeTokenWithoutVerify(tokenString)
.payload as AppPayload;

expect(decodedPayload.userId).toEqual(givenToken.userId);
expect(decodedPayload.nickname).toEqual(givenToken.nickname);
expect(decodedPayload.tag).toEqual(givenToken.tag);
expect(decodedPayload.idp).toEqual(givenToken.idp.get());
expect(decodedPayload.email).toEqual(givenToken.email);
expect(decodedPayload.accessLevel).toEqual(givenToken.accessLevel.get());
expect(decodedPayload).toEqual(
expect.objectContaining({
userId: givenToken.userId,
nickname: givenToken.nickname,
tag: givenToken.tag,
idp: givenToken.idp.get(),
email: givenToken.email,
accessLevel: givenToken.accessLevel.get()
})
);
});
});

0 comments on commit 19d8569

Please sign in to comment.