@ingram-tech/nk-auth 0.16.1 → 0.17.1
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 +44 -15
- package/dist/chain.d.ts +51 -0
- package/dist/chain.d.ts.map +1 -0
- package/dist/chain.js +79 -0
- package/dist/chain.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware.d.ts +6 -3
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +31 -0
- package/dist/server.js.map +1 -1
- package/migrations/0003_better_auth_1_7_3.sql +25 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +15 -14
- package/src/chain.ts +109 -0
- package/src/index.ts +7 -0
- package/src/middleware.ts +7 -4
- package/src/server.ts +19 -0
package/README.md
CHANGED
|
@@ -112,22 +112,51 @@ const db = await createTestDb({
|
|
|
112
112
|
});
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
129
|
-
|
|
130
|
-
|
|
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
|
package/dist/chain.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type MigrationStatus } from "@ingram-tech/nk-db/migrate";
|
|
2
|
+
import type { Pool } from "pg";
|
|
3
|
+
/**
|
|
4
|
+
* The migration chain this package ships: `migrations/` in the installed
|
|
5
|
+
* package. Resolved through the package manifest, at call time, so a bundler
|
|
6
|
+
* that rewrites `import.meta.url` (Next's server build) still lands on the
|
|
7
|
+
* folder under `node_modules`, and never sees a URL it would try to inline.
|
|
8
|
+
*/
|
|
9
|
+
export declare const authMigrationsFolder: () => string;
|
|
10
|
+
/** The journal table the README tells a site to record the chain in. */
|
|
11
|
+
export declare const AUTH_MIGRATIONS_TABLE = "__nkauth_migrations";
|
|
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 declare class AuthChainNotAppliedError extends Error {
|
|
20
|
+
readonly pending: readonly string[];
|
|
21
|
+
constructor(pending: readonly string[]);
|
|
22
|
+
}
|
|
23
|
+
export interface AuthChainCheckOptions {
|
|
24
|
+
/** The journal table the site records the chain in. Default `__nkauth_migrations`. */
|
|
25
|
+
migrationsTable?: string;
|
|
26
|
+
/** The journal table's schema. Default `drizzle`. */
|
|
27
|
+
migrationsSchema?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Verify the shipped auth chain is fully applied to the database behind `pool`.
|
|
31
|
+
*
|
|
32
|
+
* Returns the runner's status when it is, or `null` when the database has no
|
|
33
|
+
* nk-auth journal table at all: a site that owns Better Auth's tables in its
|
|
34
|
+
* own chain (a squashed baseline) never records this one, and that is not a
|
|
35
|
+
* deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
|
|
36
|
+
* exists but lags the installed package.
|
|
37
|
+
*
|
|
38
|
+
* `createAuthHelpers` runs this once per process on the first session read
|
|
39
|
+
* when the instance's `database` is a `pg` Pool; call it yourself from
|
|
40
|
+
* `instrumentation.ts` or a health check to fail a deploy earlier.
|
|
41
|
+
*/
|
|
42
|
+
export declare const assertAuthChainApplied: (pool: Pool, options?: AuthChainCheckOptions) => Promise<MigrationStatus | null>;
|
|
43
|
+
/**
|
|
44
|
+
* The once-per-process form `createAuthHelpers` uses: resolves the pool off
|
|
45
|
+
* the Better Auth options when there is one, memoizes the check, and rethrows
|
|
46
|
+
* the same error on every later call so a failed deploy stays failed rather
|
|
47
|
+
* than flapping. Anything that is not a `pg` Pool (a Drizzle or Kysely
|
|
48
|
+
* adapter) is skipped: the check has nothing to query through.
|
|
49
|
+
*/
|
|
50
|
+
export declare const authChainCheck: (database: unknown, options?: AuthChainCheckOptions) => (() => Promise<void>);
|
|
51
|
+
//# 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;AAGrF,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,QAAO,MAOtC,CAAC;AAEH,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,79 @@
|
|
|
1
|
+
import { inspectMigrations } from "@ingram-tech/nk-db/migrate";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* The migration chain this package ships: `migrations/` in the installed
|
|
6
|
+
* package. Resolved through the package manifest, at call time, so a bundler
|
|
7
|
+
* that rewrites `import.meta.url` (Next's server build) still lands on the
|
|
8
|
+
* folder under `node_modules`, and never sees a URL it would try to inline.
|
|
9
|
+
*/
|
|
10
|
+
export const authMigrationsFolder = () => dirname(dirname(createRequire(import.meta.url).resolve("@ingram-tech/nk-auth/migrations/meta/_journal.json")));
|
|
11
|
+
/** The journal table the README tells a site to record the chain in. */
|
|
12
|
+
export const AUTH_MIGRATIONS_TABLE = "__nkauth_migrations";
|
|
13
|
+
/** The shape of a `pg` Pool as far as the chain check needs it. */
|
|
14
|
+
const isPool = (value) => typeof value === "object" &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
typeof value.query === "function" &&
|
|
17
|
+
typeof value.connect === "function";
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when a site runs this package's chain but has not applied all of it:
|
|
20
|
+
* the installed nk-auth expects a schema the database does not have yet. Every
|
|
21
|
+
* Better Auth bump that touches the schema ships as a new file in the chain,
|
|
22
|
+
* so this is the "deployed before migrating" mistake, caught at the first
|
|
23
|
+
* session read instead of at a customer's sign-in.
|
|
24
|
+
*/
|
|
25
|
+
export class AuthChainNotAppliedError extends Error {
|
|
26
|
+
pending;
|
|
27
|
+
constructor(pending) {
|
|
28
|
+
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.`);
|
|
29
|
+
this.name = "AuthChainNotAppliedError";
|
|
30
|
+
this.pending = pending;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify the shipped auth chain is fully applied to the database behind `pool`.
|
|
35
|
+
*
|
|
36
|
+
* Returns the runner's status when it is, or `null` when the database has no
|
|
37
|
+
* nk-auth journal table at all: a site that owns Better Auth's tables in its
|
|
38
|
+
* own chain (a squashed baseline) never records this one, and that is not a
|
|
39
|
+
* deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
|
|
40
|
+
* exists but lags the installed package.
|
|
41
|
+
*
|
|
42
|
+
* `createAuthHelpers` runs this once per process on the first session read
|
|
43
|
+
* when the instance's `database` is a `pg` Pool; call it yourself from
|
|
44
|
+
* `instrumentation.ts` or a health check to fail a deploy earlier.
|
|
45
|
+
*/
|
|
46
|
+
export const assertAuthChainApplied = async (pool, options = {}) => {
|
|
47
|
+
const schema = options.migrationsSchema ?? "drizzle";
|
|
48
|
+
const table = options.migrationsTable ?? AUTH_MIGRATIONS_TABLE;
|
|
49
|
+
const { rows } = await pool.query("select to_regclass($1) as present", [`"${schema}"."${table}"`]);
|
|
50
|
+
if (rows[0]?.present === null || rows[0]?.present === undefined)
|
|
51
|
+
return null;
|
|
52
|
+
const status = await inspectMigrations({
|
|
53
|
+
pool,
|
|
54
|
+
migrationsFolder: authMigrationsFolder(),
|
|
55
|
+
migrationsSchema: schema,
|
|
56
|
+
migrationsTable: table,
|
|
57
|
+
});
|
|
58
|
+
if (status.pending.length > 0) {
|
|
59
|
+
throw new AuthChainNotAppliedError(status.pending.map((file) => file.tag));
|
|
60
|
+
}
|
|
61
|
+
return status;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* The once-per-process form `createAuthHelpers` uses: resolves the pool off
|
|
65
|
+
* the Better Auth options when there is one, memoizes the check, and rethrows
|
|
66
|
+
* the same error on every later call so a failed deploy stays failed rather
|
|
67
|
+
* than flapping. Anything that is not a `pg` Pool (a Drizzle or Kysely
|
|
68
|
+
* adapter) is skipped: the check has nothing to query through.
|
|
69
|
+
*/
|
|
70
|
+
export const authChainCheck = (database, options = {}) => {
|
|
71
|
+
if (!isPool(database))
|
|
72
|
+
return async () => { };
|
|
73
|
+
let checked;
|
|
74
|
+
return () => {
|
|
75
|
+
checked ??= assertAuthChainApplied(database, options).then(() => undefined);
|
|
76
|
+
return checked;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
//# 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,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAW,EAAE,CAChD,OAAO,CACN,OAAO,CACN,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CACrC,oDAAoD,CACpD,CACD,CACD,CAAC;AAEH,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,oBAAoB,EAAE;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, authMigrationsFolder, 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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,oBAAoB,EACpB,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, authMigrationsFolder, 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,oBAAoB,EACpB,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"}
|
package/dist/middleware.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* `
|
|
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
|
}
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/middleware.js
CHANGED
|
@@ -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)) {
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACN,mBAAmB,EAEnB,SAAS,GACT,MAAM,uBAAuB,CAAC;AAI/B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CACjC,OAAoB,EACpB,cAAuB;IAEvB,cAAc,CAAC,GAAG,CACjB,mBAAmB,EACnB,OAAO,CAAC,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CACjD,CAAC;AACH,CAAC;AASD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAChC,OAAoB,EACpB,MAAM,GAAuB,EAAE;IAE/B,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC;IACjD,MAAM,YAAY,GAAG,MAAM,CAAC,mBAAmB,IAAI,aAAa,CAAC;IACjE,IACC,OAAO,CAAC,OAAO,CAAC,QAAQ,KAAK,UAAU;QACvC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,EAChD,CAAC;QACF,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACnC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACtC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC/C,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACxC,8DAA8D;YAC9D,iEAAiE;YACjE,iEAAiE;YACjE,kDAAkD;YAClD,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;gBAClB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,IAAI,EAAE,GAAG;gBACT,MAAM,EACL,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;oBACnC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;aAClC,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;
|
|
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
|
package/dist/server.d.ts.map
CHANGED
|
@@ -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;
|
|
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)
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingram-tech/nk-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
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",
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"types": "./dist/client.d.ts",
|
|
61
61
|
"import": "./dist/client.js"
|
|
62
62
|
},
|
|
63
|
-
"./migrations/*": "./migrations/*"
|
|
63
|
+
"./migrations/*": "./migrations/*",
|
|
64
|
+
"./package.json": "./package.json"
|
|
64
65
|
},
|
|
65
66
|
"scripts": {
|
|
66
67
|
"build": "rm -rf dist *.tsbuildinfo && tsc -p tsconfig.build.json",
|
|
@@ -68,14 +69,14 @@
|
|
|
68
69
|
"test": "vitest run"
|
|
69
70
|
},
|
|
70
71
|
"dependencies": {
|
|
71
|
-
"@ingram-tech/nk-db": "^2.1.
|
|
72
|
+
"@ingram-tech/nk-db": "^2.1.3",
|
|
72
73
|
"bcrypt": "^6.0.0",
|
|
73
|
-
"jose": "^6.2.
|
|
74
|
-
"zod": "^4.
|
|
74
|
+
"jose": "^6.2.12",
|
|
75
|
+
"zod": "^4.6.4"
|
|
75
76
|
},
|
|
76
77
|
"peerDependencies": {
|
|
77
|
-
"@better-auth/passkey": "
|
|
78
|
-
"better-auth": "
|
|
78
|
+
"@better-auth/passkey": "1.7.4",
|
|
79
|
+
"better-auth": "1.7.4",
|
|
79
80
|
"next": "^15.0.0 || ^16.0.0",
|
|
80
81
|
"pg": "^8.13.0",
|
|
81
82
|
"react": "^18.0.0 || ^19.0.0"
|
|
@@ -95,16 +96,16 @@
|
|
|
95
96
|
}
|
|
96
97
|
},
|
|
97
98
|
"devDependencies": {
|
|
98
|
-
"@better-auth/passkey": "
|
|
99
|
-
"@ingram-tech/nk-dev": "0.
|
|
99
|
+
"@better-auth/passkey": "1.7.4",
|
|
100
|
+
"@ingram-tech/nk-dev": "0.17.1",
|
|
100
101
|
"@types/bcrypt": "^6.0.0",
|
|
101
|
-
"@types/node": "^26.
|
|
102
|
+
"@types/node": "^26.5.1",
|
|
102
103
|
"@types/pg": "^8.23.1",
|
|
103
|
-
"@types/react": "^19.
|
|
104
|
-
"better-auth": "
|
|
105
|
-
"next": "^16.3.
|
|
104
|
+
"@types/react": "^19.3.0",
|
|
105
|
+
"better-auth": "1.7.4",
|
|
106
|
+
"next": "^16.3.5",
|
|
106
107
|
"pg": "^8.23.0",
|
|
107
|
-
"react": "^19.
|
|
108
|
+
"react": "^19.3.0",
|
|
108
109
|
"typescript": "^7.0.2",
|
|
109
110
|
"vitest": "^5.0.0"
|
|
110
111
|
}
|
package/src/chain.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { inspectMigrations, type MigrationStatus } from "@ingram-tech/nk-db/migrate";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import type { Pool } from "pg";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The migration chain this package ships: `migrations/` in the installed
|
|
8
|
+
* package. Resolved through the package manifest, at call time, so a bundler
|
|
9
|
+
* that rewrites `import.meta.url` (Next's server build) still lands on the
|
|
10
|
+
* folder under `node_modules`, and never sees a URL it would try to inline.
|
|
11
|
+
*/
|
|
12
|
+
export const authMigrationsFolder = (): string =>
|
|
13
|
+
dirname(
|
|
14
|
+
dirname(
|
|
15
|
+
createRequire(import.meta.url).resolve(
|
|
16
|
+
"@ingram-tech/nk-auth/migrations/meta/_journal.json",
|
|
17
|
+
),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
/** The journal table the README tells a site to record the chain in. */
|
|
22
|
+
export const AUTH_MIGRATIONS_TABLE = "__nkauth_migrations";
|
|
23
|
+
|
|
24
|
+
/** The shape of a `pg` Pool as far as the chain check needs it. */
|
|
25
|
+
const isPool = (value: unknown): value is Pool =>
|
|
26
|
+
typeof value === "object" &&
|
|
27
|
+
value !== null &&
|
|
28
|
+
typeof (value as { query?: unknown }).query === "function" &&
|
|
29
|
+
typeof (value as { connect?: unknown }).connect === "function";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Thrown when a site runs this package's chain but has not applied all of it:
|
|
33
|
+
* the installed nk-auth expects a schema the database does not have yet. Every
|
|
34
|
+
* Better Auth bump that touches the schema ships as a new file in the chain,
|
|
35
|
+
* so this is the "deployed before migrating" mistake, caught at the first
|
|
36
|
+
* session read instead of at a customer's sign-in.
|
|
37
|
+
*/
|
|
38
|
+
export class AuthChainNotAppliedError extends Error {
|
|
39
|
+
readonly pending: readonly string[];
|
|
40
|
+
constructor(pending: readonly string[]) {
|
|
41
|
+
super(
|
|
42
|
+
`@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.`,
|
|
43
|
+
);
|
|
44
|
+
this.name = "AuthChainNotAppliedError";
|
|
45
|
+
this.pending = pending;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface AuthChainCheckOptions {
|
|
50
|
+
/** The journal table the site records the chain in. Default `__nkauth_migrations`. */
|
|
51
|
+
migrationsTable?: string;
|
|
52
|
+
/** The journal table's schema. Default `drizzle`. */
|
|
53
|
+
migrationsSchema?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Verify the shipped auth chain is fully applied to the database behind `pool`.
|
|
58
|
+
*
|
|
59
|
+
* Returns the runner's status when it is, or `null` when the database has no
|
|
60
|
+
* nk-auth journal table at all: a site that owns Better Auth's tables in its
|
|
61
|
+
* own chain (a squashed baseline) never records this one, and that is not a
|
|
62
|
+
* deployment mistake. Throws {@link AuthChainNotAppliedError} when the journal
|
|
63
|
+
* exists but lags the installed package.
|
|
64
|
+
*
|
|
65
|
+
* `createAuthHelpers` runs this once per process on the first session read
|
|
66
|
+
* when the instance's `database` is a `pg` Pool; call it yourself from
|
|
67
|
+
* `instrumentation.ts` or a health check to fail a deploy earlier.
|
|
68
|
+
*/
|
|
69
|
+
export const assertAuthChainApplied = async (
|
|
70
|
+
pool: Pool,
|
|
71
|
+
options: AuthChainCheckOptions = {},
|
|
72
|
+
): Promise<MigrationStatus | null> => {
|
|
73
|
+
const schema = options.migrationsSchema ?? "drizzle";
|
|
74
|
+
const table = options.migrationsTable ?? AUTH_MIGRATIONS_TABLE;
|
|
75
|
+
const { rows } = await pool.query<{ present: string | null }>(
|
|
76
|
+
"select to_regclass($1) as present",
|
|
77
|
+
[`"${schema}"."${table}"`],
|
|
78
|
+
);
|
|
79
|
+
if (rows[0]?.present === null || rows[0]?.present === undefined) return null;
|
|
80
|
+
const status = await inspectMigrations({
|
|
81
|
+
pool,
|
|
82
|
+
migrationsFolder: authMigrationsFolder(),
|
|
83
|
+
migrationsSchema: schema,
|
|
84
|
+
migrationsTable: table,
|
|
85
|
+
});
|
|
86
|
+
if (status.pending.length > 0) {
|
|
87
|
+
throw new AuthChainNotAppliedError(status.pending.map((file) => file.tag));
|
|
88
|
+
}
|
|
89
|
+
return status;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The once-per-process form `createAuthHelpers` uses: resolves the pool off
|
|
94
|
+
* the Better Auth options when there is one, memoizes the check, and rethrows
|
|
95
|
+
* the same error on every later call so a failed deploy stays failed rather
|
|
96
|
+
* than flapping. Anything that is not a `pg` Pool (a Drizzle or Kysely
|
|
97
|
+
* adapter) is skipped: the check has nothing to query through.
|
|
98
|
+
*/
|
|
99
|
+
export const authChainCheck = (
|
|
100
|
+
database: unknown,
|
|
101
|
+
options: AuthChainCheckOptions = {},
|
|
102
|
+
): (() => Promise<void>) => {
|
|
103
|
+
if (!isPool(database)) return async () => {};
|
|
104
|
+
let checked: Promise<void> | undefined;
|
|
105
|
+
return () => {
|
|
106
|
+
checked ??= assertAuthChainApplied(database, options).then(() => undefined);
|
|
107
|
+
return checked;
|
|
108
|
+
};
|
|
109
|
+
};
|
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
|
+
authMigrationsFolder,
|
|
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
|
-
*
|
|
146
|
-
*
|
|
147
|
-
* `
|
|
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)
|