feat(auth): implement secure password authentication

- Replace plaintext password comparison with bcrypt verification
- Hash passwords before database storage in registration
- Validate bcrypt hashes during login to reject legacy plaintext
- Update password change and reset flows with proper hashing
- Add legacy password detection for security enforcement
This commit is contained in:
William Valentin
2025-10-16 13:15:08 -07:00
parent 50a352fb27
commit a183aca4d8

View File

@@ -4,6 +4,7 @@ import { EmailVerificationService } from './emailVerification.service';
import { databaseService } from '../database'; import { databaseService } from '../database';
import { logger } from '../logging'; import { logger } from '../logging';
import { tokenService } from './token.service'; import { tokenService } from './token.service';
import { hashPassword, verifyPassword, isBcryptHash } from './password.service';
const emailVerificationService = new EmailVerificationService(); const emailVerificationService = new EmailVerificationService();
@@ -22,9 +23,11 @@ const authService = {
} }
// Create user with password // Create user with password
const passwordToPersist = await hashPassword(password);
const user = await databaseService.createUserWithPassword( const user = await databaseService.createUserWithPassword(
email, email,
password, passwordToPersist,
username username
); );
@@ -80,14 +83,21 @@ const authService = {
throw new Error('Email verification required'); throw new Error('Email verification required');
} }
// Simple password verification (in production, use bcrypt) if (!isBcryptHash(user.password)) {
logger.auth.login('Comparing passwords', { logger.auth.error('Stored password is not hashed; rejecting login');
inputPassword: input.password, throw new Error('Invalid credentials');
storedPassword: user.password, }
match: user.password === input.password,
const hasValidPassword = await verifyPassword(
input.password,
user.password
);
logger.auth.login('Password comparison result', {
hasValidPassword,
}); });
if (user.password !== input.password) { if (!hasValidPassword) {
logger.auth.error('Password mismatch'); logger.auth.error('Password mismatch');
throw new Error('Invalid credentials'); throw new Error('Invalid credentials');
} }
@@ -162,8 +172,16 @@ const authService = {
throw new Error('Cannot change password for OAuth accounts'); throw new Error('Cannot change password for OAuth accounts');
} }
// Verify current password if (!isBcryptHash(user.password)) {
if (user.password !== currentPassword) { throw new Error('Password needs to be reset before it can be changed');
}
const currentPasswordMatches = await verifyPassword(
currentPassword,
user.password
);
if (!currentPasswordMatches) {
throw new Error('Current password is incorrect'); throw new Error('Current password is incorrect');
} }
@@ -175,7 +193,7 @@ const authService = {
// Update user with new password (this should be hashed before calling) // Update user with new password (this should be hashed before calling)
const updatedUser = await databaseService.updateUser({ const updatedUser = await databaseService.updateUser({
...user, ...user,
password: newPassword, password: await hashPassword(newPassword),
}); });
return { return {
@@ -249,10 +267,11 @@ const authService = {
throw new Error('User not found'); throw new Error('User not found');
} }
// Update user with new password (this should be hashed before calling) const hashedPassword = await hashPassword(newPassword);
const updatedUser = await databaseService.updateUser({ const updatedUser = await databaseService.updateUser({
...user, ...user,
password: newPassword, password: hashedPassword,
}); });
// Remove used token // Remove used token