Skip to content

feat(plugin-auth): password history / no-reuse (ADR-0069 D1, P1) - #2371

Merged
os-zhuang merged 1 commit into
mainfrom
auth/adr-0069-pw-history
Jun 27, 2026
Merged

os-zhuang merged 1 commit into
mainfrom
auth/adr-0069-pw-history

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

ADR-0069 P1 (tracking #2281): D1 password history / no-reuse. Continues the password-policy work (after HIBP #2361 and complexity #2368), reusing the setting→before/after-hook pattern.

What

  • New auth setting password_history_count (0–24, default 0 = off).
  • On /change-password and /reset-password, a new password matching the current password or any of the last N hashes is rejected with PASSWORD_REUSE.
  • New bounded sys_account.previous_password_hashes column (JSON ring, system-managed, hidden) backs the check; before/after hooks capture the old hash and append it on success.

Reuses better-auth (no duplication)

Per a "don't reinvent" review of the installed better-auth 1.6.20: password history is not native, so it's custom — but the check reuses better-auth's native password.verify (no bespoke crypto), and the reset-flow user is resolved via the same reset-token verification lookup better-auth's own handler uses. Session revocation on reset stays on the native revokeSessionsOnPasswordReset.

ADR-0049

Default-off / additive (no upgrade behavior change); the setting ships with its enforcement.

Verification

  • Unit: 135 plugin-auth (parse / reuse-reject / ring-trim helpers with a stub verify + where-aware sys_account mock) + service-settings + platform-objects green; builds incl. strict DTS green.
  • Live dogfood (history_count=3): change P1→P2 ok; change back to P1 (historical) → 400 PASSWORD_REUSE; change P2→P2 (current) → 400 PASSWORD_REUSE; change to a fresh password ok; the ring column persists bounded real hashes.

ADR-0069 P1 status

✅ D1 HIBP (#2361) · ✅ D2 lockout + rate-limit (#2365) · ✅ D1 complexity (#2368) · ✅ D1 history (this PR). Remaining P1: D1 expiry + D3 enforced MFA — both need the session-validation gate (core-runtime, higher blast radius); planned as a focused follow-up.

🤖 Generated with Claude Code

Adds `password_history_count` (0-24, 0=off) auth setting. On /change-password
and /reset-password a new password matching the current password or any of the
last N hashes is rejected with PASSWORD_REUSE. A bounded
`sys_account.previous_password_hashes` JSON ring (system-managed, hidden) backs
the check, maintained by before/after hooks (capture old hash, append on
success).

Reuses better-auth's native `password.verify` (no bespoke crypto) and resolves
the reset-flow user via better-auth's own reset-token verification lookup.
Default-off / additive (no upgrade behavior change); ADR-0049 (enforcement ships
with the setting).

Verified live (dogfood, history_count=3): change P1->P2 ok; change back to P1
(historical) -> 400 PASSWORD_REUSE; change P2->P2 (current) -> 400
PASSWORD_REUSE; change to a fresh password ok; the ring column persists bounded
real hashes. Unit: 135 plugin-auth (incl. parse/reuse/ring helpers with a stub
verify + where-aware sys_account mock) + service-settings + platform-objects
green; builds incl. strict DTS green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 27, 2026 2:07pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jun 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/platform-objects, @objectstack/plugin-auth, packages/services.

15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/implementation-status.mdx (via @objectstack/plugin-auth)
  • content/docs/concepts/packages.mdx (via @objectstack/platform-objects, @objectstack/plugin-auth)
  • content/docs/concepts/setup-app.mdx (via @objectstack/platform-objects)
  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/auth-sso.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/authentication.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/kernel-services.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/packages.mdx (via @objectstack/platform-objects, @objectstack/plugin-auth, packages/services)
  • content/docs/guides/plugins.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/production-readiness.mdx (via @objectstack/plugin-auth)
  • content/docs/guides/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/guides/runtime-services/index.mdx (via packages/services)
  • content/docs/guides/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/protocol/objectos/i18n-standard.mdx (via packages/services)
  • content/docs/releases/v9.mdx (via @objectstack/plugin-auth)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

if (ctx?.path === '/change-password' || ctx?.path === '/reset-password') {
const stash = ctx?.context?.__osPwHistory;
if (stash?.userId) {
let succeeded = true;

This branch was successfully deployed

1 active deployment
Preview — 25d5e083 Deployed Jun 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants