fixes and new options

This commit is contained in:
Mateusz Gruszczyński
2026-07-28 10:53:36 +02:00
parent c8d441fa76
commit 176e7e4554
13 changed files with 109 additions and 21 deletions
+15 -3
View File
@@ -1,5 +1,5 @@
use chrono::Utc;
use tracing::{debug, info};
use tracing::{debug, info, warn};
use crate::{queries, state::SharedState};
@@ -172,12 +172,20 @@ pub async fn authenticate(
}
fn directory_nickname(display_name: &str, email: &str, username: &str) -> String {
let words: Vec<&str> = display_name.split_whitespace().filter(|v| !v.is_empty()).collect();
let words: Vec<&str> = display_name
.split_whitespace()
.filter(|v| !v.is_empty())
.collect();
let candidate = if words.len() >= 2 {
let first = words[0].chars().next().unwrap_or('u');
format!("{}.{}", first, words[words.len() - 1])
} else {
email.split('@').next().filter(|v| !v.is_empty()).unwrap_or(username).to_owned()
email
.split('@')
.next()
.filter(|v| !v.is_empty())
.unwrap_or(username)
.to_owned()
};
candidate.to_lowercase()
}
@@ -241,6 +249,10 @@ pub(super) async fn login(
})?
.ok_or_else(|| AuthError::unauthorized("Invalid organization login or password."))?;
let user = provision_ldap_user(state, identity).await?;
if user.is_active == 0 {
warn!(user_id = user.id, "LDAP login rejected: inactive account");
return Err(AuthError::forbidden("This account is inactive."));
}
let session = create_session(state, &user).await?;
info!(user_id = user.id, nickname = %user.nickname, "LDAP login successful");
Ok(session)
+4
View File
@@ -17,6 +17,10 @@ pub(super) async fn login(
let user = find_user_by_email(state, &email)
.await?
.ok_or_else(|| AuthError::unauthorized("Invalid e-mail address or password."))?;
if user.is_active == 0 {
warn!(user_id = user.id, "login rejected: inactive account");
return Err(AuthError::forbidden("This account is inactive."));
}
if !verify_password(&user.password_hash, password) {
warn!(user_id = user.id, "login rejected: invalid password");
return Err(AuthError::unauthorized(
+44 -3
View File
@@ -38,6 +38,7 @@ pub struct User {
pub email: String,
pub password_hash: String,
pub confirmed_at: Option<String>,
pub is_active: i64,
}
#[derive(Deserialize)]
@@ -123,6 +124,7 @@ impl<'r> sqlx::FromRow<'r, AnyRow> for User {
email: crate::row_decode::text(row, "email")?,
password_hash: crate::row_decode::text(row, "password_hash")?,
confirmed_at: crate::row_decode::optional_text(row, "confirmed_at")?,
is_active: row.try_get("is_active")?,
})
}
}
@@ -827,12 +829,51 @@ pub async fn confirm_account_action(
.map_err(AuthError::database)?;
"E-mail address changed."
} else if action == "delete" {
sqlx::query(queries::get(state.db.kind(), queries::AUTH_DELETE_USER))
.bind(user_id)
let original_nickname: Option<String> = sqlx::query_scalar(queries::get(
state.db.kind(),
queries::AUTH_NICKNAME_BY_ID,
))
.bind(user_id)
.fetch_optional(&mut *tx)
.await
.map_err(AuthError::database)?;
let deleted_nickname = format!("Deleted user #{user_id}");
if let Some(original_nickname) = original_nickname {
sqlx::query(queries::get(
state.db.kind(),
queries::AUTH_ANONYMIZE_NOTE_CREATORS,
))
.bind(&deleted_nickname)
.bind(original_nickname)
.execute(&mut *tx)
.await
.map_err(AuthError::database)?;
"Account deleted."
}
let deleted_email = format!("deleted-user-{user_id}@invalid.local");
let deleted_password = hash_password(&random_token())?;
sqlx::query(queries::get(
state.db.kind(),
queries::AUTH_DELETE_SESSIONS_BY_USER,
))
.bind(user_id)
.execute(&mut *tx)
.await
.map_err(AuthError::database)?;
sqlx::query(queries::get(
state.db.kind(),
queries::AUTH_ANONYMIZE_USER,
))
.bind(&deleted_nickname)
.bind(normalize(&deleted_nickname))
.bind(&deleted_email)
.bind(normalize(&deleted_email))
.bind(deleted_password)
.bind(now.to_rfc3339())
.bind(user_id)
.execute(&mut *tx)
.await
.map_err(AuthError::database)?;
"Account deleted. Content has been preserved under an anonymized owner."
} else {
return Err(AuthError::bad_request("Unknown account action."));
};