# Wave 8B4 — Unified Password Ownership Validation

Date: 2026-08-04

## Scope

Wave 8B4 moves the existing AccountIdentity password and persisted-token
invalidation implementation into the Accounts vertical module. It is an
ownership-only refactor. Routes, middleware order, validators, controllers,
response envelopes, i18n, schemas, Passport/JWT behavior, API documentation,
and dashboard UI remain unchanged.

## Canonical ownership

- Application orchestration and transaction boundary:
  `src/modules/accounts/application/account-password.service.js`
- AccountIdentity reads and saves:
  `src/modules/accounts/infrastructure/account-password.repository.js`
- Persisted UserToken revocation:
  `src/modules/accounts/infrastructure/account-token-session.repository.js`
- Stable password/error/auth-state constants:
  `src/modules/accounts/domain/account-password.constants.js`
- Pure owner, recovery, password-mutation, and invalidation policy:
  `src/modules/accounts/domain/account-password.policy.js`
- Public boundary: `src/modules/accounts/index.js`

`src/services/accountPasswordService.js` remains a logic-free compatibility
adapter. It exposes the same canonical singleton, service class, error class,
and error helper identities as the Accounts public entry.

## Frozen behavior

### Legacy Client and Provider

Profiles without `accountIdentity` still return the existing legacy fallback
signal. Their current callers continue to own the existing profile password,
reset, hashing, and token behavior. The canonical service creates no identity,
copies no password between profiles, performs no auto-link, starts no
transaction for that fallback, and mutates no legacy profile.

### AccountIdentity-backed profiles

Both Client and Provider modes resolve the same linked AccountIdentity and
compare against its single password. Successful change/reset/admin-reset:

1. verifies the current password or one-time recovery state;
2. assigns the new plaintext password to the AccountIdentity document so the
   unchanged model pre-save hook applies the existing HMAC-SHA256/CRYPTO_HASH
   policy;
3. increments `tokenVersion` once and records the existing
   `passwordChangedAt` value;
4. clears OTP/reset state and restores the sign-in auth flow;
5. saves the identity and revokes UserToken rows for the identity and every
   linked Client/Provider/request id in one existing MongoDB transaction.

Roles, active mode, profile links, profile password mirrors, unrelated tokens,
and all other accounts remain unchanged.

## Reset and failure safety

- Forgot-password OTP and reset-token hashes remain one-way and hidden.
- Reset eligibility, expiry, purpose, and one-time consumption use the existing
  pure `authFlow.rules` helpers.
- Wrong current password and invalid reset token perform zero writes.
- Identity-save failure commits neither password metadata nor token deletion.
- Token-revocation failure rolls back the queued identity save.
- Sessions close in `finally` on success and failure.
- Blocked, deleted, missing, mismatched, or ambiguous identities retain their
  exact existing `AccountPasswordError` codes, status names, and i18n keys.

## Token validation boundary

This wave does not sign JWTs or alter Passport. The existing issuer still puts
AccountIdentity `tokenVersion` into account tokens, and the existing resolver
still rejects stale versions and tokens issued before `passwordChangedAt`.
Persisted `UserToken` rows continue to be the Passport session existence gate.
The focused D7 tests prove an old account token fails after the version changes
and a newly issued version resolves.

## Security and compatibility evidence

- Compatibility and architecture purity:
  `test/accountPasswordArchitectureWave8B4.test.js`
- Unified password, recovery, token revocation, rollback, and legacy fallback:
  `test/accountPasswordOwnership.test.js`
- Old/new account-token rejection and reissue:
  `test/accountIdentityTokenResolution.test.js`
- Legacy HMAC/reset behavior and response sanitization:
  `test/authFlow.test.js`, `test/authService.test.js`, and
  `test/authValidation.test.js`
- Frozen hashes cover all seven identity/profile/token schemas, password routes,
  controllers, validators, Passport/token runtime, OpenAPI/Postman, Waves
  8B1–8B3, and the Auction schema.

No password, hash, OTP, reset token/hash, bearer token, `tokenVersion`, raw
AccountIdentity document, or secret is returned or logged by the canonical
implementation.

## Final gate results

- Focused Auth/Accounts suite: **239/239 passed**.
- Full `npm test`: **1133/1133 passed**.
- Changed/untracked JavaScript `node --check`: **411/411 passed**.
- `git diff --check`: passed.
- Frozen pre-audit SHA-256 comparison: **36/36 passed**, including
  identity/profile/token schemas, password routes/controllers/validators,
  Passport/token runtime, OpenAPI/Postman, Waves 8B1–8B3, and Auction.
- Dirty-worktree accountability: **3/3 passed**.
- Generated Postman metadata: clean; UI-lab screenshots: ignored/untracked.
- Focused secret-pattern and sensitive-output/log scans: **0 matches**.

This wave performs no migration, backfill, auto-link, schema change, route or
middleware change, validator change, UI change, Swagger/Postman change,
Passport/JWT rewrite, hashing change, signup/role-add/approval/switch-mode
change, or Auction change.
