Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Password Reset Flow

This document explains how the password reset pipeline in the auth module is implemented and what a frontend client must do to integrate with it. The flow is split into three RPCs: one to request a password reset email with a magic link, one to validate the reset token from the magic link, and one to finalize the password reset. The sections below describe the data contracts, validation rules, and expected UX behavior at each step so that UI engineers can wire the screens without re-reading the Rust implementation.

Step 1. Request a password reset email

Use the UserAuth.SendPasswordResetEmail RPC with the user’s email address.

FieldNotes
emailRaw email string entered by the user.

Backend behavior

  • The service first validates the email format. Requests with invalid email addresses return INVALID_EMAIL immediately.
  • If the email doesn’t map to any existing account with email login, the call returns NOT_FOUND so the UI can inform the user appropriately.
  • When rate limiting is triggered (same address requested again before resend_interval elapses, default 30 seconds), the server returns TOO_FREQUENT. The frontend should display a message asking the user to wait before requesting another reset email.
  • For a valid request, the service creates a password reset link record containing a 32-character auth_key, queues an email via RabbitMQ, and responds with SENT:
#[derive(Debug, Clone, PartialEq, Eq, sqlx::FromRow)]
pub struct EmailVerifyLink {
    pub id: i64,
    pub email: String,
    pub auth_key: String,
    pub send_at: PrimitiveDateTime,
    pub reason: EmailVerifyReason,
    pub user_id: Option<Uuid>,
    pub is_unused: bool,
}

const AUTH_KEY_LENGTH: usize = 32;

Frontend expectations

  • Present a single email field and an action button. On submission, call SendPasswordResetEmail and branch on the enum:
    • PWD_RESET_EMAIL_RESULT_SENT: Show success UI (“Check your inbox for a password reset link”) and inform the user to click the magic link in their email.
    • PWD_RESET_EMAIL_RESULT_INVALID_EMAIL: Highlight the field with a validation error.
    • PWD_RESET_EMAIL_RESULT_NOT_FOUND: Inform the user that no account exists with this email address.
    • PWD_RESET_EMAIL_RESULT_TOO_FREQUENT: Display a message asking the user to wait before requesting another reset email (default 30 seconds).
  • Add a visible countdown using the configured resend_interval (default 30 seconds) before enabling a “Resend” button.
  • The email template contains a clickable magic link that embeds the auth_key in the URL. When clicked, it should route the user directly to the password reset page (Step 2/3) with the token automatically populated from URL query parameters.

Use the UserAuth.CheckPasswordResetToken RPC to validate the token before allowing the user to enter a new password. This step is optional but provides better user experience by catching expired or invalid tokens early.

FieldNotes
tokenThe 32-character auth_key extracted from the URL query parameter

Backend behavior

  • The service looks up the token in the database and validates:
    • The token exists
    • The token is for password reset (not registration or other purposes)
    • The token hasn’t been used yet
    • The token hasn’t expired (default expiry is 5 minutes, controlled by link_expire_after)
  • If all validations pass, it returns valid: true and the expire_at timestamp (Unix timestamp in seconds).
  • If any validation fails, it returns valid: false with no expiration time.
pub struct CheckPasswordResetToken {
    pub token: String,
}

pub enum CheckPasswordResetTokenResult {
    Valid(PrimitiveDateTime),
    Invalid,
}

Frontend expectations

  • Extract the auth_key from the URL query parameters when the user lands on the password reset page via the magic link.
  • Call this RPC with the extracted token to validate it before showing the password reset form.
  • If valid is true:
    • Display the password reset form
    • Optionally show a countdown timer based on the expire_at timestamp to inform the user how much time they have left
  • If valid is false:
    • Display an error message that the reset link is invalid or has expired
    • Offer a link to request a new password reset email
  • This validation step helps prevent the user from filling out a new password only to discover the token is invalid when they submit.

Step 3. Collect the new password

The magic link is time-limited (default 5 minutes). After that, reset attempts fail with INVALID_LINK.

On the frontend, the password reset page should:

  1. Use the token (auth_key) extracted from the URL query parameters (Step 2).
  2. Collect a new password that satisfies the platform’s password policy.

Step 4. Finalize password reset

Call UserAuth.ResetPassword with the collected data:

pub struct ResetPassword {
    pub auth_key: String,
    pub new_password: String,
}
FieldRequiredNotes
auth_keyToken from Step 1. Each token is single-use.
new_passwordPlain password; hashing happens server-side.

Response handling

ResetPasswordReply can return three results based on the service outcome:

pub enum ResetPasswordResult {
    Success,
    InvalidLink,
    AccountNotFound,
}
  • RESET_PASSWORD_RESULT_SUCCESS: Password was successfully reset. All active sessions for this user have been terminated for security. Direct the user to the login page with a success message.
  • RESET_PASSWORD_RESULT_INVALID_LINK: Token was unknown, already used, expired, or not for password reset. Show an error and offer to resend a reset email.
  • RESET_PASSWORD_RESULT_ACCOUNT_NOT_FOUND: The account associated with this reset token no longer exists. This is rare but can happen if an account was deleted between steps. Display an appropriate error message.

Security considerations

When the password reset succeeds, the backend automatically:

  1. Hashes the new password using the configured password hashing algorithm
  2. Updates the password in the database
  3. Terminates all active sessions for this user account to prevent any potentially compromised sessions from remaining active
  4. Emits a PasswordResetEvent for audit logging and downstream processing

Users will need to log in again with their new password after a successful reset.

Retrying after failures

If the user encounters an INVALID_LINK error, require them to restart from Step 1. Once a token has been consumed or expired, it cannot be retried. The single-use nature of tokens prevents replay attacks.

UI checklist

  • Provide separate screens for email submission and password reset completion.
  • When the user lands on the password reset page via magic link, automatically extract the auth_key from the URL query parameters—no manual token entry is needed.
  • Show a visible countdown for link expiration (5 minutes by default, based on link_expire_after config).
  • Use CheckPasswordResetToken before showing the password reset form to provide early feedback about token validity and show an expiration countdown.
  • After successful password reset, clearly direct the user to the login screen and inform them that all their sessions have been terminated for security.
  • Consider implementing the token validation (Step 2) to improve user experience by catching invalid links before the user enters a new password.

Typical UX flow

A recommended user experience flow:

  1. Forgot Password Page: User enters email → call SendPasswordResetEmail
  2. Check Email Page: Show success message and instructions to click the magic link in their email
  3. Reset Password Page (accessed by clicking the magic link in the email):
    • Extract auth_key from URL query parameters
    • On page load: call CheckPasswordResetToken with the extracted token
    • If valid: show password form with expiration timer
    • If invalid: show error and link back to Step 1
  4. Submit New Password: call ResetPassword with the token from URL and new password
  5. Success Page: Inform user their password was reset and all sessions were terminated → redirect to login

This flow mirrors the backend implementation and should keep the frontend in sync with the server-side invariants without re-reading the Rust code every time changes are made.