UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

415 lines (414 loc) 15.9 kB
/** * Authentication API Endpoints * * RESTful API endpoints for user authentication, registration, * token management, and user profile operations. */ import { Router } from 'express'; import { z } from 'zod'; import { AuthMiddleware } from '../services/authentication.js'; import { createLogger } from '../lib/logging.js'; import { StandardError, ErrorCode } from '../lib/errors.js'; const logger = createLogger('auth-endpoints'); // Validation schemas const registerSchema = z.object({ username: z.string().min(3, 'Username must be at least 3 characters').max(50, 'Username too long'), email: z.string().email('Invalid email format'), password: z.string().min(8, 'Password must be at least 8 characters').regex(/[A-Z]/, 'Password must contain at least one uppercase letter').regex(/[a-z]/, 'Password must contain at least one lowercase letter').regex(/\d/, 'Password must contain at least one number').regex(/[!@#$%^&*(),.?":{}|<>]/, 'Password must contain at least one special character'), role: z.enum([ 'admin', 'developer', 'readonly' ]).optional().default('developer') }); const loginSchema = z.object({ email: z.string().email('Invalid email format'), password: z.string().min(1, 'Password is required') }); const refreshTokenSchema = z.object({ refreshToken: z.string().min(1, 'Refresh token is required') }); const changePasswordSchema = z.object({ currentPassword: z.string().min(1, 'Current password is required'), newPassword: z.string().min(8, 'New password must be at least 8 characters').regex(/[A-Z]/, 'New password must contain at least one uppercase letter').regex(/[a-z]/, 'New password must contain at least one lowercase letter').regex(/\d/, 'New password must contain at least one number').regex(/[!@#$%^&*(),.?":{}|<>]/, 'New password must contain at least one special character') }); const updateProfileSchema = z.object({ username: z.string().min(3, 'Username must be at least 3 characters').max(50, 'Username too long').optional(), email: z.string().email('Invalid email format').optional() }).refine((data)=>data.username || data.email, { message: 'At least one field must be provided for update' }); // Rate limiting utilities let RateLimiter = class RateLimiter { requests = new Map(); isAllowed(key, limit, windowMs) { const now = Date.now(); const request = this.requests.get(key); if (!request || now > request.resetTime) { this.requests.set(key, { count: 1, resetTime: now + windowMs }); return true; } if (request.count >= limit) { return false; } request.count++; return true; } }; const rateLimiter = new RateLimiter(); // Rate limiting middleware function rateLimit(limit, windowMs, keyGenerator) { return (req, res, next1)=>{ const key = keyGenerator(req); if (!rateLimiter.isAllowed(key, limit, windowMs)) { res.status(429).json({ error: 'Too Many Requests', message: `Rate limit exceeded. Try again later.`, retryAfter: Math.ceil(windowMs / 1000) }); return; } next1(); }; } // Error handling middleware function handleAuthError(error, req, res, next1) { logger.error('Authentication error:', error); if (error instanceof StandardError) { const statusCode = error.code === ErrorCode.CONFLICT ? 409 : error.code === ErrorCode.NOT_FOUND ? 404 : error.code === ErrorCode.VALIDATION_FAILED ? 400 : error.code === ErrorCode.UNAUTHORIZED ? 401 : error.code === ErrorCode.FORBIDDEN ? 403 : error.code === ErrorCode.CONFLICT ? 409 : 500; res.status(statusCode).json({ success: false, error: error.code, message: error.message, ...error.metadata && { details: error.metadata } }); return; } // Zod validation errors if (error instanceof z.ZodError) { res.status(400).json({ success: false, error: 'VALIDATION_ERROR', message: 'Validation failed', details: error.errors.map((err)=>({ field: err.path.join('.'), message: err.message })) }); return; } // Generic error res.status(500).json({ success: false, error: 'INTERNAL_SERVER_ERROR', message: 'An unexpected error occurred' }); } // Authentication middleware function authenticateUser(authMiddleware) { return async (req, res, next1)=>{ try { const authHeader = req.headers.authorization; if (!authHeader) { res.status(401).json({ success: false, error: 'UNAUTHORIZED', message: 'Missing authentication credentials' }); return; } const userContext = authMiddleware.extractUserContext(authHeader); // Attach user to request req.user = { userId: userContext.userId, username: userContext.username, email: userContext.email, role: userContext.role }; next1(); } catch (error) { if (error instanceof StandardError) { res.status(401).json({ success: false, error: error.code, message: error.message }); return; } res.status(401).json({ success: false, error: 'UNAUTHORIZED', message: 'Invalid authentication token' }); } }; } // Authorization middleware function authorizeUser(requiredRoles) { return (req, res, next1)=>{ if (!req.user) { res.status(401).json({ success: false, error: 'UNAUTHORIZED', message: 'Authentication required' }); return; } if (!requiredRoles.includes(req.user.role)) { res.status(403).json({ success: false, error: 'FORBIDDEN', message: 'Insufficient permissions', requiredRoles, userRole: req.user.role }); return; } next1(); }; } /** * Create authentication router */ export function authenticationRouter(authService) { const router = Router(); // Apply error handling middleware router.use(handleAuthError); // Initialize auth middleware for token validation const authMiddleware = new AuthMiddleware(process.env.JWT_SECRET || 'default-secret-change-in-production'); // POST /api/auth/register - Register new user router.post('/register', rateLimit(5, 15 * 60 * 1000, (req)=>`register:${req.ip}`), async (req, res)=>{ const validatedData = registerSchema.parse(req.body); try { const result = await authService.registerUser(validatedData); res.status(201).json({ success: true, message: 'User registered successfully', user: { id: result.user.id, username: result.user.username, email: result.user.email, role: result.user.role, createdAt: result.user.createdAt }, tokens: result.tokens }); logger.info('User registration successful', { userId: result.user.id, email: result.user.email, ip: req.ip }); } catch (error) { next(error); } }); // POST /api/auth/login - User login router.post('/login', rateLimit(10, 15 * 60 * 1000, (req)=>`login:${req.body.email || req.ip}`), async (req, res)=>{ const validatedData = loginSchema.parse(req.body); try { const result = await authService.loginUser(validatedData, req.ip, req.headers['user-agent']); res.json({ success: true, message: 'Login successful', user: { id: result.user.id, username: result.user.username, email: result.user.email, role: result.user.role, createdAt: result.user.createdAt, lastLogin: result.user.lastLogin }, tokens: result.tokens }); logger.info('User login successful', { userId: result.user.id, email: result.user.email, ip: req.ip }); } catch (error) { next(error); } }); // POST /api/auth/refresh - Refresh access token router.post('/refresh', rateLimit(20, 15 * 60 * 1000, (req)=>`refresh:${req.ip}`), async (req, res)=>{ const validatedData = refreshTokenSchema.parse(req.body); try { const tokens = await authService.refreshToken(validatedData.refreshToken); res.json({ success: true, message: 'Tokens refreshed successfully', tokens }); logger.info('Token refresh successful', { ip: req.ip }); } catch (error) { next(error); } }); // POST /api/auth/logout - User logout router.post('/logout', authenticateUser(authMiddleware), async (req, res)=>{ const { refreshToken } = req.body; const accessToken = req.headers.authorization; try { await authService.logout(req.user.userId, accessToken, refreshToken); res.json({ success: true, message: 'Logout successful' }); logger.info('User logout successful', { userId: req.user.userId, ip: req.ip }); } catch (error) { next(error); } }); // POST /api/auth/logout-all - Logout from all sessions router.post('/logout-all', authenticateUser(authMiddleware), async (req, res)=>{ const accessToken = req.headers.authorization; try { await authService.logoutAllSessions(req.user.userId, accessToken); res.json({ success: true, message: 'All sessions terminated successfully' }); logger.info('All sessions terminated', { userId: req.user.userId, ip: req.ip }); } catch (error) { next(error); } }); // GET /api/auth/profile - Get user profile router.get('/profile', authenticateUser(authMiddleware), async (req, res)=>{ try { const profile = await authService.getUserProfile(req.user.userId); res.json({ success: true, user: profile }); } catch (error) { next(error); } }); // PUT /api/auth/profile - Update user profile router.put('/profile', authenticateUser(authMiddleware), rateLimit(5, 15 * 60 * 1000, (req)=>`profile:${req.user.userId}`), async (req, res)=>{ const validatedData = updateProfileSchema.parse(req.body); try { const updatedProfile = await authService.updateUserProfile(req.user.userId, validatedData); res.json({ success: true, message: 'Profile updated successfully', user: updatedProfile }); logger.info('Profile updated successfully', { userId: req.user.userId, ip: req.ip }); } catch (error) { next(error); } }); // POST /api/auth/change-password - Change password router.post('/change-password', authenticateUser(authMiddleware), rateLimit(3, 15 * 60 * 1000, (req)=>`password:${req.user.userId}`), async (req, res)=>{ const validatedData = changePasswordSchema.parse(req.body); try { await authService.changePassword(req.user.userId, validatedData.currentPassword, validatedData.newPassword); res.json({ success: true, message: 'Password changed successfully' }); logger.info('Password changed successfully', { userId: req.user.userId, ip: req.ip }); } catch (error) { next(error); } }); // GET /api/auth/sessions - Get active sessions router.get('/sessions', authenticateUser(authMiddleware), async (req, res)=>{ try { // This would require extending the authentication service // to support session listing functionality res.json({ success: true, message: 'Session listing not yet implemented', sessions: [] }); } catch (error) { next(error); } }); // DELETE /api/auth/sessions/:sessionId - Terminate specific session router.delete('/sessions/:sessionId', authenticateUser(authMiddleware), async (req, res)=>{ try { const { sessionId } = req.params; // This would require extending the authentication service // to support specific session termination res.json({ success: true, message: 'Session termination not yet implemented' }); } catch (error) { next(error); } }); // POST /api/auth/request-password-reset - Request password reset router.post('/request-password-reset', rateLimit(3, 15 * 60 * 1000, (req)=>`reset:${req.body.email || req.ip}`), async (req, res)=>{ const { email } = req.body; if (!email) { res.status(400).json({ success: false, error: 'VALIDATION_ERROR', message: 'Email is required' }); return; } // This would require implementing password reset functionality res.json({ success: true, message: 'Password reset functionality not yet implemented' }); logger.info('Password reset requested', { email, ip: req.ip }); }); // POST /api/auth/reset-password - Reset password with token router.post('/reset-password', rateLimit(5, 60 * 60 * 1000, (req)=>`reset-confirm:${req.ip}`), async (req, res)=>{ const { token, newPassword } = req.body; if (!token || !newPassword) { res.status(400).json({ success: false, error: 'VALIDATION_ERROR', message: 'Reset token and new password are required' }); return; } // This would require implementing password reset confirmation res.json({ success: true, message: 'Password reset confirmation not yet implemented' }); logger.info('Password reset confirmation attempted', { ip: req.ip }); }); // GET /api/auth/verify-token - Verify token validity router.get('/verify-token', authenticateUser(authMiddleware), async (req, res)=>{ res.json({ success: true, message: 'Token is valid', user: req.user }); }); return router; } export { authenticateUser, authorizeUser, rateLimit, handleAuthError }; //# sourceMappingURL=auth-endpoints.js.map