/** * ╔═══════════════════════════════════════════════════════════════════════════╗ * ║ AUTHENTICATION ROUTES ║ * ╠═══════════════════════════════════════════════════════════════════════════╣ * ║ Endpoints for user authentication and token management ║ * ╚═══════════════════════════════════════════════════════════════════════════╝ */ import { Router, Request, Response } from 'express'; import { generateToken, generateRefreshToken, verifyRefreshToken, AuthenticatedRequest, authenticate } from '../middleware/authMiddleware.js'; import { hyperLog } from '../services/HyperLog.js'; const router = Router(); // ═══════════════════════════════════════════════════════════════════════════ // Types // ═══════════════════════════════════════════════════════════════════════════ interface LoginRequest { email: string; password: string; } interface TokenResponse { accessToken: string; refreshToken: string; expiresIn: number; user: { id: string; email: string; name: string; roles: string[]; }; } // ═══════════════════════════════════════════════════════════════════════════ // Demo Users (Replace with database in production) // ═══════════════════════════════════════════════════════════════════════════ const DEMO_USERS: Record = { 'admin@widgetdc.local': { password: 'admin123', name: 'Admin User', roles: ['admin', 'user'] }, 'user@widgetdc.local': { password: 'user123', name: 'Regular User', roles: ['user'] }, 'agent@widgetdc.local': { password: 'agent123', name: 'AI Agent', roles: ['agent', 'service'] } }; // ═══════════════════════════════════════════════════════════════════════════ // Routes // ═══════════════════════════════════════════════════════════════════════════ /** * POST /api/auth/login * Login with email and password */ router.post('/login', async (req: Request, res: Response) => { const { email, password } = req.body as LoginRequest; if (!email || !password) { return res.status(400).json({ error: 'Email and password required', code: 'MISSING_CREDENTIALS' }); } // Check user exists const user = DEMO_USERS[email.toLowerCase()]; if (!user || user.password !== password) { hyperLog.logEvent('AUTH_LOGIN_FAILED', { email, reason: 'Invalid credentials' }); return res.status(401).json({ error: 'Invalid email or password', code: 'INVALID_CREDENTIALS' }); } // Generate tokens const userId = email.toLowerCase().replace(/[^a-z0-9]/g, '_'); const accessToken = generateToken({ sub: userId, email: email.toLowerCase(), name: user.name, roles: user.roles }); const refreshToken = generateRefreshToken(userId); hyperLog.logEvent('AUTH_LOGIN_SUCCESS', { email, userId, roles: user.roles }); const response: TokenResponse = { accessToken, refreshToken, expiresIn: 86400, // 24 hours user: { id: userId, email: email.toLowerCase(), name: user.name, roles: user.roles } }; res.json(response); }); /** * POST /api/auth/refresh * Refresh access token using refresh token */ router.post('/refresh', async (req: Request, res: Response) => { const { refreshToken } = req.body; if (!refreshToken) { return res.status(400).json({ error: 'Refresh token required', code: 'MISSING_REFRESH_TOKEN' }); } const userId = verifyRefreshToken(refreshToken); if (!userId) { hyperLog.logEvent('AUTH_REFRESH_FAILED', { reason: 'Invalid refresh token' }); return res.status(401).json({ error: 'Invalid or expired refresh token', code: 'INVALID_REFRESH_TOKEN' }); } // Find user by ID (in demo, derive from userId) const email = Object.keys(DEMO_USERS).find( e => e.toLowerCase().replace(/[^a-z0-9]/g, '_') === userId ); const user = email ? DEMO_USERS[email] : null; if (!user || !email) { return res.status(401).json({ error: 'User not found', code: 'USER_NOT_FOUND' }); } // Generate new tokens const newAccessToken = generateToken({ sub: userId, email, name: user.name, roles: user.roles }); const newRefreshToken = generateRefreshToken(userId); hyperLog.logEvent('AUTH_REFRESH_SUCCESS', { userId }); res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken, expiresIn: 86400 }); }); /** * POST /api/auth/logout * Invalidate refresh token (in production, add to blacklist) */ router.post('/logout', authenticate, async (req: AuthenticatedRequest, res: Response) => { const userId = req.user?.sub; // In production: Add refresh token to blacklist in Redis hyperLog.logEvent('AUTH_LOGOUT', { userId }); res.json({ success: true, message: 'Logged out successfully' }); }); /** * GET /api/auth/me * Get current user info */ router.get('/me', authenticate, async (req: AuthenticatedRequest, res: Response) => { if (!req.user) { return res.status(401).json({ error: 'Not authenticated' }); } res.json({ id: req.user.sub, email: req.user.email, name: req.user.name, roles: req.user.roles, authMethod: req.authMethod }); }); /** * GET /api/auth/status * Check authentication status (public endpoint) */ router.get('/status', (req: Request, res: Response) => { const authEnabled = process.env.AUTH_ENABLED !== 'false'; const devBypass = process.env.NODE_ENV === 'development' && process.env.AUTH_BYPASS === 'true'; res.json({ authEnabled, devBypass, mode: devBypass ? 'bypass' : (authEnabled ? 'enforced' : 'disabled'), hint: authEnabled ? 'Use POST /api/auth/login to obtain tokens' : 'Authentication is disabled' }); }); export default router;