@@ -0,0 +1,65 @@
|
||||
import { CurrentUserGuard } from '../../src/modules/identity/presentation/guards/current-user.guard';
|
||||
import { SyncIdentityHandler } from '../../src/modules/identity/application/handlers/user/sync-identity.handler';
|
||||
import { IUser } from '../../src/modules/identity/domain/repositories/user.interface';
|
||||
import { IdentityData } from '../../src/core/auth/interfaces/identity-data';
|
||||
import { UnauthorizedException } from '@nestjs/common';
|
||||
|
||||
describe('CurrentUserGuard', () => {
|
||||
let guard: CurrentUserGuard;
|
||||
let mockSync: Partial<SyncIdentityHandler>;
|
||||
let mockUserRepo: Partial<IUser>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockSync = { execute: jest.fn() };
|
||||
mockUserRepo = { getById: jest.fn() };
|
||||
guard = new CurrentUserGuard(mockSync as any, mockUserRepo as any);
|
||||
});
|
||||
|
||||
function makeContext(req: any): any {
|
||||
return { switchToHttp: () => ({ getRequest: () => req }) } as any;
|
||||
}
|
||||
|
||||
test('raylabContext fast path sets currentUser and returns true', async () => {
|
||||
const user = { id: 'u1' } as any;
|
||||
const req: any = { raylabContext: { user } };
|
||||
const res = await guard.canActivate(makeContext(req));
|
||||
expect(res).toBe(true);
|
||||
expect(req.currentUser).toBe(user);
|
||||
});
|
||||
|
||||
test('missing identity throws UnauthorizedException', async () => {
|
||||
const req: any = {};
|
||||
await expect(guard.canActivate(makeContext(req))).rejects.toThrow(UnauthorizedException);
|
||||
});
|
||||
|
||||
test('internal identity resolves by getById and does not call sync', async () => {
|
||||
const identity = new IdentityData('uid', 'u', 'e');
|
||||
const req: any = { identity, identitySource: 'internal' };
|
||||
(mockUserRepo.getById as jest.Mock).mockResolvedValue({ id: 'uid', roles: [], permissions: [] });
|
||||
|
||||
const res = await guard.canActivate(makeContext(req));
|
||||
expect(res).toBe(true);
|
||||
expect(req.currentUser).toBeDefined();
|
||||
expect((mockSync.execute as jest.Mock)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('internal identity with missing user throws UnauthorizedException', async () => {
|
||||
const identity = new IdentityData('missing', 'u', 'e');
|
||||
const req: any = { identity, identitySource: 'internal' };
|
||||
(mockUserRepo.getById as jest.Mock).mockRejectedValue(new Error('User tidak ditemukan.'));
|
||||
|
||||
await expect(guard.canActivate(makeContext(req))).rejects.toThrow(UnauthorizedException);
|
||||
expect((mockSync.execute as jest.Mock)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('external identity calls syncIdentityHandler', async () => {
|
||||
const identity = new IdentityData('extsub', 'u', 'e');
|
||||
const req: any = { identity, identitySource: 'external' };
|
||||
(mockSync.execute as jest.Mock).mockResolvedValue({ id: 'user-ext' });
|
||||
|
||||
const res = await guard.canActivate(makeContext(req));
|
||||
expect(res).toBe(true);
|
||||
expect(req.currentUser).toBeDefined();
|
||||
expect((mockUserRepo.getById as jest.Mock)).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
// Mock 'jose' module before importing JwtAuthGuard so Jest doesn't try to parse ESM from node_modules
|
||||
let mockJwtVerify: (token: any) => Promise<any> = async () => { throw new Error('no jwks') };
|
||||
|
||||
jest.mock('jose', () => ({
|
||||
createRemoteJWKSet: () => ({}),
|
||||
jwtVerify: async (token: any, jwks: any, opts: any) => mockJwtVerify(token),
|
||||
}));
|
||||
|
||||
import { JwtAuthGuard } from '../../src/core/auth/guards/jwt-auth.guard';
|
||||
import { ExecutionContext } from '@nestjs/common';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
||||
// These tests focus on identitySource being set based on verification path.
|
||||
|
||||
describe('JwtAuthGuard', () => {
|
||||
let guard: JwtAuthGuard;
|
||||
|
||||
beforeEach(() => {
|
||||
guard = new JwtAuthGuard();
|
||||
process.env.RAYLAB_JWT_SECRET = 'test-secret';
|
||||
delete process.env.AUTHENTIK_JWKS_URI; // ensure JWKS not attempted unless test sets it
|
||||
// reset mock behavior
|
||||
mockJwtVerify = async () => { throw new Error('no jwks') };
|
||||
});
|
||||
|
||||
function makeCtxWithAuthHeader(token: string) {
|
||||
const req: any = { headers: { authorization: `Bearer ${token}` }, cookies: {} };
|
||||
const ctx: any = { switchToHttp: () => ({ getRequest: () => req }) } as ExecutionContext;
|
||||
return { ctx, req };
|
||||
}
|
||||
|
||||
test('internal verification sets identitySource=internal', async () => {
|
||||
const token = jwt.sign({ sub: 'uid', preferred_username: 'u', email: 'e' }, process.env.RAYLAB_JWT_SECRET || 'test-secret');
|
||||
const { ctx, req } = makeCtxWithAuthHeader(token);
|
||||
|
||||
const res = await guard.canActivate(ctx);
|
||||
expect(res).toBe(true);
|
||||
expect(req.identity).toBeDefined();
|
||||
expect((req.identitySource)).toBe('internal');
|
||||
});
|
||||
|
||||
test('external verification sets identitySource=external if JWKS verifies', async () => {
|
||||
process.env.AUTHENTIK_JWKS_URI = 'https://example.com/.well-known/jwks.json';
|
||||
// set mock to succeed
|
||||
mockJwtVerify = async (token: any) => ({ payload: { sub: 'extsub', preferred_username: 'eu', email: 'ee' } });
|
||||
|
||||
const token = 'dummy';
|
||||
const { ctx, req } = makeCtxWithAuthHeader(token);
|
||||
|
||||
const res = await guard.canActivate(ctx);
|
||||
expect(res).toBe(true);
|
||||
expect(req.identity).toBeDefined();
|
||||
expect((req.identitySource)).toBe('external');
|
||||
});
|
||||
|
||||
test('external verification fails then internal succeeds -> identitySource=internal', async () => {
|
||||
process.env.AUTHENTIK_JWKS_URI = 'https://example.com/.well-known/jwks.json';
|
||||
// make jwks fail
|
||||
mockJwtVerify = async (token: any) => { throw new Error('jwks fail'); };
|
||||
|
||||
const token = jwt.sign({ sub: 'uid2', preferred_username: 'u2', email: 'e2' }, process.env.RAYLAB_JWT_SECRET || 'test-secret');
|
||||
const { ctx, req } = makeCtxWithAuthHeader(token);
|
||||
|
||||
const res = await guard.canActivate(ctx);
|
||||
expect(res).toBe(true);
|
||||
expect(req.identity).toBeDefined();
|
||||
expect((req.identitySource)).toBe('internal');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user