@ingram-tech/nk-auth 0.8.0 → 0.9.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 +57 -7
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jwt.d.ts +9 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +24 -4
- package/dist/jwt.js.map +1 -1
- package/dist/options.d.ts +22 -3
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +26 -3
- package/dist/options.js.map +1 -1
- package/migrations/0001_better_auth.sql +10 -10
- package/package.json +14 -19
package/README.md
CHANGED
|
@@ -14,14 +14,15 @@ exactly one Better Auth copy in the app.
|
|
|
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
16
|
| `createAuthPool` (`./pool`) | `pg` Pool with optional SSL CA verification |
|
|
17
|
-
| `
|
|
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) |
|
|
18
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 |
|
|
19
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 |
|
|
20
21
|
|
|
21
22
|
> **Data access + RLS** is owned by [`@ingram-tech/nk-db`](../nk-db): query over a
|
|
22
23
|
> direct `pg` connection and enforce per-request Row Level Security with
|
|
23
24
|
> `withRls` / `withRlsTransaction` (claims taken from the Better Auth session — no
|
|
24
|
-
> JWT minting, no
|
|
25
|
+
> JWT minting, no REST proxy).
|
|
25
26
|
>
|
|
26
27
|
> **Backend-JWT + org sites** (a backend API plus the org plugin): compose `createAuthPool`,
|
|
27
28
|
> `backendJwtOptions({ audience })`, `nkOrganizationDefaults`, and
|
|
@@ -74,9 +75,8 @@ import { fromAddress, sendEmail } from "@ingram-tech/nk-email";
|
|
|
74
75
|
import {
|
|
75
76
|
authBasePath,
|
|
76
77
|
authEnv,
|
|
77
|
-
bcryptPassword,
|
|
78
78
|
makeEmailSenders,
|
|
79
|
-
|
|
79
|
+
passkeyOptionsForBaseUrl,
|
|
80
80
|
uuidGenerateId,
|
|
81
81
|
} from "@ingram-tech/nk-auth";
|
|
82
82
|
import { betterAuth } from "better-auth";
|
|
@@ -100,7 +100,8 @@ export const auth = betterAuth({
|
|
|
100
100
|
// mints a fresh one directly for text-id sites. All from `@ingram-tech/nk-auth`.
|
|
101
101
|
emailAndPassword: {
|
|
102
102
|
enabled: true,
|
|
103
|
-
|
|
103
|
+
// Better Auth hashes with scrypt by default. Only sites carrying
|
|
104
|
+
// pre-existing bcrypt hashes set `password: bcryptPassword` (legacy).
|
|
104
105
|
sendResetPassword: email.sendResetPassword,
|
|
105
106
|
},
|
|
106
107
|
emailVerification: { sendVerificationEmail: email.sendVerificationEmail },
|
|
@@ -111,7 +112,10 @@ export const auth = betterAuth({
|
|
|
111
112
|
},
|
|
112
113
|
},
|
|
113
114
|
plugins: [
|
|
114
|
-
|
|
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")),
|
|
115
119
|
],
|
|
116
120
|
});
|
|
117
121
|
```
|
|
@@ -130,7 +134,7 @@ Data access lives in [`@ingram-tech/nk-db`](../nk-db), not here. Query over the
|
|
|
130
134
|
direct `pg` connection and wrap reads/writes in `withRls` / `withRlsTransaction`,
|
|
131
135
|
which set `request.jwt.claims` + `SET LOCAL ROLE` per transaction from the Better
|
|
132
136
|
Auth session — so `auth.uid()` policies fire unchanged, with no JWT minting and
|
|
133
|
-
no
|
|
137
|
+
no REST proxy. See its README for the pattern.
|
|
134
138
|
|
|
135
139
|
## 4. Client
|
|
136
140
|
|
|
@@ -239,3 +243,49 @@ the cookie-present case the guard reads it from the `x-nk-auth-path` header
|
|
|
239
243
|
middleware injects — so the self-heal (next + clearing) needs the middleware.
|
|
240
244
|
Sites that skip middleware still get validated gating from the server helpers,
|
|
241
245
|
just without automatic `next`/clearing.
|
|
246
|
+
|
|
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
|
|
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
|
|
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,7 +1,7 @@
|
|
|
1
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gBAAgB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,KAAK,OAAO,EAAE,OAAO,EAAE,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"}
|
|
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
|
@@ -5,7 +5,7 @@
|
|
|
5
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";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,4EAA4E;AAC5E,wEAAwE;AAExE,OAAO,EAAyB,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACtF,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAgB,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EACN,cAAc,EACd,gBAAgB,EAChB,kBAAkB,
|
|
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
|
@@ -7,7 +7,7 @@ import { type JWTPayload } from "jose";
|
|
|
7
7
|
* exposed at `/api/auth/jwks` so the backend can verify it (`verifyBackendJwt`).
|
|
8
8
|
*/
|
|
9
9
|
export interface BackendJwtConfig {
|
|
10
|
-
/** Expected audience of the site's backend, e.g. "
|
|
10
|
+
/** Expected audience of the site's backend, e.g. "my-app-backend". */
|
|
11
11
|
audience: string;
|
|
12
12
|
/** Token lifetime, e.g. "15m". */
|
|
13
13
|
expirationTime?: string;
|
|
@@ -23,6 +23,14 @@ export declare const backendJwtOptions: (config: BackendJwtConfig) => JwtOptions
|
|
|
23
23
|
* Verify a Better-Auth-minted backend JWT (EdDSA) against the issuer's JWKS.
|
|
24
24
|
* For use by the backend that consumes `backendJwtOptions` tokens. Throws on an
|
|
25
25
|
* invalid/expired token or audience/issuer mismatch; returns the claims.
|
|
26
|
+
*
|
|
27
|
+
* Hardened against Better Auth signing-key rotation: jose's `createRemoteJWKSet`
|
|
28
|
+
* refuses to refetch the JWKS for `cooldownDuration` (30s default) after any
|
|
29
|
+
* fetch, so a token signed with a freshly rotated key whose `kid` isn't yet in
|
|
30
|
+
* the cached set would fail for the *whole* cooldown window — a ~30s burst of
|
|
31
|
+
* auth failures on every request that verifies a token. On a no-matching-key
|
|
32
|
+
* miss we force a single `.reload()` (which bypasses the cooldown) and retry, so
|
|
33
|
+
* a rotation costs one extra fetch instead of a brief outage.
|
|
26
34
|
*/
|
|
27
35
|
export declare const verifyBackendJwt: (params: {
|
|
28
36
|
token: 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,
|
|
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"}
|
package/dist/jwt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
1
|
+
import { createRemoteJWKSet, errors as joseErrors, jwtVerify, } from "jose";
|
|
2
2
|
/** `jwt` plugin options for a backend-API token (custom audience). */
|
|
3
3
|
export const backendJwtOptions = (config) => ({
|
|
4
4
|
disableSettingJwtHeader: config.disableSettingJwtHeader,
|
|
@@ -12,6 +12,14 @@ const jwksCache = new Map();
|
|
|
12
12
|
* Verify a Better-Auth-minted backend JWT (EdDSA) against the issuer's JWKS.
|
|
13
13
|
* For use by the backend that consumes `backendJwtOptions` tokens. Throws on an
|
|
14
14
|
* invalid/expired token or audience/issuer mismatch; returns the claims.
|
|
15
|
+
*
|
|
16
|
+
* Hardened against Better Auth signing-key rotation: jose's `createRemoteJWKSet`
|
|
17
|
+
* refuses to refetch the JWKS for `cooldownDuration` (30s default) after any
|
|
18
|
+
* fetch, so a token signed with a freshly rotated key whose `kid` isn't yet in
|
|
19
|
+
* the cached set would fail for the *whole* cooldown window — a ~30s burst of
|
|
20
|
+
* auth failures on every request that verifies a token. On a no-matching-key
|
|
21
|
+
* miss we force a single `.reload()` (which bypasses the cooldown) and retry, so
|
|
22
|
+
* a rotation costs one extra fetch instead of a brief outage.
|
|
15
23
|
*/
|
|
16
24
|
export const verifyBackendJwt = async (params) => {
|
|
17
25
|
const url = typeof params.jwksUrl === "string" ? new URL(params.jwksUrl) : params.jwksUrl;
|
|
@@ -21,11 +29,23 @@ export const verifyBackendJwt = async (params) => {
|
|
|
21
29
|
jwks = createRemoteJWKSet(url);
|
|
22
30
|
jwksCache.set(cacheKey, jwks);
|
|
23
31
|
}
|
|
24
|
-
const
|
|
32
|
+
const options = {
|
|
25
33
|
audience: params.audience,
|
|
26
34
|
issuer: params.issuer,
|
|
27
35
|
algorithms: ["EdDSA"],
|
|
28
|
-
}
|
|
29
|
-
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
const { payload } = await jwtVerify(params.token, jwks, options);
|
|
39
|
+
return payload;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (!(err instanceof joseErrors.JWKSNoMatchingKey))
|
|
43
|
+
throw err;
|
|
44
|
+
// `kid` absent from the cached JWKS — almost always a just-rotated signing
|
|
45
|
+
// key. Force a refresh past the cooldown and retry exactly once.
|
|
46
|
+
await jwks.reload();
|
|
47
|
+
const { payload } = await jwtVerify(params.token, jwks, options);
|
|
48
|
+
return payload;
|
|
49
|
+
}
|
|
30
50
|
};
|
|
31
51
|
//# sourceMappingURL=jwt.js.map
|
package/dist/jwt.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
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"}
|
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
|
|
10
|
+
* presets live in `./jwt` and `./organization`. See the package README.
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
|
-
* `emailAndPassword.password` config
|
|
14
|
-
*
|
|
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,6 +35,20 @@ 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;
|
|
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;
|
|
33
52
|
/** Send one transactional email (wire to `@ingram-tech/nk-email`'s `sendEmail`). */
|
|
34
53
|
export type SendEmail = (message: {
|
|
35
54
|
to: string;
|
package/dist/options.d.ts.map
CHANGED
|
@@ -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
|
|
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
|
|
13
|
+
* presets live in `./jwt` and `./organization`. See the package README.
|
|
14
14
|
*/
|
|
15
15
|
/**
|
|
16
|
-
* `emailAndPassword.password` config
|
|
17
|
-
*
|
|
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.
|
package/dist/options.js.map
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
11
|
-
--
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
-- 2. Deny-all RLS on every Better Auth table
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--
|
|
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
|
|
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.
|
|
4
|
-
"description": "The Ingram Better Auth foundation: composable presets (org, dual-shape JWT,
|
|
3
|
+
"version": "0.9.1",
|
|
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": "^
|
|
64
|
+
"@ingram-tech/nk-db": "^1.1.0",
|
|
65
65
|
"bcrypt": "^6.0.0",
|
|
66
|
-
"jose": "^6.
|
|
67
|
-
"zod": "^4.
|
|
66
|
+
"jose": "^6.2.3",
|
|
67
|
+
"zod": "^4.4.3"
|
|
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
|
},
|
|
@@ -92,18 +88,17 @@
|
|
|
92
88
|
}
|
|
93
89
|
},
|
|
94
90
|
"devDependencies": {
|
|
95
|
-
"@better-auth/passkey": "^1.6.
|
|
96
|
-
"@ingram-tech/nk-dev": "0.2.
|
|
97
|
-
"@supabase/supabase-js": "^2.45.0",
|
|
91
|
+
"@better-auth/passkey": "^1.6.22",
|
|
92
|
+
"@ingram-tech/nk-dev": "0.2.4",
|
|
98
93
|
"@types/bcrypt": "^6.0.0",
|
|
99
|
-
"@types/node": "^
|
|
100
|
-
"@types/pg": "^8.
|
|
101
|
-
"@types/react": "^19.
|
|
102
|
-
"better-auth": "^1.6.
|
|
94
|
+
"@types/node": "^26.0.1",
|
|
95
|
+
"@types/pg": "^8.20.0",
|
|
96
|
+
"@types/react": "^19.2.17",
|
|
97
|
+
"better-auth": "^1.6.22",
|
|
103
98
|
"next": "^16.2.9",
|
|
104
|
-
"pg": "^8.
|
|
105
|
-
"react": "^19.
|
|
99
|
+
"pg": "^8.22.0",
|
|
100
|
+
"react": "^19.2.7",
|
|
106
101
|
"typescript": "^6.0.3",
|
|
107
|
-
"vitest": "^4.1.
|
|
102
|
+
"vitest": "^4.1.9"
|
|
108
103
|
}
|
|
109
104
|
}
|