@ingram-tech/nk-auth 0.7.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,23 +6,23 @@ composable presets each Ingram Next.js site spreads into its *own*
6
6
  plain Better Auth (prime directive), keeping full plugin type inference. Import
7
7
  only what you need from focused subpaths.
8
8
 
9
- `better-auth`, `pg`, `@better-auth/passkey`, `@supabase/supabase-js` are
10
- **peer dependencies** so there's exactly one Better Auth copy in the app.
9
+ `better-auth`, `pg`, `@better-auth/passkey` are **peer dependencies** so there's
10
+ exactly one Better Auth copy in the app.
11
11
 
12
12
  | Export (subpath) | For |
13
13
  | --- | --- |
14
- | `rlsJwtOptions` (`./jwt`) | Supabase RLS bridge token (`role:"authenticated"`) |
15
14
  | `backendJwtOptions` / `verifyBackendJwt` (`./jwt`) | a JWT for the site's own backend API (custom `audience`) |
16
15
  | `nkOrganizationDefaults`, `lastActiveOrganizationHooks`, `lastActiveOrganizationUserField` (`./organization`) | org-plugin defaults + active-org restore/persist |
17
16
  | `createAuthPool` (`./pool`) | `pg` Pool with optional SSL CA verification |
18
- | `bcryptPassword`, `makeEmailSenders`, `makePasskeyOptions`, `uuidGenerateId` (`./`) | password migration, email hooks, passkeys, UUID ids |
19
- | `createServerSupabase` (`./`) | RLS-aware supabase-js client (attaches the session JWT) |
17
+ | `makeEmailSenders`, `makePasskeyOptions`, `passkeyOptionsForBaseUrl`, `uuidGenerateId` (`./`) | email hooks, passkeys (`passkeyOptionsForBaseUrl` derives `rpID`/`origin` from a single base URL), UUID ids |
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) |
20
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 |
21
20
  | `createAuthMiddleware` (`./middleware`) | loop-safe edge middleware: gates unauthenticated users off protected paths, preserves `next`, and clears a stale session cookie so a bad session self-heals |
22
21
 
23
- > **Supabase Auth Better Auth + RLS migration?** Read
24
- > [`docs/better-auth-migration.md`](../../docs/better-auth-migration.md) the
25
- > RLS bridge, the migration runbook, and the gotchas.
22
+ > **Data access + RLS** is owned by [`@ingram-tech/nk-db`](../nk-db): query over a
23
+ > direct `pg` connection and enforce per-request Row Level Security with
24
+ > `withRls` / `withRlsTransaction` (claims taken from the Better Auth session — no
25
+ > JWT minting, no REST proxy).
26
26
  >
27
27
  > **Backend-JWT + org sites** (a backend API plus the org plugin): compose `createAuthPool`,
28
28
  > `backendJwtOptions({ audience })`, `nkOrganizationDefaults`, and
@@ -37,7 +37,7 @@ only what you need from focused subpaths.
37
37
  ## Install
38
38
 
39
39
  ```bash
40
- bun add @ingram-tech/nk-auth @supabase/supabase-js better-auth @better-auth/passkey pg bcrypt
40
+ bun add @ingram-tech/nk-auth better-auth @better-auth/passkey pg bcrypt
41
41
  ```
42
42
 
43
43
  Set the env contract (validated by `keys.ts`):
@@ -45,46 +45,41 @@ Set the env contract (validated by `keys.ts`):
45
45
  ```dotenv
46
46
  BETTER_AUTH_SECRET=… # openssl rand -hex 32
47
47
  BETTER_AUTH_URL=https://example.com
48
- DATABASE_URL=… # DIRECT Postgres (session pooler / :5432), NOT PostgREST
49
- NEXT_PUBLIC_SUPABASE_URL=…
50
- NEXT_PUBLIC_SUPABASE_ANON_KEY=…
48
+ DATABASE_URL=… # direct Postgres connection (:5432)
51
49
  ```
52
50
 
53
51
  ## 1. Apply the schema
54
52
 
55
53
  ```bash
56
54
  cp node_modules/@ingram-tech/nk-auth/migrations/0001_better_auth.sql \
57
- supabase/migrations/$(date +%Y%m%d%H%M%S)_better_auth.sql
55
+ migrations/$(date +%Y%m%d%H%M%S)_better_auth.sql
58
56
  ```
59
57
 
60
58
  It creates Better Auth's tables (`user`, `session`, `account`, `verification`,
61
59
  `jwks`, `passkey`), defaults new user ids to UUIDs, and puts **deny-all RLS** on
62
- all of them (they're exposed via PostgREST; Better Auth uses its own privileged
63
- connection). Reconcile against your pinned `better-auth` with
64
- `npx @better-auth/cli generate` after upgrades.
60
+ all of them (Better Auth reaches them through its own privileged connection).
61
+ Reconcile against your pinned `better-auth` with `npx @better-auth/cli generate`
62
+ after upgrades.
65
63
 
66
64
  ## 2. Configure the server
67
65
 
68
66
  This package is **not** a `betterAuth()` wrapper — your site calls `betterAuth`
69
67
  itself and spreads in our presets. That keeps full Better Auth type inference at
70
68
  the call site (so `auth.api.*` stays typed) and keeps the site plain Better Auth,
71
- per the prime directive. The presets carry the RLS-preserving bits.
69
+ per the prime directive.
72
70
 
73
71
  ```ts
74
72
  // lib/auth.ts
75
73
  import { passkey } from "@better-auth/passkey";
76
- import { fromAddress, sendEmail } from "@ingram-tech/email";
74
+ import { fromAddress, sendEmail } from "@ingram-tech/nk-email";
77
75
  import {
78
76
  authBasePath,
79
77
  authEnv,
80
- bcryptPassword,
81
78
  makeEmailSenders,
82
- makePasskeyOptions,
83
- rlsJwtOptions,
79
+ passkeyOptionsForBaseUrl,
84
80
  uuidGenerateId,
85
81
  } from "@ingram-tech/nk-auth";
86
82
  import { betterAuth } from "better-auth";
87
- import { jwt } from "better-auth/plugins/jwt";
88
83
  import { Pool } from "pg";
89
84
 
90
85
  const env = authEnv();
@@ -98,14 +93,15 @@ export const auth = betterAuth({
98
93
  baseURL: env.baseURL,
99
94
  basePath: authBasePath, // mount at /auth, not the framework default /api/auth
100
95
  advanced: { database: { generateId: uuidGenerateId } }, // UUIDv7 ids
101
- // ^ stored as hyphenated UUIDv7 (uuid columns / Supabase RLS stay valid).
96
+ // ^ stored as hyphenated UUIDv7 (uuid columns / RLS policies stay valid).
102
97
  // To show those same ids as prefixed base58 on the wire/UI — `team_…`,
103
98
  // matching the Ingram Cloud API's `agt_`/`smt_` ids — skin them with
104
99
  // `toPrefixedId(uuid, "team")` / recover with `fromPrefixedId`. `base58Id`
105
100
  // mints a fresh one directly for text-id sites. All from `@ingram-tech/nk-auth`.
106
101
  emailAndPassword: {
107
102
  enabled: true,
108
- password: bcryptPassword, // verifies migrated Supabase bcrypt hashes
103
+ // Better Auth hashes with scrypt by default. Only sites carrying
104
+ // pre-existing bcrypt hashes set `password: bcryptPassword` (legacy).
109
105
  sendResetPassword: email.sendResetPassword,
110
106
  },
111
107
  emailVerification: { sendVerificationEmail: email.sendVerificationEmail },
@@ -116,8 +112,10 @@ export const auth = betterAuth({
116
112
  },
117
113
  },
118
114
  plugins: [
119
- jwt(rlsJwtOptions), // the RLS bridge
120
- passkey(makePasskeyOptions({ rpId: "example.com", rpName: "Example", origin: env.baseURL })),
115
+ // Single sign-in origin: derive rpID (host) + origin from the base URL.
116
+ // For multi-origin / parent-domain sites use makePasskeyOptions({ rpId,
117
+ // rpName, origin }) and pass the registrable domain explicitly.
118
+ passkey(passkeyOptionsForBaseUrl(env.baseURL, "Example")),
121
119
  ],
122
120
  });
123
121
  ```
@@ -132,29 +130,11 @@ export const { GET, POST } = auth.handler;
132
130
 
133
131
  ## 3. Query data with RLS intact
134
132
 
135
- Always go through `createServerSupabase` instead of constructing supabase-js
136
- yourself it attaches the Better Auth JWT so `auth.uid()` keeps working. Wire
137
- `getToken` to your instance's jwt endpoint (fully typed because *your* site owns
138
- the `betterAuth` call):
139
-
140
- ```ts
141
- import { authEnv, createServerSupabase } from "@ingram-tech/nk-auth";
142
- import { auth } from "@/lib/auth";
143
-
144
- export async function GET(request: Request) {
145
- const env = authEnv();
146
- const supabase = createServerSupabase({
147
- getToken: async () =>
148
- (await auth.api.getToken({ headers: request.headers }))?.token ?? null,
149
- supabaseUrl: env.supabaseUrl,
150
- supabaseAnonKey: env.supabaseAnonKey,
151
- });
152
- // Reads/writes run as the signed-in user; existing RLS policies apply.
153
- const { data, error } = await supabase.from("notes").select("*");
154
- if (error) throw new Error(error.message);
155
- return Response.json(data);
156
- }
157
- ```
133
+ Data access lives in [`@ingram-tech/nk-db`](../nk-db), not here. Query over the
134
+ direct `pg` connection and wrap reads/writes in `withRls` / `withRlsTransaction`,
135
+ which set `request.jwt.claims` + `SET LOCAL ROLE` per transaction from the Better
136
+ Auth session — so `auth.uid()` policies fire unchanged, with no JWT minting and
137
+ no REST proxy. See its README for the pattern.
158
138
 
159
139
  ## 4. Client
160
140
 
@@ -264,11 +244,48 @@ middleware injects — so the self-heal (next + clearing) needs the middleware.
264
244
  Sites that skip middleware still get validated gating from the server helpers,
265
245
  just without automatic `next`/clearing.
266
246
 
267
- ## RLS bridge (the important part)
268
-
269
- `auth.uid()` reads the `sub` claim of the JWT PostgREST receives. The `jwt`
270
- plugin (configured here) mints an asymmetric token with `sub` = the user's UUID
271
- and `role: "authenticated"`, exposed at `/auth/jwks`. Register that JWKS URL
272
- as a Supabase **third-party auth** issuer, and every existing policy works
273
- unchanged. Full rationale and the HS256 fallback:
274
- [`docs/better-auth-migration.md`](../../docs/better-auth-migration.md).
247
+ ## Migrating bcrypt passwords to scrypt
248
+
249
+ `bcryptPassword` is **legacy support only** (see its `@deprecated` note). It
250
+ exists so sites whose `account.password` hashes are bcrypt keep verifying. Better
251
+ Auth's default hasher is **scrypt** (`<salt-hex>:<key-hex>`), and bcrypt hashes
252
+ are trivially distinguishable (they start with `$2a$` / `$2b$` / `$2y$`), so a
253
+ clean migration is possible without a schema change.
254
+
255
+ **What Better Auth gives you natively (v1.6):**
256
+
257
+ - A custom `emailAndPassword.password.verify` / `.hash` — but `verify` only
258
+ receives `{ hash, password }`; it gets **no** `userId`/adapter, so it can't
259
+ persist an upgraded hash by itself.
260
+ - The full **password-reset flow** — `requestPasswordReset` → email →
261
+ `resetPassword`, which re-hashes with the configured (scrypt) hasher. We
262
+ already wire `sendResetPassword` via `makeEmailSenders`.
263
+ - Admin `setUserPassword` (admin plugin) for out-of-band overrides.
264
+
265
+ **What it does NOT have (we'd build it):**
266
+
267
+ - **Rehash-on-login.** Better Auth never re-hashes a password on successful
268
+ sign-in. There is no `needsRehash`.
269
+ - **A "must reset password" gate.** No native flag blocks sign-in until a user
270
+ resets; that's an extra `user` field + a `before` sign-in hook if you want it.
271
+
272
+ **The plan.** Replace `bcryptPassword` with a **dual-format verifier** (override
273
+ only `verify`, leaving `hash` as the scrypt default): branch on
274
+ `hash.startsWith("$2")` → bcrypt compare, else fall through to Better Auth's
275
+ scrypt verify. Old hashes keep working; every new signup, password change, and
276
+ reset is written as scrypt. Then upgrade existing hashes by one of:
277
+
278
+ 1. **Lazy (preferred):** wrap the sign-in route (an nk-auth plugin endpoint or a
279
+ thin site route) so that, after a successful bcrypt verify, it re-hashes the
280
+ submitted plaintext with scrypt and persists it via
281
+ `internalAdapter.updatePassword(userId, newHash)`. This reconstructs the
282
+ rehash-on-login that core lacks, using only supported adapter calls — the
283
+ plaintext is only available here, at the sign-in request.
284
+ 2. **Eager:** run a `requestPasswordReset` campaign for all bcrypt users; their
285
+ next reset writes scrypt. Pair with the dual-format verifier as the bridge
286
+ (there's no native "must reset" gate, so un-migrated users still log in via
287
+ bcrypt until they reset).
288
+
289
+ Either way the dual-format verifier is the one piece nk-auth should standardize;
290
+ the forced-reset gate is only worth building if a site needs a hard cutover.
291
+ **Status: proposed** — not yet shipped; `bcryptPassword` remains the stopgap.
package/dist/client.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * plugins: [jwtClient(), passkeyClient()],
14
14
  * });
15
15
  *
16
- * This exposes the call surface that replaces `supabase.auth.*`:
16
+ * This exposes the client auth call surface:
17
17
  * signUp.email / signIn.email / signIn.social / signOut / useSession,
18
18
  * plus passkey.* (register / authenticate).
19
19
  */
package/dist/client.js CHANGED
@@ -13,7 +13,7 @@
13
13
  * plugins: [jwtClient(), passkeyClient()],
14
14
  * });
15
15
  *
16
- * This exposes the call surface that replaces `supabase.auth.*`:
16
+ * This exposes the client auth call surface:
17
17
  * signUp.email / signIn.email / signIn.social / signOut / useSession,
18
18
  * plus passkey.* (register / authenticate).
19
19
  */
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- export { type BackendJwtConfig, backendJwtOptions, rlsJwtOptions, verifyBackendJwt, } from "./jwt.js";
1
+ export { type BackendJwtConfig, backendJwtOptions, verifyBackendJwt } from "./jwt.js";
2
2
  export { base58Id, fromPrefixedId, toPrefixedId, uuidGenerateId } from "./id.js";
3
3
  export { type AuthEnv, authEnv, isConfigured } from "./keys.js";
4
- export { bcryptPassword, makeEmailSenders, makePasskeyOptions, type PasskeyConfig, type SendEmail, } from "./options.js";
4
+ export { bcryptPassword, makeEmailSenders, makePasskeyOptions, type PasskeyConfig, passkeyOptionsForBaseUrl, type SendEmail, } from "./options.js";
5
5
  export { lastActiveOrganizationHooks, lastActiveOrganizationUserField, nkOrganizationDefaults, } from "./organization.js";
6
6
  export { authBasePath } from "./paths.js";
7
7
  export { createAuthPool } from "./pool.js";
8
- export { createServerSupabase, type ServerSupabaseConfig } from "./supabase.js";
9
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACN,KAAK,gBAAgB,EACrB,iBAAiB,EACjB,aAAa,EACb,gBAAgB,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACN,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,aAAa,EAClB,KAAK,SAAS,GACd,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
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,YAAY,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACN,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,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  // Server entry — the Ingram Better Auth foundation. Browser re-exports live at
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
- // only what it needs (e.g. avoid bcrypt/supabase when it uses neither).
5
- export { backendJwtOptions, rlsJwtOptions, verifyBackendJwt, } from "./jwt.js";
4
+ // only what it needs (e.g. avoid bcrypt when it doesn't use passwords).
5
+ export { backendJwtOptions, verifyBackendJwt } from "./jwt.js";
6
6
  export { base58Id, fromPrefixedId, toPrefixedId, uuidGenerateId } from "./id.js";
7
7
  export { authEnv, isConfigured } from "./keys.js";
8
- export { bcryptPassword, makeEmailSenders, makePasskeyOptions, } from "./options.js";
8
+ export { bcryptPassword, makeEmailSenders, makePasskeyOptions, passkeyOptionsForBaseUrl, } from "./options.js";
9
9
  export { lastActiveOrganizationHooks, lastActiveOrganizationUserField, nkOrganizationDefaults, } from "./organization.js";
10
10
  export { authBasePath } from "./paths.js";
11
11
  export { createAuthPool } from "./pool.js";
12
- export { createServerSupabase } from "./supabase.js";
13
12
  //# sourceMappingURL=index.js.map
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,EAEN,iBAAiB,EACjB,aAAa,EACb,gBAAgB,GAChB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAgB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACN,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GAGlB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,2BAA2B,EAC3B,+BAA+B,EAC/B,sBAAsB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAA6B,MAAM,eAAe,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,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,YAAY,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACN,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,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
package/dist/jwt.d.ts CHANGED
@@ -1,19 +1,11 @@
1
1
  import type { JwtOptions } from "better-auth/plugins/jwt";
2
2
  import { type JWTPayload } from "jose";
3
3
  /**
4
- * `jwt` plugin presets. Two shapes, because Ingram sites mint JWTs for two
5
- * different audiences:
6
- *
7
- * - `rlsJwtOptions` a Supabase RLS bridge token (`role: "authenticated"`),
8
- * so PostgREST's `auth.uid()` keeps working. See docs/better-auth-migration.md.
9
- * - `backendJwtOptions` — a short-lived token for a site's OWN backend API
10
- * (a specific `audience`), typically carrying extra claims the site adds via
11
- * `auth.api.signJWT`.
12
- *
13
- * Both default to EdDSA (Better Auth's default), exposed at `/api/auth/jwks`.
4
+ * `jwt` plugin preset for a site's OWN backend API: a short-lived token with a
5
+ * specific `audience`, typically carrying extra claims the site adds via
6
+ * `auth.api.signJWT`. Defaults to EdDSA (Better Auth's default), with the JWKS
7
+ * exposed at `/api/auth/jwks` so the backend can verify it (`verifyBackendJwt`).
14
8
  */
15
- /** Supabase RLS bridge: mints `sub` = user id, `role`/`aud` = "authenticated". */
16
- export declare const rlsJwtOptions: JwtOptions;
17
9
  export interface BackendJwtConfig {
18
10
  /** Expected audience of the site's backend, e.g. "ingram-wiki-backend". */
19
11
  audience: string;
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,EAAsB,KAAK,UAAU,EAAa,MAAM,MAAM,CAAC;AAEtE;;;;;;;;;;;GAWG;AAEH,kFAAkF;AAClF,eAAO,MAAM,aAAa,EAAE,UAS3B,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAChC,2EAA2E;IAC3E,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;;;;GAIG;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,CAerB,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,EAAsB,KAAK,UAAU,EAAa,MAAM,MAAM,CAAC;AAEtE;;;;;GAKG;AAEH,MAAM,WAAW,gBAAgB;IAChC,2EAA2E;IAC3E,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;;;;GAIG;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,CAerB,CAAC"}
package/dist/jwt.js CHANGED
@@ -1,27 +1,4 @@
1
1
  import { createRemoteJWKSet, jwtVerify } from "jose";
2
- /**
3
- * `jwt` plugin presets. Two shapes, because Ingram sites mint JWTs for two
4
- * different audiences:
5
- *
6
- * - `rlsJwtOptions` — a Supabase RLS bridge token (`role: "authenticated"`),
7
- * so PostgREST's `auth.uid()` keeps working. See docs/better-auth-migration.md.
8
- * - `backendJwtOptions` — a short-lived token for a site's OWN backend API
9
- * (a specific `audience`), typically carrying extra claims the site adds via
10
- * `auth.api.signJWT`.
11
- *
12
- * Both default to EdDSA (Better Auth's default), exposed at `/api/auth/jwks`.
13
- */
14
- /** Supabase RLS bridge: mints `sub` = user id, `role`/`aud` = "authenticated". */
15
- export const rlsJwtOptions = {
16
- jwks: { keyPairConfig: { alg: "EdDSA" } },
17
- jwt: {
18
- definePayload: ({ user }) => ({
19
- sub: user.id,
20
- role: "authenticated",
21
- aud: "authenticated",
22
- }),
23
- },
24
- };
25
2
  /** `jwt` plugin options for a backend-API token (custom audience). */
26
3
  export const backendJwtOptions = (config) => ({
27
4
  disableSettingJwtHeader: config.disableSettingJwtHeader,
package/dist/jwt.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAmB,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtE;;;;;;;;;;;GAWG;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,aAAa,GAAe;IACxC,IAAI,EAAE,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;IACzC,GAAG,EAAE;QACJ,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7B,GAAG,EAAE,IAAI,CAAC,EAAE;YACZ,IAAI,EAAE,eAAe;YACrB,GAAG,EAAE,eAAe;SACpB,CAAC;KACF;CACD,CAAC;AAcF,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;;;;GAIG;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,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE;QACvD,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,CAAC,OAAO,CAAC;KACrB,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AAChB,CAAC,CAAC"}
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAmB,SAAS,EAAE,MAAM,MAAM,CAAC;AAqBtE,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;;;;GAIG;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,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE;QACvD,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,CAAC,OAAO,CAAC;KACrB,CAAC,CAAC;IACH,OAAO,OAAO,CAAC;AAChB,CAAC,CAAC"}
package/dist/keys.d.ts CHANGED
@@ -4,22 +4,17 @@
4
4
  * Following the "each package owns its own env validation" pattern. Env vars are
5
5
  * external input, so we parse them with Zod (per docs/code-style.md) rather than
6
6
  * reading `process.env` ad hoc. `authEnv()` returns a validated, config-shaped
7
- * object you can spread straight into `createAuth` / `createServerSupabase`.
7
+ * object you can spread straight into your `betterAuth()` call.
8
8
  *
9
9
  * Required:
10
- * BETTER_AUTH_SECRET — session/CSRF signing key (`openssl rand -hex 32`)
11
- * BETTER_AUTH_URL — canonical site origin, e.g. "https://example.com"
12
- * DATABASE_URL DIRECT Postgres connection for Better Auth
13
- * (session-mode pooler or :5432, NOT PostgREST)
14
- * NEXT_PUBLIC_SUPABASE_URL — Supabase project URL (for the data client)
15
- * NEXT_PUBLIC_SUPABASE_ANON_KEY — Supabase anon key (RLS still enforced)
10
+ * BETTER_AUTH_SECRET — session/CSRF signing key (`openssl rand -hex 32`)
11
+ * BETTER_AUTH_URL — canonical site origin, e.g. "https://example.com"
12
+ * DATABASE_URL direct Postgres connection for Better Auth (:5432)
16
13
  */
17
14
  export interface AuthEnv {
18
15
  secret: string;
19
16
  baseURL: string;
20
17
  databaseUrl: string;
21
- supabaseUrl: string;
22
- supabaseAnonKey: string;
23
18
  }
24
19
  /**
25
20
  * Read and validate all nk-auth env vars at once. Throws a single error listing
@@ -1 +1 @@
1
- {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAYH,MAAM,WAAW,OAAO;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAO,OAgB1B,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;;;;;;;;;;;;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"}
package/dist/keys.js CHANGED
@@ -4,23 +4,18 @@
4
4
  * Following the "each package owns its own env validation" pattern. Env vars are
5
5
  * external input, so we parse them with Zod (per docs/code-style.md) rather than
6
6
  * reading `process.env` ad hoc. `authEnv()` returns a validated, config-shaped
7
- * object you can spread straight into `createAuth` / `createServerSupabase`.
7
+ * object you can spread straight into your `betterAuth()` call.
8
8
  *
9
9
  * Required:
10
- * BETTER_AUTH_SECRET — session/CSRF signing key (`openssl rand -hex 32`)
11
- * BETTER_AUTH_URL — canonical site origin, e.g. "https://example.com"
12
- * DATABASE_URL DIRECT Postgres connection for Better Auth
13
- * (session-mode pooler or :5432, NOT PostgREST)
14
- * NEXT_PUBLIC_SUPABASE_URL — Supabase project URL (for the data client)
15
- * NEXT_PUBLIC_SUPABASE_ANON_KEY — Supabase anon key (RLS still enforced)
10
+ * BETTER_AUTH_SECRET — session/CSRF signing key (`openssl rand -hex 32`)
11
+ * BETTER_AUTH_URL — canonical site origin, e.g. "https://example.com"
12
+ * DATABASE_URL direct Postgres connection for Better Auth (:5432)
16
13
  */
17
14
  import { z } from "zod";
18
15
  const schema = z.object({
19
16
  BETTER_AUTH_SECRET: z.string().min(1),
20
17
  BETTER_AUTH_URL: z.string().url(),
21
18
  DATABASE_URL: z.string().min(1),
22
- NEXT_PUBLIC_SUPABASE_URL: z.string().url(),
23
- NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
24
19
  });
25
20
  /**
26
21
  * Read and validate all nk-auth env vars at once. Throws a single error listing
@@ -40,8 +35,6 @@ export const authEnv = () => {
40
35
  secret: env.BETTER_AUTH_SECRET,
41
36
  baseURL: env.BETTER_AUTH_URL,
42
37
  databaseUrl: env.DATABASE_URL,
43
- supabaseUrl: env.NEXT_PUBLIC_SUPABASE_URL,
44
- supabaseAnonKey: env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
45
38
  };
46
39
  };
47
40
  /** Whether nk-auth is fully configured (lets callers degrade in local/dev). */
package/dist/keys.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;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;IAC/B,wBAAwB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC1C,6BAA6B,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAChD,CAAC,CAAC;AAUH;;;;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;QAC7B,WAAW,EAAE,GAAG,CAAC,wBAAwB;QACzC,eAAe,EAAE,GAAG,CAAC,6BAA6B;KAClD,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;;;;;;;;;;;;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"}
package/dist/options.d.ts CHANGED
@@ -7,11 +7,16 @@ export { uuidGenerateId } from "./id.js";
7
7
  * and spreads these presets in. That keeps full plugin type inference at the
8
8
  * call site (where `declaration` is off) and respects the prime directive — the
9
9
  * site stays plain Better Auth, we just ship the shared config. JWT + org
10
- * presets live in `./jwt` and `./organization`. See docs/better-auth-migration.md.
10
+ * presets live in `./jwt` and `./organization`. See the package README.
11
11
  */
12
12
  /**
13
- * `emailAndPassword.password` config. Verifies with bcrypt so passwords
14
- * migrated from Supabase (bcrypt) keep working — Better Auth defaults to scrypt.
13
+ * `emailAndPassword.password` config that hashes/verifies with bcrypt.
14
+ *
15
+ * @deprecated LEGACY SUPPORT ONLY. Do not wire this into a new site. It exists
16
+ * solely so sites with pre-existing **bcrypt** password hashes keep verifying.
17
+ * Better Auth's default is scrypt — new sites should omit this and use that
18
+ * default. Sites still on bcrypt should migrate hashes to scrypt and drop this;
19
+ * see the nk-auth README (§"Migrating bcrypt passwords to scrypt") for the path.
15
20
  */
16
21
  export declare const bcryptPassword: {
17
22
  hash: (password: string) => Promise<string>;
@@ -30,7 +35,21 @@ export interface PasskeyConfig {
30
35
  }
31
36
  /** Build `passkey` plugin options. Use as `passkey(makePasskeyOptions(cfg))`. */
32
37
  export declare const makePasskeyOptions: (cfg: PasskeyConfig) => PasskeyOptions;
33
- /** Send one transactional email (wire to `@ingram-tech/email`'s `sendEmail`). */
38
+ /**
39
+ * Derive passkey options from the site's single base URL: `rpID` = the URL's
40
+ * hostname (the WebAuthn effective domain — host only, no scheme or port) and
41
+ * `origin` = the URL itself. This is the common case (one sign-in origin), and
42
+ * it keeps the relying-party id and the registered origin in lockstep so they
43
+ * can't drift. Pass the same value the instance signs sessions for, e.g.:
44
+ *
45
+ * passkey(passkeyOptionsForBaseUrl(env.baseURL, "Example"))
46
+ *
47
+ * Reach for `makePasskeyOptions` directly when a site spans multiple origins or
48
+ * must register against a parent registrable domain (e.g. rpID "example.com"
49
+ * for an "app.example.com" origin).
50
+ */
51
+ export declare const passkeyOptionsForBaseUrl: (baseURL: string, rpName: string) => PasskeyOptions;
52
+ /** Send one transactional email (wire to `@ingram-tech/nk-email`'s `sendEmail`). */
34
53
  export type SendEmail = (message: {
35
54
  to: string;
36
55
  subject: string;
@@ -1 +1 @@
1
- {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAM3D,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;;GAQG;AAEH;;;GAGG;AACH,eAAO,MAAM,cAAc;qBACT,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;kCAItC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KACjB,KAAG,OAAO,CAAC,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,GAAI,KAAK,aAAa,KAAG,cAItD,CAAC;AAEH,iFAAiF;AACjF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACZ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvB;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS;wCAI5C;QACF,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;KACZ,KAAG,OAAO,CAAC,IAAI,CAAC;4CAMd;QACF,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;KACZ,KAAG,OAAO,CAAC,IAAI,CAAC;CAGhB,CAAC"}
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAM3D,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;;GAQG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc;qBACT,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;kCAItC;QACF,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;KACjB,KAAG,OAAO,CAAC,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC1B;AAED,iFAAiF;AACjF,eAAO,MAAM,kBAAkB,GAAI,KAAK,aAAa,KAAG,cAItD,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,GACpC,SAAS,MAAM,EACf,QAAQ,MAAM,KACZ,cAKA,CAAC;AAEJ,oFAAoF;AACpF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,EAAE;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACZ,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvB;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,SAAS;wCAI5C;QACF,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;KACZ,KAAG,OAAO,CAAC,IAAI,CAAC;4CAMd;QACF,IAAI,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;KACZ,KAAG,OAAO,CAAC,IAAI,CAAC;CAGhB,CAAC"}
package/dist/options.js CHANGED
@@ -10,11 +10,16 @@ export { uuidGenerateId } from "./id.js";
10
10
  * and spreads these presets in. That keeps full plugin type inference at the
11
11
  * call site (where `declaration` is off) and respects the prime directive — the
12
12
  * site stays plain Better Auth, we just ship the shared config. JWT + org
13
- * presets live in `./jwt` and `./organization`. See docs/better-auth-migration.md.
13
+ * presets live in `./jwt` and `./organization`. See the package README.
14
14
  */
15
15
  /**
16
- * `emailAndPassword.password` config. Verifies with bcrypt so passwords
17
- * migrated from Supabase (bcrypt) keep working — Better Auth defaults to scrypt.
16
+ * `emailAndPassword.password` config that hashes/verifies with bcrypt.
17
+ *
18
+ * @deprecated LEGACY SUPPORT ONLY. Do not wire this into a new site. It exists
19
+ * solely so sites with pre-existing **bcrypt** password hashes keep verifying.
20
+ * Better Auth's default is scrypt — new sites should omit this and use that
21
+ * default. Sites still on bcrypt should migrate hashes to scrypt and drop this;
22
+ * see the nk-auth README (§"Migrating bcrypt passwords to scrypt") for the path.
18
23
  */
19
24
  export const bcryptPassword = {
20
25
  hash: (password) => bcrypt.hash(password, 10),
@@ -26,6 +31,24 @@ export const makePasskeyOptions = (cfg) => ({
26
31
  rpName: cfg.rpName,
27
32
  origin: cfg.origin,
28
33
  });
34
+ /**
35
+ * Derive passkey options from the site's single base URL: `rpID` = the URL's
36
+ * hostname (the WebAuthn effective domain — host only, no scheme or port) and
37
+ * `origin` = the URL itself. This is the common case (one sign-in origin), and
38
+ * it keeps the relying-party id and the registered origin in lockstep so they
39
+ * can't drift. Pass the same value the instance signs sessions for, e.g.:
40
+ *
41
+ * passkey(passkeyOptionsForBaseUrl(env.baseURL, "Example"))
42
+ *
43
+ * Reach for `makePasskeyOptions` directly when a site spans multiple origins or
44
+ * must register against a parent registrable domain (e.g. rpID "example.com"
45
+ * for an "app.example.com" origin).
46
+ */
47
+ export const passkeyOptionsForBaseUrl = (baseURL, rpName) => makePasskeyOptions({
48
+ rpId: new URL(baseURL).hostname,
49
+ rpName,
50
+ origin: baseURL,
51
+ });
29
52
  /**
30
53
  * Email callbacks for `emailAndPassword.sendResetPassword` and
31
54
  * `emailVerification.sendVerificationEmail`, routed through your sender.
@@ -1 +1 @@
1
- {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,iFAAiF;AACjF,kFAAkF;AAClF,iEAAiE;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;;GAQG;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,IAAI,EAAE,CAAC,QAAgB,EAAmB,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;IACtE,MAAM,EAAE,CAAC,EACR,IAAI,EACJ,QAAQ,GAIR,EAAoB,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;CACtD,CAAC;AAWF,iFAAiF;AACjF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAkB,EAAkB,EAAE,CAAC,CAAC;IAC1E,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,MAAM,EAAE,GAAG,CAAC,MAAM;CAClB,CAAC,CAAC;AASH;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAe,EAAE,EAAE,CAAC,CAAC;IACrD,iBAAiB,EAAE,KAAK,EAAE,EACzB,IAAI,EACJ,GAAG,GAIH,EAAiB,EAAE;QACnB,MAAM,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,qBAAqB,EAAE,KAAK,EAAE,EAC7B,IAAI,EACJ,GAAG,GAIH,EAAiB,EAAE;QACnB,MAAM,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IACnE,CAAC;CACD,CAAC,CAAC"}
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,iFAAiF;AACjF,kFAAkF;AAClF,iEAAiE;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC;;;;;;;;GAQG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,IAAI,EAAE,CAAC,QAAgB,EAAmB,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;IACtE,MAAM,EAAE,CAAC,EACR,IAAI,EACJ,QAAQ,GAIR,EAAoB,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;CACtD,CAAC;AAWF,iFAAiF;AACjF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,GAAkB,EAAkB,EAAE,CAAC,CAAC;IAC1E,IAAI,EAAE,GAAG,CAAC,IAAI;IACd,MAAM,EAAE,GAAG,CAAC,MAAM;IAClB,MAAM,EAAE,GAAG,CAAC,MAAM;CAClB,CAAC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACvC,OAAe,EACf,MAAc,EACG,EAAE,CACnB,kBAAkB,CAAC;IAClB,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ;IAC/B,MAAM;IACN,MAAM,EAAE,OAAO;CACf,CAAC,CAAC;AASJ;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,IAAe,EAAE,EAAE,CAAC,CAAC;IACrD,iBAAiB,EAAE,KAAK,EAAE,EACzB,IAAI,EACJ,GAAG,GAIH,EAAiB,EAAE;QACnB,MAAM,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,qBAAqB,EAAE,GAAG,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,qBAAqB,EAAE,KAAK,EAAE,EAC7B,IAAI,EACJ,GAAG,GAIH,EAAiB,EAAE;QACnB,MAAM,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,CAAC;IACnE,CAAC;CACD,CAAC,CAAC"}
@@ -1,4 +1,4 @@
1
- -- @ingram-tech/nk-auth — Better Auth schema for Supabase, hardened for RLS.
1
+ -- @ingram-tech/nk-auth — Better Auth schema, hardened for RLS.
2
2
  --
3
3
  -- This mirrors Better Auth's core tables (user / session / account /
4
4
  -- verification), the `jwt` plugin's `jwks` table, and the `passkey` plugin's
@@ -7,14 +7,14 @@
7
7
  -- npx @better-auth/cli generate
8
8
  -- and re-run after upgrading better-auth.
9
9
  --
10
- -- TWO hardening steps the generator does NOT produce, both required (see
11
- -- docs/better-auth-migration.md):
12
- -- 1. New users default to a UUID id, because Supabase's auth.uid() casts the
13
- -- JWT `sub` to `uuid`. A non-UUID id silently breaks RLS for new signups.
14
- -- 2. Deny-all RLS on every Better Auth table. They live in `public` and are
15
- -- exposed by PostgREST; Better Auth itself reaches them through its own
16
- -- privileged DATABASE_URL connection, which bypasses RLS, so denying anon /
17
- -- authenticated access here costs nothing and closes the leak.
10
+ -- TWO hardening steps the generator does NOT produce, both required:
11
+ -- 1. New users default to a UUID id, because `auth.uid()`-style RLS policies
12
+ -- cast the JWT `sub` to `uuid`. A non-UUID id silently breaks RLS for new
13
+ -- signups.
14
+ -- 2. Deny-all RLS on every Better Auth table defense in depth. Better Auth
15
+ -- itself reaches them through its own privileged DATABASE_URL connection,
16
+ -- which bypasses RLS, so denying the app's RLS role any access here costs
17
+ -- nothing and keeps the auth tables off-limits to user-facing queries.
18
18
 
19
19
  create extension if not exists "pgcrypto" with schema "extensions";
20
20
 
@@ -64,7 +64,7 @@ create table if not exists "public"."verification" (
64
64
  "updatedAt" timestamptz not null default now()
65
65
  );
66
66
 
67
- -- `jwt` plugin: holds the asymmetric keypair used to sign the RLS bridge token.
67
+ -- `jwt` plugin: holds the asymmetric keypair used to sign session JWTs.
68
68
  create table if not exists "public"."jwks" (
69
69
  "id" text primary key,
70
70
  "publicKey" text not null,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ingram-tech/nk-auth",
3
- "version": "0.7.6",
4
- "description": "The Ingram Better Auth foundation: composable presets (org, dual-shape JWT, Supabase RLS bridge, active-org hooks, pg pool) for Next.js sites.",
3
+ "version": "0.9.0",
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",
7
7
  "repository": {
@@ -61,14 +61,13 @@
61
61
  "test": "vitest run"
62
62
  },
63
63
  "dependencies": {
64
- "@ingram-tech/nk-db": "^0.9.0",
64
+ "@ingram-tech/nk-db": "^1.0.0",
65
65
  "bcrypt": "^6.0.0",
66
66
  "jose": "^6.0.0",
67
67
  "zod": "^4.0.0"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@better-auth/passkey": "^1.6.0",
71
- "@supabase/supabase-js": ">=2.0.0",
72
71
  "better-auth": "^1.6.0",
73
72
  "next": "^15.0.0 || ^16.0.0",
74
73
  "pg": "^8.13.0",
@@ -78,9 +77,6 @@
78
77
  "@better-auth/passkey": {
79
78
  "optional": true
80
79
  },
81
- "@supabase/supabase-js": {
82
- "optional": true
83
- },
84
80
  "next": {
85
81
  "optional": true
86
82
  },
@@ -93,8 +89,7 @@
93
89
  },
94
90
  "devDependencies": {
95
91
  "@better-auth/passkey": "^1.6.0",
96
- "@ingram-tech/nk-dev": "workspace:*",
97
- "@supabase/supabase-js": "^2.45.0",
92
+ "@ingram-tech/nk-dev": "0.2.4",
98
93
  "@types/bcrypt": "^6.0.0",
99
94
  "@types/node": "^25.0.0",
100
95
  "@types/pg": "^8.11.0",
@@ -1,26 +0,0 @@
1
- import { type SupabaseClient } from "@supabase/supabase-js";
2
- /**
3
- * The RLS-aware Supabase data client — the single chokepoint that keeps Row
4
- * Level Security working after the move off Supabase Auth.
5
- *
6
- * Instead of `supabase.auth`, it pulls the Better Auth session JWT (via the
7
- * injected `getToken`) and hands it to supabase-js's v2 `accessToken` callback.
8
- * PostgREST then sets `request.jwt.claims`, so `auth.uid()` (and every existing
9
- * policy) keeps returning the right user. With no session, `getToken` yields
10
- * `null` and the request runs as `anon` — RLS is still enforced, never bypassed.
11
- *
12
- * Sites should import THIS rather than constructing supabase-js directly, so the
13
- * token bridge can't be forgotten. (Enforce with a Biome/GritQL rule banning raw
14
- * `createClient` for data — see docs/better-auth-migration.md.)
15
- *
16
- * Wire `getToken` to the site's own Better Auth instance, e.g.:
17
- * getToken: async () => (await auth.api.getToken({ headers }))?.token ?? null
18
- */
19
- export interface ServerSupabaseConfig {
20
- /** Returns the current request's Better Auth JWT, or null when signed out. */
21
- getToken: () => Promise<string | null>;
22
- supabaseUrl: string;
23
- supabaseAnonKey: string;
24
- }
25
- export declare const createServerSupabase: (config: ServerSupabaseConfig) => SupabaseClient;
26
- //# sourceMappingURL=supabase.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../src/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE1E;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,oBAAoB;IACpC,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACvC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,oBAAoB,GAAI,QAAQ,oBAAoB,KAAG,cAMjE,CAAC"}
package/dist/supabase.js DELETED
@@ -1,8 +0,0 @@
1
- import { createClient } from "@supabase/supabase-js";
2
- export const createServerSupabase = (config) => createClient(config.supabaseUrl, config.supabaseAnonKey, {
3
- accessToken: async () => {
4
- const token = await config.getToken();
5
- return token ?? "";
6
- },
7
- });
8
- //# sourceMappingURL=supabase.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"supabase.js","sourceRoot":"","sources":["../src/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAuB,MAAM,uBAAuB,CAAC;AA2B1E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAA4B,EAAkB,EAAE,CACpF,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,eAAe,EAAE;IACxD,WAAW,EAAE,KAAK,IAAI,EAAE;QACvB,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,OAAO,KAAK,IAAI,EAAE,CAAC;IACpB,CAAC;CACD,CAAC,CAAC"}