feat(v1.1.8): password reset flow (migration 0029 + revokes sessions)

migration 0029: app_user_password_resets table — same shape as
verification (token_hash PK, app_id + user_id FKs, expires_at,
consumed_at). One-shot via atomic UPDATE WHERE consumed_at IS NULL.
Default TTL 1h (shorter than verification's 48h — reset tokens are
higher-risk).

UsersServiceImpl gains password_resets: Arc<dyn AppUserPasswordResetRepo>.

users::request_password_reset(email, opts):
  * Returns Ok(()) regardless of whether the email matched — no
    existence-leak signal in script-land (per brief).
  * Email-not-configured surfaces as NotConfigured so scripts can
    fall back to a synchronous reset path. Other email errors are
    silently swallowed and logged server-side; surfacing them would
    leak which addresses produced a "real send attempted" signal vs
    a no-op.

users::complete_password_reset(token, new_password):
  * Atomically consumes the token, updates the Argon2id hash, and
    revokes EVERY active session for that user (anyone with a stale
    token shouldn't be able to ride out the reset). Emits
    users::password_changed.
  * Returns the user on success, () on bad/expired/already-used.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-06 12:06:47 +02:00
parent c855739559
commit 45242e2d92
5 changed files with 237 additions and 11 deletions

View File

@@ -17,6 +17,7 @@ pub mod app_members_api;
pub mod app_members_repo;
pub mod app_repo;
pub mod app_secrets_repo;
pub mod app_user_password_reset_repo;
pub mod app_user_repo;
pub mod app_user_session_repo;
pub mod app_user_verification_repo;
@@ -88,6 +89,9 @@ pub use app_user_session_repo::{
AppUserSessionLookup, AppUserSessionRepository, AppUserSessionRepositoryError,
PostgresAppUserSessionRepository,
};
pub use app_user_password_reset_repo::{
AppUserPasswordResetRepo, AppUserPasswordResetRepoError, PostgresAppUserPasswordResetRepo,
};
pub use app_user_verification_repo::{
AppUserVerificationRepo, AppUserVerificationRepoError, PostgresAppUserVerificationRepo,
};