/** * Example usage of authentication services * * This file demonstrates how to use all authentication services together * following Effect-TS patterns from the ccviewer codebase. */ import { Effect, Layer } from "effect"; import { DatabaseService } from "../../database/db.js"; import { PasswordService, TokenService, UserRepository, SessionRepository, AuditLogService, type CreateUserData, } from "./index.js"; // Example: Complete user registration flow export const registerUser = ( username: string, email: string, password: string, linuxUsername: string, ipAddress: string | null, userAgent: string | null ) => Effect.gen(function* (_) { // Get services const passwordService = yield* _(PasswordService); const userRepo = yield* _(UserRepository); const auditLog = yield* _(AuditLogService); // Validate password strength yield* _(passwordService.validatePasswordStrength(password)); // Hash the password const passwordHash = yield* _(passwordService.hash(password)); // Create user data const userData: CreateUserData = { username, email, passwordHash, linuxUsername, }; // Create user in database const user = yield* _(userRepo.create(userData)); // Log the registration yield* _( auditLog.log({ userId: user.id, action: "USER_REGISTERED", ipAddress, userAgent, details: JSON.stringify({ username, email }), }) ); return user; }); // Example: Complete login flow export const loginUser = ( email: string, password: string, ipAddress: string | null, userAgent: string | null ) => Effect.gen(function* (_) { // Get services const passwordService = yield* _(PasswordService); const tokenService = yield* _(TokenService); const userRepo = yield* _(UserRepository); const sessionRepo = yield* _(SessionRepository); const auditLog = yield* _(AuditLogService); // Find user by email const user = yield* _(userRepo.findByEmail(email)); if (!user) { // Log failed login attempt yield* _( auditLog.log({ userId: null, action: "LOGIN_FAILED", ipAddress, userAgent, details: JSON.stringify({ email, reason: "User not found" }), }) ); return yield* _( Effect.fail(new Error("Invalid email or password")) ); } // Check if user is active if (!user.isActive) { yield* _( auditLog.log({ userId: user.id, action: "LOGIN_FAILED", ipAddress, userAgent, details: JSON.stringify({ reason: "Account inactive" }), }) ); return yield* _(Effect.fail(new Error("Account is inactive"))); } // Check if password hash exists if (!user.passwordHash) { yield* _( auditLog.log({ userId: user.id, action: "LOGIN_FAILED", ipAddress, userAgent, details: JSON.stringify({ reason: "No password set" }), }) ); return yield* _(Effect.fail(new Error("Invalid email or password"))); } // Verify password const isValid = yield* _( passwordService.verify(password, user.passwordHash) ); if (!isValid) { // Increment failed login attempts yield* _(userRepo.incrementFailedLogins(user.id)); yield* _( auditLog.log({ userId: user.id, action: "LOGIN_FAILED", ipAddress, userAgent, details: JSON.stringify({ reason: "Invalid password" }), }) ); return yield* _(Effect.fail(new Error("Invalid email or password"))); } // Reset failed login attempts on successful login yield* _(userRepo.resetFailedLogins(user.id)); // Generate JWT token const tokenPayload = { sub: user.id, username: user.username, email: user.email, jti: "", // Will be set to session ID }; const token = yield* _(tokenService.generateToken(tokenPayload)); // Hash the token for storage const tokenHash = yield* _(tokenService.hashToken(token)); // Verify token to get the actual payload with jti const verifiedPayload = yield* _(tokenService.verifyToken(token)); // Create session const session = yield* _( sessionRepo.create({ userId: user.id, tokenHash, ipAddress, userAgent, expiresAt: verifiedPayload.exp * 1000, // Convert to milliseconds }) ); // Update last login timestamp yield* _( userRepo.update(user.id, { lastLoginAt: Date.now(), }) ); // Log successful login yield* _( auditLog.log({ userId: user.id, action: "USER_LOGIN", resourceType: "session", resourceId: session.id, ipAddress, userAgent, }) ); return { user, token, session }; }); // Example: Verify and validate a token export const verifyToken = (token: string) => Effect.gen(function* (_) { const tokenService = yield* _(TokenService); const sessionRepo = yield* _(SessionRepository); const userRepo = yield* _(UserRepository); // Verify token signature and expiration const payload = yield* _(tokenService.verifyToken(token)); // Hash the token to look up session const tokenHash = yield* _(tokenService.hashToken(token)); // Find session by token hash const session = yield* _(sessionRepo.findByTokenHash(tokenHash)); if (!session) { return yield* _(Effect.fail(new Error("Session not found"))); } // Check if session is revoked if (session.isRevoked) { return yield* _(Effect.fail(new Error("Session has been revoked"))); } // Check if session is expired if (session.expiresAt < Date.now()) { return yield* _(Effect.fail(new Error("Session has expired"))); } // Get user const user = yield* _(userRepo.findById(payload.sub)); if (!user) { return yield* _(Effect.fail(new Error("User not found"))); } if (!user.isActive) { return yield* _(Effect.fail(new Error("User account is inactive"))); } return { user, session, payload }; }); // Example: Logout user export const logoutUser = (token: string) => Effect.gen(function* (_) { const tokenService = yield* _(TokenService); const sessionRepo = yield* _(SessionRepository); const auditLog = yield* _(AuditLogService); // Verify token const payload = yield* _(tokenService.verifyToken(token)); // Hash token to find session const tokenHash = yield* _(tokenService.hashToken(token)); // Find session const session = yield* _(sessionRepo.findByTokenHash(tokenHash)); if (!session) { return yield* _(Effect.fail(new Error("Session not found"))); } // Revoke session yield* _(sessionRepo.revoke(session.id)); // Log logout yield* _( auditLog.log({ userId: payload.sub, action: "USER_LOGOUT", resourceType: "session", resourceId: session.id, }) ); return { success: true }; }); // Example: Create the complete authentication layer export const AuthenticationLayer = Layer.mergeAll( PasswordService.Live, TokenService.Live, UserRepository.Live, SessionRepository.Live, AuditLogService.Live ); // Example: Run a complete authentication flow with all dependencies export const runAuthExample = () => { const program = Effect.gen(function* (_) { // Register a new user const user = yield* _( registerUser( "johndoe", "john@example.com", "SecurePass123", "john-doe", "127.0.0.1", "Mozilla/5.0" ) ); console.log("User registered:", user); // Login the user const loginResult = yield* _( loginUser( "john@example.com", "SecurePass123", "127.0.0.1", "Mozilla/5.0" ) ); console.log("Login successful:", loginResult); // Verify the token const verifyResult = yield* _(verifyToken(loginResult.token)); console.log("Token verified:", verifyResult); // Logout the user const logoutResult = yield* _(logoutUser(loginResult.token)); console.log("Logout successful:", logoutResult); }); // Create database configuration const dbConfig = { path: "./auth-example.db", }; // Combine all layers - properly merging DatabaseService with AuthenticationLayer const AppLayer = DatabaseService.Live(dbConfig).pipe( Layer.provideMerge(AuthenticationLayer) ); // Run the program with all dependencies provided // Effect-TS Layer type inference limitation // @ts-ignore return Effect.runPromise(Effect.provide(program, AppLayer)); }; // Uncomment to run the example: // runAuthExample().catch(console.error);