Fix #7 -- backend now uses and stores hashed password
This commit is contained in:
parent
a03497a552
commit
7280672726
@ -99,6 +99,7 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
|||||||
const [passwordValidation, setPasswordValidation] = useState<{ isValid: boolean; issues: string[] }>({ isValid: true, issues: [] });
|
const [passwordValidation, setPasswordValidation] = useState<{ isValid: boolean; issues: string[] }>({ isValid: true, issues: [] });
|
||||||
const name = (user?.userType === 'candidate') ? user.username : user?.email || '';
|
const name = (user?.userType === 'candidate') ? user.username : user?.email || '';
|
||||||
const [location, setLocation] = useState<Partial<Location>>({});
|
const [location, setLocation] = useState<Partial<Location>>({});
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
// Password visibility states
|
// Password visibility states
|
||||||
const [showLoginPassword, setShowLoginPassword] = useState(false);
|
const [showLoginPassword, setShowLoginPassword] = useState(false);
|
||||||
@ -169,6 +170,23 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
|||||||
}
|
}
|
||||||
}, [phone, registerForm]);
|
}, [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) => {
|
const handleLogin = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -177,6 +195,7 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
|||||||
const success = await login(loginForm);
|
const success = await login(loginForm);
|
||||||
if (success) {
|
if (success) {
|
||||||
setSuccess('Login successful!');
|
setSuccess('Login successful!');
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -239,8 +258,8 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
|||||||
if (registerForm.userType === 'candidate') {
|
if (registerForm.userType === 'candidate') {
|
||||||
window.location.href = '/candidate/dashboard';
|
window.location.href = '/candidate/dashboard';
|
||||||
}
|
}
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||||
@ -383,9 +402,9 @@ const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{error && (
|
{errorMessage && (
|
||||||
<Alert severity="error" sx={{ mb: 2 }}>
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
{error}
|
{errorMessage}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -4,10 +4,11 @@ Secure Authentication Utilities
|
|||||||
Provides password hashing, verification, and security features
|
Provides password hashing, verification, and security features
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import traceback
|
||||||
import bcrypt # type: ignore
|
import bcrypt # type: ignore
|
||||||
import secrets
|
import secrets
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone, timedelta
|
||||||
from typing import Dict, Any, Optional, Tuple
|
from typing import Dict, Any, Optional, Tuple
|
||||||
from pydantic import BaseModel # type: ignore
|
from pydantic import BaseModel # type: ignore
|
||||||
|
|
||||||
@ -156,8 +157,14 @@ class AuthenticationManager:
|
|||||||
|
|
||||||
# Check if account is locked
|
# Check if account is locked
|
||||||
if auth_data.locked_until and auth_data.locked_until > datetime.now(timezone.utc):
|
if auth_data.locked_until and auth_data.locked_until > datetime.now(timezone.utc):
|
||||||
logger.warning(f"🔒 Account locked for user {login}")
|
time_until_unlock = auth_data.locked_until - datetime.now(timezone.utc)
|
||||||
return False, None, "Account is temporarily locked due to too many failed attempts"
|
# 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
|
# Verify password
|
||||||
if not self.password_security.verify_password(password, auth_data.password_hash):
|
if not self.password_security.verify_password(password, auth_data.password_hash):
|
||||||
@ -166,7 +173,6 @@ class AuthenticationManager:
|
|||||||
|
|
||||||
# Lock account if too many attempts
|
# Lock account if too many attempts
|
||||||
if auth_data.login_attempts >= SecurityConfig.MAX_LOGIN_ATTEMPTS:
|
if auth_data.login_attempts >= SecurityConfig.MAX_LOGIN_ATTEMPTS:
|
||||||
from datetime import timedelta
|
|
||||||
auth_data.locked_until = datetime.now(timezone.utc) + timedelta(
|
auth_data.locked_until = datetime.now(timezone.utc) + timedelta(
|
||||||
minutes=SecurityConfig.ACCOUNT_LOCKOUT_DURATION_MINUTES
|
minutes=SecurityConfig.ACCOUNT_LOCKOUT_DURATION_MINUTES
|
||||||
)
|
)
|
||||||
@ -188,6 +194,7 @@ class AuthenticationManager:
|
|||||||
return True, user_data, None
|
return True, user_data, None
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
logger.error(f"❌ Authentication error for user {login}: {e}")
|
logger.error(f"❌ Authentication error for user {login}: {e}")
|
||||||
return False, None, "Authentication failed"
|
return False, None, "Authentication failed"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user