UNPKG

recoder-code

Version:

🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!

416 lines • 16 kB
"use strict"; /** * Authentication Routes * User authentication, registration, and API key management */ Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = require("express"); const container_1 = require("../container"); const AuthService_1 = require("../services/AuthService"); const UserService_1 = require("../services/UserService"); const User_1 = require("../entities/User"); const ApiKey_1 = require("../entities/ApiKey"); // Types for Express augmentation const router = (0, express_1.Router)(); // Helper to get typed user from request const getUser = (req) => req.user; // Middleware for authenticated routes const authenticate = async (req, res, next) => { try { const authService = new AuthService_1.AuthService(); const token = req.headers.authorization?.replace('Bearer ', '') || req.headers.authorization?.replace('token ', ''); if (!token) { return res.status(401).json({ error: 'Authentication required' }); } const result = await authService.validateToken(token); if (!result.valid) { return res.status(401).json({ error: 'Invalid authentication token' }); } req.user = result.user; req.apiKey = result.apiKey; next(); } catch (error) { res.status(500).json({ error: 'Authentication failed' }); } }; // Rate limiting middleware const rateLimit = async (req, res, next) => { try { // Simple rate limiting implementation - could be enhanced later const identifier = getUser(req)?.id || req.ip; // For now, allow all requests - implement proper rate limiting later const isAllowed = true; if (!isAllowed) { return res.status(429).json({ error: 'Rate limit exceeded' }); } next(); } catch (error) { next(); } }; // User registration router.post('/register', rateLimit, async (req, res) => { try { const userService = new UserService_1.UserService(); const authService = new AuthService_1.AuthService(); const { username, email, password, full_name } = req.body; // Validation if (!username || !email || !password) { return res.status(400).json({ error: 'Username, email, and password are required' }); } if (password.length < 8) { return res.status(400).json({ error: 'Password must be at least 8 characters long' }); } if (!email.includes('@')) { return res.status(400).json({ error: 'Invalid email format' }); } if (!/^[a-zA-Z0-9_-]+$/.test(username)) { return res.status(400).json({ error: 'Username can only contain letters, numbers, underscores, and hyphens' }); } // Check if user already exists const existingUserByUsername = await userService.findByUsername(username); const existingUserByEmail = await userService.findByEmail(email); if (existingUserByUsername || existingUserByEmail) { return res.status(409).json({ error: 'Username or email already exists' }); } // Hash password const hashedPassword = await authService.hashPassword(password); // Create user const user = await userService.create({ username, email, password_hash: hashedPassword, full_name, role: User_1.UserRole.USER }); // Generate verification token if email verification is enabled const verificationToken = user.generateVerificationToken(); // Generate initial API key const apiKeyResult = await authService.createApiKey(user.id, { name: 'Default API Key', scopes: [ApiKey_1.ApiKeyScope.READ, ApiKey_1.ApiKeyScope.WRITE, ApiKey_1.ApiKeyScope.PUBLISH] }); res.status(201).json({ success: true, user: user.toPublicFormat(), api_key: apiKeyResult.key, verification_required: !!verificationToken, message: 'User registered successfully' }); } catch (error) { res.status(500).json({ error: 'Registration failed' }); } }); // User login router.post('/login', rateLimit, async (req, res) => { try { const authService = new AuthService_1.AuthService(); const userService = new UserService_1.UserService(); const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username and password are required' }); } // Find user by username or email let user = await userService.findByUsername(username); if (!user) { user = await userService.findByEmail(username); } if (!user) { return res.status(401).json({ error: 'Invalid credentials' }); } // Verify password const isValidPassword = await authService.verifyPassword(password, user.password_hash); if (!isValidPassword) { return res.status(401).json({ error: 'Invalid credentials' }); } // Update login info user.recordLogin(req.ip); await userService.update(user.id, { last_login: user.last_login, login_count: user.login_count, last_login_ip: user.last_login_ip }); // Generate session token const token = await authService.generateToken(user.id); res.json({ success: true, user: user.toPrivateFormat(), token, message: 'Login successful' }); } catch (error) { res.status(500).json({ error: 'Login failed' }); } }); // Logout router.post('/logout', authenticate, async (req, res) => { try { const authService = new AuthService_1.AuthService(); const token = req.headers.authorization?.replace('Bearer ', '') || req.headers.authorization?.replace('token ', ''); if (token) { await authService.revokeToken(token); } res.json({ success: true, message: 'Logout successful' }); } catch (error) { res.status(500).json({ error: 'Logout failed' }); } }); // Get current user profile router.get('/profile', authenticate, async (req, res) => { try { const user = getUser(req); res.json({ user: user?.toPrivateFormat(), api_key_info: req.apiKey?.toSafeFormat() }); } catch (error) { res.status(500).json({ error: 'Failed to get profile' }); } }); // Update user profile router.patch('/profile', authenticate, async (req, res) => { try { const userService = container_1.Container.get(UserService_1.UserService); const allowedFields = ['full_name', 'bio', 'website', 'location', 'avatar_url']; const updates = {}; for (const field of allowedFields) { if (req.body[field] !== undefined) { updates[field] = req.body[field]; } } if (Object.keys(updates).length === 0) { return res.status(400).json({ error: 'No valid fields to update' }); } const result = await userService.updateUser(getUser(req).id, updates); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, user: result.user ? result.user.toPrivateFormat() : null, message: 'Profile updated successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to update profile' }); } }); // Change password router.post('/change-password', authenticate, async (req, res) => { try { const authService = new AuthService_1.AuthService(); const { current_password, new_password } = req.body; if (!current_password || !new_password) { return res.status(400).json({ error: 'Current and new passwords are required' }); } if (new_password.length < 8) { return res.status(400).json({ error: 'New password must be at least 8 characters long' }); } // Verify current password const isValid = await authService.verifyPassword(getUser(req).id, current_password); if (!isValid) { return res.status(401).json({ error: 'Current password is incorrect' }); } // Update password const result = await authService.changePassword(getUser(req).id, new_password); if (!result.success) { return res.status(400).json({ error: result.error }); } // Revoke all existing tokens except current await authService.revokeAllTokens(getUser(req).id, req.apiKey?.id); res.json({ success: true, message: 'Password changed successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to change password' }); } }); // Create API key router.post('/api-keys', authenticate, async (req, res) => { try { const authService = new AuthService_1.AuthService(); const { name, scopes = [], description, expires_at, restrictions } = req.body; if (!name) { return res.status(400).json({ error: 'API key name is required' }); } // Validate scopes const validScopes = Object.values(ApiKey_1.ApiKeyScope); const invalidScopes = scopes.filter((scope) => !validScopes.includes(scope)); if (invalidScopes.length > 0) { return res.status(400).json({ error: `Invalid scopes: ${invalidScopes.join(', ')}` }); } // Check if user can create API keys with admin scope if (scopes.includes(ApiKey_1.ApiKeyScope.ADMIN) && !getUser(req).is_admin) { return res.status(403).json({ error: 'Insufficient permissions for admin scope' }); } const result = await authService.createApiKey(getUser(req).id, { name, scopes, description, expires_at: expires_at ? new Date(expires_at) : undefined, restrictions }); if (!result.success) { return res.status(400).json({ error: result.error }); } res.status(201).json({ success: true, api_key: result.key, key_info: result.apiKey ? result.apiKey.toSafeFormat() : null, message: 'API key created successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to create API key' }); } }); // List API keys router.get('/api-keys', authenticate, async (req, res) => { try { const authService = new AuthService_1.AuthService(); const apiKeys = await authService.getUserApiKeys(getUser(req).id); res.json({ api_keys: apiKeys.map(key => key.toSafeFormat()), total: apiKeys.length }); } catch (error) { res.status(500).json({ error: 'Failed to list API keys' }); } }); // Revoke API key router.delete('/api-keys/:keyId', authenticate, async (req, res) => { try { const authService = new AuthService_1.AuthService(); const keyId = req.params.keyId; const result = await authService.revokeApiKey(getUser(req).id, keyId); if (!result.success) { return res.status(404).json({ error: result.error }); } res.json({ success: true, message: 'API key revoked successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to revoke API key' }); } }); // Verify email router.post('/verify-email', async (req, res) => { try { const userService = container_1.Container.get(UserService_1.UserService); const { token } = req.body; if (!token) { return res.status(400).json({ error: 'Verification token is required' }); } const result = await userService.verifyEmail(token); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Email verified successfully' }); } catch (error) { res.status(500).json({ error: 'Email verification failed' }); } }); // Request password reset router.post('/forgot-password', rateLimit, async (req, res) => { try { const userService = container_1.Container.get(UserService_1.UserService); const { email } = req.body; if (!email) { return res.status(400).json({ error: 'Email is required' }); } // Always return success to prevent email enumeration await userService.requestPasswordReset(email); res.json({ success: true, message: 'If an account with that email exists, a password reset link has been sent' }); } catch (error) { res.status(500).json({ error: 'Failed to process password reset request' }); } }); // Reset password router.post('/reset-password', rateLimit, async (req, res) => { try { const userService = new UserService_1.UserService(); const authService = new AuthService_1.AuthService(); const { token, new_password } = req.body; if (!token || !new_password) { return res.status(400).json({ error: 'Reset token and new password are required' }); } if (new_password.length < 8) { return res.status(400).json({ error: 'Password must be at least 8 characters long' }); } const result = await userService.resetPassword(token, new_password); if (!result.success) { return res.status(400).json({ error: result.error }); } // Revoke all API keys and tokens for security if (result.user) { await authService.revokeAllTokens(result.user.id); } res.json({ success: true, message: 'Password reset successfully' }); } catch (error) { res.status(500).json({ error: 'Password reset failed' }); } }); // Get user packages router.get('/packages', authenticate, async (req, res) => { try { const userService = container_1.Container.get(UserService_1.UserService); const { page = 1, limit = 20 } = req.query; const packages = await userService.getUserPackages(getUser(req).id); res.json(packages); } catch (error) { res.status(500).json({ error: 'Failed to get user packages' }); } }); // Get user statistics router.get('/stats', authenticate, async (req, res) => { try { const userService = container_1.Container.get(UserService_1.UserService); const stats = await userService.getUserStats(getUser(req).id); res.json(stats); } catch (error) { res.status(500).json({ error: 'Failed to get user statistics' }); } }); // Delete account router.delete('/account', authenticate, async (req, res) => { try { const userService = new UserService_1.UserService(); const authService = new AuthService_1.AuthService(); const { password, confirm } = req.body; if (!password || confirm !== 'DELETE') { return res.status(400).json({ error: 'Password and confirmation ("DELETE") are required' }); } // Verify password const isValid = await authService.verifyPassword(getUser(req).id, password); if (!isValid) { return res.status(401).json({ error: 'Password is incorrect' }); } const result = await userService.deleteUser(getUser(req).id); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Account deleted successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to delete account' }); } }); exports.default = router; //# sourceMappingURL=auth.js.map