@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 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`) | `pg` Pool with optional SSL CA verification |
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 `createAuthPool`,
28
- > `backendJwtOptions({ audience })`, `nkOrganizationDefaults`, and
29
- > `lastActiveOrganizationHooks(pool)` in your `betterAuth()`; verify backend
30
- > tokens with `verifyBackendJwt`. Keep app-specific bits (SSO restrictions,
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 { Pool } from "pg";
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: new Pool({ connectionString: env.databaseUrl }),
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.handler;
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. Better
331
- Auth's default hasher is **scrypt** (`<salt-hex>:<key-hex>`), and bcrypt hashes
332
- are trivially distinguishable (they start with `$2a$` / `$2b$` / `$2y$`), so a
333
- clean migration is possible without a schema change.
334
-
335
- **What Better Auth gives you natively (v1.6):**
336
-
337
- - A custom `emailAndPassword.password.verify` / `.hash` but `verify` only
338
- receives `{ hash, password }`; it gets **no** `userId`/adapter, so it can't
339
- persist an upgraded hash by itself.
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;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAI7E;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"}
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"}
@@ -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;;;GAGG;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,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"}
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;AAIH;;;;;;;;;;;;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,CAyBrB,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;KACrB,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,iEAAiE;QACjE,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"}
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;
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,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,OAc1B,CAAC;AAEF,+EAA+E;AAC/E,eAAO,MAAM,YAAY,QAAO,OAAgD,CAAC"}
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
- const schema = z.object({
16
- BETTER_AUTH_SECRET: z.string().min(1),
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 = schema.safeParse(process.env);
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 = () => schema.safeParse(process.env).success;
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;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACvB,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrC,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;AAQH;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,GAAY,EAAE;IACpC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,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,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,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC"}
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"}
@@ -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,CAoD9D"}
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"}
@@ -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
- res.cookies.delete(cookie.name);
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
- config.protectedPaths.some((p) => path.startsWith(p))) {
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;
@@ -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":"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,EAAE,CAAC;oBACxC,8DAA8D;oBAC9D,iEAAiE;oBACjE,iEAAiE;oBACjE,kDAAkD;oBAClD,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;wBAClB,IAAI,EAAE,MAAM,CAAC,IAAI;wBACjB,IAAI,EAAE,GAAG;wBACT,MAAM,EACL,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;4BACnC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;qBAClC,CAAC,CAAC;gBACJ,CAAC;YACF,CAAC;YACD,OAAO,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,gBAAgB,GAAG,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAErD,qEAAqE;QACrE,qCAAqC;QACrC,IACC,CAAC,gBAAgB;YACjB,uEAAuE;YACvE,kBAAkB;YAClB,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EACxE,CAAC;YACF,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;YACnE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnC,EAAE,CAAC,QAAQ,GAAG,UAAU,CAAC;YACzB,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;YACrC,IAAI,IAAI;gBAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC5C,OAAO,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,2EAA2E;QAC3E,IACC,gBAAgB;YAChB,MAAM,CAAC,gBAAgB;YACvB,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,EAC5B,CAAC;YACF,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACnC,EAAE,CAAC,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC;YACtC,EAAE,CAAC,MAAM,GAAG,EAAE,CAAC;YACf,OAAO,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;QAED,2EAA2E;QAC3E,qDAAqD;QACrD,MAAM,cAAc,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,cAAc,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACvE,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC,CAAC;AACH,CAAC"}
@@ -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.10.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.1.0",
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.4",
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",