@point3/logto-module
Version:
포인트3 내부 logto Authentication 모듈입니다
210 lines • 10.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const testing_1 = require("@nestjs/testing");
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const guard_1 = require("./guard");
const token_1 = require("../token");
const point3_common_tool_1 = require("point3-common-tool");
const client_1 = require("client");
describe('LogtoTokenGuard 테스트', () => {
let guard;
let tokenUtil;
let reflector;
let logger;
const testToken = 'eyJhbGciOiJFUzM4NCIsInR5cCI6ImF0K2p3dCIsImtpZCI6ImxKUjU3SkFqVmV1dHk4eWljVzUtdFFySDM2WFl6NUlzWFhXSDVzeXV0dEEifQ.eyJ1c2VyUm9sZXMiOlsicDMtQ0lTTy0wIl0sIm1hbmFnZXJJZCI6Im1hbmFnZXItMDE5NjQ0NWMtOGVjNy03MDc4LWExNDItNGU3ZGI5YTRhYWVhIiwiY2xpZW50SWQiOiJwb2ludDMtMDE5NjNjODUtNDQ2ZS03NGM5LWFmNzktNDhlMjU0NjVjMzI3IiwianRpIjoiV0RYTmxoTWkwT0tHQ1pTRzFKZnBrIiwic3ViIjoieXVsaXVmdHNvMWQwIiwiaWF0IjoxNzQ5MDI0NzIzLCJleHAiOjE3NDkwMjgzMjMsInNjb3BlIjoiIiwiY2xpZW50X2lkIjoiNXFydmk5eW0wajJ0YTJ6YXBnbHU0IiwiaXNzIjoiaHR0cHM6Ly9sb2d0by5wb2ludDMuaW8vb2lkYyIsImF1ZCI6Imh0dHBzOi8vZGVmYXVsdC5sb2d0by5hcHAvYXBpIn0.nZdzvdxQ74m2oFEklVTfQlcqYBkRrRxtHQEgz1L6DjST9_9Wa7H7J1gKJVEjm8NnjFCQXljYM_hTVx1ABTmUgDrEKVjtHFVKUyPoSzxQitXexwmBZY5l8WdyqJDqAy8d';
const mockPayload = {
userRoles: ['p3-CISO-0'],
managerId: 'manager-0196445c-8ec7-7078-a142-4e7db9a4aaea',
clientId: 'point3-019663c85-446e-74c9-af79-48e25465c327',
jti: 'WDXNlhMi0OKGCZSG1Jfpk',
sub: 'yuliuftso1d0',
iat: 1749024723,
exp: 1749028323,
scope: '',
client_id: '5qrvi9ym0j2ta2zapglu4',
iss: 'https://logto.point3.io/oidc',
aud: 'https://default.logto.app/api'
};
beforeEach(async () => {
const mockTokenUtil = {
verifyToken: jest.fn(),
};
const mockReflector = {
get: jest.fn(),
};
const mockLogger = {
warn: jest.fn(),
error: jest.fn(),
log: jest.fn(),
};
const module = await testing_1.Test.createTestingModule({
providers: [
guard_1.LogtoTokenGuard,
{
provide: token_1.LogtoTokenVerifierToken,
useValue: mockTokenUtil,
},
{
provide: core_1.Reflector,
useValue: mockReflector,
},
{
provide: client_1.LogtoLoggerServiceToken,
useValue: mockLogger,
},
],
}).compile();
guard = module.get(guard_1.LogtoTokenGuard);
tokenUtil = module.get(token_1.LogtoTokenVerifierToken);
reflector = module.get(core_1.Reflector);
logger = module.get(client_1.LogtoLoggerServiceToken);
jest.clearAllMocks();
});
const createMockExecutionContext = (headers = {}, route = { path: '/test' }) => {
const mockRequest = {
headers,
route,
user: undefined
};
return {
switchToHttp: () => ({
getRequest: () => mockRequest,
getResponse: jest.fn(),
getNext: jest.fn(),
}),
getHandler: jest.fn(),
getClass: jest.fn(),
getArgs: jest.fn(),
getArgByIndex: jest.fn(),
switchToRpc: jest.fn(),
switchToWs: jest.fn(),
getType: jest.fn(),
};
};
describe('🔐 성공적인 인증 테스트', () => {
it('유효한 토큰이 제공되었을 때 인증하고 사용자 데이터를 설정해야 함', async () => {
const context = createMockExecutionContext({
authorization: `Bearer ${testToken}`,
});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(['p3-CISO-0']);
tokenUtil.verifyToken.mockResolvedValueOnce(mockPayload);
const result = await guard.canActivate(context);
const request = context.switchToHttp().getRequest();
expect(result).toBe(true);
expect(tokenUtil.verifyToken).toHaveBeenCalledWith(testToken, undefined, ['p3-CISO-0']);
expect(request.user).toEqual({
userId: 'yuliuftso1d0',
managerId: expect.objectContaining({
toString: expect.any(Function)
}),
clientId: expect.objectContaining({
toString: expect.any(Function)
}),
});
expect(request.user.managerId.toString()).toContain('manager');
expect(request.user.managerId.toString()).toContain('0196445c-8ec7-7078-a142-4e7db9a4aaea');
expect(request.user.clientId.toString()).toContain('point3');
expect(request.user.clientId.toString()).toContain('019663c85-446e-74c9-af79-48e25465c327');
});
it('필수 스코프나 역할이 없을 때도 동작해야 함', async () => {
const context = createMockExecutionContext({
authorization: `Bearer ${testToken}`,
});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(undefined);
tokenUtil.verifyToken.mockResolvedValueOnce(mockPayload);
const result = await guard.canActivate(context);
expect(result).toBe(true);
expect(tokenUtil.verifyToken).toHaveBeenCalledWith(testToken, undefined, undefined);
});
});
describe('🚫 토큰 추출 실패 테스트', () => {
it('Authorization 헤더가 없을 때 UnauthorizedException을 던져야 함', async () => {
const context = createMockExecutionContext({});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(['p3-CISO-0']);
await expect(guard.canActivate(context)).rejects.toThrow(common_1.UnauthorizedException);
await expect(guard.canActivate(context)).rejects.toThrow('Authorization header is missing');
});
it('Authorization 헤더가 Bearer가 아닐 때 UnauthorizedException을 던져야 함', async () => {
const context = createMockExecutionContext({
authorization: 'Basic sometoken',
});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(['p3-CISO-0']);
await expect(guard.canActivate(context)).rejects.toThrow(common_1.UnauthorizedException);
await expect(guard.canActivate(context)).rejects.toThrow('Authorization token type not supported');
});
it('Bearer 헤더에서 토큰을 올바르게 추출해야 함', async () => {
const context = createMockExecutionContext({
authorization: `Bearer ${testToken}`,
});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(['p3-CISO-0']);
tokenUtil.verifyToken.mockResolvedValueOnce(mockPayload);
await guard.canActivate(context);
expect(tokenUtil.verifyToken).toHaveBeenCalledWith(testToken, undefined, ['p3-CISO-0']);
});
});
describe('❌ 토큰 검증 실패 테스트', () => {
it('토큰 검증에서 UnauthorizedException이 발생하면 다시 던져야 함', async () => {
const context = createMockExecutionContext({
authorization: `Bearer ${testToken}`,
});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(['p3-CISO-0']);
const authError = new common_1.UnauthorizedException('Invalid token');
tokenUtil.verifyToken.mockRejectedValueOnce(authError);
await expect(guard.canActivate(context)).rejects.toThrow(common_1.UnauthorizedException);
});
it('다른 에러가 발생하면 일반적인 에러 메시지를 던져야 함', async () => {
const context = createMockExecutionContext({
authorization: `Bearer ${testToken}`,
});
reflector.get
.mockReturnValueOnce(undefined)
.mockReturnValueOnce(['p3-CISO-0']);
tokenUtil.verifyToken.mockRejectedValueOnce(new Error('Some other error'));
await expect(guard.canActivate(context)).rejects.toThrow('요청을 처리하지 못하였습니다.');
});
});
describe('🔍 실제 JWT 토큰 분석', () => {
it('제공된 JWT 토큰의 페이로드를 올바르게 디코딩해야 함', () => {
const [header, payload, signature] = testToken.split('.');
const decodedPayload = JSON.parse(Buffer.from(payload, 'base64url').toString());
console.log('🔍 디코딩된 토큰 페이로드:');
console.log(JSON.stringify(decodedPayload, null, 2));
expect(decodedPayload.userRoles).toEqual(['p3-CISO-0']);
expect(decodedPayload.managerId).toBe('manager-0196445c-8ec7-7078-a142-4e7db9a4aaea');
expect(decodedPayload.clientId).toBe('point3-01963c85-446e-74c9-af79-48e25465c327');
expect(decodedPayload.sub).toBe('yuliuftso1d0');
expect(decodedPayload.iss).toBe('https://logto.point3.io/oidc');
const expirationDate = new Date(decodedPayload.exp * 1000);
const issuedDate = new Date(decodedPayload.iat * 1000);
console.log(`📅 토큰 발급 시간: ${issuedDate.toISOString()}`);
console.log(`⏰ 토큰 만료 시간: ${expirationDate.toISOString()}`);
console.log(`🏢 발급자: ${decodedPayload.iss}`);
console.log(`👤 사용자 역할: ${decodedPayload.userRoles.join(', ')}`);
});
it('토큰에서 추출된 GUID 값들이 올바른 형식인지 확인해야 함', () => {
const [header, payload, signature] = testToken.split('.');
const decodedPayload = JSON.parse(Buffer.from(payload, 'base64url').toString());
const managerId = point3_common_tool_1.p3Values.Guid.parse(decodedPayload.managerId);
expect(managerId.Prefix == 'manager');
const clientId = point3_common_tool_1.p3Values.Guid.parse(decodedPayload.clientId);
expect(clientId.Prefix == 'point3');
console.log('✅ GUID 형식 검증 완료:');
console.log(` Manager ID: ${managerId.toString()}`);
console.log(` Client ID: ${clientId.toString()}`);
});
});
});
//# sourceMappingURL=guard.spec.js.map