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:
141
apps/api/src/auth/auth.service.spec.ts
Normal file
141
apps/api/src/auth/auth.service.spec.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
|
||||
describe("AuthService", () => {
|
||||
let service: AuthService;
|
||||
let prisma: PrismaService;
|
||||
|
||||
const mockPrismaService = {
|
||||
user: {
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
AuthService,
|
||||
{
|
||||
provide: PrismaService,
|
||||
useValue: mockPrismaService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
prisma = module.get<PrismaService>(PrismaService);
|
||||
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("getAuth", () => {
|
||||
it("should return BetterAuth instance", () => {
|
||||
const auth = service.getAuth();
|
||||
expect(auth).toBeDefined();
|
||||
expect(auth.handler).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserById", () => {
|
||||
const mockUser = {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
authProviderId: "auth-123",
|
||||
};
|
||||
|
||||
it("should get user by ID", async () => {
|
||||
mockPrismaService.user.findUnique.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await service.getUserById("user-123");
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(mockPrismaService.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { id: "user-123" },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
authProviderId: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserByEmail", () => {
|
||||
const mockUser = {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
authProviderId: "auth-123",
|
||||
};
|
||||
|
||||
it("should get user by email", async () => {
|
||||
mockPrismaService.user.findUnique.mockResolvedValue(mockUser);
|
||||
|
||||
const result = await service.getUserByEmail("test@example.com");
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
expect(mockPrismaService.user.findUnique).toHaveBeenCalledWith({
|
||||
where: { email: "test@example.com" },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
authProviderId: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("verifySession", () => {
|
||||
const mockSessionData = {
|
||||
user: {
|
||||
id: "user-123",
|
||||
email: "test@example.com",
|
||||
name: "Test User",
|
||||
},
|
||||
session: {
|
||||
id: "session-123",
|
||||
token: "test-token",
|
||||
},
|
||||
};
|
||||
|
||||
it("should return session data for valid token", async () => {
|
||||
const auth = service.getAuth();
|
||||
const mockGetSession = vi.fn().mockResolvedValue(mockSessionData);
|
||||
auth.api = { getSession: mockGetSession } as any;
|
||||
|
||||
const result = await service.verifySession("valid-token");
|
||||
|
||||
expect(result).toEqual(mockSessionData);
|
||||
expect(mockGetSession).toHaveBeenCalledWith({
|
||||
headers: {
|
||||
authorization: "Bearer valid-token",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return null for invalid session", async () => {
|
||||
const auth = service.getAuth();
|
||||
const mockGetSession = vi.fn().mockResolvedValue(null);
|
||||
auth.api = { getSession: mockGetSession } as any;
|
||||
|
||||
const result = await service.verifySession("invalid-token");
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should return null and log error on verification failure", async () => {
|
||||
const auth = service.getAuth();
|
||||
const mockGetSession = vi.fn().mockRejectedValue(new Error("Verification failed"));
|
||||
auth.api = { getSession: mockGetSession } as any;
|
||||
|
||||
const result = await service.verifySession("error-token");
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user