Skip to content

Password Management

Security protocols for managing user and client credentials, including real-time change validation and security notifications.

Change Password Flow

Users and Clients can update their current password. This process is secured by requiring the existing password and enforcing complexity/difference rules.

  1. Request Change: The authenticated subject provides currentPassword and newPassword.
  2. Validation:
    • Schema level: newPassword MUST be different from currentPassword.
    • Business level: currentPassword MUST match the stored hash.
  3. Persistence: If valid, the new password is hashed (SHA-256) and the entity's passwordHash is updated.
  4. Notification: An integration event (PasswordChangedIntegrationEvent) is published.

Sequence Diagram

sequenceDiagram
    participant User as Authenticated Subject
    participant API as User/Client Auth Controller
    participant Bus as CommandBus
    participant Handler as ChangePasswordHandler
    participant Geo as Geolocation Service
    participant DB as Identity Database
    participant Pub as IdentityPublisher

    User->>API: POST /auth/user/change-password {current, new}
    API->>Bus: Execute ChangeUserPasswordCommand
    Bus->>Handler: execute()
    Handler->>DB: Fetch entity
    Handler->>Handler: Verify currentPassword matches hash
    Handler->>Geo: Resolve location via IP (IGeolocationPort)
    Handler->>DB: Save entity with new hashed password
    Handler->>Pub: publishPasswordChanged()
    Pub-->>User: Security notification email (via EventBus)
    Handler-->>API: Result.ok
    API-->>User: 200 OK

Security Notifications

Every time a password is changed successfully, a notification email is sent to the account owner. This is a critical security measure to alert owners of unauthorized changes.

Email Content Includes:

  • Device Info: Browser name and OS.
  • IP Address: The origin of the change request.
  • Geolocation: Physical location (resolved via Geolocation API) associated with the IP.
  • Timestamp: Exact time of the modification.
  • Next Steps: Advice on how to secure the account if the change was not authorized.

Scope

  • Users: Fully supported through UserAuthController.
  • Clients: Fully supported through ClientAuthController.

Unlike new-device login notifications (which are Users-only), Password Change notifications are sent to both Users and Clients.

Set Initial Password (Google-only accounts)

Users that registered or signed in via Google have passwordHash = null and cannot use the change-password flow because there is no current password to verify. The Set Initial Password flow lets these users add an email + password method without losing their Google link.

Endpoint

POST /identity/auth/user/set-initial-password

  • Body: { newPassword: string (min 8) } — validated by setInitialPasswordUserSchema.
  • Auth: requires an authenticated user session (@AuthUser()).
  • 2FA: protected by @RequiresTwoFactor(TwoFactorChallengePurpose.CHANGE_PASSWORD) — the same challenge gate as change-password.
  • Rate limit: 3 requests per 15 minutes per userId (matches change-password).

Handler rules

SetInitialUserPasswordHandler enforces the operation's invariants:

  • Returns IDENTITY.PASSWORD_ALREADY_SET (409) when the user already has a passwordHash — those users must use change-password.
  • Returns IDENTITY.GOOGLE_NOT_LINKED (403) when the user has no googleId — set-initial-password is only valid for Google-linked accounts.
  • Returns IDENTITY.USER_NOT_FOUND (404) when the user cannot be loaded.
  • Returns IDENTITY.SESSION_NOT_FOUND (404) when the current session is missing from active sessions.

On success, the handler hashes the new password, persists it, revokes every other active session for the user, rotates the current session's tokens, and publishes a PasswordChangedIntegrationEvent (same notification path as the change flow).

Detecting eligibility from the panel

The panel reads two derived booleans from GET /identity/user/current (userResponseSchema):

  • hasPassword: true when passwordHash !== null.
  • googleLinked: true when googleId !== null.

The panel's Security card switches into Set Initial Password mode when hasPassword === false && googleLinked === true. In that mode the form hides the Current Password field, shows an explanatory banner about the Google link, and renders a Confirm Password field to prevent typos that would lock the user out of password sign-in.

Raw fields passwordHash and googleId are NEVER exposed by the API. The mapper computes the boolean flags so the panel cannot accidentally rely on internal state.