perf(auth): cached image-free user-flags lookup for per-request guards
Every WebDAV / CalDAV / CardDAV request paid one full-row user fetch in
require_internal_user_layer just to read `is_external` (and the NC Basic
Auth middleware repeated it right after its own cache hit). That SELECT
includes the `image` column — a data URI of up to 512 KiB — so a sync
client issuing hundreds of PROPFINDs per minute dragged hundreds of MB
of avatar bytes out of Postgres to evaluate a boolean.
- New `UserFlags { role, is_external, active }` + a repo query selecting
only those three columns (inherent method, mirroring `update_image`).
- `AuthApplicationService::get_user_flags`: moka cache, 30 s TTL,
10k capacity. `change_user_role` / `set_user_active` invalidate
eagerly, so admin changes still apply immediately; anything else is
visible within the TTL — preserving the documented "no token rotation
needed" semantics at a per-request cost of zero DB round-trips when
warm.
- `require_internal_user`, `require_admin_user` and the NC Basic Auth
external check now go through the flags lookup.
https://claude.ai/code/session_01Dp3oWon5GBMVn4j3QXZdgx
This commit is contained in:
@@ -5,7 +5,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::application::ports::auth_ports::UserStoragePort;
|
||||
use crate::common::errors::DomainError;
|
||||
use crate::domain::entities::user::{User, UserRole};
|
||||
use crate::domain::entities::user::{User, UserFlags, UserRole};
|
||||
use crate::domain::repositories::user_repository::{
|
||||
StorageStats, UserRepository, UserRepositoryError, UserRepositoryResult,
|
||||
};
|
||||
@@ -51,6 +51,40 @@ impl UserPgRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch only the authorization-relevant flags of a user. Not part of
|
||||
/// the `UserRepository` trait — called directly from
|
||||
/// `AuthApplicationService::get_user_flags`.
|
||||
///
|
||||
/// Deliberately selects three tiny columns instead of the full row:
|
||||
/// the full-row SELECT includes `image` (a data URI of up to 512 KiB),
|
||||
/// which per-request middleware guards were paying on every WebDAV /
|
||||
/// CalDAV / CardDAV request just to read `is_external` or `role`.
|
||||
pub async fn get_user_flags(&self, id: Uuid) -> UserRepositoryResult<UserFlags> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
SELECT role::text as role_text, is_external, active
|
||||
FROM auth.users
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(id)
|
||||
.fetch_one(&*self.pool)
|
||||
.await
|
||||
.map_err(Self::map_sqlx_error)?;
|
||||
|
||||
let role_str: Option<String> = row.try_get("role_text").unwrap_or(None);
|
||||
let role = match role_str.as_deref() {
|
||||
Some("admin") => UserRole::Admin,
|
||||
_ => UserRole::User,
|
||||
};
|
||||
|
||||
Ok(UserFlags {
|
||||
role,
|
||||
is_external: row.get("is_external"),
|
||||
active: row.get("active"),
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates a user's profile image (URL or data URI). Not part of the
|
||||
/// `UserRepository` trait — called directly from `AuthApplicationService`.
|
||||
pub async fn update_image(
|
||||
|
||||
Reference in New Issue
Block a user