Files
rxminder/services/auth/auth.service.ts

249 lines
6.7 KiB
TypeScript

import { v4 as uuidv4 } from 'uuid';
import { dbService } from '../../services/couchdb.factory';
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.warn('🔐 Login attempt for:', input.email);
// Find user by email
const user = await dbService.findUserByEmail(input.email);
if (!user) {
console.warn('❌ User not found for email:', input.email);
throw new Error('User not found');
}
console.warn('👤 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.warn('❌ 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.warn('🔍 Comparing passwords:', {
inputPassword: input.password,
storedPassword: user.password,
match: user.password === input.password,
});
if (user.password !== input.password) {
console.warn('❌ Password mismatch');
throw new Error('Invalid password');
}
console.warn('✅ 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: { 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');
}
// Update password
const updatedUser = await dbService.changeUserPassword(
resetToken.userId,
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;