@ingram-tech/nk-auth 0.10.0 → 0.11.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 +24 -50
- package/dist/gating-internals.d.ts +5 -0
- package/dist/gating-internals.d.ts.map +1 -1
- package/dist/gating-internals.js +8 -0
- package/dist/gating-internals.js.map +1 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +17 -1
- package/dist/jwt.js.map +1 -1
- package/dist/keys.d.ts +6 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +28 -4
- package/dist/keys.js.map +1 -1
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +15 -3
- package/dist/middleware.js.map +1 -1
- package/migrations/0001_better_auth.sql +0 -2
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ exactly one Better Auth copy in the app.
|
|
|
13
13
|
| --- | --- |
|
|
14
14
|
| `backendJwtOptions` / `verifyBackendJwt` (`./jwt`) | a JWT for the site's own backend API (custom `audience`) |
|
|
15
15
|
| `nkOrganizationDefaults`, `lastActiveOrganizationHooks`, `lastActiveOrganizationUserField` (`./organization`) | org-plugin defaults + active-org restore/persist |
|
|
16
|
-
| `createAuthPool` (`./pool`) | `
|
|
16
|
+
| `createAuthPool` (`./pool`) | **deprecated** — alias of `createPool` from [`@ingram-tech/nk-db`](../nk-db); inject the site's shared pool instead |
|
|
17
17
|
| `makeEmailSenders`, `makePasskeyOptions`, `passkeyOptionsForBaseUrl`, `uuidGenerateId` (`./`) | email hooks, passkeys (`passkeyOptionsForBaseUrl` derives `rpID`/`origin` from a single base URL), UUID ids |
|
|
18
18
|
| `bcryptPassword` (`./`) | **legacy only** — bcrypt verifier for sites with pre-existing bcrypt hashes. New sites omit it (Better Auth defaults to scrypt). See [Migrating bcrypt passwords to scrypt](#migrating-bcrypt-passwords-to-scrypt) |
|
|
19
19
|
| `createAuthHelpers`, `safeNext` (`./server`) | validated App Router session helpers (`getSession` / `getUser` / `requireSession` / `requireUser` / `redirectIfAuthenticated`) with automatic `next` + stale-cookie signalling; `safeNext` validates a `?next=` param |
|
|
@@ -24,11 +24,11 @@ exactly one Better Auth copy in the app.
|
|
|
24
24
|
> `withRls` / `withRlsTransaction` (claims taken from the Better Auth session — no
|
|
25
25
|
> JWT minting, no REST proxy).
|
|
26
26
|
>
|
|
27
|
-
> **Backend-JWT + org sites** (a backend API plus the org plugin): compose
|
|
28
|
-
> `backendJwtOptions({ audience })`,
|
|
29
|
-
> `lastActiveOrganizationHooks(pool)` in your
|
|
30
|
-
> tokens with `verifyBackendJwt`. Keep
|
|
31
|
-
> permissions/roles, connectors) in the app.
|
|
27
|
+
> **Backend-JWT + org sites** (a backend API plus the org plugin): compose the
|
|
28
|
+
> site's shared nk-db pool, `backendJwtOptions({ audience })`,
|
|
29
|
+
> `nkOrganizationDefaults`, and `lastActiveOrganizationHooks(pool)` in your
|
|
30
|
+
> `betterAuth()`; verify backend tokens with `verifyBackendJwt`. Keep
|
|
31
|
+
> app-specific bits (SSO restrictions, permissions/roles, connectors) in the app.
|
|
32
32
|
>
|
|
33
33
|
> **Note:** pin `kysely@0.28.x` in the consuming app (0.29 moved
|
|
34
34
|
> `DEFAULT_MIGRATION_TABLE` out of its barrel, breaking the adapter + the
|
|
@@ -48,6 +48,10 @@ BETTER_AUTH_URL=https://example.com
|
|
|
48
48
|
DATABASE_URL=… # direct Postgres connection (:5432)
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Outside production, `BETTER_AUTH_SECRET` falls back to a well-known insecure
|
|
52
|
+
placeholder, so local dev and tests run without setting it (a warning is logged).
|
|
53
|
+
In production it stays required — a missing secret throws at startup.
|
|
54
|
+
|
|
51
55
|
## 1. Apply the schema
|
|
52
56
|
|
|
53
57
|
```bash
|
|
@@ -80,7 +84,7 @@ import {
|
|
|
80
84
|
uuidGenerateId,
|
|
81
85
|
} from "@ingram-tech/nk-auth";
|
|
82
86
|
import { betterAuth } from "better-auth";
|
|
83
|
-
import {
|
|
87
|
+
import { pool } from "@/lib/db"; // the ONE shared createPool() from @ingram-tech/nk-db
|
|
84
88
|
|
|
85
89
|
const env = authEnv();
|
|
86
90
|
const email = makeEmailSenders(({ to, subject, url }) =>
|
|
@@ -88,7 +92,7 @@ const email = makeEmailSenders(({ to, subject, url }) =>
|
|
|
88
92
|
);
|
|
89
93
|
|
|
90
94
|
export const auth = betterAuth({
|
|
91
|
-
database:
|
|
95
|
+
database: pool, // inject the shared pool — exactly one pool per process
|
|
92
96
|
secret: env.secret,
|
|
93
97
|
baseURL: env.baseURL,
|
|
94
98
|
basePath: authBasePath, // mount at /auth, not the framework default /api/auth
|
|
@@ -124,8 +128,9 @@ export const auth = betterAuth({
|
|
|
124
128
|
// app/auth/[...all]/route.ts — a standard Next.js route handler.
|
|
125
129
|
// Lives at /auth (set via `basePath: authBasePath`), NOT /api/auth: auth is a
|
|
126
130
|
// user-facing surface (sign-in, OAuth callbacks), not an internal machine API.
|
|
131
|
+
import { toNextJsHandler } from "better-auth/next-js";
|
|
127
132
|
import { auth } from "@/lib/auth";
|
|
128
|
-
export const { GET, POST } = auth
|
|
133
|
+
export const { GET, POST } = toNextJsHandler(auth);
|
|
129
134
|
```
|
|
130
135
|
|
|
131
136
|
## 3. Query data with RLS intact
|
|
@@ -327,45 +332,14 @@ async redirects() {
|
|
|
327
332
|
## Migrating bcrypt passwords to scrypt
|
|
328
333
|
|
|
329
334
|
`bcryptPassword` is **legacy support only** (see its `@deprecated` note). It
|
|
330
|
-
exists so sites whose `account.password` hashes are bcrypt keep verifying.
|
|
331
|
-
Auth's default hasher is **scrypt** (`<salt-hex>:<key-hex>`), and bcrypt
|
|
332
|
-
are trivially distinguishable (they start with `$2a
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
- The full **password-reset flow** — `requestPasswordReset` → email →
|
|
341
|
-
`resetPassword`, which re-hashes with the configured (scrypt) hasher. We
|
|
342
|
-
already wire `sendResetPassword` via `makeEmailSenders`.
|
|
343
|
-
- Admin `setUserPassword` (admin plugin) for out-of-band overrides.
|
|
344
|
-
|
|
345
|
-
**What it does NOT have (we'd build it):**
|
|
346
|
-
|
|
347
|
-
- **Rehash-on-login.** Better Auth never re-hashes a password on successful
|
|
348
|
-
sign-in. There is no `needsRehash`.
|
|
349
|
-
- **A "must reset password" gate.** No native flag blocks sign-in until a user
|
|
350
|
-
resets; that's an extra `user` field + a `before` sign-in hook if you want it.
|
|
351
|
-
|
|
352
|
-
**The plan.** Replace `bcryptPassword` with a **dual-format verifier** (override
|
|
353
|
-
only `verify`, leaving `hash` as the scrypt default): branch on
|
|
354
|
-
`hash.startsWith("$2")` → bcrypt compare, else fall through to Better Auth's
|
|
355
|
-
scrypt verify. Old hashes keep working; every new signup, password change, and
|
|
356
|
-
reset is written as scrypt. Then upgrade existing hashes by one of:
|
|
357
|
-
|
|
358
|
-
1. **Lazy (preferred):** wrap the sign-in route (an nk-auth plugin endpoint or a
|
|
359
|
-
thin site route) so that, after a successful bcrypt verify, it re-hashes the
|
|
360
|
-
submitted plaintext with scrypt and persists it via
|
|
361
|
-
`internalAdapter.updatePassword(userId, newHash)`. This reconstructs the
|
|
362
|
-
rehash-on-login that core lacks, using only supported adapter calls — the
|
|
363
|
-
plaintext is only available here, at the sign-in request.
|
|
364
|
-
2. **Eager:** run a `requestPasswordReset` campaign for all bcrypt users; their
|
|
365
|
-
next reset writes scrypt. Pair with the dual-format verifier as the bridge
|
|
366
|
-
(there's no native "must reset" gate, so un-migrated users still log in via
|
|
367
|
-
bcrypt until they reset).
|
|
368
|
-
|
|
369
|
-
Either way the dual-format verifier is the one piece nk-auth should standardize;
|
|
370
|
-
the forced-reset gate is only worth building if a site needs a hard cutover.
|
|
335
|
+
exists so sites whose `account.password` hashes are bcrypt keep verifying.
|
|
336
|
+
Better Auth's default hasher is **scrypt** (`<salt-hex>:<key-hex>`), and bcrypt
|
|
337
|
+
hashes are trivially distinguishable (they start with `$2a$`/`$2b$`/`$2y$`), so
|
|
338
|
+
the migration path is a **dual-format verifier**: override only
|
|
339
|
+
`emailAndPassword.password.verify` to branch on `hash.startsWith("$2")` →
|
|
340
|
+
bcrypt compare, else Better Auth's scrypt verify. Old hashes keep working;
|
|
341
|
+
every new signup, password change, and reset writes scrypt. (Better Auth has no
|
|
342
|
+
rehash-on-login and no "must reset" gate, so bcrypt hashes only upgrade when
|
|
343
|
+
the user resets — or via a sign-in wrapper that persists a re-hash with
|
|
344
|
+
`internalAdapter.updatePassword`.)
|
|
371
345
|
**Status: proposed** — not yet shipped; `bcryptPassword` remains the stopgap.
|
|
@@ -13,6 +13,11 @@ export declare const NK_AUTH_PATH_HEADER = "x-nk-auth-path";
|
|
|
13
13
|
/**
|
|
14
14
|
* Accept only an internal, non-protocol-relative path as a post-login redirect,
|
|
15
15
|
* so `next` can never be turned into an open redirect to another origin.
|
|
16
|
+
*
|
|
17
|
+
* Beyond the `//` check, backslashes and ASCII controls must also be rejected:
|
|
18
|
+
* browsers treat `\` as `/` in http(s) URLs (so `/\evil.com` resolves to
|
|
19
|
+
* `https://evil.com/`) and the URL parser strips tab/newline (so an encoded
|
|
20
|
+
* `/\t/evil.com` collapses to `//evil.com`).
|
|
16
21
|
*/
|
|
17
22
|
export declare function safeNextParam(value: string | null | undefined): string | null;
|
|
18
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gating-internals.d.ts","sourceRoot":"","sources":["../src/gating-internals.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD
|
|
1
|
+
{"version":3,"file":"gating-internals.d.ts","sourceRoot":"","sources":["../src/gating-internals.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAM7E;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACxB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAC7C,MAAM,CAOR"}
|
package/dist/gating-internals.js
CHANGED
|
@@ -13,12 +13,20 @@ export const NK_AUTH_PATH_HEADER = "x-nk-auth-path";
|
|
|
13
13
|
/**
|
|
14
14
|
* Accept only an internal, non-protocol-relative path as a post-login redirect,
|
|
15
15
|
* so `next` can never be turned into an open redirect to another origin.
|
|
16
|
+
*
|
|
17
|
+
* Beyond the `//` check, backslashes and ASCII controls must also be rejected:
|
|
18
|
+
* browsers treat `\` as `/` in http(s) URLs (so `/\evil.com` resolves to
|
|
19
|
+
* `https://evil.com/`) and the URL parser strips tab/newline (so an encoded
|
|
20
|
+
* `/\t/evil.com` collapses to `//evil.com`).
|
|
16
21
|
*/
|
|
17
22
|
export function safeNextParam(value) {
|
|
18
23
|
if (!value)
|
|
19
24
|
return null;
|
|
20
25
|
if (!value.startsWith("/") || value.startsWith("//"))
|
|
21
26
|
return null;
|
|
27
|
+
// oxlint-disable-next-line no-control-regex -- rejecting control chars is the point
|
|
28
|
+
if (/[\\\u0000-\u001f\u007f]/.test(value))
|
|
29
|
+
return null;
|
|
22
30
|
return value;
|
|
23
31
|
}
|
|
24
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gating-internals.js","sourceRoot":"","sources":["../src/gating-internals.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD
|
|
1
|
+
{"version":3,"file":"gating-internals.js","sourceRoot":"","sources":["../src/gating-internals.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAgC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,oFAAoF;IACpF,IAAI,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,OAAO,KAAK,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CACxB,UAAkB,EAClB,IAA+C;IAE/C,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,IAAI;QAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,OAAO,EAAE,CAAC,CAAC,CAAC,GAAG,UAAU,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;AAChD,CAAC"}
|
package/dist/jwt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAGN,KAAK,UAAU,EAEf,MAAM,MAAM,CAAC;AAEd;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;IAChC,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,GAAI,QAAQ,gBAAgB,KAAG,UAM3D,CAAC;
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAGN,KAAK,UAAU,EAEf,MAAM,MAAM,CAAC;AAEd;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;IAChC,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,GAAI,QAAQ,gBAAgB,KAAG,UAM3D,CAAC;AAWH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAAU,QAAQ;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf,KAAG,OAAO,CAAC,UAAU,CAmCrB,CAAC"}
|
package/dist/jwt.js
CHANGED
|
@@ -8,6 +8,12 @@ export const backendJwtOptions = (config) => ({
|
|
|
8
8
|
},
|
|
9
9
|
});
|
|
10
10
|
const jwksCache = new Map();
|
|
11
|
+
// Forced-reload throttle (per JWKS URL). The no-matching-key path is reachable
|
|
12
|
+
// by anyone who can send a request with a made-up `kid`, and `.reload()`
|
|
13
|
+
// deliberately bypasses jose's cooldown — unthrottled, garbage tokens would
|
|
14
|
+
// each cost a fresh fetch against the auth origin.
|
|
15
|
+
const RELOAD_COOLDOWN_MS = 30_000;
|
|
16
|
+
const lastForcedReload = new Map();
|
|
11
17
|
/**
|
|
12
18
|
* Verify a Better-Auth-minted backend JWT (EdDSA) against the issuer's JWKS.
|
|
13
19
|
* For use by the backend that consumes `backendJwtOptions` tokens. Throws on an
|
|
@@ -33,6 +39,9 @@ export const verifyBackendJwt = async (params) => {
|
|
|
33
39
|
audience: params.audience,
|
|
34
40
|
issuer: params.issuer,
|
|
35
41
|
algorithms: ["EdDSA"],
|
|
42
|
+
// Tolerate a few seconds of clock skew between the minting and verifying
|
|
43
|
+
// hosts; jose's default of 0 fails legitimate tokens at exp/nbf boundaries.
|
|
44
|
+
clockTolerance: 5,
|
|
36
45
|
};
|
|
37
46
|
try {
|
|
38
47
|
const { payload } = await jwtVerify(params.token, jwks, options);
|
|
@@ -42,7 +51,14 @@ export const verifyBackendJwt = async (params) => {
|
|
|
42
51
|
if (!(err instanceof joseErrors.JWKSNoMatchingKey))
|
|
43
52
|
throw err;
|
|
44
53
|
// `kid` absent from the cached JWKS — almost always a just-rotated signing
|
|
45
|
-
// key. Force a refresh past the cooldown and retry exactly once
|
|
54
|
+
// key. Force a refresh past the cooldown and retry exactly once, at most
|
|
55
|
+
// once per cooldown window so unknown-`kid` garbage can't hammer the
|
|
56
|
+
// issuer.
|
|
57
|
+
const now = Date.now();
|
|
58
|
+
if (now - (lastForcedReload.get(cacheKey) ?? 0) < RELOAD_COOLDOWN_MS) {
|
|
59
|
+
throw err;
|
|
60
|
+
}
|
|
61
|
+
lastForcedReload.set(cacheKey, now);
|
|
46
62
|
await jwks.reload();
|
|
47
63
|
const { payload } = await jwtVerify(params.token, jwks, options);
|
|
48
64
|
return payload;
|
package/dist/jwt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AACA,OAAO,EACN,kBAAkB,EAClB,MAAM,IAAI,UAAU,EAEpB,SAAS,GACT,MAAM,MAAM,CAAC;AAqBd,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAwB,EAAc,EAAE,CAAC,CAAC;IAC3E,uBAAuB,EAAE,MAAM,CAAC,uBAAuB;IACvD,GAAG,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,cAAc,EAAE,MAAM,CAAC,cAAc;KACrC;CACD,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,MAMtC,EAAuB,EAAE;IACzB,MAAM,GAAG,GACR,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAC/E,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC/B,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,GAAG;QACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,CAAC,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AACA,OAAO,EACN,kBAAkB,EAClB,MAAM,IAAI,UAAU,EAEpB,SAAS,GACT,MAAM,MAAM,CAAC;AAqBd,sEAAsE;AACtE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAwB,EAAc,EAAE,CAAC,CAAC;IAC3E,uBAAuB,EAAE,MAAM,CAAC,uBAAuB;IACvD,GAAG,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,cAAc,EAAE,MAAM,CAAC,cAAc;KACrC;CACD,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAiD,CAAC;AAE3E,+EAA+E;AAC/E,yEAAyE;AACzE,4EAA4E;AAC5E,mDAAmD;AACnD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEnD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,MAMtC,EAAuB,EAAE;IACzB,MAAM,GAAG,GACR,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;IAC/E,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAChC,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,IAAI,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC/B,SAAS,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,GAAG;QACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,CAAC,OAAO,CAAC;QACrB,yEAAyE;QACzE,4EAA4E;QAC5E,cAAc,EAAE,CAAC;KACjB,CAAC;IACF,IAAI,CAAC;QACJ,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACjE,OAAO,OAAO,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,IAAI,CAAC,CAAC,GAAG,YAAY,UAAU,CAAC,iBAAiB,CAAC;YAAE,MAAM,GAAG,CAAC;QAC9D,2EAA2E;QAC3E,yEAAyE;QACzE,qEAAqE;QACrE,UAAU;QACV,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,kBAAkB,EAAE,CAAC;YACtE,MAAM,GAAG,CAAC;QACX,CAAC;QACD,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACpC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACjE,OAAO,OAAO,CAAC;IAChB,CAAC;AACF,CAAC,CAAC"}
|
package/dist/keys.d.ts
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* BETTER_AUTH_SECRET — session/CSRF signing key (`openssl rand -hex 32`)
|
|
11
11
|
* BETTER_AUTH_URL — canonical site origin, e.g. "https://example.com"
|
|
12
12
|
* DATABASE_URL — direct Postgres connection for Better Auth (:5432)
|
|
13
|
+
*
|
|
14
|
+
* Outside production, BETTER_AUTH_SECRET falls back to a well-known insecure
|
|
15
|
+
* placeholder so local dev and tests run without hand-setting it (`nk dev` and
|
|
16
|
+
* plain `next dev` alike). In production it stays strictly required — a missing
|
|
17
|
+
* secret throws at startup rather than silently signing sessions with a value
|
|
18
|
+
* anyone could guess.
|
|
13
19
|
*/
|
|
14
20
|
export interface AuthEnv {
|
|
15
21
|
secret: string;
|
package/dist/keys.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA6BH,MAAM,WAAW,OAAO;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAO,OAoB1B,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,YAAY,QAAO,OAAuD,CAAC"}
|
package/dist/keys.js
CHANGED
|
@@ -10,20 +10,40 @@
|
|
|
10
10
|
* BETTER_AUTH_SECRET — session/CSRF signing key (`openssl rand -hex 32`)
|
|
11
11
|
* BETTER_AUTH_URL — canonical site origin, e.g. "https://example.com"
|
|
12
12
|
* DATABASE_URL — direct Postgres connection for Better Auth (:5432)
|
|
13
|
+
*
|
|
14
|
+
* Outside production, BETTER_AUTH_SECRET falls back to a well-known insecure
|
|
15
|
+
* placeholder so local dev and tests run without hand-setting it (`nk dev` and
|
|
16
|
+
* plain `next dev` alike). In production it stays strictly required — a missing
|
|
17
|
+
* secret throws at startup rather than silently signing sessions with a value
|
|
18
|
+
* anyone could guess.
|
|
13
19
|
*/
|
|
14
20
|
import { z } from "zod";
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Well-known, deliberately insecure secret used ONLY when NODE_ENV is not
|
|
23
|
+
* "production". Never applied to a production build — see `secretField`.
|
|
24
|
+
*/
|
|
25
|
+
const DEV_SECRET_PLACEHOLDER = "nk-dev-insecure-placeholder-not-for-production";
|
|
26
|
+
/**
|
|
27
|
+
* In production the secret is required; everywhere else it defaults to the
|
|
28
|
+
* insecure placeholder. Read NODE_ENV at parse time (not module load) so the
|
|
29
|
+
* same process can be exercised under either mode.
|
|
30
|
+
*/
|
|
31
|
+
const secretField = () => process.env.NODE_ENV === "production"
|
|
32
|
+
? z.string().min(1)
|
|
33
|
+
: z.string().min(1).default(DEV_SECRET_PLACEHOLDER);
|
|
34
|
+
const buildSchema = () => z.object({
|
|
35
|
+
BETTER_AUTH_SECRET: secretField(),
|
|
17
36
|
BETTER_AUTH_URL: z.string().url(),
|
|
18
37
|
DATABASE_URL: z.string().min(1),
|
|
19
38
|
});
|
|
39
|
+
let warnedPlaceholder = false;
|
|
20
40
|
/**
|
|
21
41
|
* Read and validate all nk-auth env vars at once. Throws a single error listing
|
|
22
42
|
* everything missing/invalid, so a misconfigured site fails fast at startup
|
|
23
43
|
* rather than at first sign-in.
|
|
24
44
|
*/
|
|
25
45
|
export const authEnv = () => {
|
|
26
|
-
const result =
|
|
46
|
+
const result = buildSchema().safeParse(process.env);
|
|
27
47
|
if (!result.success) {
|
|
28
48
|
const issues = result.error.issues
|
|
29
49
|
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
@@ -31,6 +51,10 @@ export const authEnv = () => {
|
|
|
31
51
|
throw new Error(`@ingram-tech/nk-auth: invalid environment — ${issues}`);
|
|
32
52
|
}
|
|
33
53
|
const env = result.data;
|
|
54
|
+
if (env.BETTER_AUTH_SECRET === DEV_SECRET_PLACEHOLDER && !warnedPlaceholder) {
|
|
55
|
+
warnedPlaceholder = true;
|
|
56
|
+
console.warn("@ingram-tech/nk-auth: BETTER_AUTH_SECRET is unset — using an insecure dev placeholder. Set it before deploying.");
|
|
57
|
+
}
|
|
34
58
|
return {
|
|
35
59
|
secret: env.BETTER_AUTH_SECRET,
|
|
36
60
|
baseURL: env.BETTER_AUTH_URL,
|
|
@@ -38,5 +62,5 @@ export const authEnv = () => {
|
|
|
38
62
|
};
|
|
39
63
|
};
|
|
40
64
|
/** Whether nk-auth is fully configured (lets callers degrade in local/dev). */
|
|
41
|
-
export const isConfigured = () =>
|
|
65
|
+
export const isConfigured = () => buildSchema().safeParse(process.env).success;
|
|
42
66
|
//# sourceMappingURL=keys.js.map
|
package/dist/keys.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,sBAAsB,GAAG,gDAAgD,CAAC;AAEhF;;;;GAIG;AACH,MAAM,WAAW,GAAG,GAAG,EAAE,CACxB,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;IACpC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACnB,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAEtD,MAAM,WAAW,GAAG,GAAG,EAAE,CACxB,CAAC,CAAC,MAAM,CAAC;IACR,kBAAkB,EAAE,WAAW,EAAE;IACjC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACjC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC/B,CAAC,CAAC;AAEJ,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAQ9B;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,GAAY,EAAE;IACpC,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAChC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC;IACxB,IAAI,GAAG,CAAC,kBAAkB,KAAK,sBAAsB,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC7E,iBAAiB,GAAG,IAAI,CAAC;QACzB,OAAO,CAAC,IAAI,CACX,iHAAiH,CACjH,CAAC;IACH,CAAC;IACD,OAAO;QACN,MAAM,EAAE,GAAG,CAAC,kBAAkB;QAC9B,OAAO,EAAE,GAAG,CAAC,eAAe;QAC5B,WAAW,EAAE,GAAG,CAAC,YAAY;KAC7B,CAAC;AACH,CAAC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,YAAY,GAAG,GAAY,EAAE,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC"}
|
package/dist/middleware.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG7D,MAAM,WAAW,oBAAoB;IACpC;;;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,IAyBrC,SAAS,WAAW,KAAG,YAAY,
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG7D,MAAM,WAAW,oBAAoB;IACpC;;;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,IAyBrC,SAAS,WAAW,KAAG,YAAY,CAkE9D"}
|
package/dist/middleware.js
CHANGED
|
@@ -56,8 +56,18 @@ export function createAuthMiddleware(config) {
|
|
|
56
56
|
to.searchParams.delete("stale");
|
|
57
57
|
const res = NextResponse.redirect(to);
|
|
58
58
|
for (const cookie of request.cookies.getAll()) {
|
|
59
|
-
if (cookie.name.includes(cookiePrefix))
|
|
60
|
-
|
|
59
|
+
if (cookie.name.includes(cookiePrefix)) {
|
|
60
|
+
// `__Secure-`/`__Host-` cookies: browsers reject the deletion
|
|
61
|
+
// Set-Cookie unless it carries the Secure attribute itself, so a
|
|
62
|
+
// bare delete() would silently leave the dead cookie in place on
|
|
63
|
+
// HTTPS and re-run this handshake on every visit.
|
|
64
|
+
res.cookies.delete({
|
|
65
|
+
name: cookie.name,
|
|
66
|
+
path: "/",
|
|
67
|
+
secure: cookie.name.startsWith("__Secure-") ||
|
|
68
|
+
cookie.name.startsWith("__Host-"),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
61
71
|
}
|
|
62
72
|
return res;
|
|
63
73
|
}
|
|
@@ -65,7 +75,9 @@ export function createAuthMiddleware(config) {
|
|
|
65
75
|
// 2. Unauthenticated (no cookie) on a protected path -> sign in, and
|
|
66
76
|
// remember where they were going.
|
|
67
77
|
if (!hasSessionCookie &&
|
|
68
|
-
|
|
78
|
+
// Segment-boundary match: "/app" must gate "/app" and "/app/x" but not
|
|
79
|
+
// "/application".
|
|
80
|
+
config.protectedPaths.some((p) => path === p || path.startsWith(`${p}/`))) {
|
|
69
81
|
const original = request.nextUrl.pathname + request.nextUrl.search;
|
|
70
82
|
const to = request.nextUrl.clone();
|
|
71
83
|
to.pathname = signInPath;
|
package/dist/middleware.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA0B3E,MAAM,UAAU,oBAAoB,CAAC,MAA4B;IAChE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,QAAQ,CAAC;IACjD,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC;IACnD,MAAM,YAAY,GAAG,MAAM,CAAC,mBAAmB,IAAI,aAAa,CAAC;IAEjE,uEAAuE;IACvE,kEAAkE;IAClE,4EAA4E;IAC5E,qCAAqC;IACrC,IAAI,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CACd,qCAAqC,UAAU,8FAA8F,CAC7I,CAAC;IACH,CAAC;IACD,IAAI,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACd,qCAAqC,UAAU,sFAAsF,CACrI,CAAC;IACH,CAAC;IACD,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACd,gFAAgF,CAChF,CAAC;IACH,CAAC;IAED,OAAO,SAAS,UAAU,CAAC,OAAoB;QAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;QAEtC,2EAA2E;QAC3E,2EAA2E;QAC3E,yEAAyE;QACzE,2EAA2E;QAC3E,IAAI,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;YAC9E,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAChC,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC/C,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,8 +16,6 @@
|
|
|
16
16
|
-- which bypasses RLS, so denying the app's RLS role any access here costs
|
|
17
17
|
-- nothing and keeps the auth tables off-limits to user-facing queries.
|
|
18
18
|
|
|
19
|
-
create extension if not exists "pgcrypto" with schema "extensions";
|
|
20
|
-
|
|
21
19
|
create table if not exists "public"."user" (
|
|
22
20
|
"id" text primary key default gen_random_uuid()::text, -- hardening (1)
|
|
23
21
|
"name" text 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.11.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,12 +60,12 @@
|
|
|
60
60
|
"./migrations/*": "./migrations/*"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
|
-
"build": "tsc -p tsconfig.json",
|
|
63
|
+
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json",
|
|
64
64
|
"type-check": "tsc -p tsconfig.json --noEmit",
|
|
65
65
|
"test": "vitest run"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@ingram-tech/nk-db": "^1.
|
|
68
|
+
"@ingram-tech/nk-db": "^1.2.0",
|
|
69
69
|
"bcrypt": "^6.0.0",
|
|
70
70
|
"jose": "^6.2.3",
|
|
71
71
|
"zod": "^4.4.3"
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
},
|
|
94
94
|
"devDependencies": {
|
|
95
95
|
"@better-auth/passkey": "^1.6.22",
|
|
96
|
-
"@ingram-tech/nk-dev": "0.2.
|
|
96
|
+
"@ingram-tech/nk-dev": "0.2.5",
|
|
97
97
|
"@types/bcrypt": "^6.0.0",
|
|
98
98
|
"@types/node": "^26.0.1",
|
|
99
99
|
"@types/pg": "^8.20.0",
|