import { v4 as uuidv4 } from 'uuid'; import { dbService } from '../../services/couchdb.factory'; import { AccountStatus } from './auth.constants'; import { User } from '../../types'; import { AuthenticatedUser } from './auth.types'; import { EmailVerificationService } from './emailVerification.service'; const emailVerificationService = new EmailVerificationService(); const authService = { async register(email: string, password: string, username?: string) { try { // Create user with password const user = await dbService.createUserWithPassword( email, password, username ); // 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('An account with this email already exists'); } throw error; } }, async login(input: { email: string; password: string }) { console.log('🔐 Login attempt for:', input.email); // Find user by email const user = await dbService.findUserByEmail(input.email); if (!user) { console.log('❌ User not found for email:', input.email); throw new Error('User not found'); } console.log('👤 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) { console.log('❌ No password found - OAuth account'); throw new Error( 'This account was created with OAuth. Please use Google or GitHub to sign in.' ); } // Simple password verification (in production, use bcrypt) console.log('🔍 Comparing passwords:', { inputPassword: input.password, storedPassword: user.password, match: user.password === input.password, }); if (user.password !== input.password) { console.log('❌ Password mismatch'); throw new Error('Invalid password'); } console.log('✅ 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 dbService.findUserByEmail(userData.email); if (!user) { // Create new user from OAuth data user = await dbService.createUserFromOAuth(userData); } // 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 dbService.getUserById(userId); if (!user) { 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 password const updatedUser = await dbService.changeUserPassword(userId, newPassword); return { user: updatedUser, message: 'Password changed successfully', }; }, async requestPasswordReset(email: string) { const user = await dbService.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: any) => 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'); } // Update password const updatedUser = await dbService.changeUserPassword( resetToken.userId, newPassword ); // Remove used token const filteredTokens = resetTokens.filter((t: any) => t.token !== token); localStorage.setItem( 'password_reset_tokens', JSON.stringify(filteredTokens) ); return { user: updatedUser, message: 'Password reset successfully', }; }, }; export { authService }; export default authService;