payload-auth/plans at main · payload-auth/payload-auth · GitHub
Skip to content

Latest commit

 

History

History

plans

README.md

Implementation Plans

Generated by the improve skill on 2026-07-29 at commit 90411f2 (standard-effort audit; all findings vetted against source before planning). Extended the same day with a full review of all open GitHub issues (18) and PRs (6) on payload-auth/payload-auth — plans 007–010 and the "GitHub reconciliation" section come from that pass. Execute in the order below unless dependencies say otherwise. Each executor: read the plan fully before starting, honor its STOP conditions, and update your row when done.

Selection note: this run executed non-interactively, so per the skill's default the top findings by leverage were planned. The full vetted backlog below lists everything else found; any backlog item can be promoted to a plan on request.

Execution order & status

Recommended order: 007 first (secrets leak — ship + release + rotation advisory), then 001 → 002, then the rest as staffing allows.

Plan Title Priority Effort Depends on Status
007 Strip secrets from admin serverProps (land PR #153) + release + rotation advisory P0 S — DONE (approved 2026-07-29; branch advisor/007-strip-secrets in worktree agent-a60fb8419788204d7 — awaiting user merge; merge + release + secret-rotation advisory ASAP)
001 Portable test infra (env-driven DB, compose, root scripts) P1 S — DONE (approved 2026-07-29; branch advisor/001-portable-test-infra in worktree agent-a18a995c7754a2309 — awaiting user merge; baseline suite 21/21 files green after pnpm build)
002 CI quality gate + frozen-lockfile release install P1 S 001 DONE (approved 2026-07-29; branch advisor/002-ci-quality-gate in worktree agent-aa5b7b1ebea1e5120, includes 001's commits — awaiting user merge)
010 nextCookies infinite form-state loop — read-only session lookup in auth strategy (#139) P1 S — DONE (approved 2026-07-29; branch advisor/010-disable-refresh-strategy in worktree agent-aaa737c8e74dbfbc2 — awaiting user merge)
003 March security punchlist (open redirect, invite expiry, cascade re-throw, api-key refs) P1 M 001 DONE (approved 2026-08-04; 6 commits in worktree agent-a3c0bb44a5921823c — suite 260 passed verified by reviewer. Note: api-key FK is referenceId post-BA-1.5, fix wired accordingly; migration expires pre-existing invite tokens immediately — release note)
004 Join/relationship normalization correctness in the adapter P1 M 001 DONE (approved 2026-08-04; branch advisor/004-join-normalization, merged into v3; 252-test suite green in worktree; 004×009 interaction specs verified green on v3)
008 Better Auth 1.6.x upgrade + implement missing adapter methods (consumeOne, #160) P1 M–L 001, 005 DONE (approved 2026-08-04; branch advisor/008-ba-1.6-compat, merged into v3. BA 1.6.25; consumeOne + incrementOne implemented — all 13 DBAdapter members covered; #124's feared team-join gap did NOT materialize on 1.6.25)
011 v3 integration fixes (BA 1.6 × session forward-join root cause + NaN hardening) P1 S 004, 008, 009 DONE (approved 2026-08-05; branch advisor/011-v3-integration-fixes, merged into v3. Root cause: BA 1.6 getAuthTables hardcodes session/account userId.references.model to literal "user"; patched to resolved slug in createTransform. convertWhereValue now throws descriptive errors instead of emitting params: NaN)
009 Scope role transforms to users collection (#112, #128, case preservation) P1 M 001 DONE (approved 2026-07-29; branch advisor/009-role-transform-scoping in worktree agent-a1c0aa400d60ab797 — awaiting user merge. #112 confirmed+fixed; #128 did NOT reproduce — read path already converts array→string; locked in with impersonate.test.ts. Release note needed: role casing now preserved)
005 Package manifest integrity (phantom/unused deps, exports, d.ts aliases, peer floors) P2 M — DONE (approved 2026-07-29; branch advisor/005-manifest-integrity in worktree agent-a80c703465c688271 — awaiting user merge; full suite verified green by reviewer)
006 2FA admin-panel lockout bugs P2 M — DONE (approved 2026-07-29; 5 commits in worktree agent-a8a3f7984e19ebd33 — awaiting user merge; full suite + 5 new OTP tests verified green by reviewer)

Status values: TODO | IN PROGRESS | DONE | BLOCKED (with one-line reason) | REJECTED (with one-line rationale)

Integration state (2026-08-05): all 11 plans are merged into the v3 branch, verified by a full-suite run on a from-scratch database (auth_test_v3). Scratch databases used during execution (auth_test_003/004/008/009/v3/v3fix) can be dropped once v3 merges. Release notes for the eventual publish must mention: (1) secret-rotation advisory (plan 007 — secrets were previously served in login-page HTML), (2) pre-existing admin invite tokens are expired immediately by the 003 migration, (3) role values are no longer lowercased (plan 009), (4) Better Auth peer floor is now effectively 1.6.x-tested (plan 008).

Dependency notes

  • 002 requires 001: the CI workflow runs the test suite, which is unrunnable off the author's machine until the DB connection is env-driven.
  • 003, 004, 008, 009 require 001 to verify (their regression tests need a runnable suite); the code changes themselves don't depend on it.
  • 008 and 005 both edit packages/payload-auth/package.json — land 005's manifest changes first or rebase 008 on it.
  • 010 must confirm the installed better-auth supports disableRefresh; if not, it sequences after 008.
  • 005 and 006 are independent; 005's full-suite verification is cheaper after 001.

GitHub reconciliation (all open issues & PRs as of 2026-07-29)

Every open item mapped to a disposition. "Close-with-release" = fixed on main but never published; cut a release, then comment + close.

Item Summary Disposition
PR #153 Secrets leaked into admin login HTML via serverProps Plan 007 (verified still unfixed on main; land it, release, advise secret rotation)
#139 nextCookies() infinite buildFormState loop Plan 010 (community root cause verified against better-auth-strategy.ts)
#160 txAdapter.consumeOne is not a function on BA ≥1.6 Plan 008 (adapter misses new DBAdapter methods; verified absent)
#112 Role array transform breaks member/invitation creation Plan 009 (verified: normalizeData role branch is model-agnostic)
#128 Impersonate throws .split is not a function (role array) Plan 009 (same root area; reproduce-first step included)
#124 Org reverse joins fail (members/invitations) Likely fixed on main (organizations.ts now has member/invitation/organizationRole join fields); plan 008 step 5 adds the get-full-organization e2e as closure evidence; residual team join gap checked there
#154 Released package still logs credentials from login form Fixed on main (90411f2) but no release cut — close-with-release; folds into plan 007's "release immediately" step
#138 Stripe cancelAt not cleared (null dropped) Fixed on main (transformInput now passes null; verified line ~398) — close-with-release; null-handling.test.ts already covers it
#148 getPayloadAuth return type loses plugin endpoint types Backlog (new finding, M effort): make options generic flow through (readonly roles arrays + generic PayloadAuthOptions); issue contains three viable approaches
#136 2FA users defaulted to TOTP; OTP email never sent Backlog, extend plan 006's scope later: verify view supports only TOTP; needs "send code by email" path driving otpOptions.sendOTP
#119 API keys don't authenticate Payload routes (403) Backlog investigate: strategy passes headers to getSession so enableSessionForAPIKeys should work — needs repro; check role shape on API-key mock session + P2-12 fix interaction
#123 findOne hangs on admin-invitations during signup (Cloudflare Workers) Backlog investigate (LOW confidence): likely connection starvation (adapter query inside request while Payload holds the connection — Workers/Hyperdrive single-connection); needs env repro
#130 / PR #131 Query-depth config option Maintainer decision — recommend NOT merging as-is: reintroduces the P1-13 cookie-bloat/data-shape problem the depth-0 contract fixed (see ADAPTER-04 backlog). If accepted, require join-style explicit population + cookie-cache exclusion. Respond on the issue with the collectionOverrides/local-API alternative
#121 Custom session / rolling refresh guidance Backlog (direction-adjacent): document the pattern; consider a strategy callback hook. Interacts with plan 010's read-only strategy note
#129 Reset-password page styling/customization Backlog (S): the view already uses MinimalTemplate; expose a className/override or document custom views
#157 / PR #158 open-api plugin docs page 404 / adds the page Fold into DOCS-01 docs overhaul; PR #158 mergeable meanwhile (maintainer review)
#122, #7, PR #156, PR #40 Docs 404s, "Better docs?", link fix, stale docs chore All fold into DOCS-01/DOCS-02 (docs are vendored Better Auth content; site deployment disabled in vercel.json). PR #156's one-line link fix is trivially mergeable
PR #147 Adds Vercel social provider Maintainer review: feature PR from a fork's main; check against current provider list and BA 1.6 provider API before merging (plan 008 may bitrot it)
#117 "How to donate?" Non-technical; add FUNDING.yml if desired

Vetted backlog (found, confirmed, not yet planned)

Vetted backlog (found, confirmed, not yet planned)

Highest-leverage first. IDs refer to the audit session; evidence was verified against 90411f2.

Security / correctness

  • PLUGIN-08 — users collection spreads ...existingUserCollection?.access LAST (plugin/lib/build-collections/users/index.ts:~256-267), letting a pre-existing users collection silently overwrite the plugin's access guards (same class as fixed P1-4, higher-value collection). S effort.
  • PLUGIN-05 — getSafeRedirect (plugin/payload/utils/get-safe-redirect.ts) is prefix-check based; encoded control chars (e.g. /%09/evil.com) survive and browsers normalize to protocol-relative URLs. Harden with new URL(value, base) origin check + control-char rejection + unit tests. S effort.
  • ADAPTER-11 — after-signup invite middleware deletes the token before assigning the role with no error handling/compensation; a failed role update burns the invitation and skips originalAfter. S–M effort; preserve delete-first single-use semantics (P0-4).
  • PLUGIN-03 — AdminButtons calls useMemo after an early return null (admin-buttons/index.tsx:22-34): conditional hook → React 19 crash of the edit view when id resolves. S.
  • PLUGIN-11 — admin-invite modal caches the generated link; switching role then Copy/Send distributes a token for the previously selected role. S.
  • ADAPTER-10 — normalizeData lowercases all role values on write; any camelCase configured role breaks select validation/admin checks. S, MED risk (existing data).
  • PLUGIN-18 — self-update access returns false when Payload evaluates permissions without id/data, making the admin UI render the account view read-only for non-admins; also strict === id comparison across idType. S, MED risk.
  • ADAPTER-08 — updateMany/deleteMany discard Payload's per-doc errors array; partial failure (e.g. "revoke all sessions") reports success. S.
  • PLUGIN-17 — refresh-token.ts looks up the dontRememberToken signed cookie without its __Secure- prefix, so "don't remember me" is ignored on HTTPS deployments. S.
  • PLUGIN-04 — optional username field rejects empty string (regex refine ignores required: false), blocking default-config admin signup when the field is left blank. S.
  • ADAPTER-12 — findMany single-ID fast path ignores offset/sortBy; findOne/update return undefined instead of null. S.
  • PLUGIN-09/10/13/14/15/16/19 — smaller admin-UI issues: silent passkey fetch/delete failures; dead loading state on social/passkey buttons; soft navigation after passkey/2FA success vs hard reload on password path; hydration-unsafe error query read; unsafe data.token deref + unmemoized auth client in signup; password policy not enforced client-side on reset/signup; token field keyed on hardcoded name. S each.
  • NEW-4 (from March review, still open) — small find-then-delete race window on invite consumption; NEW-7 — cookie token format unvalidated in after-logout.ts; NEW-9/NEW-10/NEW-11 — role-hierarchy gap, missing Zod validation, allowedFields TODO. Small items; NEW-6 (?? false no-op) is trivial.

Performance

  • ADAPTER-03 — forward joins are N+1: one sequential findByID per doc per join in findMany, plus per-doc flattenAllFields. Batch with one in-query per joined collection. M.
  • ADAPTER-07 — get-collection.ts field-name mapping helpers re-flatten the collection field tree on every call (~2N flattens per transformed doc); add per-slug memo like the existing flattenedFieldsCache. S.
  • ADAPTER-04 — reverse joins escalate the whole query to depth: 1, contradicting the documented depth-0 contract (ARCHITECTURE.md:829); non-requested relationships come back populated. Needs a design decision (collapse non-requested relationships) + doc update. M.

Tests (all blocked on 001 for practicality)

  • TEST-02 — no integration test authenticates a Payload request with a real BA session cookie (payload.auth() untested end-to-end).
  • TEST-03 — no e2e guard against self-role-escalation (overrideAccess: false write-path tests missing; existing admin update test bypasses access entirely).
  • TEST-05 — self-defeating assertions: empty-catch fixture creation in cascade-delete test, either/or access-control assertions, not.toBe(200).
  • TEST-06 — cascade delete + transactions verified only against mocks; test config sets transactionOptions: false.
  • TEST-09 — 12 BA plugins enabled in the test config, ~8 with zero runtime assertions (captureOTP harness paid for and unused — magic-link/email-OTP tests are near-free).
  • TEST-04 — dev-docs/TEST-PLAN.md promises 13 test files, 0 exist; inventory paths stale. Reconcile doc + land T7/T1/T9 first.
  • TEST-07 — 32 client .tsx files (3,737 lines), zero tests, zero component-test infra; cheapest slice is unit-testing get-safe-redirect.ts and shared/form/validation.ts.
  • TEST-08 — 4 test files bypass the shared Payload singleton (second PG pool); global truncation cleanup blocks parallelism.

Dependencies / DX / docs

  • NEW (found by plan-005 execution, CONFIRMED) — strict-Node-ESM consumers can't import("payload-auth"): barrel files use extensionless directory re-exports (src/index.ts → export * from "./better-auth"), which swc emits verbatim; Node's ESM resolver rejects them with ERR_UNSUPPORTED_DIR_IMPORT. Pre-existing (reproduced at 90411f2); only bundler-based consumers work today. Fix: add explicit /index.js-resolvable specifiers or an extension-rewriting build step across src/** barrels. S–M effort; promotes the DEPS-06 MED-confidence note to confirmed.
  • DEPS-04 — demo pins @payloadcms/* 3.67.0 (critical drizzle SQLi advisory) against hoisted payload 3.79.0; latest specifiers on radix packages; TS/types/zod versions inconsistent across workspaces. M.
  • DX-01/DX-02 — Biome and Prettier both configured with opposing styles (source matches Biome; pnpm format would rewrite the repo); Biome's 30-line linter config has enabled: false; prettier + its 3 plugins undeclared at root. Decide → delete one → enable linting → add scripts. S, MED risk (big mechanical diff).
  • DX-04 — root clean-all script deletes pnpm-lock.yaml, has broken find precedence, no -prune on node_modules, and unquoted xargs rm -rf (repo path contains spaces!). S — genuinely hazardous as written.
  • DX-05 — no CLAUDE.md/AGENTS.md; agents rediscover build/test topology each session and risk hand-editing generated-types.ts. S.
  • DX-03 — commitlint config imports packages declared nowhere; no local hook. S.
  • DX-10 — demo prepare script rewrites demo/package.json on every install (lockfile-drift driver). S.
  • DX-08 — stale duplicate vite.config.ts (vitest uses vitest.config.ts); delete. Trivial.
  • DEBT-01 — the two 900+-line adapter closure factories (adapter/index.ts, adapter/transform/index.ts) — the 5-hotfix join area is untestable in isolation. L; do after 004 lands.
  • DEBT-02 — two divergent model-key→slug resolvers (get-collection-slug.ts switch vs schema modelName), a documented drift hazard that has already caused bugs. M.
  • DEBT-03 — mixed @/ alias vs relative imports (158 vs 302) with the alias configured in 4 places. S codemod; do after 005 (tsc-alias interacts).
  • DEBT-05/06 — dead try-catch.ts/shared/index.ts; inverted Session/User type aliases in tests/dev/index.ts:280-283. S.
  • DOCS-01 — docs/ is 99 vendored Better Auth pages documenting a different product (installation page installs better-auth, not payload-auth); actively misleading. L (design decision: rewrite vs delete-and-link).
  • DOCS-02 — the npm package ships no README (files: ["dist"], no packages/payload-auth/README.md); npmjs.com page is blank. S–M, high visibility.
  • DOCS-03/04 — ARCHITECTURE.md drifted (wrong export path, phantom root commands, stale test inventory); CONTRIBUTING.md is a 2-line stub. S each.

Direction (maintainer strategy calls — see final audit report for trade-offs)

  • DIR-01 — replace vendored BA docs with plugin-native reference docs; immediate slice = the npm README (DOCS-02).
  • DIR-02 — ship a createPayloadAuthClient factory deriving client plugins + role typing from server options; the demo's hand-rolled client has a broken import and wrong role type today, proving the need.
  • DIR-03 — decide whether the billing/enterprise surface (stripe/polar/scim/sso in supportedBAPluginIds + collection builders, zero configurators/tests/docs) is supported or should be de-advertised.
  • DIR-04 — absorb the demo's ~1,475 lines of hand-rolled org/team/multi-session admin UI; smallest slice is the members.ts:50 role-selector TODO.

Findings considered and rejected

  • P1-11 (adapter transaction no-op), P2-4 (password verify via login()), P2-15 (cookie domain on delete), P3-11 (role string↔array bridge): documented known limitations / by-design — re-confirmed, not re-planned.
  • Depth-0 everywhere: deliberate data-shape contract per ARCHITECTURE.md; only the join-related deviation from it is a finding (ADAPTER-04, backlog).
  • @better-auth/api-key/sso/stripe/scim as devDeps: correctly scoped (used by the type-generation script) — not dead weight.
  • onlyBuiltDependencies duplicated in root package.json vs pnpm-workspace.yaml: inert cosmetic issue (pnpm 10 reads the workspace file).
  • P1-1 WHERE connector semantics and P3-4 like-on-MongoDB: confirmed NOT bugs (match official BA adapters / Payload abstracts operators).
  • Big-bang removal of the 152 as any casts: not worth doing as a project; adopt no-new-as any-in-adapter rule and fix opportunistically (DEBT-04).