Browse Source

refactor(scim): batch user lookup and clarify scim_api_key

Add User::find_by_uuids for a single batched query, replacing the per-member
User::find_by_uuid loop in the SCIM Users list handler. Correct the
ScimApiKey::save comment to describe the actual upsert idiom and document the
enabled column as reserved for a future disable endpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/7443/head
David Croft 3 days ago
parent
commit
bcbbf370ed
  1. 8
      src/db/models/scim_api_key.rs
  2. 6
      src/db/models/user.rs

8
src/db/models/scim_api_key.rs

@ -18,6 +18,9 @@ pub struct ScimApiKey {
// sha256 hex digest of the token secret. The plaintext secret is shown once // sha256 hex digest of the token secret. The plaintext secret is shown once
// at generation time and never stored or logged. // at generation time and never stored or logged.
pub key_hash: String, pub key_hash: String,
// Reserved for a future disable-without-deleting management endpoint.
// Nothing sets this to false yet, but the guard already honors it via
// find_active_by_org (asserted in the uniform-401 authz matrix test).
pub enabled: bool, pub enabled: bool,
pub created_at: NaiveDateTime, pub created_at: NaiveDateTime,
pub revision_date: NaiveDateTime, pub revision_date: NaiveDateTime,
@ -47,8 +50,9 @@ impl ScimApiKey {
} }
pub async fn save(&self, conn: &DbConn) -> EmptyResult { pub async fn save(&self, conn: &DbConn) -> EmptyResult {
// Rotation replaces the row wholesale (delete + insert), so a plain // Standard Vaultwarden upsert idiom: replace_into with an update
// insert with an update fallback covers every backend identically. // fallback on sqlite/mysql, ON CONFLICT upsert on postgresql.
// Rotation itself goes through delete_all_by_organization + save.
db_run! { conn: db_run! { conn:
sqlite, mysql { sqlite, mysql {
match diesel::replace_into(scim_api_key::table) match diesel::replace_into(scim_api_key::table)

6
src/db/models/user.rs

@ -394,6 +394,12 @@ impl User {
conn.run(move |conn| users::table.filter(users::uuid.eq(uuid)).first::<Self>(conn).ok()).await conn.run(move |conn| users::table.filter(users::uuid.eq(uuid)).first::<Self>(conn).ok()).await
} }
pub async fn find_by_uuids(uuids: &[UserId], conn: &DbConn) -> Vec<Self> {
let uuids = uuids.to_vec();
conn.run(move |conn| users::table.filter(users::uuid.eq_any(uuids)).load::<Self>(conn).unwrap_or_default())
.await
}
pub async fn find_by_device_for_email2fa(device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> { pub async fn find_by_device_for_email2fa(device_uuid: &DeviceId, conn: &DbConn) -> Option<Self> {
if let Some(user_uuid) = conn if let Some(user_uuid) = conn
.run(move |conn| { .run(move |conn| {

Loading…
Cancel
Save