Skip to content
SafePass

SafePass OPAQUE password change v1

An account password change requires both a valid, authenticated API session and a fresh OPAQUE proof of the current password. The API never receives either password in plaintext. This protocol changes the server's OPAQUE password file; it does not re-encrypt vault records or change the recovery key/envelope.

Protocol

All routes below require a current bearer access token. The account is taken from the token's sub claim; clients cannot select another account in the request body.

  1. POST /v1/account/password-change/reauth/start accepts a canonical Base64URL OPAQUE credential request and returns the login session ID and OPAQUE credential response. The server state is encrypted at rest and expires after three minutes.
  2. POST /v1/account/password-change/reauth/finish accepts the session ID and the client's OPAQUE finalization. A successful proof returns a random 256-bit password-change ticket that expires after two minutes. The database stores only the ticket's SHA-256 hash. The login session is purpose-bound so the normal login endpoint cannot exchange it for a regular session.
  3. POST /v1/account/password-change/registration/start accepts the ticket and a new-password OPAQUE registration request. It returns the registration response only while the ticket is valid for the authenticated account.
  4. POST /v1/account/password-change/finish accepts the ticket and the registration upload. In one PostgreSQL transaction the API consumes the ticket, replaces the OPAQUE password file, consumes other password-change tickets, revokes all existing refresh-token families and removes pending login states. The response contains a new access/refresh-token pair. A ticket cannot be replayed, used by another account, or used after expiry.

The account-security routes have an additional per-account rate limit of ten requests per minute. Invalid or expired tickets return a generic authorization failure. A failed proof or registration does not replace the existing credential.

Client responsibilities

  • Keep OPAQUE states, passwords, tickets, registration uploads, and tokens in memory only for the duration required by their protocol. Do not log them.
  • Verify the pinned server key through the existing OPAQUE client before sending a finalization message.
  • Locally rewrap the vault key under the new password and atomically persist the new local envelope. This client-side step is separate from the API credential update; encrypted record revisions remain unchanged.
  • Store the returned refresh token only in the SQLCipher account link and keep the returned access token only in memory. Replace the prior refresh token after the server confirms completion.
  • If the ticket expires, is replayed, or registration fails, restart with a fresh current-password proof. A network failure after the server commits may require a new login with the new password.

Revocation immediately invalidates access JWTs with sid for the affected sessions. Legacy JWTs without sid can remain valid until their 15-minute lifetime ends. See device session lifecycle. The API does not persist plaintext passwords, password-change tickets, registration uploads, or vault contents.

Documentation