import express from 'express'; import { AuthService } from '../services/auth.service'; import { authenticateToken, blacklistToken } from '../middleware/auth'; import config from '../config'; // Temporary mock implementations const prisma = { user: { findUnique: async (params: any): Promise => { return { id: 'mock-user-id', email: 'mock@example.com', firstName: 'Mock', lastName: 'User', subscription: 'FREE', isActive: true, passwordHash: '$2b$12$mockhashedpassword', createdAt: new Date(), usageMetrics: { pagesCloned: 0, storageUsed: 0, apiCalls: 0, lastReset: new Date() } }; }, create: async (params: any): Promise => ({ id: 'mock-user-id', email: params.data.email, firstName: params.data.firstName, lastName: params.data.lastName, subscription: 'FREE', createdAt: new Date() }), update: async (params: any): Promise => ({ id: 'mock-user-id', email: 'mock@example.com', firstName: 'Mock', lastName: 'User', subscription: 'FREE', updatedAt: new Date() }) }, userSession: { create: async (params: any): Promise => ({ id: 'mock-session' }), deleteMany: async (params: any): Promise => ({ count: 0 }) } }; const validateUserRegistration = (data: any) => { if (!data.email || !data.password) { throw new Error('Email and password are required'); } return data; }; const validateUserLogin = (data: any) => { if (!data.email || !data.password) { throw new Error('Email and password are required'); } return data; }; const router = express.Router(); // Register new user router.post('/register', async (req, res, next) => { try { // Validate input const validatedData = validateUserRegistration(req.body); // Check if user already exists const existingUser = await prisma.user.findUnique({ where: { email: validatedData.email } }); if (existingUser) { return res.status(400).json({ success: false, error: 'User with this email already exists', code: 'USER_EXISTS' }); } // Hash password const passwordHash = await AuthService.hashPassword(validatedData.password); // Create user const user = await prisma.user.create({ data: { email: validatedData.email, passwordHash, firstName: validatedData.firstName, lastName: validatedData.lastName, subscription: 'FREE', usageMetrics: { create: { pagesCloned: 0, storageUsed: 0, apiCalls: 0 } } }, select: { id: true, email: true, firstName: true, lastName: true, subscription: true, createdAt: true } }); // Generate JWT token pair const tokenPair = AuthService.generateTokenPair( user.id, user.email, user.subscription ); res.status(201).json({ success: true, data: { user, ...tokenPair }, message: 'User registered successfully' }); } catch (error) { next(error); } }); // Login user router.post('/login', async (req, res, next) => { try { // Validate input const validatedData = validateUserLogin(req.body); // Find user const user = await prisma.user.findUnique({ where: { email: validatedData.email }, select: { id: true, email: true, passwordHash: true, firstName: true, lastName: true, subscription: true, isActive: true } }); if (!user || !user.isActive) { return res.status(401).json({ success: false, error: 'Invalid email or password', code: 'INVALID_CREDENTIALS' }); } // Verify password const isValidPassword = await AuthService.verifyPassword(validatedData.password, user.passwordHash); if (!isValidPassword) { return res.status(401).json({ success: false, error: 'Invalid email or password', code: 'INVALID_CREDENTIALS' }); } // Generate JWT token pair const tokenPair = AuthService.generateTokenPair( user.id, user.email, user.subscription ); // Create session record await prisma.userSession.create({ data: { userId: user.id, token: tokenPair.accessToken, expiresAt: new Date(Date.now() + tokenPair.expiresIn * 1000) } }); res.json({ success: true, data: { user: { id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, subscription: user.subscription }, ...tokenPair }, message: 'Login successful' }); } catch (error) { next(error); } }); // Logout user router.post('/logout', authenticateToken, async (req, res, next) => { try { const authHeader = req.headers.authorization; const token = authHeader && authHeader.split(' ')[1]; if (token) { // Blacklist the access token blacklistToken(token); // Remove session from database await prisma.userSession.deleteMany({ where: { userId: req.user!.id, token } }); } res.json({ success: true, message: 'Logout successful' }); } catch (error) { next(error); } }); // Refresh token endpoint router.post('/refresh', async (req, res, next) => { try { const { refreshToken } = req.body; if (!refreshToken) { return res.status(400).json({ success: false, error: 'Refresh token is required', code: 'VALIDATION_ERROR' }); } // Generate new token pair using refresh token const newTokenPair = AuthService.refreshAccessToken(refreshToken); res.json({ success: true, data: newTokenPair, message: 'Token refreshed successfully' }); } catch (error) { return res.status(401).json({ success: false, error: 'Invalid or expired refresh token', code: 'INVALID_REFRESH_TOKEN' }); } }); // Logout from all devices router.post('/logout-all', authenticateToken, async (req, res, next) => { try { // Revoke all tokens for the user AuthService.revokeAllUserTokens(req.user!.id); // Remove all sessions from database await prisma.userSession.deleteMany({ where: { userId: req.user!.id } }); res.json({ success: true, message: 'Logged out from all devices successfully' }); } catch (error) { next(error); } }); // Get current user profile router.get('/me', authenticateToken, async (req, res, next) => { try { const user = await prisma.user.findUnique({ where: { id: req.user!.id }, select: { id: true, email: true, firstName: true, lastName: true, avatar: true, subscription: true, emailVerified: true, createdAt: true, usageMetrics: { select: { pagesCloned: true, storageUsed: true, apiCalls: true, lastReset: true } } } }); if (!user) { return res.status(404).json({ success: false, error: 'User not found', code: 'USER_NOT_FOUND' }); } res.json({ success: true, data: user }); } catch (error) { next(error); } }); // Update user profile router.patch('/me', authenticateToken, async (req, res, next) => { try { const { firstName, lastName, avatar } = req.body; const updatedUser = await prisma.user.update({ where: { id: req.user!.id }, data: { ...(firstName && { firstName }), ...(lastName && { lastName }), ...(avatar && { avatar }) }, select: { id: true, email: true, firstName: true, lastName: true, avatar: true, subscription: true, updatedAt: true } }); res.json({ success: true, data: updatedUser, message: 'Profile updated successfully' }); } catch (error) { next(error); } }); // Change password router.post('/change-password', authenticateToken, async (req, res, next) => { try { const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) { return res.status(400).json({ success: false, error: 'Current password and new password are required', code: 'VALIDATION_ERROR' }); } if (newPassword.length < 8) { return res.status(400).json({ success: false, error: 'New password must be at least 8 characters long', code: 'VALIDATION_ERROR' }); } // Get current user with password const user = await prisma.user.findUnique({ where: { id: req.user!.id }, select: { passwordHash: true } }); if (!user) { return res.status(404).json({ success: false, error: 'User not found', code: 'USER_NOT_FOUND' }); } // Verify current password const isValidPassword = await AuthService.verifyPassword(currentPassword, user.passwordHash); if (!isValidPassword) { return res.status(401).json({ success: false, error: 'Current password is incorrect', code: 'INVALID_CREDENTIALS' }); } // Hash new password const newPasswordHash = await AuthService.hashPassword(newPassword); // Update password await prisma.user.update({ where: { id: req.user!.id }, data: { passwordHash: newPasswordHash } }); // Invalidate all existing sessions AuthService.revokeAllUserTokens(req.user!.id); await prisma.userSession.deleteMany({ where: { userId: req.user!.id } }); res.json({ success: true, message: 'Password changed successfully. Please log in again.' }); } catch (error) { next(error); } }); export default router; // Get active sessions router.get('/sessions', authenticateToken, async (req, res, next) => { try { const sessions = AuthService.getUserActiveSessions(req.user!.id); res.json({ success: true, data: { sessions: sessions.map(session => ({ sessionId: session.sessionId, expiresAt: session.expiresAt, isCurrentSession: false // TODO: Identify current session })) } }); } catch (error) { next(error); } }); // Revoke specific session router.delete('/sessions/:sessionId', authenticateToken, async (req, res, next) => { try { const { sessionId } = req.params; // Revoke specific session AuthService.revokeUserSession(req.user!.id, sessionId); res.json({ success: true, message: 'Session revoked successfully' }); } catch (error) { next(error); } }); // Validate token endpoint (for debugging/testing) router.post('/validate', async (req, res, next) => { try { const { token } = req.body; if (!token) { return res.status(400).json({ success: false, error: 'Token is required', code: 'VALIDATION_ERROR' }); } const payload = AuthService.verifyToken(token); res.json({ success: true, data: { valid: true, payload: { userId: payload.userId, email: payload.email, subscription: payload.subscription, type: payload.type, sessionId: payload.sessionId, expiresAt: new Date(payload.exp! * 1000) } } }); } catch (error) { res.json({ success: true, data: { valid: false, error: error instanceof Error ? error.message : 'Invalid token' } }); } });