recoder-code
Version:
🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!
416 lines • 16 kB
JavaScript
"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