Skip to content

Commit 97d2a03

Browse files
os-warrenclaude
andauthored
docs(plugin-auth): re-measure the version-stamped vendor attestations against the installed 1.7.1 (#10187)
The `^1.7.1` bump (#3002) moved the better-auth family past the versions 20 comment attestations in `plugin-auth/src` named, turning "verified against X" into claims nobody had checked. Each site below was re-measured against the INSTALLED packages (better-auth 1.7.1, @better-auth/sso 1.7.1, @better-auth/oauth-provider 1.7.1, @better-auth/core 1.7.1) on 2026-08-20 and re-stamped with the version, the date, and the shipped file:line it was read out of, so the next reader can falsify it with one grep instead of trusting it. Not a find-and-replace: 12 further hits are left exactly as they are, because they are deliberately historical (`admin-user-endpoints.ts:70`'s rc.2/stable `accountId` contrast, the `auth-schema-config.ts` rename narratives) or are already-corrected #10072 sites naming the stamp they superseded. Two sites needed more than a new number: - `objectql-adapter.ts` called the dependency a **prerelease**. 1.7.1 is stable, so that half of the reason is deleted rather than re-stamped; the half that still carries the argument (a vendor `dist` internal, invisible to any published type, not covering the raw-adapter path) is kept and re-anchored at the corrected lines `:127,146,602,615`. - `auth-manager.ts` cited `@better-auth/core/src/types/init-options.ts:946-971` — a `src/` path the package does not publish, so no reader could check it. Re-pointed at the shipped `dist/types/init-options.d.mts:835-857`. `auth-schema-config.ts`'s `ssoProvider` mapping carried a field-surface claim about `@better-auth/sso@1.6.20` that had never been measured. Measured now by resolving the model the way the adapter does over `getAuthTables({ plugins: [sso()] })`: all 7 declared fields (8 with `domainVerification`) resolve to columns `sys_sso_provider` declares. Recorded with the gap that makes it worth re-checking — this mapping has no parity gate, because `better-auth-schema-parity.test.ts` deliberately passes no `sso` plugin. Comment-only: no assertion, pin or gate input is touched. Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4d29450 commit 97d2a03

17 files changed

Lines changed: 184 additions & 68 deletions

packages/plugins/plugin-auth/src/adopt-membership.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@
3636
* ## Why the seam is HERE, at the better-auth → ObjectQL adapter
3737
*
3838
* The three hook seams the framework already owns on this route all run at the
39-
* wrong moment or with the wrong reach, verified against better-auth
40-
* `1.7.0-rc.2`:
39+
* wrong moment or with the wrong reach. Measured 2026-08-20 against the
40+
* installed better-auth `1.7.1`, whose
41+
* `dist/plugins/organization/routes/crud-invites.mjs` still calls
42+
* `adapter.createMember(...)` unconditionally at `:324` and, in the `.catch`
43+
* at `:332-339`, rolls the invitation back to `"pending"` and rethrows —
44+
* `beforeAcceptInvitation` fires ahead of all of it at `:280`:
4145
*
4246
* - `organizationHooks.beforeAcceptInvitation` fires *before* `createMember`
4347
* and can only throw or mutate. The one mutation that would make the insert

packages/plugins/plugin-auth/src/auth-manager.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,15 +1081,20 @@ export class AuthManager {
10811081
// one adds only the notice. So `sendChangeEmailConfirmation` stays
10821082
// absent above — and that is a measured decision, not an omission:
10831083
//
1084-
// `user.changeEmail` in better-auth 1.7.0-rc.2 declares EXACTLY three
1085-
// members (`@better-auth/core/src/types/init-options.ts:946-971`):
1086-
// `enabled`, `sendChangeEmailConfirmation`, and
1087-
// `updateEmailWithoutVerification`. There is NO notify-only hook, and
1088-
// `sendChangeEmailConfirmation` is not one: in `update-user.mjs:457`
1089-
// it becomes `canSendConfirmation`, and the branch at :495 RETURNS
1090-
// right after invoking it — the new address is never mailed until the
1091-
// old one clicks. Setting it is therefore structurally the approval
1092-
// gate the ruling refuses, not a way to notify.
1084+
// `user.changeEmail` declares EXACTLY three members: `enabled`,
1085+
// `sendChangeEmailConfirmation`, and `updateEmailWithoutVerification`.
1086+
// Measured 2026-08-20 against the installed
1087+
// `@better-auth/core@1.7.1`, in the file the package actually SHIPS —
1088+
// `dist/types/init-options.d.mts:835-857`. (The previous stamp cited
1089+
// `src/types/init-options.ts:946-971`; no `src/` is published, so
1090+
// that reference could not be checked by anyone reading it.)
1091+
// There is NO notify-only hook, and
1092+
// `sendChangeEmailConfirmation` is not one: in better-auth 1.7.1's
1093+
// `dist/api/routes/update-user.mjs:457` it becomes
1094+
// `canSendConfirmation`, and the branch opening at `:496` RETURNS at
1095+
// `:505` right after invoking it — the new address is never mailed
1096+
// until the old one clicks. Setting it is therefore structurally the
1097+
// approval gate the ruling refuses, not a way to notify.
10931098
//
10941099
// The notice is consequently sent by the framework, from the global
10951100
// `after` hook on `/change-email` (search `__osChangeEmailFrom`), using
@@ -3603,8 +3608,16 @@ export class AuthManager {
36033608
// [#7724] A subject-erasure request is ONE unit of work, and better-auth
36043609
// does not treat it as one: `internalAdapter.deleteUser` deletes the
36053610
// sessions, then the accounts, then the user, in three unrelated adapter
3606-
// calls with no transaction (verified in better-auth 1.7.0-rc.2 —
3607-
// `dist/db/internal-adapter.mjs` mentions no transaction at all). Anything
3611+
// calls with no transaction. Measured 2026-08-20 against the installed
3612+
// better-auth 1.7.1: `deleteUser` at `dist/db/internal-adapter.mjs:233-247`
3613+
// is three separate `deleteManyWithHooks` / `deleteWithHooks` calls
3614+
// (session `:235`, account `:239`, user `:243`), and the string
3615+
// "transaction" does not occur anywhere in that file — a zero result that
3616+
// means something because "deleteManyWithHooks" occurs 8 times in it, so
3617+
// the search does reach the text. (The session call is now conditional on
3618+
// `!secondaryStorage || storeSessionInDatabase`; ObjectStack wires no
3619+
// `secondaryStorage` — deliberately, see `session-tombstone.ts` — so all
3620+
// three fire here.) Anything
36083621
// that refuses the LAST of those three leaves the first two committed: the
36093622
// credential rows are gone, the `sys_user` row is not, and the deployment
36103623
// is left with an identity that still occupies the org roster and can no

packages/plugins/plugin-auth/src/auth-schema-config.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;
914914
* Each row is an external OIDC/SAML IdP this environment federates login to
915915
* (the relying-party side — ADR-0024's OPEN per-env SSO mechanism). The
916916
* protocol detail lives in JSON blobs (`oidcConfig` / `samlConfig`); the model
917-
* itself is thin. Mirrors @better-auth/sso@1.6.20's `BaseSSOProvider`.
917+
* itself is thin.
918918
*
919919
* | camelCase (better-auth) | snake_case (ObjectStack) |
920920
* |:------------------------|:-------------------------|
@@ -924,6 +924,30 @@ export const buildOidcProviderPluginSchema = buildOauthProviderPluginSchema;
924924
* | userId | user_id |
925925
* | organizationId | organization_id |
926926
* | issuer / domain | (same name — no remap) |
927+
* | domainVerified | domain_verified |
928+
*
929+
* ## Coverage, measured 2026-08-20 against `@better-auth/sso@1.7.1`
930+
*
931+
* The previous note here said only "Mirrors `@better-auth/sso@1.6.20`'s
932+
* `BaseSSOProvider`" — a field-surface claim about a version two minors behind
933+
* the installed one, which nobody had re-checked. Re-measured by resolving the
934+
* plugin's real model the way the adapter does (`field.fieldName ?? key`) over
935+
* `getAuthTables({ plugins: [sso()] }).ssoProvider.fields`:
936+
*
937+
* - `sso()` declares 7 fields — `issuer`, `oidcConfig`, `samlConfig`,
938+
* `userId`, `providerId`, `organizationId`, `domain` — exactly the members
939+
* of the shipped `BaseSSOProvider` type
940+
* (`dist/index-CZytzKv6.d.mts:189-197`).
941+
* - `sso({ domainVerification: { enabled: true } })` — the shape
942+
* `OS_SSO_DOMAIN_VERIFICATION` turns on — adds an 8th, `domainVerified`.
943+
* - Every one of those 8 resolves to a column `sys_sso_provider` declares.
944+
* Nothing in the map is orphaned, and nothing in the model is unmapped.
945+
*
946+
* ⚠️ Unlike the core models, this mapping has **no parity gate**:
947+
* `better-auth-schema-parity.test.ts` deliberately passes `getAuthTables()` no
948+
* `sso` plugin, so an upstream field added to `ssoProvider` would land here
949+
* silently. Until that changes, re-run the resolution above by hand when the
950+
* `@better-auth/sso` pin moves — the check is one `getAuthTables` call.
927951
*/
928952
export const AUTH_SSO_PROVIDER_SCHEMA = {
929953
modelName: 'sys_sso_provider',

packages/plugins/plugin-auth/src/change-email-delete-user-wiring.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,8 +504,10 @@ describe('#8019 — change-email notifies the previous address without gating on
504504

505505
it('keeps `sendChangeEmailConfirmation` OFF — the notice is not the gate in disguise', async () => {
506506
// ⛔ Ruling edge 1: #7735's 「策略按 better-auth 常规」 still governs the
507-
// CONFIRMATION option, and in better-auth 1.7.0-rc.2 that option is not a
508-
// notifier — `update-user.mjs` returns immediately after invoking it, so
507+
// CONFIRMATION option, and that option is not a notifier — measured
508+
// 2026-08-20 against the installed better-auth 1.7.1, whose
509+
// `dist/api/routes/update-user.mjs` opens the branch at `:496` and returns
510+
// `ctx.json({ status: true })` at `:505`, immediately after invoking it, so
509511
// the NEW address is never mailed until the OLD one clicks. Setting it
510512
// would silently convert this card's notification into the approval gate
511513
// the ruling refuses, and every assertion above would still pass. Read off

packages/plugins/plugin-auth/src/impersonation-bearer-rotation.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
* better-auth's `bearer()` plugin authenticates a request by OVERWRITING the
99
* request's session cookie with the bearer token (a before-hook calling
1010
* `setRequestCookie(headers, authCookies.sessionToken.name, decodedToken)` —
11-
* `dist/plugins/bearer/index.mjs`, verified in 1.7.0-rc.2). The admin plugin's
11+
* `dist/plugins/bearer/index.mjs:46` — measured 2026-08-20 against the
12+
* installed better-auth `1.7.1`). The admin plugin's
1213
* `POST /admin/impersonate-user` does the opposite: it mints an impersonation
1314
* session and hands it over as a *cookie* (`setSessionCookie`), parking the
1415
* admin's own session token in a signed `admin_session` cookie for the exit

packages/plugins/plugin-auth/src/managed-extension-fields.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,10 @@ const UNMAPPED_MANAGED_OBJECTS: Record<string, UnmappedManagedObject> = {
183183
reason:
184184
'Hand-rolled ObjectStack table, not a better-auth model at all. '
185185
+ '`packages/core/src/security/api-key.ts` mints and verifies the key and POST /api/v1/keys '
186-
+ 'writes the row, and better-auth 1.7.0-rc.2 ships NO apiKey plugin: there is no '
187-
+ '"better-auth/plugins/api-key" export subpath and "better-auth/plugins" exports no apiKey. '
186+
+ 'writes the row, and better-auth ships NO apiKey plugin — measured 2026-08-20 against '
187+
+ 'the installed 1.7.1: package.json declares no "./plugins/api-key" export subpath and '
188+
+ 'importing "better-auth/plugins" yields apiKey === undefined (bearer and admin are '
189+
+ 'functions in the same import, so the read is not a silent miss). '
188190
+ 'So no model exists to derive and no column on this table can change hands. '
189191
+ 'Pinned by the premise test at the bottom of this file (#7770).',
190192
noBetterAuthColumns: true,
@@ -915,9 +917,11 @@ describe('sys_api_key exemption premise (#7770)', () => {
915917
// This is the whole warrant for `noBetterAuthColumns: true` on
916918
// sys_api_key: the columns the issue worried about (`name`, `prefix`,
917919
// `key`, `userId`, `expiresAt`, `permissions`, `metadata`) belong to a
918-
// plugin that does not exist at the pinned version — better-auth 1.7.0-rc.2
919-
// publishes no `./plugins/api-key` subpath and `better-auth/plugins`
920-
// exports no `apiKey`.
920+
// plugin that does not exist at the installed version — measured
921+
// 2026-08-20 against better-auth 1.7.1, which publishes no
922+
// `./plugins/api-key` subpath and whose `better-auth/plugins` exports no
923+
// `apiKey`. The assertion below re-measures this on every run, so the
924+
// stamp is a reading aid and the test is the actual check.
921925
//
922926
// Going red here is CORRECT and is the point: a bump that (re)introduces
923927
// the plugin makes the exemption's premise expire BEFORE anyone can enable

packages/plugins/plugin-auth/src/member-role-canonical.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@
2222
* branches fails the extraction and reddens the suite, instead of silently
2323
* leaving a pin that verifies nothing.
2424
*
25-
* The three branches (better-auth 1.7.0-rc.2,
26-
* `dist/plugins/organization/routes/crud-members.mjs`):
25+
* The three branches, re-read 2026-08-20 out of the installed
26+
* better-auth 1.7.1, `dist/plugins/organization/routes/crud-members.mjs`
27+
* (the line numbers are an aid — the extraction below keys on the text, so a
28+
* shift moves them without breaking the pin, while a rewrite reddens it):
2729
*
28-
* 1. `removeMember` — `const roles = toBeRemovedMember.role.split(",");`
29-
* … `if (roles.includes(creatorRole))`
30+
* 1. `removeMember` — `const roles = toBeRemovedMember.role.split(",");` (`:193`)
31+
* … `if (roles.includes(creatorRole))` (`:195`)
3032
* 2. `updateMemberRole` — `const isUpdatingCreator =
31-
* toBeUpdatedMember.role.split(",").includes(creatorRole);`
32-
* 3. `organization/leave` — `if (member.role.split(",").includes(creatorRole))`
33+
* toBeUpdatedMember.role.split(",").includes(creatorRole);` (`:288`)
34+
* 3. `organization/leave` — `if (member.role.split(",").includes(creatorRole))` (`:420`)
3335
*/
3436

3537
import { describe, it, expect, beforeEach } from 'vitest';

packages/plugins/plugin-auth/src/member-role-canonical.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
* 1. the #5942 grade ladder (`orgRoleGrade` / `isOrgAdminGrade`,
1212
* `invitation-role-cap.ts`) — `split(',')` then `trim().toLowerCase()`;
1313
* 2. `mapMembershipRole` (`@objectstack/spec/identity`) — `trim().toLowerCase()`;
14-
* 3. **better-auth itself** — `better-auth@1.7.0-rc.2`,
14+
* 3. **better-auth itself** — measured 2026-08-20 against the installed
15+
* `better-auth@1.7.1`,
1516
* `dist/plugins/organization/routes/crud-members.mjs`, a raw
1617
* `role.split(",")` with NO trim and NO lower-casing, in three branches:
17-
* `removeMember`'s "only an owner may remove an owner", `updateMemberRole`'s
18-
* creator protection, and `organization/leave`'s last-owner count.
18+
* `removeMember`'s "only an owner may remove an owner" (`:193`),
19+
* `updateMemberRole`'s creator protection (`:288`), and
20+
* `organization/leave`'s last-owner count (`:420`).
1921
*
2022
* For a row stored as `Owner` (or `' owner'`), (1) and (2) say owner and (3)
2123
* says plain member. The vendor therefore skips its owner branch entirely and

packages/plugins/plugin-auth/src/objectql-adapter.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,20 @@ function normaliseComparand(value: unknown): unknown {
239239
*
240240
* It is deliberately NOT a redundant belt over better-auth's own lower-casing.
241241
* better-auth's `internalAdapter` does lower-case `user.email` on
242-
* `createUser` / `createOAuthUser` / `updateUser` / `updateUserByEmail`
243-
* (`better-auth@1.7.0-rc.2/dist/db/internal-adapter.mjs:120,139,594,607`), but
244-
* that is an *internal* of a **prerelease** dependency, invisible to any
242+
* `createOAuthUser` / `createUser` / `updateUser` / `updateUserByEmail` —
243+
* measured 2026-08-20 against the installed `better-auth@1.7.1`, at
244+
* `dist/db/internal-adapter.mjs:127,146,602,615`.
245+
*
246+
* ⚠️ Two things about that sentence changed under it, and only one of them was
247+
* the version number. The previous stamp read `1.7.0-rc.2/…:120,139,594,607`
248+
* and called the dependency a **prerelease**; `1.7.1` is a stable release, so
249+
* that half of the reason is simply dead and is not restamped — it is removed.
250+
* What survives is the half that still holds and still carries the argument:
251+
* the lower-casing is an *internal* of the vendor's `dist`, invisible to any
245252
* published type, and it does not cover the raw {@link createObjectQLAdapter}
246253
* path (hand-built calls that never pass through better-auth at all). The
247-
* invariant the read half depends on has to be owned where it is relied upon.
254+
* invariant the read half depends on has to be owned where it is relied upon —
255+
* which is why this function exists whether or not the vendor keeps doing it.
248256
*
249257
* Idempotent by construction, so a payload better-auth already normalised is
250258
* unchanged — which is why this adds no behaviour to any existing write.

packages/plugins/plugin-auth/src/register-sso-provider.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ describe('runRegisterSsoProviderFromForm (OIDC) — the emitted body must satisf
4848
// Regression pin for the end-to-end break where the bridge always emitted
4949
// `oidcConfig.mapping.id`, which `oidcMappingSchema` (a `z.strictObject` with
5050
// no `id` member since 1.7.0-rc.2) rejects outright — every OIDC registration
51-
// answered `400 [body.oidcConfig.mapping] Unrecognized key: "id"`.
51+
// answered `400 [body.oidcConfig.mapping] Unrecognized key: "id"`. "since
52+
// 1.7.0-rc.2" is provenance, not a stamp: the member has been absent from
53+
// every release since, the installed `@better-auth/sso@1.7.1` included
54+
// (`dist/index.mjs:1852`, re-read 2026-08-20).
5255
//
5356
// These cases drive the REAL `/sso/register` endpoint. `@better-auth/sso`
5457
// validates the request body BEFORE the endpoint's session gate, so an

0 commit comments

Comments
 (0)