@ingram-tech/nk-auth 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,22 +112,51 @@ const db = await createTestDb({
112
112
  });
113
113
  ```
114
114
 
115
- **Upgrading better-auth** never means hand-writing a migration in your app: a
116
- schema-changing upgrade ships as a new file in nk-auth's chain, so you bump the
117
- dependency and your next migrate applies it. The chain is kept honest by
118
- `src/migrations.test.ts`, which diffs the applied chain against the schema the
119
- pinned `better-auth` asks for (`getAuthTables`) — a bump that adds a column fails
120
- that test until the delta is shipped as the next `000N_*.sql` (a maintainer task,
121
- done once in this package).
122
-
123
- One delta needs a word: `0002_better_auth_1_7` backfills the `account.issuer`
124
- column Better Auth 1.7 keys accounts on, with the exact value 1.7 writes per
125
- provider (`local:credential`, `https://accounts.google.com`,
126
- `local:oauth:<providerId>`, …). A provider whose issuer is per-tenant or
115
+ ## Upgrading Better Auth
116
+
117
+ `better-auth` and `@better-auth/*` move **only with nk-auth**, and a site pins
118
+ them to the exact version nk-auth's `peerDependencies` names (currently
119
+ `1.7.4`), never a range. `nk doctor` fails on a site whose pin differs, and on
120
+ a range. The reason is the schema: a Better Auth version that changes a table
121
+ ships here as the next `000N_*.sql` in the chain, with `src/migrations.test.ts`
122
+ diffing the applied chain against that version's `getAuthTables()`, so package
123
+ and schema cannot drift apart inside this repo. A site that bumps `better-auth`
124
+ on its own reopens the gap this closes.
125
+
126
+ Each bump then follows one order on every site, no exceptions:
127
+
128
+ 1. bump `@ingram-tech/nk-auth` and set `better-auth` to the version it names;
129
+ 2. run `db:migrate` against the **target** database (the auth chain first, as
130
+ the script above does);
131
+ 3. deploy that commit.
132
+
133
+ Migrating first is always safe: a chain delta is written so the version still
134
+ running tolerates it. Deploying first is what broke sign-in across the fleet
135
+ twice, so `createAuthHelpers` now checks, once per process on the first session
136
+ read, that the chain recorded in the database is not behind the installed
137
+ package, and throws `AuthChainNotAppliedError` naming the missing files instead
138
+ of letting sign-in fail somewhere downstream. The check runs when the
139
+ instance's `database` is a `pg` Pool and the database records the chain; a site
140
+ that owns Better Auth's tables in its own baseline never records it and is
141
+ skipped. Call `assertAuthChainApplied(pool)` from `instrumentation.ts` or a
142
+ health check to fail a deploy even earlier, and pass `chainCheck: false` to
143
+ the helpers to opt out.
144
+
145
+ **The 1.7 detour.** Better Auth 1.7.0 to 1.7.2 keyed accounts on a new
146
+ `account.issuer` column (NOT NULL, unique with `accountId`); nk-auth 0.16.0
147
+ shipped that as `0002_better_auth_1_7`, backfilling `issuer` with exactly the
148
+ value 1.7.2 writes per provider. A provider whose issuer is per-tenant or
127
149
  discovered at runtime (`microsoft`, `cognito`, `paybin`, the generic-oauth
128
- plugin) cannot be derived, so the migration refuses and names the `providerId`s;
129
- set `issuer` on those rows by hand and re-run. Nothing to do on a site that only
130
- has password and built-in social sign-in.
150
+ plugin) cannot be derived, so 0002 refuses and names the `providerId`s. Better
151
+ Auth 1.7.3 then reverted the whole change (accounts are keyed on
152
+ `(providerId, accountId)` again, and upstream has committed to keeping the
153
+ core schema fixed for the rest of v1), and 1.7.3+ never writes `issuer`, so
154
+ 0002's NOT NULL rejects every sign-up until it is relaxed. nk-auth 0.17.0 ships
155
+ `0003_better_auth_1_7_3`, which drops the index and the NOT NULL and leaves the
156
+ column nullable and unread; it is safe under 1.7.2 and 1.7.3+ alike. A 1.6
157
+ database applies 0002 and 0003 back to back, and where 0002 refuses a provider,
158
+ set `issuer` to any placeholder: the value is never read again. Dropping the
159
+ column is a later, optional delta, possible once no 1.7.2 site remains.
131
160
 
132
161
  > **Adopting from the old copy-in model?** Earlier versions told you to `cp` the
133
162
  > baseline into your own `drizzle/` chain. If you already did, keep that file
@@ -0,0 +1,46 @@
1
+ import { type MigrationStatus } from "@ingram-tech/nk-db/migrate";
2
+ import type { Pool } from "pg";
3
+ /** The migration chain this package ships (`migrations/` next to `dist/`). */
4
+ export declare const AUTH_MIGRATIONS_FOLDER: string;
5
+ /** The journal table the README tells a site to record the chain in. */
6
+ export declare const AUTH_MIGRATIONS_TABLE = "__nkauth_migrations";
7
+ /**
8
+ * Thrown when a site runs this package's chain but has not applied all of it:
9
+ * the installed nk-auth expects a schema the database does not have yet. Every
10
+ * Better Auth bump that touches the schema ships as a new file in the chain,
11
+ * so this is the "deployed before migrating" mistake, caught at the first
12
+ * session read instead of at a customer's sign-in.
13
+ */
14
+ export declare class AuthChainNotAppliedError extends Error {
15
+ readonly pending: readonly string[];
16
+ constructor(pending: readonly string[]);
17
+ }
18
+ export interface AuthChainCheckOptions {
19
+ /** The journal table the site records the chain in. Default `__nkauth_migrations`. */
20
+ migrationsTable?: string;
21
+ /** The journal table's schema. Default `drizzle`. */
22
+ migrationsSchema?: string;
23
+ }
24
+ /**
25
+ * Verify the shipped auth chain is fully applied to the database behind `pool`.
26
+ *
27
+ * Returns the runner's status when it is, or `null` when the database has no
28
+ * nk-auth journal table at all: a site that owns Better Auth's tables in its
29
+ * own chain (a squashed baseline) never records this one, and that is not a
30
+ * deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
31
+ * exists but lags the installed package.
32
+ *
33
+ * `createAuthHelpers` runs this once per process on the first session read
34
+ * when the instance's `database` is a `pg` Pool; call it yourself from
35
+ * `instrumentation.ts` or a health check to fail a deploy earlier.
36
+ */
37
+ export declare const assertAuthChainApplied: (pool: Pool, options?: AuthChainCheckOptions) => Promise<MigrationStatus | null>;
38
+ /**
39
+ * The once-per-process form `createAuthHelpers` uses: resolves the pool off
40
+ * the Better Auth options when there is one, memoizes the check, and rethrows
41
+ * the same error on every later call so a failed deploy stays failed rather
42
+ * than flapping. Anything that is not a `pg` Pool (a Drizzle or Kysely
43
+ * adapter) is skipped: the check has nothing to query through.
44
+ */
45
+ export declare const authChainCheck: (database: unknown, options?: AuthChainCheckOptions) => (() => Promise<void>);
46
+ //# sourceMappingURL=chain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAErF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,EAAE,MAEpC,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,qBAAqB,wBAAwB,CAAC;AAS3D;;;;;;GAMG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,YAAY,OAAO,EAAE,SAAS,MAAM,EAAE,EAMrC;CACD;AAED,MAAM,WAAW,qBAAqB;IACrC,sFAAsF;IACtF,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,SAC5B,IAAI,YACD,qBAAqB,KAC5B,OAAO,CAAC,eAAe,GAAG,IAAI,CAkBhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,aAChB,OAAO,YACR,qBAAqB,KAC5B,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAOtB,CAAC"}
package/dist/chain.js ADDED
@@ -0,0 +1,73 @@
1
+ import { inspectMigrations } from "@ingram-tech/nk-db/migrate";
2
+ import { fileURLToPath } from "node:url";
3
+ /** The migration chain this package ships (`migrations/` next to `dist/`). */
4
+ export const AUTH_MIGRATIONS_FOLDER = fileURLToPath(new URL("../migrations", import.meta.url));
5
+ /** The journal table the README tells a site to record the chain in. */
6
+ export const AUTH_MIGRATIONS_TABLE = "__nkauth_migrations";
7
+ /** The shape of a `pg` Pool as far as the chain check needs it. */
8
+ const isPool = (value) => typeof value === "object" &&
9
+ value !== null &&
10
+ typeof value.query === "function" &&
11
+ typeof value.connect === "function";
12
+ /**
13
+ * Thrown when a site runs this package's chain but has not applied all of it:
14
+ * the installed nk-auth expects a schema the database does not have yet. Every
15
+ * Better Auth bump that touches the schema ships as a new file in the chain,
16
+ * so this is the "deployed before migrating" mistake, caught at the first
17
+ * session read instead of at a customer's sign-in.
18
+ */
19
+ export class AuthChainNotAppliedError extends Error {
20
+ pending;
21
+ constructor(pending) {
22
+ super(`@ingram-tech/nk-auth: ${pending.length} migration(s) of the shipped auth chain are not applied to this database (${pending.join(", ")}). Run \`db:migrate\` (nk-pg-migrate --migrations node_modules/@ingram-tech/nk-auth/migrations --table ${AUTH_MIGRATIONS_TABLE}) before deploying this version.`);
23
+ this.name = "AuthChainNotAppliedError";
24
+ this.pending = pending;
25
+ }
26
+ }
27
+ /**
28
+ * Verify the shipped auth chain is fully applied to the database behind `pool`.
29
+ *
30
+ * Returns the runner's status when it is, or `null` when the database has no
31
+ * nk-auth journal table at all: a site that owns Better Auth's tables in its
32
+ * own chain (a squashed baseline) never records this one, and that is not a
33
+ * deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
34
+ * exists but lags the installed package.
35
+ *
36
+ * `createAuthHelpers` runs this once per process on the first session read
37
+ * when the instance's `database` is a `pg` Pool; call it yourself from
38
+ * `instrumentation.ts` or a health check to fail a deploy earlier.
39
+ */
40
+ export const assertAuthChainApplied = async (pool, options = {}) => {
41
+ const schema = options.migrationsSchema ?? "drizzle";
42
+ const table = options.migrationsTable ?? AUTH_MIGRATIONS_TABLE;
43
+ const { rows } = await pool.query("select to_regclass($1) as present", [`"${schema}"."${table}"`]);
44
+ if (rows[0]?.present === null || rows[0]?.present === undefined)
45
+ return null;
46
+ const status = await inspectMigrations({
47
+ pool,
48
+ migrationsFolder: AUTH_MIGRATIONS_FOLDER,
49
+ migrationsSchema: schema,
50
+ migrationsTable: table,
51
+ });
52
+ if (status.pending.length > 0) {
53
+ throw new AuthChainNotAppliedError(status.pending.map((file) => file.tag));
54
+ }
55
+ return status;
56
+ };
57
+ /**
58
+ * The once-per-process form `createAuthHelpers` uses: resolves the pool off
59
+ * the Better Auth options when there is one, memoizes the check, and rethrows
60
+ * the same error on every later call so a failed deploy stays failed rather
61
+ * than flapping. Anything that is not a `pg` Pool (a Drizzle or Kysely
62
+ * adapter) is skipped: the check has nothing to query through.
63
+ */
64
+ export const authChainCheck = (database, options = {}) => {
65
+ if (!isPool(database))
66
+ return async () => { };
67
+ let checked;
68
+ return () => {
69
+ checked ??= assertAuthChainApplied(database, options).then(() => undefined);
70
+ return checked;
71
+ };
72
+ };
73
+ //# sourceMappingURL=chain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chain.js","sourceRoot":"","sources":["../src/chain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,4BAA4B,CAAC;AACrF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,sBAAsB,GAAW,aAAa,CAC1D,IAAI,GAAG,CAAC,eAAe,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CACzC,CAAC;AAEF,wEAAwE;AACxE,MAAM,CAAC,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAE3D,mEAAmE;AACnE,MAAM,MAAM,GAAG,CAAC,KAAc,EAAiB,EAAE,CAChD,OAAO,KAAK,KAAK,QAAQ;IACzB,KAAK,KAAK,IAAI;IACd,OAAQ,KAA6B,CAAC,KAAK,KAAK,UAAU;IAC1D,OAAQ,KAA+B,CAAC,OAAO,KAAK,UAAU,CAAC;AAEhE;;;;;;GAMG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACzC,OAAO,CAAoB;IACpC,YAAY,OAA0B;QACrC,KAAK,CACJ,yBAAyB,OAAO,CAAC,MAAM,6EAA6E,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,0GAA0G,qBAAqB,kCAAkC,CACvS,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;QACvC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;CACD;AASD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,KAAK,EAC1C,IAAU,EACV,OAAO,GAA0B,EAAE,EACD,EAAE;IACpC,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,IAAI,SAAS,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,eAAe,IAAI,qBAAqB,CAAC;IAC/D,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAChC,mCAAmC,EACnC,CAAC,IAAI,MAAM,MAAM,KAAK,GAAG,CAAC,CAC1B,CAAC;IACF,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC7E,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC;QACtC,IAAI;QACJ,gBAAgB,EAAE,sBAAsB;QACxC,gBAAgB,EAAE,MAAM;QACxB,eAAe,EAAE,KAAK;KACtB,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5E,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC7B,QAAiB,EACjB,OAAO,GAA0B,EAAE,EACX,EAAE;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,IAAI,EAAE,GAAE,CAAC,CAAC;IAC7C,IAAI,OAAkC,CAAC;IACvC,OAAO,GAAG,EAAE;QACX,OAAO,KAAK,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC5E,OAAO,OAAO,CAAC;IAChB,CAAC,CAAC;AACH,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { assertAuthChainApplied, AUTH_MIGRATIONS_FOLDER, AUTH_MIGRATIONS_TABLE, type AuthChainCheckOptions, AuthChainNotAppliedError, } from "./chain.js";
1
2
  export { type BackendJwtConfig, backendJwtOptions, verifyBackendJwt } from "./jwt.js";
2
3
  export { base58Id, fromPrefixedId, toPrefixedId, uuidGenerateId } from "./id.js";
3
4
  export { type AuthEnv, authEnv, authSecret, isConfigured } from "./keys.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,aAAa,EAClB,wBAAwB,EACxB,KAAK,SAAS,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,KAAK,cAAc,EACnB,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,mBAAmB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACN,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,wBAAwB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,aAAa,EAClB,wBAAwB,EACxB,KAAK,SAAS,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAC3B,KAAK,cAAc,EACnB,cAAc,EACd,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,mBAAmB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // "@ingram-tech/nk-auth/client" so importing the server presets never pulls in
3
3
  // React. Focused subpaths (./jwt, ./organization, ./pool) let a site import
4
4
  // only what it needs (e.g. avoid bcrypt when it doesn't use passwords).
5
+ export { assertAuthChainApplied, AUTH_MIGRATIONS_FOLDER, AUTH_MIGRATIONS_TABLE, AuthChainNotAppliedError, } from "./chain.js";
5
6
  export { backendJwtOptions, verifyBackendJwt } from "./jwt.js";
6
7
  export { base58Id, fromPrefixedId, toPrefixedId, uuidGenerateId } from "./id.js";
7
8
  export { authEnv, authSecret, isConfigured } from "./keys.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AAExE,OAAO,EAAyB,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAgB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAIN,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAElB,wBAAwB,GAExB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAE3B,cAAc,EAGd,mBAAmB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AAExE,OAAO,EACN,sBAAsB,EACtB,sBAAsB,EACtB,qBAAqB,EAErB,wBAAwB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAyB,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAgB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC5E,OAAO,EAIN,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAElB,wBAAwB,GAExB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,sBAAsB,EACtB,2BAA2B,EAC3B,2BAA2B,EAE3B,cAAc,EAGd,mBAAmB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
@@ -58,9 +58,12 @@ export interface AuthMiddlewareConfig extends NextParamOptions {
58
58
  /** Destination for the front-door redirect. Required when `frontDoorPaths` is set. */
59
59
  signedInRedirect?: string;
60
60
  /**
61
- * Cookie-name fragment for the cookies cleared on a stale session. Default
62
- * `better-auth`, which matches `better-auth.session_token` and the
63
- * `__Secure-…` production variant.
61
+ * The app's Better Auth `advanced.cookiePrefix`, when it sets one. Used both
62
+ * to detect the session cookie and to clear it on the stale handshake.
63
+ * Default `better-auth`, which matches `better-auth.session_token` and the
64
+ * `__Secure-…` production variant. An app on a shared parent domain (a
65
+ * second site under `.example.com`) needs its own prefix, and without it
66
+ * here the gate never sees the cookie and loops with the server guard.
64
67
  */
65
68
  sessionCookiePrefix?: string;
66
69
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAEN,KAAK,gBAAgB,EAErB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,OAAO,GACrB,IAAI,CAKN;AAED,MAAM,WAAW,kBAAkB;IAClC,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,WAAW,EACpB,MAAM,GAAE,kBAAuB,GAC7B,YAAY,GAAG,IAAI,CA4BrB;AAED,MAAM,WAAW,qBAAqB;IACrC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC7D;;;OAGG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,aAkCtD,WAAW,YACX,qBAAqB,KAC5B,YAAY,CAgDf"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAmCA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAEN,KAAK,gBAAgB,EAErB,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,WAAW,EACpB,cAAc,EAAE,OAAO,GACrB,IAAI,CAKN;AAED,MAAM,WAAW,kBAAkB;IAClC,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,WAAW,EACpB,MAAM,GAAE,kBAAuB,GAC7B,YAAY,GAAG,IAAI,CA4BrB;AAED,MAAM,WAAW,qBAAqB;IACrC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC7D;;;OAGG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,sFAAsF;IACtF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,aAkCtD,WAAW,YACX,qBAAqB,KAC5B,YAAY,CAgDf"}
@@ -121,7 +121,7 @@ export function createAuthMiddleware(config) {
121
121
  });
122
122
  if (stale)
123
123
  return stale;
124
- const hasSessionCookie = !!getSessionCookie(request);
124
+ const hasSessionCookie = !!getSessionCookie(request, { cookiePrefix });
125
125
  // 2. Unauthenticated (no cookie) on a protected path -> sign in, and
126
126
  // remember where they were going.
127
127
  if (!hasSessionCookie && isProtected(path)) {
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  * `redirectIfAuthenticated` here, so a stale cookie resolves to "no session"
25
25
  * and falls through to the form instead of ping-ponging forever.
26
26
  */
27
+ import { type AuthChainCheckOptions } from "./chain.js";
27
28
  import type { IdHelper } from "@ingram-tech/nk-db/id";
28
29
  import { type NextParamOptions } from "./gating-internals.js";
29
30
  /**
@@ -43,6 +44,13 @@ interface SessionLike {
43
44
  * inferred user shape (additional fields, org id, …) — no `any`, no casts.
44
45
  */
45
46
  interface AuthLike<S extends SessionLike> {
47
+ /**
48
+ * The options the instance was built with. `database` is the `pg` Pool a
49
+ * site passes to `betterAuth()`; the chain check queries through it.
50
+ */
51
+ options?: {
52
+ database?: unknown;
53
+ };
46
54
  api: {
47
55
  getSession: (input: {
48
56
  headers: Headers;
@@ -86,6 +94,14 @@ export interface AuthHelpersOptions extends NextParamOptions {
86
94
  * which matches `better-auth.session_token` and `__Secure-…` in production.
87
95
  */
88
96
  sessionCookiePrefix?: string;
97
+ /**
98
+ * Verify, once per process on the first session read, that the shipped
99
+ * auth migration chain is fully applied (see `assertAuthChainApplied`).
100
+ * Runs only when the instance's `database` is a `pg` Pool and the database
101
+ * records the chain. Pass `false` to opt out, or the journal location when
102
+ * the site records the chain under another table.
103
+ */
104
+ chainCheck?: false | AuthChainCheckOptions;
89
105
  }
90
106
  /**
91
107
  * A session with its ids in public form: `user.id`, `session.userId` and
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAEN,KAAK,gBAAgB,EAErB,MAAM,uBAAuB,CAAC;AAG/B;;;;GAIG;AACH,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,oEAAoE;AACpE,UAAU,WAAW;IACpB,IAAI,EAAE,OAAO,CAAC;CACd;AAED;;;;GAIG;AACH,UAAU,QAAQ,CAAC,CAAC,SAAS,WAAW;IACvC,GAAG,EAAE;QACJ,UAAU,EAAE,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/D;;;;;WAKG;QACH,gBAAgB,EAAE,CAAC,KAAK,EAAE;YACzB,OAAO,EAAE,OAAO,CAAC;SACjB,KAAK,OAAO,CAAC,KAAK,CAAC;YAAE,UAAU,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAC;KAC7C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,YAAY,CAAC,EAAE,QAAQ,CAAC;CACxB;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC3D,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EACrD,OAAO,EAAE,CAAC,EACV,GAAG,EAAE,UAAU,GACb,CAAC,CAyBH;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,EACtD,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,OAAO,GAAE,kBAAuB;sBAiBG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;mBAQ1B,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;0BA+DlB,OAAO,CAAC,CAAC,CAAC;uBAXb,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;wBA3BjB,OAAO,CAAC,MAAM,CAAC;kCAkDH,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;8BAU5B,OAAO,CAAC,MAAM,EAAE,CAAC;gCAcf,OAAO,CAAC,OAAO,CAAC;EAevD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,KAAK,qBAAqB,EAAkB,MAAM,YAAY,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAEN,KAAK,gBAAgB,EAErB,MAAM,uBAAuB,CAAC;AAG/B;;;;GAIG;AACH,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D,oEAAoE;AACpE,UAAU,WAAW;IACpB,IAAI,EAAE,OAAO,CAAC;CACd;AAED;;;;GAIG;AACH,UAAU,QAAQ,CAAC,CAAC,SAAS,WAAW;IACvC;;;OAGG;IACH,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACjC,GAAG,EAAE;QACJ,UAAU,EAAE,CAAC,KAAK,EAAE;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC/D;;;;;WAKG;QACH,gBAAgB,EAAE,CAAC,KAAK,EAAE;YACzB,OAAO,EAAE,OAAO,CAAC;SACjB,KAAK,OAAO,CAAC,KAAK,CAAC;YAAE,UAAU,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC,CAAC;KAC7C,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,YAAY,CAAC,EAAE,QAAQ,CAAC;CACxB;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC3D,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,KAAK,GAAG,qBAAqB,CAAC;CAC3C;AAKD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,WAAW,EACrD,OAAO,EAAE,CAAC,EACV,GAAG,EAAE,UAAU,GACb,CAAC,CAyBH;AAED,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,WAAW,EACtD,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,OAAO,GAAE,kBAAuB;sBAqBG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;mBAS1B,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;0BA+DlB,OAAO,CAAC,CAAC,CAAC;uBAXb,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;wBA3BjB,OAAO,CAAC,MAAM,CAAC;kCAkDH,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;8BAU5B,OAAO,CAAC,MAAM,EAAE,CAAC;gCAcf,OAAO,CAAC,OAAO,CAAC;EAevD"}
package/dist/server.js CHANGED
@@ -1,3 +1,30 @@
1
+ /**
2
+ * Server-side session helpers for the App Router, at
3
+ * "@ingram-tech/nk-auth/server" so the framework-agnostic core entry never
4
+ * pulls in `next`. A site binds these once to its own Better Auth instance:
5
+ *
6
+ * // lib/auth/session.ts
7
+ * import { createAuthHelpers } from "@ingram-tech/nk-auth/server";
8
+ * import { auth } from "@/lib/auth";
9
+ *
10
+ * export const { getSession, getUser, requireSession, requireUser,
11
+ * redirectIfAuthenticated } = createAuthHelpers(auth);
12
+ *
13
+ * These are the **validated** authority: they resolve through
14
+ * `auth.api.getSession`, which checks the session against the database — not
15
+ * merely the presence of a cookie. The read is memoized per request with React
16
+ * `cache()`, so a render fanning out many `getUser()` calls validates once,
17
+ * not once per call. When a guard finds no valid session it redirects to the sign-in page,
18
+ * preserving the requested path as `next` (read from the header the middleware
19
+ * injects) and, when a session cookie is present-but-invalid, adding `stale=1`
20
+ * so the middleware clears the dead cookie. The optimistic, cookie-presence
21
+ * check belongs in middleware (see "@ingram-tech/nk-auth/middleware"); it may
22
+ * only ever push *unauthenticated* users off protected routes. The decision to
23
+ * send a signed-in user *away* from the sign-in page must run through
24
+ * `redirectIfAuthenticated` here, so a stale cookie resolves to "no session"
25
+ * and falls through to the form instead of ping-ponging forever.
26
+ */
27
+ import { authChainCheck } from "./chain.js";
1
28
  import { cookies, headers } from "next/headers";
2
29
  import { redirect } from "next/navigation";
3
30
  import { cache } from "react";
@@ -54,7 +81,11 @@ export function createAuthHelpers(auth, options = {}) {
54
81
  * pre-mutation snapshot; re-read through `auth.api.getSession` directly if
55
82
  * that ever matters.
56
83
  */
84
+ const ensureChain = options.chainCheck === false
85
+ ? async () => { }
86
+ : authChainCheck(auth.options?.database, options.chainCheck);
57
87
  const getSession = cache(async () => {
88
+ await ensureChain();
58
89
  const session = await auth.api.getSession({ headers: await headers() });
59
90
  return session && options.ids
60
91
  ? encodeSessionIds(session, options.ids)
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ -- @ingram-tech/nk-auth — Better Auth 1.7.3 schema delta (better-auth 1.7.4).
2
+ --
3
+ -- 1.7.3 reverted the 1.7.0-1.7.2 account identity change: accounts are keyed
4
+ -- on (providerId, accountId) again, exactly as in 1.6, and upstream has
5
+ -- committed to keeping the core schema fixed for the rest of v1. 1.7.3+ never
6
+ -- writes `account.issuer`, so the NOT NULL that 0002 set rejects every sign-up
7
+ -- and account link, and the (issuer, accountId) unique index would collapse
8
+ -- into uniqueness on `accountId` alone once the column is always null.
9
+ --
10
+ -- This relaxes both. It is safe under either package version: 1.7.2 keeps
11
+ -- writing `issuer` into a nullable column, 1.7.3+ ignores it. So a site
12
+ -- migrates first and deploys second with no window where sign-in fails.
13
+ --
14
+ -- The column itself stays. Dropping it breaks 1.7.2, which reads it on every
15
+ -- sign-in, so it can only go once no 1.7.2 code runs anywhere; that is a
16
+ -- separate later delta, and it is optional (1.7.3+ tolerates the extra
17
+ -- nullable column). A 1.6 database applies 0002 and this file back to back;
18
+ -- the backfilled value is then never read.
19
+ --
20
+ -- Append-only: this file is hashed by the runner and must never be edited once
21
+ -- shipped. The next better-auth schema change goes in 0004.
22
+
23
+ drop index if exists "public"."account_issuer_accountId_uidx";
24
+ --> statement-breakpoint
25
+ alter table "public"."account" alter column "issuer" drop not null;
@@ -15,6 +15,13 @@
15
15
  "when": 1756598400000,
16
16
  "tag": "0002_better_auth_1_7",
17
17
  "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1757721600000,
23
+ "tag": "0003_better_auth_1_7_3",
24
+ "breakpoints": true
18
25
  }
19
26
  ]
20
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingram-tech/nk-auth",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "The Ingram Better Auth foundation: composable presets (org, dual-shape JWT, active-org hooks, pg pool) for Next.js sites.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -68,14 +68,14 @@
68
68
  "test": "vitest run"
69
69
  },
70
70
  "dependencies": {
71
- "@ingram-tech/nk-db": "^2.1.2",
71
+ "@ingram-tech/nk-db": "^2.1.3",
72
72
  "bcrypt": "^6.0.0",
73
- "jose": "^6.2.10",
74
- "zod": "^4.5.4"
73
+ "jose": "^6.2.12",
74
+ "zod": "^4.6.4"
75
75
  },
76
76
  "peerDependencies": {
77
- "@better-auth/passkey": "^1.7.2",
78
- "better-auth": "^1.7.2",
77
+ "@better-auth/passkey": "1.7.4",
78
+ "better-auth": "1.7.4",
79
79
  "next": "^15.0.0 || ^16.0.0",
80
80
  "pg": "^8.13.0",
81
81
  "react": "^18.0.0 || ^19.0.0"
@@ -95,17 +95,17 @@
95
95
  }
96
96
  },
97
97
  "devDependencies": {
98
- "@better-auth/passkey": "^1.7.2",
99
- "@ingram-tech/nk-dev": "0.14.1",
98
+ "@better-auth/passkey": "1.7.4",
99
+ "@ingram-tech/nk-dev": "0.17.0",
100
100
  "@types/bcrypt": "^6.0.0",
101
- "@types/node": "^26.4.0",
101
+ "@types/node": "^26.5.1",
102
102
  "@types/pg": "^8.23.1",
103
- "@types/react": "^19.2.18",
104
- "better-auth": "^1.7.2",
105
- "next": "^16.3.3",
103
+ "@types/react": "^19.3.0",
104
+ "better-auth": "1.7.4",
105
+ "next": "^16.3.5",
106
106
  "pg": "^8.23.0",
107
- "react": "^19.2.8",
107
+ "react": "^19.3.0",
108
108
  "typescript": "^7.0.2",
109
- "vitest": "^4.1.11"
109
+ "vitest": "^5.0.0"
110
110
  }
111
111
  }
package/src/chain.ts ADDED
@@ -0,0 +1,98 @@
1
+ import { inspectMigrations, type MigrationStatus } from "@ingram-tech/nk-db/migrate";
2
+ import { fileURLToPath } from "node:url";
3
+ import type { Pool } from "pg";
4
+
5
+ /** The migration chain this package ships (`migrations/` next to `dist/`). */
6
+ export const AUTH_MIGRATIONS_FOLDER: string = fileURLToPath(
7
+ new URL("../migrations", import.meta.url),
8
+ );
9
+
10
+ /** The journal table the README tells a site to record the chain in. */
11
+ export const AUTH_MIGRATIONS_TABLE = "__nkauth_migrations";
12
+
13
+ /** The shape of a `pg` Pool as far as the chain check needs it. */
14
+ const isPool = (value: unknown): value is Pool =>
15
+ typeof value === "object" &&
16
+ value !== null &&
17
+ typeof (value as { query?: unknown }).query === "function" &&
18
+ typeof (value as { connect?: unknown }).connect === "function";
19
+
20
+ /**
21
+ * Thrown when a site runs this package's chain but has not applied all of it:
22
+ * the installed nk-auth expects a schema the database does not have yet. Every
23
+ * Better Auth bump that touches the schema ships as a new file in the chain,
24
+ * so this is the "deployed before migrating" mistake, caught at the first
25
+ * session read instead of at a customer's sign-in.
26
+ */
27
+ export class AuthChainNotAppliedError extends Error {
28
+ readonly pending: readonly string[];
29
+ constructor(pending: readonly string[]) {
30
+ super(
31
+ `@ingram-tech/nk-auth: ${pending.length} migration(s) of the shipped auth chain are not applied to this database (${pending.join(", ")}). Run \`db:migrate\` (nk-pg-migrate --migrations node_modules/@ingram-tech/nk-auth/migrations --table ${AUTH_MIGRATIONS_TABLE}) before deploying this version.`,
32
+ );
33
+ this.name = "AuthChainNotAppliedError";
34
+ this.pending = pending;
35
+ }
36
+ }
37
+
38
+ export interface AuthChainCheckOptions {
39
+ /** The journal table the site records the chain in. Default `__nkauth_migrations`. */
40
+ migrationsTable?: string;
41
+ /** The journal table's schema. Default `drizzle`. */
42
+ migrationsSchema?: string;
43
+ }
44
+
45
+ /**
46
+ * Verify the shipped auth chain is fully applied to the database behind `pool`.
47
+ *
48
+ * Returns the runner's status when it is, or `null` when the database has no
49
+ * nk-auth journal table at all: a site that owns Better Auth's tables in its
50
+ * own chain (a squashed baseline) never records this one, and that is not a
51
+ * deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
52
+ * exists but lags the installed package.
53
+ *
54
+ * `createAuthHelpers` runs this once per process on the first session read
55
+ * when the instance's `database` is a `pg` Pool; call it yourself from
56
+ * `instrumentation.ts` or a health check to fail a deploy earlier.
57
+ */
58
+ export const assertAuthChainApplied = async (
59
+ pool: Pool,
60
+ options: AuthChainCheckOptions = {},
61
+ ): Promise<MigrationStatus | null> => {
62
+ const schema = options.migrationsSchema ?? "drizzle";
63
+ const table = options.migrationsTable ?? AUTH_MIGRATIONS_TABLE;
64
+ const { rows } = await pool.query<{ present: string | null }>(
65
+ "select to_regclass($1) as present",
66
+ [`"${schema}"."${table}"`],
67
+ );
68
+ if (rows[0]?.present === null || rows[0]?.present === undefined) return null;
69
+ const status = await inspectMigrations({
70
+ pool,
71
+ migrationsFolder: AUTH_MIGRATIONS_FOLDER,
72
+ migrationsSchema: schema,
73
+ migrationsTable: table,
74
+ });
75
+ if (status.pending.length > 0) {
76
+ throw new AuthChainNotAppliedError(status.pending.map((file) => file.tag));
77
+ }
78
+ return status;
79
+ };
80
+
81
+ /**
82
+ * The once-per-process form `createAuthHelpers` uses: resolves the pool off
83
+ * the Better Auth options when there is one, memoizes the check, and rethrows
84
+ * the same error on every later call so a failed deploy stays failed rather
85
+ * than flapping. Anything that is not a `pg` Pool (a Drizzle or Kysely
86
+ * adapter) is skipped: the check has nothing to query through.
87
+ */
88
+ export const authChainCheck = (
89
+ database: unknown,
90
+ options: AuthChainCheckOptions = {},
91
+ ): (() => Promise<void>) => {
92
+ if (!isPool(database)) return async () => {};
93
+ let checked: Promise<void> | undefined;
94
+ return () => {
95
+ checked ??= assertAuthChainApplied(database, options).then(() => undefined);
96
+ return checked;
97
+ };
98
+ };
package/src/index.ts CHANGED
@@ -3,6 +3,13 @@
3
3
  // React. Focused subpaths (./jwt, ./organization, ./pool) let a site import
4
4
  // only what it needs (e.g. avoid bcrypt when it doesn't use passwords).
5
5
 
6
+ export {
7
+ assertAuthChainApplied,
8
+ AUTH_MIGRATIONS_FOLDER,
9
+ AUTH_MIGRATIONS_TABLE,
10
+ type AuthChainCheckOptions,
11
+ AuthChainNotAppliedError,
12
+ } from "./chain.js";
6
13
  export { type BackendJwtConfig, backendJwtOptions, verifyBackendJwt } from "./jwt.js";
7
14
  export { base58Id, fromPrefixedId, toPrefixedId, uuidGenerateId } from "./id.js";
8
15
  export { type AuthEnv, authEnv, authSecret, isConfigured } from "./keys.js";
package/src/middleware.ts CHANGED
@@ -142,9 +142,12 @@ export interface AuthMiddlewareConfig extends NextParamOptions {
142
142
  /** Destination for the front-door redirect. Required when `frontDoorPaths` is set. */
143
143
  signedInRedirect?: string;
144
144
  /**
145
- * Cookie-name fragment for the cookies cleared on a stale session. Default
146
- * `better-auth`, which matches `better-auth.session_token` and the
147
- * `__Secure-…` production variant.
145
+ * The app's Better Auth `advanced.cookiePrefix`, when it sets one. Used both
146
+ * to detect the session cookie and to clear it on the stale handshake.
147
+ * Default `better-auth`, which matches `better-auth.session_token` and the
148
+ * `__Secure-…` production variant. An app on a shared parent domain (a
149
+ * second site under `.example.com`) needs its own prefix, and without it
150
+ * here the gate never sees the cookie and loops with the server guard.
148
151
  */
149
152
  sessionCookiePrefix?: string;
150
153
  }
@@ -196,7 +199,7 @@ export function createAuthMiddleware(config: AuthMiddlewareConfig) {
196
199
  });
197
200
  if (stale) return stale;
198
201
 
199
- const hasSessionCookie = !!getSessionCookie(request);
202
+ const hasSessionCookie = !!getSessionCookie(request, { cookiePrefix });
200
203
 
201
204
  // 2. Unauthenticated (no cookie) on a protected path -> sign in, and
202
205
  // remember where they were going.
package/src/server.ts CHANGED
@@ -24,6 +24,7 @@
24
24
  * `redirectIfAuthenticated` here, so a stale cookie resolves to "no session"
25
25
  * and falls through to the form instead of ping-ponging forever.
26
26
  */
27
+ import { type AuthChainCheckOptions, authChainCheck } from "./chain.js";
27
28
  import type { IdHelper } from "@ingram-tech/nk-db/id";
28
29
  import { cookies, headers } from "next/headers";
29
30
  import { redirect } from "next/navigation";
@@ -54,6 +55,11 @@ interface SessionLike {
54
55
  * inferred user shape (additional fields, org id, …) — no `any`, no casts.
55
56
  */
56
57
  interface AuthLike<S extends SessionLike> {
58
+ /**
59
+ * The options the instance was built with. `database` is the `pg` Pool a
60
+ * site passes to `betterAuth()`; the chain check queries through it.
61
+ */
62
+ options?: { database?: unknown };
57
63
  api: {
58
64
  getSession: (input: { headers: Headers }) => Promise<S | null>;
59
65
  /**
@@ -95,6 +101,14 @@ export interface AuthHelpersOptions extends NextParamOptions {
95
101
  * which matches `better-auth.session_token` and `__Secure-…` in production.
96
102
  */
97
103
  sessionCookiePrefix?: string;
104
+ /**
105
+ * Verify, once per process on the first session read, that the shipped
106
+ * auth migration chain is fully applied (see `assertAuthChainApplied`).
107
+ * Runs only when the instance's `database` is a `pg` Pool and the database
108
+ * records the chain. Pass `false` to opt out, or the journal location when
109
+ * the site records the chain under another table.
110
+ */
111
+ chainCheck?: false | AuthChainCheckOptions;
98
112
  }
99
113
 
100
114
  const encodeIfString = (helper: IdHelper | undefined, value: unknown): unknown =>
@@ -155,7 +169,12 @@ export function createAuthHelpers<S extends SessionLike>(
155
169
  * pre-mutation snapshot; re-read through `auth.api.getSession` directly if
156
170
  * that ever matters.
157
171
  */
172
+ const ensureChain =
173
+ options.chainCheck === false
174
+ ? async (): Promise<void> => {}
175
+ : authChainCheck(auth.options?.database, options.chainCheck);
158
176
  const getSession = cache(async (): Promise<S | null> => {
177
+ await ensureChain();
159
178
  const session = await auth.api.getSession({ headers: await headers() });
160
179
  return session && options.ids
161
180
  ? encodeSessionIds(session, options.ids)