@nifrajs/better-auth 2.13.0 → 3.1.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 +3 -3
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +22 -14
package/README.md
CHANGED
|
@@ -58,8 +58,8 @@ app.get("/me", async (c) => {
|
|
|
58
58
|
|
|
59
59
|
## Protect a route
|
|
60
60
|
|
|
61
|
-
`requireSession(auth, request, options?)` returns the non-null session or **throws a `
|
|
62
|
-
nifra
|
|
61
|
+
`requireSession(auth, request, options?)` returns the non-null session or **throws a Nifra `ResponseResult`** -
|
|
62
|
+
nifra handles the thrown `ResponseResult` as control flow, short-circuiting the handler:
|
|
63
63
|
|
|
64
64
|
```ts
|
|
65
65
|
import { requireSession } from "@nifrajs/better-auth"
|
|
@@ -83,7 +83,7 @@ export const loader = async ({ request }) => {
|
|
|
83
83
|
| --- | --- |
|
|
84
84
|
| `betterAuth(auth, options?)` | Plugin that mounts better-auth's handler at `${basePath}/*` for GET + POST. |
|
|
85
85
|
| `getSession(auth, request)` | `Promise<SessionOf<A> \| null>` - typed wrapper over `auth.api.getSession`. |
|
|
86
|
-
| `requireSession(auth, request, options?)` | Returns the session or throws a `
|
|
86
|
+
| `requireSession(auth, request, options?)` | Returns the session or throws a Nifra `ResponseResult` (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. |
|
|
89
89
|
|
package/dist/index.d.ts
CHANGED
|
@@ -71,8 +71,8 @@ export interface RequireSessionOptions {
|
|
|
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 `
|
|
75
|
-
*
|
|
74
|
+
* Returns the (non-null) session when present; otherwise **throws a Nifra `ResponseResult`** (302/401).
|
|
75
|
+
* Nifra handles the thrown control-flow value on the same rendering lane as `@nifrajs/auth`.
|
|
76
76
|
*
|
|
77
77
|
* ```ts
|
|
78
78
|
* const { user } = await requireSession(auth, c.req, { redirectTo: "/login" })
|
|
@@ -130,14 +130,14 @@ export type WithPrincipal<S extends AnyServer, P> = S extends Server<infer R, in
|
|
|
130
130
|
principal: P;
|
|
131
131
|
}> : never;
|
|
132
132
|
/**
|
|
133
|
-
* Resolve the better-auth session and map it to a {@link Principal}, or **throw a `
|
|
133
|
+
* Resolve the better-auth session and map it to a {@link Principal}, or **throw a Nifra `ResponseResult`** so the
|
|
134
134
|
* handler never runs unauthenticated:
|
|
135
135
|
*
|
|
136
136
|
* - No/invalid session -> `302` to `options.redirectTo` when set, else `401` JSON (`requireSession`).
|
|
137
137
|
* - `requireTenant: true` and no tenant resolves -> `403` JSON (`{ ok: false, error: "forbidden" }`).
|
|
138
138
|
*
|
|
139
|
-
* nifra
|
|
140
|
-
* or by {@link authed}. `tenantId` is a non-optional `string` in the return type when `requireTenant`.
|
|
139
|
+
* nifra handles the thrown control-flow value as a short-circuit, so this is the fail-closed guard used
|
|
140
|
+
* inline or by {@link authed}. `tenantId` is a non-optional `string` in the return type when `requireTenant`.
|
|
141
141
|
*
|
|
142
142
|
* ```ts
|
|
143
143
|
* const principal = await requirePrincipal(auth, c.req, { requireTenant: true })
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineIdentityPlugin } from "@nifrajs/core/server";
|
|
1
|
+
import { defineIdentityPlugin, isSameOriginPath, status, } from "@nifrajs/core/server";
|
|
2
2
|
// better-auth dispatches only GET (session, OAuth/email callbacks) and POST (sign-in/up/out, etc.).
|
|
3
3
|
const HANDLED_METHODS = ["GET", "POST"];
|
|
4
4
|
/**
|
|
@@ -53,18 +53,19 @@ export function getSession(auth, request) {
|
|
|
53
53
|
const rejection = (options) => {
|
|
54
54
|
const to = options.redirectTo;
|
|
55
55
|
if (to === undefined)
|
|
56
|
-
return
|
|
57
|
-
//
|
|
58
|
-
// "//host"
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
return status(401, { ok: false, error: "unauthorized" });
|
|
57
|
+
// The kernel's same-origin predicate, shared with `@nifrajs/web`'s `redirect` and the
|
|
58
|
+
// `@nifrajs/auth` guards: a single leading "/", never "//host", an absolute URL, or a form a URL
|
|
59
|
+
// parser resolves off-origin. `redirectTo` is dev-authored, so a bad value is a config bug - fail loud.
|
|
60
|
+
if (!isSameOriginPath(to)) {
|
|
61
|
+
throw new Error(`[nifra/better-auth] requireSession redirectTo must be a same-origin path beginning with "/" - never "//", a backslash, or a control character (got ${JSON.stringify(to)})`);
|
|
61
62
|
}
|
|
62
|
-
return
|
|
63
|
+
return status(302, undefined, { headers: { location: to } });
|
|
63
64
|
};
|
|
64
65
|
/**
|
|
65
66
|
* Require an authenticated better-auth session at the top of a protected handler/loader/action.
|
|
66
|
-
* Returns the (non-null) session when present; otherwise **throws a `
|
|
67
|
-
*
|
|
67
|
+
* Returns the (non-null) session when present; otherwise **throws a Nifra `ResponseResult`** (302/401).
|
|
68
|
+
* Nifra handles the thrown control-flow value on the same rendering lane as `@nifrajs/auth`.
|
|
68
69
|
*
|
|
69
70
|
* ```ts
|
|
70
71
|
* const { user } = await requireSession(auth, c.req, { redirectTo: "/login" })
|
|
@@ -76,16 +77,16 @@ export async function requireSession(auth, request, options = {}) {
|
|
|
76
77
|
return session;
|
|
77
78
|
throw rejection(options);
|
|
78
79
|
}
|
|
79
|
-
const forbidden = () =>
|
|
80
|
+
const forbidden = () => status(403, { ok: false, error: "forbidden" });
|
|
80
81
|
/**
|
|
81
|
-
* Resolve the better-auth session and map it to a {@link Principal}, or **throw a `
|
|
82
|
+
* Resolve the better-auth session and map it to a {@link Principal}, or **throw a Nifra `ResponseResult`** so the
|
|
82
83
|
* handler never runs unauthenticated:
|
|
83
84
|
*
|
|
84
85
|
* - No/invalid session -> `302` to `options.redirectTo` when set, else `401` JSON (`requireSession`).
|
|
85
86
|
* - `requireTenant: true` and no tenant resolves -> `403` JSON (`{ ok: false, error: "forbidden" }`).
|
|
86
87
|
*
|
|
87
|
-
* nifra
|
|
88
|
-
* or by {@link authed}. `tenantId` is a non-optional `string` in the return type when `requireTenant`.
|
|
88
|
+
* nifra handles the thrown control-flow value as a short-circuit, so this is the fail-closed guard used
|
|
89
|
+
* inline or by {@link authed}. `tenantId` is a non-optional `string` in the return type when `requireTenant`.
|
|
89
90
|
*
|
|
90
91
|
* ```ts
|
|
91
92
|
* const principal = await requirePrincipal(auth, c.req, { requireTenant: true })
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/better-auth",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@nifrajs/core": "^
|
|
43
|
+
"@nifrajs/core": "^3.1.0",
|
|
44
44
|
"better-auth": ">=1.0.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependenciesMeta": {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AnyServer,
|
|
3
|
+
defineIdentityPlugin,
|
|
4
|
+
isSameOriginPath,
|
|
5
|
+
type ResponseResult,
|
|
6
|
+
type Server,
|
|
7
|
+
status,
|
|
8
|
+
} from "@nifrajs/core/server"
|
|
2
9
|
|
|
3
10
|
/**
|
|
4
11
|
* The structural slice of a [better-auth](https://better-auth.com) instance this package needs.
|
|
@@ -102,23 +109,24 @@ export interface RequireSessionOptions {
|
|
|
102
109
|
readonly redirectTo?: string
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
const rejection = (options: RequireSessionOptions):
|
|
112
|
+
const rejection = (options: RequireSessionOptions): ResponseResult => {
|
|
106
113
|
const to = options.redirectTo
|
|
107
|
-
if (to === undefined) return
|
|
108
|
-
//
|
|
109
|
-
// "//host"
|
|
110
|
-
|
|
114
|
+
if (to === undefined) return status(401, { ok: false, error: "unauthorized" })
|
|
115
|
+
// The kernel's same-origin predicate, shared with `@nifrajs/web`'s `redirect` and the
|
|
116
|
+
// `@nifrajs/auth` guards: a single leading "/", never "//host", an absolute URL, or a form a URL
|
|
117
|
+
// parser resolves off-origin. `redirectTo` is dev-authored, so a bad value is a config bug - fail loud.
|
|
118
|
+
if (!isSameOriginPath(to)) {
|
|
111
119
|
throw new Error(
|
|
112
|
-
`[nifra/better-auth] requireSession redirectTo must be a same-origin path beginning with "/" (got ${JSON.stringify(to)})`,
|
|
120
|
+
`[nifra/better-auth] requireSession redirectTo must be a same-origin path beginning with "/" - never "//", a backslash, or a control character (got ${JSON.stringify(to)})`,
|
|
113
121
|
)
|
|
114
122
|
}
|
|
115
|
-
return
|
|
123
|
+
return status(302, undefined, { headers: { location: to } })
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
/**
|
|
119
127
|
* Require an authenticated better-auth session at the top of a protected handler/loader/action.
|
|
120
|
-
* Returns the (non-null) session when present; otherwise **throws a `
|
|
121
|
-
*
|
|
128
|
+
* Returns the (non-null) session when present; otherwise **throws a Nifra `ResponseResult`** (302/401).
|
|
129
|
+
* Nifra handles the thrown control-flow value on the same rendering lane as `@nifrajs/auth`.
|
|
122
130
|
*
|
|
123
131
|
* ```ts
|
|
124
132
|
* const { user } = await requireSession(auth, c.req, { redirectTo: "/login" })
|
|
@@ -187,17 +195,17 @@ export type PrincipalFor<User, RequireTenant extends boolean> = RequireTenant ex
|
|
|
187
195
|
export type WithPrincipal<S extends AnyServer, P> =
|
|
188
196
|
S extends Server<infer R, infer C> ? Server<R, C & { principal: P }> : never
|
|
189
197
|
|
|
190
|
-
const forbidden = ():
|
|
198
|
+
const forbidden = (): ResponseResult => status(403, { ok: false, error: "forbidden" })
|
|
191
199
|
|
|
192
200
|
/**
|
|
193
|
-
* Resolve the better-auth session and map it to a {@link Principal}, or **throw a `
|
|
201
|
+
* Resolve the better-auth session and map it to a {@link Principal}, or **throw a Nifra `ResponseResult`** so the
|
|
194
202
|
* handler never runs unauthenticated:
|
|
195
203
|
*
|
|
196
204
|
* - No/invalid session -> `302` to `options.redirectTo` when set, else `401` JSON (`requireSession`).
|
|
197
205
|
* - `requireTenant: true` and no tenant resolves -> `403` JSON (`{ ok: false, error: "forbidden" }`).
|
|
198
206
|
*
|
|
199
|
-
* nifra
|
|
200
|
-
* or by {@link authed}. `tenantId` is a non-optional `string` in the return type when `requireTenant`.
|
|
207
|
+
* nifra handles the thrown control-flow value as a short-circuit, so this is the fail-closed guard used
|
|
208
|
+
* inline or by {@link authed}. `tenantId` is a non-optional `string` in the return type when `requireTenant`.
|
|
201
209
|
*
|
|
202
210
|
* ```ts
|
|
203
211
|
* const principal = await requirePrincipal(auth, c.req, { requireTenant: true })
|