Fix #7 -- backend now uses and stores hashed password

This commit is contained in:
James Ketr 2025-05-30 12:19:28 -07:00
parent a03497a552
commit 7280672726
2 changed files with 33 additions and 7 deletions

View File

@ -99,6 +99,7 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const [passwordValidation, setPasswordValidation] = useState<{ isValid: boolean; issues: string[] }>({ isValid: true, issues: [] });
const name = (user?.userType === 'candidate') ? user.username : user?.email || '';
const [location, setLocation] = useState<Partial<Location>>({});
const [errorMessage, setErrorMessage] = useState<string | null>(null);
// Password visibility states
const [showLoginPassword, setShowLoginPassword] = useState(false);
@ -169,6 +170,23 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
}
}, [phone, registerForm]);
useEffect(() => {
if (!loading || !error) {
return;
}
if (loading && error) {
/* Remove 'HTTP .*: ' from error string */
const jsonStr = error.replace(/^[^{]*/, '');
const data = JSON.parse(jsonStr);
setErrorMessage(data.error.message);
setSnack(data.error.message, "error");
setTimeout(() => {
setErrorMessage(null);
setLoading(false);
}, 3000);
}
}, [error, loading]);
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
@ -177,6 +195,7 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const success = await login(loginForm);
if (success) {
setSuccess('Login successful!');
setLoading(false);
}
};
@ -239,8 +258,8 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
if (registerForm.userType === 'candidate') {
window.location.href = '/candidate/dashboard';
}
setLoading(false);
}
setLoading(false);
};
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
@ -383,9 +402,9 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
</Tabs>
</Box>
{error && (
{errorMessage && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
{errorMessage}
</Alert>
)}

View File

@ -4,10 +4,11 @@ Secure Authentication Utilities
Provides password hashing, verification, and security features
"""
import traceback
import bcrypt # type: ignore
import secrets
import logging
from datetime import datetime, timezone
from datetime import datetime, timezone, timedelta
from typing import Dict, Any, Optional, Tuple
from pydantic import BaseModel # type: ignore
@ -156,8 +157,14 @@ class AuthenticationManager:
# Check if account is locked
if auth_data.locked_until and auth_data.locked_until > datetime.now(timezone.utc):
logger.warning(f"🔒 Account locked for user {login}")
return False, None, "Account is temporarily locked due to too many failed attempts"
time_until_unlock = auth_data.locked_until - datetime.now(timezone.utc)
# Convert time_until_unlock to minutes:seconds format
total_seconds = time_until_unlock.total_seconds()
minutes = int(total_seconds // 60)
seconds = int(total_seconds % 60)
time_until_unlock_str = f"{minutes}m {seconds}s"
logger.warning(f"🔒 Account is locked for user {login} for another {time_until_unlock_str}.")
return False, None, f"Account is temporarily locked due to too many failed attempts. Retry after {time_until_unlock_str}"
# Verify password
if not self.password_security.verify_password(password, auth_data.password_hash):
@ -166,7 +173,6 @@ class AuthenticationManager:
# Lock account if too many attempts
if auth_data.login_attempts >= SecurityConfig.MAX_LOGIN_ATTEMPTS:
from datetime import timedelta
auth_data.locked_until = datetime.now(timezone.utc) + timedelta(
minutes=SecurityConfig.ACCOUNT_LOCKOUT_DURATION_MINUTES
)
@ -188,6 +194,7 @@ class AuthenticationManager:
return True, user_data, None
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"❌ Authentication error for user {login}: {e}")
return False, None, "Authentication failed"