@nifrajs/better-auth 2.2.0 → 2.4.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 +5 -5
- package/dist/index.d.ts +8 -8
- package/dist/index.js +7 -7
- package/package.json +3 -3
- package/src/index.ts +10 -10
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ Mount [better-auth](https://better-auth.com) into a [nifra](https://github.com/n
|
|
|
5
5
|
session, 2FA, magic links, …) under `/api/auth/*`, plus typed `getSession` / `requireSession` guards
|
|
6
6
|
for reading and protecting routes.
|
|
7
7
|
|
|
8
|
-
This package has **no runtime dependency on better-auth**
|
|
8
|
+
This package has **no runtime dependency on better-auth** - it consumes your `auth` object
|
|
9
9
|
structurally, so your better-auth types flow through by inference and your tests need no DB.
|
|
10
10
|
|
|
11
11
|
## Install
|
|
@@ -51,14 +51,14 @@ works in both core handlers (`c.req`) and `@nifrajs/web` loaders/actions (`reque
|
|
|
51
51
|
import { getSession } from "@nifrajs/better-auth"
|
|
52
52
|
|
|
53
53
|
app.get("/me", async (c) => {
|
|
54
|
-
const session = await getSession(auth, c.req) // { user, session } | null
|
|
54
|
+
const session = await getSession(auth, c.req) // { user, session } | null - fully typed
|
|
55
55
|
return session ? { email: session.user.email } : { email: null }
|
|
56
56
|
})
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
## Protect a route
|
|
60
60
|
|
|
61
|
-
`requireSession(auth, request, options?)` returns the non-null session or **throws a `Response`**
|
|
61
|
+
`requireSession(auth, request, options?)` returns the non-null session or **throws a `Response`** -
|
|
62
62
|
nifra returns a thrown `Response` as-is, short-circuiting the handler:
|
|
63
63
|
|
|
64
64
|
```ts
|
|
@@ -82,7 +82,7 @@ export const loader = async ({ request }) => {
|
|
|
82
82
|
| Export | Description |
|
|
83
83
|
| --- | --- |
|
|
84
84
|
| `betterAuth(auth, options?)` | Plugin that mounts better-auth's handler at `${basePath}/*` for GET + POST. |
|
|
85
|
-
| `getSession(auth, request)` | `Promise<SessionOf<A> \| null>`
|
|
85
|
+
| `getSession(auth, request)` | `Promise<SessionOf<A> \| null>` - typed wrapper over `auth.api.getSession`. |
|
|
86
86
|
| `requireSession(auth, request, options?)` | Returns the session or throws a `Response` (302 `redirectTo` / 401). |
|
|
87
87
|
| `SessionOf<A>` | The non-null session payload type inferred from your `auth`. |
|
|
88
88
|
| `BetterAuthLike` | The structural contract a better-auth instance satisfies. |
|
|
@@ -93,7 +93,7 @@ MIT
|
|
|
93
93
|
|
|
94
94
|
## For AI agents
|
|
95
95
|
|
|
96
|
-
Start with [`LLM.md`](./LLM.md)
|
|
96
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
97
97
|
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
98
98
|
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
99
99
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type AnyServer, type Server } from "@nifrajs/core/server";
|
|
|
6
6
|
* {@link getSession} / {@link requireSession} via inference.
|
|
7
7
|
*/
|
|
8
8
|
export interface BetterAuthLike {
|
|
9
|
-
/** better-auth's catch-all handler
|
|
9
|
+
/** better-auth's catch-all handler - serves every request under `basePath`. */
|
|
10
10
|
readonly handler: (request: Request) => Response | Promise<Response>;
|
|
11
11
|
readonly api: {
|
|
12
12
|
/** Resolve the session from request headers (cookie or bearer). Returns a nullable payload. */
|
|
@@ -18,7 +18,7 @@ export interface BetterAuthLike {
|
|
|
18
18
|
* better-auth's resolved options; `basePath` (when set) defaults the mount path. Intersected with
|
|
19
19
|
* `Record<string, unknown>` so a real better-auth instance (whose `options` is a large concrete object
|
|
20
20
|
* with no structural overlap to a bare `{ basePath? }`) stays assignable to `BetterAuthLike` WITHOUT a
|
|
21
|
-
* cast
|
|
21
|
+
* cast - keeping `A` concrete so `SessionOf<A>` recovers the real session type instead of collapsing
|
|
22
22
|
* to `{}` and forcing per-call session casts downstream.
|
|
23
23
|
*/
|
|
24
24
|
readonly options?: {
|
|
@@ -36,8 +36,8 @@ export interface BetterAuthOptions {
|
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
38
|
* Mount a better-auth instance into a nifra app: registers its handler at `${basePath}/*`
|
|
39
|
-
* (default `/api/auth/*`) for `GET` + `POST`, so every better-auth endpoint
|
|
40
|
-
* callbacks, session, 2FA, magic links, …
|
|
39
|
+
* (default `/api/auth/*`) for `GET` + `POST`, so every better-auth endpoint - sign-in/up/out, OAuth
|
|
40
|
+
* callbacks, session, 2FA, magic links, … - is served by your nifra server.
|
|
41
41
|
*
|
|
42
42
|
* ```ts
|
|
43
43
|
* import { betterAuth, requireSession } from "@nifrajs/better-auth"
|
|
@@ -47,12 +47,12 @@ export interface BetterAuthOptions {
|
|
|
47
47
|
* .get("/me", async (c) => (await requireSession(auth, c.req)).user)
|
|
48
48
|
* ```
|
|
49
49
|
*
|
|
50
|
-
* Idempotent (named `"better-auth"`
|
|
50
|
+
* Idempotent (named `"better-auth"` - applying twice mounts once). Read the session with
|
|
51
51
|
* {@link getSession} / {@link requireSession}, which infer your better-auth types.
|
|
52
52
|
*/
|
|
53
53
|
export declare function betterAuth(auth: BetterAuthLike, options?: BetterAuthOptions): import("@nifrajs/core/server").IdentityPlugin;
|
|
54
54
|
/**
|
|
55
|
-
* Resolve the better-auth session for a request
|
|
55
|
+
* Resolve the better-auth session for a request - a thin, typed wrapper over `auth.api.getSession`.
|
|
56
56
|
* Returns `null` when unauthenticated. Takes the raw `Request` so it works in both server handlers
|
|
57
57
|
* (`c.req`) and web loaders/actions (`request`).
|
|
58
58
|
*
|
|
@@ -64,14 +64,14 @@ export declare function betterAuth(auth: BetterAuthLike, options?: BetterAuthOpt
|
|
|
64
64
|
export declare function getSession<A extends BetterAuthLike>(auth: A, request: Request): Promise<SessionOf<A> | null>;
|
|
65
65
|
/**
|
|
66
66
|
* What {@link requireSession} does on a missing session: `302` to `redirectTo` (a same-origin path),
|
|
67
|
-
* or
|
|
67
|
+
* or - when omitted - a `401` JSON (`{ ok: false, error: "unauthorized" }`). Mirrors `@nifrajs/auth` guards.
|
|
68
68
|
*/
|
|
69
69
|
export interface RequireSessionOptions {
|
|
70
70
|
readonly redirectTo?: string;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* Require an authenticated better-auth session at the top of a protected handler/loader/action.
|
|
74
|
-
* Returns the (non-null) session when present; otherwise **throws a `Response`** (302/401)
|
|
74
|
+
* Returns the (non-null) session when present; otherwise **throws a `Response`** (302/401) - nifra
|
|
75
75
|
* returns a thrown `Response` as-is, short-circuiting the rest of the handler.
|
|
76
76
|
*
|
|
77
77
|
* ```ts
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { defineIdentityPlugin } from "@nifrajs/core/server";
|
|
|
3
3
|
const HANDLED_METHODS = ["GET", "POST"];
|
|
4
4
|
/**
|
|
5
5
|
* Mount a better-auth instance into a nifra app: registers its handler at `${basePath}/*`
|
|
6
|
-
* (default `/api/auth/*`) for `GET` + `POST`, so every better-auth endpoint
|
|
7
|
-
* callbacks, session, 2FA, magic links, …
|
|
6
|
+
* (default `/api/auth/*`) for `GET` + `POST`, so every better-auth endpoint - sign-in/up/out, OAuth
|
|
7
|
+
* callbacks, session, 2FA, magic links, … - is served by your nifra server.
|
|
8
8
|
*
|
|
9
9
|
* ```ts
|
|
10
10
|
* import { betterAuth, requireSession } from "@nifrajs/better-auth"
|
|
@@ -14,7 +14,7 @@ const HANDLED_METHODS = ["GET", "POST"];
|
|
|
14
14
|
* .get("/me", async (c) => (await requireSession(auth, c.req)).user)
|
|
15
15
|
* ```
|
|
16
16
|
*
|
|
17
|
-
* Idempotent (named `"better-auth"`
|
|
17
|
+
* Idempotent (named `"better-auth"` - applying twice mounts once). Read the session with
|
|
18
18
|
* {@link getSession} / {@link requireSession}, which infer your better-auth types.
|
|
19
19
|
*/
|
|
20
20
|
export function betterAuth(auth, options = {}) {
|
|
@@ -22,7 +22,7 @@ export function betterAuth(auth, options = {}) {
|
|
|
22
22
|
const pattern = `${base.replace(/\/+$/, "")}/*`; // strip trailing slash(es), then wildcard the subtree
|
|
23
23
|
// A type-IDENTITY plugin (see defineIdentityPlugin): mounting the auth handler must NOT change the
|
|
24
24
|
// route registry's type. A plain `definePlugin((app) => app)` would infer `app: Server<any, any>`, so
|
|
25
|
-
// `use`'s result
|
|
25
|
+
// `use`'s result - and the entire typed client derived from it - collapsed to `any`. This was the #1
|
|
26
26
|
// reported anti-drift bug: routes declared after `.use(betterAuth(...))` silently lost their types.
|
|
27
27
|
return defineIdentityPlugin("better-auth", (app) => {
|
|
28
28
|
for (const method of HANDLED_METHODS) {
|
|
@@ -34,7 +34,7 @@ export function betterAuth(auth, options = {}) {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Resolve the better-auth session for a request
|
|
37
|
+
* Resolve the better-auth session for a request - a thin, typed wrapper over `auth.api.getSession`.
|
|
38
38
|
* Returns `null` when unauthenticated. Takes the raw `Request` so it works in both server handlers
|
|
39
39
|
* (`c.req`) and web loaders/actions (`request`).
|
|
40
40
|
*
|
|
@@ -53,7 +53,7 @@ const rejection = (options) => {
|
|
|
53
53
|
if (to === undefined)
|
|
54
54
|
return Response.json({ ok: false, error: "unauthorized" }, { status: 401 });
|
|
55
55
|
// Same-origin guard (mirrors @nifrajs/web `redirect` + @nifrajs/auth guards): a single leading "/", never
|
|
56
|
-
// "//host" or an absolute URL. `redirectTo` is dev-authored, so a bad value is a config bug
|
|
56
|
+
// "//host" or an absolute URL. `redirectTo` is dev-authored, so a bad value is a config bug - fail loud.
|
|
57
57
|
if (!to.startsWith("/") || to.startsWith("//")) {
|
|
58
58
|
throw new Error(`[nifra/better-auth] requireSession redirectTo must be a same-origin path beginning with "/" (got ${JSON.stringify(to)})`);
|
|
59
59
|
}
|
|
@@ -61,7 +61,7 @@ const rejection = (options) => {
|
|
|
61
61
|
};
|
|
62
62
|
/**
|
|
63
63
|
* Require an authenticated better-auth session at the top of a protected handler/loader/action.
|
|
64
|
-
* Returns the (non-null) session when present; otherwise **throws a `Response`** (302/401)
|
|
64
|
+
* Returns the (non-null) session when present; otherwise **throws a `Response`** (302/401) - nifra
|
|
65
65
|
* returns a thrown `Response` as-is, short-circuiting the rest of the handler.
|
|
66
66
|
*
|
|
67
67
|
* ```ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/better-auth",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Mount better-auth into a nifra app
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Mount better-auth into a nifra app - one app.use() wires /api/auth/*, plus typed getSession/requireSession guards. Structural typing: no hard dependency on better-auth.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
7
7
|
"typescript",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@nifrajs/core": "^2.
|
|
43
|
+
"@nifrajs/core": "^2.4.0",
|
|
44
44
|
"better-auth": ">=1.0.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { type AnyServer, defineIdentityPlugin, type Server } from "@nifrajs/core
|
|
|
7
7
|
* {@link getSession} / {@link requireSession} via inference.
|
|
8
8
|
*/
|
|
9
9
|
export interface BetterAuthLike {
|
|
10
|
-
/** better-auth's catch-all handler
|
|
10
|
+
/** better-auth's catch-all handler - serves every request under `basePath`. */
|
|
11
11
|
readonly handler: (request: Request) => Response | Promise<Response>
|
|
12
12
|
readonly api: {
|
|
13
13
|
/** Resolve the session from request headers (cookie or bearer). Returns a nullable payload. */
|
|
@@ -17,7 +17,7 @@ export interface BetterAuthLike {
|
|
|
17
17
|
* better-auth's resolved options; `basePath` (when set) defaults the mount path. Intersected with
|
|
18
18
|
* `Record<string, unknown>` so a real better-auth instance (whose `options` is a large concrete object
|
|
19
19
|
* with no structural overlap to a bare `{ basePath? }`) stays assignable to `BetterAuthLike` WITHOUT a
|
|
20
|
-
* cast
|
|
20
|
+
* cast - keeping `A` concrete so `SessionOf<A>` recovers the real session type instead of collapsing
|
|
21
21
|
* to `{}` and forcing per-call session casts downstream.
|
|
22
22
|
*/
|
|
23
23
|
readonly options?: { readonly basePath?: string } & Record<string, unknown>
|
|
@@ -41,8 +41,8 @@ const HANDLED_METHODS = ["GET", "POST"] as const
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Mount a better-auth instance into a nifra app: registers its handler at `${basePath}/*`
|
|
44
|
-
* (default `/api/auth/*`) for `GET` + `POST`, so every better-auth endpoint
|
|
45
|
-
* callbacks, session, 2FA, magic links, …
|
|
44
|
+
* (default `/api/auth/*`) for `GET` + `POST`, so every better-auth endpoint - sign-in/up/out, OAuth
|
|
45
|
+
* callbacks, session, 2FA, magic links, … - is served by your nifra server.
|
|
46
46
|
*
|
|
47
47
|
* ```ts
|
|
48
48
|
* import { betterAuth, requireSession } from "@nifrajs/better-auth"
|
|
@@ -52,7 +52,7 @@ const HANDLED_METHODS = ["GET", "POST"] as const
|
|
|
52
52
|
* .get("/me", async (c) => (await requireSession(auth, c.req)).user)
|
|
53
53
|
* ```
|
|
54
54
|
*
|
|
55
|
-
* Idempotent (named `"better-auth"`
|
|
55
|
+
* Idempotent (named `"better-auth"` - applying twice mounts once). Read the session with
|
|
56
56
|
* {@link getSession} / {@link requireSession}, which infer your better-auth types.
|
|
57
57
|
*/
|
|
58
58
|
export function betterAuth(auth: BetterAuthLike, options: BetterAuthOptions = {}) {
|
|
@@ -60,7 +60,7 @@ export function betterAuth(auth: BetterAuthLike, options: BetterAuthOptions = {}
|
|
|
60
60
|
const pattern = `${base.replace(/\/+$/, "")}/*` // strip trailing slash(es), then wildcard the subtree
|
|
61
61
|
// A type-IDENTITY plugin (see defineIdentityPlugin): mounting the auth handler must NOT change the
|
|
62
62
|
// route registry's type. A plain `definePlugin((app) => app)` would infer `app: Server<any, any>`, so
|
|
63
|
-
// `use`'s result
|
|
63
|
+
// `use`'s result - and the entire typed client derived from it - collapsed to `any`. This was the #1
|
|
64
64
|
// reported anti-drift bug: routes declared after `.use(betterAuth(...))` silently lost their types.
|
|
65
65
|
return defineIdentityPlugin("better-auth", <S extends AnyServer>(app: S): S => {
|
|
66
66
|
for (const method of HANDLED_METHODS) {
|
|
@@ -75,7 +75,7 @@ export function betterAuth(auth: BetterAuthLike, options: BetterAuthOptions = {}
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
|
-
* Resolve the better-auth session for a request
|
|
78
|
+
* Resolve the better-auth session for a request - a thin, typed wrapper over `auth.api.getSession`.
|
|
79
79
|
* Returns `null` when unauthenticated. Takes the raw `Request` so it works in both server handlers
|
|
80
80
|
* (`c.req`) and web loaders/actions (`request`).
|
|
81
81
|
*
|
|
@@ -95,7 +95,7 @@ export function getSession<A extends BetterAuthLike>(
|
|
|
95
95
|
|
|
96
96
|
/**
|
|
97
97
|
* What {@link requireSession} does on a missing session: `302` to `redirectTo` (a same-origin path),
|
|
98
|
-
* or
|
|
98
|
+
* or - when omitted - a `401` JSON (`{ ok: false, error: "unauthorized" }`). Mirrors `@nifrajs/auth` guards.
|
|
99
99
|
*/
|
|
100
100
|
export interface RequireSessionOptions {
|
|
101
101
|
readonly redirectTo?: string
|
|
@@ -105,7 +105,7 @@ const rejection = (options: RequireSessionOptions): Response => {
|
|
|
105
105
|
const to = options.redirectTo
|
|
106
106
|
if (to === undefined) return Response.json({ ok: false, error: "unauthorized" }, { status: 401 })
|
|
107
107
|
// Same-origin guard (mirrors @nifrajs/web `redirect` + @nifrajs/auth guards): a single leading "/", never
|
|
108
|
-
// "//host" or an absolute URL. `redirectTo` is dev-authored, so a bad value is a config bug
|
|
108
|
+
// "//host" or an absolute URL. `redirectTo` is dev-authored, so a bad value is a config bug - fail loud.
|
|
109
109
|
if (!to.startsWith("/") || to.startsWith("//")) {
|
|
110
110
|
throw new Error(
|
|
111
111
|
`[nifra/better-auth] requireSession redirectTo must be a same-origin path beginning with "/" (got ${JSON.stringify(to)})`,
|
|
@@ -116,7 +116,7 @@ const rejection = (options: RequireSessionOptions): Response => {
|
|
|
116
116
|
|
|
117
117
|
/**
|
|
118
118
|
* Require an authenticated better-auth session at the top of a protected handler/loader/action.
|
|
119
|
-
* Returns the (non-null) session when present; otherwise **throws a `Response`** (302/401)
|
|
119
|
+
* Returns the (non-null) session when present; otherwise **throws a `Response`** (302/401) - nifra
|
|
120
120
|
* returns a thrown `Response` as-is, short-circuiting the rest of the handler.
|
|
121
121
|
*
|
|
122
122
|
* ```ts
|