import { v4 as uuidv4 } from 'uuid'; import { AuthenticatedUser } from './auth.types'; import { EmailVerificationService } from './emailVerification.service'; import { databaseService } from '../database'; import { logger } from '../logging'; const emailVerificationService = new EmailVerificationService(); const authService = { async register(email: string, password: string, username?: string) { try { logger.auth.register(`Attempting to register user: ${email}`); // Check if user already exists const existingUser = await databaseService.findUserByEmail(email); if (existingUser) { logger.auth.error( `Registration failed: User already exists with email ${email}` ); throw new Error('User already exists'); } // Create user with password const user = await databaseService.createUserWithPassword( email, password, username ); logger.auth.register(`User registered successfully: ${user._id}`, { userId: user._id, email, }); // Generate and send verification token (in production) const verificationToken = await emailVerificationService.generateVerificationToken( user as AuthenticatedUser ); return { user, verificationToken }; } catch (error) { if (error.message.includes('already exists')) { throw new Error('User already exists'); } throw error; } }, async login(input: { email: string; password: string }) { logger.auth.login(`Login attempt for: ${input.email}`); // Find user by email const user = await databaseService.findUserByEmail(input.email); if (!user) { logger.auth.error(`User not found for email: ${input.email}`); throw new Error('User not found'); } logger.auth.login('User found', { email: user.email, hasPassword: !!user.password, role: user.role, status: user.status, emailVerified: user.emailVerified, }); // Check if user has a password (email-based account) if (!user.password) { logger.auth.error('No password found - OAuth account'); throw new Error( 'This account was created with OAuth. Please use Google or GitHub to sign in.' ); } // Check if email is verified if (!user.emailVerified) { throw new Error('Email verification required'); } // Simple password verification (in production, use bcrypt) logger.auth.login('Comparing passwords', { inputPassword: input.password, storedPassword: user.password, match: user.password === input.password, }); if (user.password !== input.password) { logger.auth.error('Password mismatch'); throw new Error('Invalid credentials'); } logger.auth.login(`Login successful for: ${user.email}`); // Return mock tokens for frontend compatibility return { user, accessToken: 'mock_access_token_' + Date.now(), refreshToken: 'mock_refresh_token_' + Date.now(), }; }, async loginWithOAuth( provider: 'google' | 'github', userData: { email: string; username: string; avatar?: string } ) { try { // Try to find existing user by email let user = await databaseService.findUserByEmail(userData.email); if (!user) { // Create new user from OAuth data user = await databaseService.createUserFromOAuth( userData.email, userData.username, provider ); } // Generate access tokens return { user, accessToken: `oauth_${provider}_token_` + Date.now(), refreshToken: `oauth_${provider}_refresh_` + Date.now(), }; } catch (error) { throw new Error(`OAuth login failed: ${error.message}`); } }, async verifyEmail(token: string) { const user = await emailVerificationService.validateVerificationToken(token); if (!user) { throw new Error('Invalid or expired verification token'); } await emailVerificationService.markEmailVerified(user); return user; }, async changePassword( userId: string, currentPassword: string, newPassword: string ) { // Get user by ID const user = await databaseService.getUserById(userId); if (!user) { logger.auth.error( `Update user profile failed: User not found for ID ${userId}` ); throw new Error('User not found'); } // Check if user has a password (not OAuth user) if (!user.password) { throw new Error('Cannot change password for OAuth accounts'); } // Verify current password if (user.password !== currentPassword) { throw new Error('Current password is incorrect'); } // Validate new password if (newPassword.length < 6) { throw new Error('New password must be at least 6 characters long'); } // Update user with new password (this should be hashed before calling) const updatedUser = await databaseService.updateUser({ ...user, password: newPassword, }); return { user: updatedUser, message: 'Password changed successfully', }; }, async requestPasswordReset(email: string) { const user = await databaseService.findUserByEmail(email); if (!user) { // Don't reveal if email exists or not for security return { message: 'If an account with this email exists, a password reset link has been sent.', }; } if (!user.password) { throw new Error('Cannot reset password for OAuth accounts'); } // Generate reset token (similar to verification token) const resetToken = uuidv4().replace(/-/g, ''); const expiresAt = new Date(); expiresAt.setHours(expiresAt.getHours() + 1); // 1 hour expiry // Store reset token (in production, save to database) const resetTokens = JSON.parse( localStorage.getItem('password_reset_tokens') || '[]' ); resetTokens.push({ userId: user._id, email: user.email, token: resetToken, expiresAt, }); localStorage.setItem('password_reset_tokens', JSON.stringify(resetTokens)); // Send reset email const emailSent = await emailVerificationService.sendPasswordResetEmail( user.email!, resetToken ); return { message: 'If an account with this email exists, a password reset link has been sent.', emailSent, }; }, async resetPassword(token: string, newPassword: string) { // Get reset tokens const resetTokens = JSON.parse( localStorage.getItem('password_reset_tokens') || '[]' ); const resetToken = resetTokens.find( (t: { token: string; userId: string; email: string; expiresAt: Date }) => t.token === token ); if (!resetToken) { throw new Error('Invalid or expired reset token'); } // Check if token is expired if (new Date() > new Date(resetToken.expiresAt)) { throw new Error('Reset token has expired'); } // Validate new password if (newPassword.length < 6) { throw new Error('Password must be at least 6 characters long'); } // Get user by ID first const user = await databaseService.getUserById(resetToken.userId); if (!user) { throw new Error('User not found'); } // Update user with new password (this should be hashed before calling) const updatedUser = await databaseService.updateUser({ ...user, password: newPassword, }); // Remove used token const filteredTokens = resetTokens.filter( (t: { token: string; userId: string; email: string; expiresAt: Date }) => t.token !== token ); localStorage.setItem( 'password_reset_tokens', JSON.stringify(filteredTokens) ); return { user: updatedUser, message: 'Password reset successfully', }; }, }; export { authService }; export default authService;