Private
Public Access
1
0

feat: Phase 1 - user/password API extensions and auth route fix
Some checks failed
CI Pipeline / Rust Format Check (push) Failing after 5s
CI Pipeline / Clippy Lints (push) Successful in 46s
CI Pipeline / Rust Unit Tests (push) Successful in 1m1s
CI Pipeline / Security Audit (push) Successful in 5s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 10s
CI Pipeline / Build .deb & Release (push) Has been skipped

This commit is contained in:
2026-05-07 16:21:53 +00:00
parent 42392ed9c7
commit 0a70afbbe9
8 changed files with 352 additions and 9 deletions

View File

@ -24,6 +24,8 @@ pub enum SessionError {
InvalidCredentials,
#[error("Account is disabled")]
AccountDisabled,
#[error("Password reset required")]
PasswordResetRequired,
#[error("MFA required")]
MfaRequired,
#[error("Invalid MFA code")]
@ -75,6 +77,7 @@ struct DbUser {
totp_secret: Option<String>,
mfa_enabled: bool,
is_active: bool,
force_password_reset: bool,
}
/// Login request payload.
@ -107,7 +110,7 @@ pub async fn login(
let user: Option<DbUser> = sqlx::query_as(
r#"
SELECT id, username, display_name, role, auth_provider,
password_hash, totp_secret, mfa_enabled, is_active
password_hash, totp_secret, mfa_enabled, is_active, force_password_reset
FROM users
WHERE username = $1 AND auth_provider = 'local'
"#,
@ -141,6 +144,12 @@ pub async fn login(
return Err(SessionError::AccountDisabled);
}
// 3b. Check if password reset is required
if user.force_password_reset {
tracing::warn!(username = %req.username, "Login blocked: password reset required");
return Err(SessionError::PasswordResetRequired);
}
// 4. MFA check
if user.mfa_enabled {
let code = req.totp_code.as_deref().ok_or(SessionError::MfaRequired)?;
@ -207,7 +216,7 @@ pub async fn refresh_session(
let user: DbUser = sqlx::query_as(
r#"
SELECT id, username, display_name, role, auth_provider,
password_hash, totp_secret, mfa_enabled, is_active
password_hash, totp_secret, mfa_enabled, is_active, force_password_reset
FROM users WHERE id = $1
"#,
)