UNPKG

@point3/logto-module

Version:

포인트3 내부 logto Authentication 모듈입니다

210 lines 10.8 kB
"use strict"; 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