style: Apply rustfmt with stable-only config
Some checks failed
CI Pipeline / Clippy Lints (push) Failing after 0s
CI Pipeline / Rust Unit Tests (push) Failing after 0s
CI Pipeline / Rust Format Check (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 0s
CI Pipeline / Security Audit (push) Failing after 3s
CI Pipeline / Build .deb & Release (push) Has been skipped
Some checks failed
CI Pipeline / Clippy Lints (push) Failing after 0s
CI Pipeline / Rust Unit Tests (push) Failing after 0s
CI Pipeline / Rust Format Check (push) Successful in 4s
CI Pipeline / Frontend Lint & Type Check (push) Failing after 0s
CI Pipeline / Security Audit (push) Failing after 3s
CI Pipeline / Build .deb & Release (push) Has been skipped
- Fixed rustfmt.toml to only use stable options (removed nightly-only) - Applied cargo fmt --all to fix formatting violations - Stable options: edition=2021, max_width=100, reorder_imports/modules, match_block_trailing_comma
This commit is contained in:
@ -91,10 +91,7 @@ pub fn issue_access_token(
|
||||
}
|
||||
|
||||
/// Validate and decode an access token using the Ed25519 public key PEM.
|
||||
pub fn validate_access_token(
|
||||
token: &str,
|
||||
verify_key_pem: &str,
|
||||
) -> Result<AccessClaims, JwtError> {
|
||||
pub fn validate_access_token(token: &str, verify_key_pem: &str) -> Result<AccessClaims, JwtError> {
|
||||
let key = DecodingKey::from_ed_pem(verify_key_pem.as_bytes())
|
||||
.map_err(|e| JwtError::KeyLoad(e.to_string()))?;
|
||||
|
||||
@ -115,14 +112,12 @@ pub fn validate_access_token(
|
||||
|
||||
/// Load the Ed25519 signing key from a PEM file path.
|
||||
pub fn load_signing_key(path: &str) -> Result<String, JwtError> {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|e| JwtError::KeyLoad(format!("Cannot read {path}: {e}")))
|
||||
std::fs::read_to_string(path).map_err(|e| JwtError::KeyLoad(format!("Cannot read {path}: {e}")))
|
||||
}
|
||||
|
||||
/// Load the Ed25519 verification (public) key from a PEM file path.
|
||||
pub fn load_verify_key(path: &str) -> Result<String, JwtError> {
|
||||
std::fs::read_to_string(path)
|
||||
.map_err(|e| JwtError::KeyLoad(format!("Cannot read {path}: {e}")))
|
||||
std::fs::read_to_string(path).map_err(|e| JwtError::KeyLoad(format!("Cannot read {path}: {e}")))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -66,9 +66,9 @@ fn build_totp(username: &str, secret_base32: &str) -> Result<TOTP, TotpError> {
|
||||
// new(issuer, account_name, algorithm, digits, skew, step, secret)
|
||||
TOTP::new(
|
||||
Algorithm::SHA1,
|
||||
6, // digits
|
||||
1, // skew
|
||||
30, // step (seconds)
|
||||
6, // digits
|
||||
1, // skew
|
||||
30, // step (seconds)
|
||||
secret_bytes,
|
||||
Some(ISSUER.to_string()),
|
||||
username.to_string(),
|
||||
|
||||
@ -7,17 +7,15 @@
|
||||
//! - Parallelism: 1
|
||||
|
||||
use argon2::{
|
||||
password_hash::{
|
||||
rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString,
|
||||
},
|
||||
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
||||
Argon2, Params, Version,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
/// Argon2id parameters per spec.
|
||||
const M_COST: u32 = 65536; // 64 MiB
|
||||
const T_COST: u32 = 3; // 3 iterations
|
||||
const P_COST: u32 = 1; // 1 thread
|
||||
const T_COST: u32 = 3; // 3 iterations
|
||||
const P_COST: u32 = 1; // 1 thread
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PasswordError {
|
||||
@ -33,7 +31,11 @@ pub enum PasswordError {
|
||||
fn argon2() -> Result<Argon2<'static>, PasswordError> {
|
||||
let params = Params::new(M_COST, T_COST, P_COST, None)
|
||||
.map_err(|e| PasswordError::HashError(e.to_string()))?;
|
||||
Ok(Argon2::new(argon2::Algorithm::Argon2id, Version::V0x13, params))
|
||||
Ok(Argon2::new(
|
||||
argon2::Algorithm::Argon2id,
|
||||
Version::V0x13,
|
||||
params,
|
||||
))
|
||||
}
|
||||
|
||||
/// Hash a plaintext password using Argon2id with a random salt.
|
||||
@ -54,8 +56,7 @@ pub fn hash_password(password: &str) -> Result<String, PasswordError> {
|
||||
///
|
||||
/// Returns `Ok(true)` if the password matches, `Ok(false)` if not.
|
||||
pub fn verify_password(password: &str, hash: &str) -> Result<bool, PasswordError> {
|
||||
let parsed_hash =
|
||||
PasswordHash::new(hash).map_err(|_| PasswordError::InvalidHash)?;
|
||||
let parsed_hash = PasswordHash::new(hash).map_err(|_| PasswordError::InvalidHash)?;
|
||||
|
||||
let argon2 = argon2()?;
|
||||
|
||||
|
||||
@ -143,11 +143,7 @@ fn forbidden(message: &str) -> Response {
|
||||
///
|
||||
/// Inserts `AuthUser` into request extensions on success.
|
||||
/// Rejects with 401 if token is missing/invalid, 403 if IP is blocked.
|
||||
pub async fn require_auth(
|
||||
auth_config: Arc<AuthConfig>,
|
||||
mut req: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
pub async fn require_auth(auth_config: Arc<AuthConfig>, mut req: Request, next: Next) -> Response {
|
||||
// IP whitelist check
|
||||
if let Some(ip) = extract_remote_ip(req.headers()) {
|
||||
if !auth_config.is_ip_allowed(&ip) {
|
||||
@ -168,7 +164,7 @@ pub async fn require_auth(
|
||||
Err(e) => {
|
||||
tracing::debug!(error = %e, "JWT validation failed");
|
||||
return unauthorized("Invalid token");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let role = match UserRole::from_str(&claims.role) {
|
||||
|
||||
@ -123,12 +123,10 @@ pub async fn rotate(
|
||||
}
|
||||
|
||||
// Revoke old token
|
||||
sqlx::query(
|
||||
"UPDATE refresh_tokens SET revoked = TRUE, revoked_at = NOW() WHERE id = $1",
|
||||
)
|
||||
.bind(stored.id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
sqlx::query("UPDATE refresh_tokens SET revoked = TRUE, revoked_at = NOW() WHERE id = $1")
|
||||
.bind(stored.id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
// Issue new token
|
||||
let new_token = issue(pool, stored.user_id, user_agent, ip_address).await?;
|
||||
@ -138,10 +136,7 @@ pub async fn rotate(
|
||||
}
|
||||
|
||||
/// Revoke all refresh tokens for a user (force logout).
|
||||
pub async fn revoke_all_for_user(
|
||||
pool: &PgPool,
|
||||
user_id: Uuid,
|
||||
) -> Result<u64, RefreshError> {
|
||||
pub async fn revoke_all_for_user(pool: &PgPool, user_id: Uuid) -> Result<u64, RefreshError> {
|
||||
let result = sqlx::query(
|
||||
"UPDATE refresh_tokens SET revoked = TRUE, revoked_at = NOW() WHERE user_id = $1 AND revoked = FALSE",
|
||||
)
|
||||
@ -154,10 +149,7 @@ pub async fn revoke_all_for_user(
|
||||
}
|
||||
|
||||
/// Revoke a single refresh token by its raw value.
|
||||
pub async fn revoke(
|
||||
pool: &PgPool,
|
||||
raw_token: &str,
|
||||
) -> Result<(), RefreshError> {
|
||||
pub async fn revoke(pool: &PgPool, raw_token: &str) -> Result<(), RefreshError> {
|
||||
let hash = hex::encode(Sha256::digest(raw_token.as_bytes()));
|
||||
|
||||
sqlx::query(
|
||||
|
||||
@ -122,13 +122,12 @@ pub async fn login(
|
||||
// Prevent timing-based username enumeration
|
||||
let _ = password::hash_password("dummy-timing-fill");
|
||||
return Err(SessionError::InvalidCredentials);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 2. Verify password
|
||||
let hash = user.password_hash.as_deref().unwrap_or("");
|
||||
let valid = password::verify_password(&req.password, hash)
|
||||
.unwrap_or(false);
|
||||
let valid = password::verify_password(&req.password, hash).unwrap_or(false);
|
||||
|
||||
if !valid {
|
||||
tracing::warn!(username = %req.username, "Login failed: invalid password");
|
||||
@ -146,8 +145,7 @@ pub async fn login(
|
||||
let code = req.totp_code.as_deref().ok_or(SessionError::MfaRequired)?;
|
||||
let secret = user.totp_secret.as_deref().unwrap_or("");
|
||||
|
||||
let mfa_ok = mfa_totp::verify_code(&user.username, secret, code)
|
||||
.unwrap_or(false);
|
||||
let mfa_ok = mfa_totp::verify_code(&user.username, secret, code).unwrap_or(false);
|
||||
|
||||
if !mfa_ok {
|
||||
tracing::warn!(username = %req.username, "Login failed: invalid MFA code");
|
||||
@ -246,19 +244,13 @@ pub async fn refresh_session(
|
||||
}
|
||||
|
||||
/// Logout: revoke the current refresh token.
|
||||
pub async fn logout(
|
||||
pool: &PgPool,
|
||||
raw_refresh_token: &str,
|
||||
) -> Result<(), SessionError> {
|
||||
pub async fn logout(pool: &PgPool, raw_refresh_token: &str) -> Result<(), SessionError> {
|
||||
refresh::revoke(pool, raw_refresh_token).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Force-logout: revoke all refresh tokens for a user.
|
||||
pub async fn force_logout(
|
||||
pool: &PgPool,
|
||||
user_id: Uuid,
|
||||
) -> Result<u64, SessionError> {
|
||||
pub async fn force_logout(pool: &PgPool, user_id: Uuid) -> Result<u64, SessionError> {
|
||||
let count = refresh::revoke_all_for_user(pool, user_id).await?;
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user