feat(#4): Implement Authentik OIDC authentication with BetterAuth
- Integrated BetterAuth library for modern authentication - Added Session, Account, and Verification database tables - Created complete auth module with service, controller, guards, and decorators - Implemented shared authentication types in @mosaic/shared package - Added comprehensive test coverage (26 tests passing) - Documented type sharing strategy for monorepo - Updated environment configuration with OIDC and JWT settings Key architectural decisions: - BetterAuth over Passport.js for better TypeScript support - Separation of User (DB entity) vs AuthUser (client-safe subset) - Shared types package to prevent FE/BE drift - Factory pattern for auth config to use shared Prisma instance Ready for frontend integration (Issue #6). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> Fixes #4
This commit is contained in:
98
apps/api/src/auth/guards/auth.guard.spec.ts
Normal file
98
apps/api/src/auth/guards/auth.guard.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { ExecutionContext, UnauthorizedException } from "@nestjs/common";
|
||||
import { AuthGuard } from "./auth.guard";
|
||||
import { AuthService } from "../auth.service";
|
||||
|
||||
describe("AuthGuard", () => {
|
||||
let guard: AuthGuard;
|
||||
let authService: AuthService;
|
||||
|
||||
const mockAuthService = {
|
||||
verifySession: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AuthGuard,
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: mockAuthService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
guard = module.get<AuthGuard>(AuthGuard);
|
||||
authService = module.get<AuthService>(AuthService);
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
const createMockExecutionContext = (headers: any = {}): ExecutionContext => {
|
||||
const mockRequest = {
|
||||
headers,
|
||||
};
|
||||
|
||||
return {
|
||||
switchToHttp: () => ({
|
||||
getRequest: () => mockRequest,
|
||||
}),
|
||||
} as ExecutionContext;
|
||||
};
|
||||
|
||||
describe("canActivate", () => {
|
||||
it("should return true for valid session", async () => {
|
||||
const mockSessionData = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
},
|
||||
session: {
|
||||
id: "session-123",
|
||||
},
|
||||
};
|
||||
|
||||
mockAuthService.verifySession.mockResolvedValue(mockSessionData);
|
||||
|
||||
const context = createMockExecutionContext({
|
||||
authorization: "Bearer valid-token",
|
||||
});
|
||||
|
||||
const result = await guard.canActivate(context);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockAuthService.verifySession).toHaveBeenCalledWith("valid-token");
|
||||
});
|
||||
|
||||
it("should throw UnauthorizedException if no token provided", async () => {
|
||||
const context = createMockExecutionContext({});
|
||||
|
||||
await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException);
|
||||
await expect(guard.canActivate(context)).rejects.toThrow("No authentication token provided");
|
||||
});
|
||||
|
||||
it("should throw UnauthorizedException if session is invalid", async () => {
|
||||
mockAuthService.verifySession.mockResolvedValue(null);
|
||||
|
||||
const context = createMockExecutionContext({
|
||||
authorization: "Bearer invalid-token",
|
||||
});
|
||||
|
||||
await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException);
|
||||
await expect(guard.canActivate(context)).rejects.toThrow("Invalid or expired session");
|
||||
});
|
||||
|
||||
it("should throw UnauthorizedException if session verification fails", async () => {
|
||||
mockAuthService.verifySession.mockRejectedValue(new Error("Verification failed"));
|
||||
|
||||
const context = createMockExecutionContext({
|
||||
authorization: "Bearer error-token",
|
||||
});
|
||||
|
||||
await expect(guard.canActivate(context)).rejects.toThrow(UnauthorizedException);
|
||||
await expect(guard.canActivate(context)).rejects.toThrow("Authentication failed");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user