feat(pass reset): request a pass change on 1st login

This commit is contained in:
Edouard Vanbelle
2026-08-04 21:05:08 +02:00
parent 6965855388
commit 2de476d281
13 changed files with 731 additions and 19 deletions
@@ -62,9 +62,12 @@ impl UserPgRepository {
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
SELECT role::text as role_text,
is_external,
active,
force_password_change_at_next_login
FROM auth.users
WHERE id = $1
"#,
)
.bind(id)
@@ -82,6 +85,7 @@ impl UserPgRepository {
role,
is_external: row.get("is_external"),
active: row.get("active"),
force_password_change: row.get("force_password_change_at_next_login"),
})
}
@@ -161,6 +165,30 @@ impl UserPgRepository {
Ok(())
}
/// Set `force_password_change_at_next_login = TRUE`. Used by
/// admin-initiated password reset when the OPAQUE substrate is NOT
/// wired. When it IS wired, callers should prefer
/// `OpaquePgRepository::clear_registration` which does the same
/// flag flip AND invalidates the OPAQUE envelope in one UPDATE
/// (see the port doc on `clear_registration` for the atomicity
/// contract). This method exists so OPAQUE-off deployments still
/// get the "admin's temp password prompts change on next login"
/// behaviour without having to depend on the OPAQUE code path.
pub async fn set_force_password_change(&self, id: Uuid) -> UserRepositoryResult<()> {
sqlx::query(
r#"
UPDATE auth.users
SET force_password_change_at_next_login = TRUE
WHERE id = $1
"#,
)
.bind(id)
.execute(&*self.pool)
.await
.map_err(Self::map_sqlx_error)?;
Ok(())
}
/// 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(