@nifrajs/better-auth 4.0.2 → 5.0.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/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +48 -13
package/dist/index.d.ts
CHANGED
|
@@ -62,12 +62,23 @@ export declare function betterAuth(auth: BetterAuthLike, options?: BetterAuthOpt
|
|
|
62
62
|
* ```
|
|
63
63
|
*/
|
|
64
64
|
export declare function getSession<A extends BetterAuthLike>(auth: A, input: Request | Headers | AuthHeaders): Promise<SessionOf<A> | null>;
|
|
65
|
+
/** The response a guard denies a missing session with, in place of the default `401` JSON. */
|
|
66
|
+
export interface UnauthenticatedResponse {
|
|
67
|
+
/** An error status, 400-599. Default `401`. */
|
|
68
|
+
readonly status?: number;
|
|
69
|
+
/** The body; present but `undefined` sends none. Default `{ ok: false, error: "unauthorized" }`. */
|
|
70
|
+
readonly body?: unknown;
|
|
71
|
+
/** Response headers, such as a `www-authenticate` challenge. */
|
|
72
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
73
|
+
}
|
|
65
74
|
/**
|
|
66
75
|
* What {@link requireSession} does on a missing session: `302` to `redirectTo` (a same-origin path),
|
|
67
|
-
* or - when omitted -
|
|
76
|
+
* or - when omitted - the `unauthenticated` response, by default a `401` JSON
|
|
77
|
+
* (`{ ok: false, error: "unauthorized" }`). Mirrors `@nifrajs/auth` guards.
|
|
68
78
|
*/
|
|
69
79
|
export interface RequireSessionOptions {
|
|
70
80
|
readonly redirectTo?: string;
|
|
81
|
+
readonly unauthenticated?: UnauthenticatedResponse;
|
|
71
82
|
}
|
|
72
83
|
/**
|
|
73
84
|
* Require an authenticated better-auth session at the top of a protected handler/loader/action.
|
|
@@ -113,6 +124,8 @@ export interface AuthedOptions<User> {
|
|
|
113
124
|
readonly requireTenant?: boolean;
|
|
114
125
|
/** Browser redirect target (`302`) for a missing session, instead of the default `401` JSON. Same-origin. */
|
|
115
126
|
readonly redirectTo?: string;
|
|
127
|
+
/** The response for a missing session when there is no `redirectTo`. Default `401` JSON. */
|
|
128
|
+
readonly unauthenticated?: UnauthenticatedResponse;
|
|
116
129
|
/** Resolve the tenant id from the user. Default: `user.tenantId ?? user.orgId` (string-valued only). */
|
|
117
130
|
readonly tenantOf?: (user: User) => string | undefined;
|
|
118
131
|
}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,SAAS,EAEd,KAAK,WAAW,EAMhB,KAAK,MAAM,EAEZ,MAAM,sBAAsB,CAAA;AAE7B;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACpE,QAAQ,CAAC,GAAG,EAAE;QACZ,+FAA+F;QAC/F,QAAQ,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE;YAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;SAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;KAClF,CAAA;IACD;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC5E;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,cAAc,IAAI,WAAW,CAC3D,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAC5C,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAKD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,GAAE,iBAAsB,iDAkB/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,cAAc,EACjD,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,WAAW,GACrC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAc9B;AAED
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -61,10 +61,32 @@ export function getSession(auth, input) {
|
|
|
61
61
|
// Normalize both forms so every caller has one fail-closed branch.
|
|
62
62
|
return result.then((session) => (session == null ? null : session));
|
|
63
63
|
}
|
|
64
|
+
const UNAUTHORIZED = { ok: false, error: "unauthorized" };
|
|
65
|
+
/** A dev-authored denial that is not an error status, or that competes with a redirect, fails loud. */
|
|
66
|
+
function assertUnauthenticated(options) {
|
|
67
|
+
const custom = options.unauthenticated;
|
|
68
|
+
if (custom === undefined)
|
|
69
|
+
return;
|
|
70
|
+
if (options.redirectTo !== undefined) {
|
|
71
|
+
throw new Error("[nifra/better-auth] a guard takes redirectTo or unauthenticated, not both");
|
|
72
|
+
}
|
|
73
|
+
const code = custom.status;
|
|
74
|
+
if (code !== undefined && (!Number.isInteger(code) || code < 400 || code > 599)) {
|
|
75
|
+
throw new RangeError(`[nifra/better-auth] guard unauthenticated.status must be an integer 400-599 (got ${String(code)})`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const unauthenticatedResult = (custom) => {
|
|
79
|
+
if (custom === undefined)
|
|
80
|
+
return status(401, UNAUTHORIZED);
|
|
81
|
+
const body = "body" in custom ? custom.body : UNAUTHORIZED;
|
|
82
|
+
return custom.headers === undefined
|
|
83
|
+
? status(custom.status ?? 401, body)
|
|
84
|
+
: status(custom.status ?? 401, body, { headers: custom.headers });
|
|
85
|
+
};
|
|
64
86
|
const rejection = (options) => {
|
|
65
87
|
const to = options.redirectTo;
|
|
66
88
|
if (to === undefined)
|
|
67
|
-
return
|
|
89
|
+
return unauthenticatedResult(options.unauthenticated);
|
|
68
90
|
return status(302, undefined, { headers: { location: to } });
|
|
69
91
|
};
|
|
70
92
|
function assertSafeRedirectTo(to, owner) {
|
|
@@ -86,6 +108,7 @@ function assertSafeRedirectTo(to, owner) {
|
|
|
86
108
|
*/
|
|
87
109
|
export async function requireSession(auth, request, options = {}) {
|
|
88
110
|
assertSafeRedirectTo(options.redirectTo, "requireSession");
|
|
111
|
+
assertUnauthenticated(options);
|
|
89
112
|
const session = await getSession(auth, request);
|
|
90
113
|
if (session !== null)
|
|
91
114
|
return session;
|
|
@@ -153,7 +176,7 @@ function principalFromSession(session, options) {
|
|
|
153
176
|
*/
|
|
154
177
|
export async function requirePrincipal(auth, request, options) {
|
|
155
178
|
// Reuse requireSession's throw path (302/redirect or 401) verbatim - one owner for the no-session gate.
|
|
156
|
-
const session = await requireSession(auth, request, options
|
|
179
|
+
const session = await requireSession(auth, request, options ?? {});
|
|
157
180
|
// better-auth sessions are `{ user: { id: string, ... }, session: { id: string, ... } }`; view the
|
|
158
181
|
// fields we map. The concrete user type flows through `SessionUserOf<A>` for `principal.user`.
|
|
159
182
|
const mapped = principalFromSession(session, options);
|
|
@@ -185,6 +208,7 @@ export async function requirePrincipal(auth, request, options) {
|
|
|
185
208
|
*/
|
|
186
209
|
export function authed(auth, options) {
|
|
187
210
|
assertSafeRedirectTo(options?.redirectTo, "authed");
|
|
211
|
+
assertUnauthenticated(options ?? {});
|
|
188
212
|
const plugin = (app) => app.authenticate({
|
|
189
213
|
id: "better-auth",
|
|
190
214
|
mode: "async",
|
|
@@ -192,7 +216,7 @@ export function authed(auth, options) {
|
|
|
192
216
|
try {
|
|
193
217
|
const session = await getSession(auth, input.headers);
|
|
194
218
|
if (session === null) {
|
|
195
|
-
return rejected("unauthenticated", rejection(options
|
|
219
|
+
return rejected("unauthenticated", rejection(options ?? {}));
|
|
196
220
|
}
|
|
197
221
|
const mapped = principalFromSession(session, options);
|
|
198
222
|
if (mapped.kind === "forbidden")
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,OAAO,EAIL,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAEhB,QAAQ,EAER,MAAM,GACP,MAAM,sBAAsB,CAAA;AAsC7B,oGAAoG;AACpG,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,CAAU,CAAA;AAEhD;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,UAAU,CAAC,IAAoB,EAAE,UAA6B,EAAE;IAC9E,IAAI,IAAI,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,QAAQ,IAAI,WAAW,CAAA;IACpE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA,CAAC,wEAAwE;IAC5H,MAAM,OAAO,GAAG,GAAG,IAAI,IAAI,CAAA,CAAC,4BAA4B;IACxD,mGAAmG;IACnG,sGAAsG;IACtG,qGAAqG;IACrG,oGAAoG;IACpG,OAAO,oBAAoB,CAAC,aAAa,EAAE,CAAsB,GAAM,EAAK,EAAE;QAC5E,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;YACrC,iGAAiG;YACjG,oGAAoG;YACpG,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAA4B,EAAE,EAAE,CACxE,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CACpB,CAAA;QACH,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU,CACxB,IAAO,EACP,KAAsC;IAEtC,6FAA6F;IAC7F,uFAAuF;IACvF,gGAAgG;IAChG,kGAAkG;IAClG,MAAM,MAAM,GAAG,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAA;IACzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;IAC7B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC7B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC7B,CAAC,CAAC,CAAA;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAA6C,CAAA;IAC3F,gGAAgG;IAChG,mEAAmE;IACnE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;AACrE,CAAC;
|
|
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": "5.0.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",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@nifrajs/core": "^
|
|
46
|
+
"@nifrajs/core": "^5.0.0",
|
|
47
47
|
"better-auth": ">=1.0.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
package/src/index.ts
CHANGED
|
@@ -116,17 +116,57 @@ export function getSession<A extends BetterAuthLike>(
|
|
|
116
116
|
return result.then((session) => (session == null ? null : session))
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/** The response a guard denies a missing session with, in place of the default `401` JSON. */
|
|
120
|
+
export interface UnauthenticatedResponse {
|
|
121
|
+
/** An error status, 400-599. Default `401`. */
|
|
122
|
+
readonly status?: number
|
|
123
|
+
/** The body; present but `undefined` sends none. Default `{ ok: false, error: "unauthorized" }`. */
|
|
124
|
+
readonly body?: unknown
|
|
125
|
+
/** Response headers, such as a `www-authenticate` challenge. */
|
|
126
|
+
readonly headers?: Readonly<Record<string, string>>
|
|
127
|
+
}
|
|
128
|
+
|
|
119
129
|
/**
|
|
120
130
|
* What {@link requireSession} does on a missing session: `302` to `redirectTo` (a same-origin path),
|
|
121
|
-
* or - when omitted -
|
|
131
|
+
* or - when omitted - the `unauthenticated` response, by default a `401` JSON
|
|
132
|
+
* (`{ ok: false, error: "unauthorized" }`). Mirrors `@nifrajs/auth` guards.
|
|
122
133
|
*/
|
|
123
134
|
export interface RequireSessionOptions {
|
|
124
135
|
readonly redirectTo?: string
|
|
136
|
+
readonly unauthenticated?: UnauthenticatedResponse
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const UNAUTHORIZED = { ok: false, error: "unauthorized" } as const
|
|
140
|
+
|
|
141
|
+
/** A dev-authored denial that is not an error status, or that competes with a redirect, fails loud. */
|
|
142
|
+
function assertUnauthenticated(options: {
|
|
143
|
+
readonly redirectTo?: string
|
|
144
|
+
readonly unauthenticated?: UnauthenticatedResponse
|
|
145
|
+
}): void {
|
|
146
|
+
const custom = options.unauthenticated
|
|
147
|
+
if (custom === undefined) return
|
|
148
|
+
if (options.redirectTo !== undefined) {
|
|
149
|
+
throw new Error("[nifra/better-auth] a guard takes redirectTo or unauthenticated, not both")
|
|
150
|
+
}
|
|
151
|
+
const code = custom.status
|
|
152
|
+
if (code !== undefined && (!Number.isInteger(code) || code < 400 || code > 599)) {
|
|
153
|
+
throw new RangeError(
|
|
154
|
+
`[nifra/better-auth] guard unauthenticated.status must be an integer 400-599 (got ${String(code)})`,
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const unauthenticatedResult = (custom: UnauthenticatedResponse | undefined): ResponseResult => {
|
|
160
|
+
if (custom === undefined) return status(401, UNAUTHORIZED)
|
|
161
|
+
const body = "body" in custom ? custom.body : UNAUTHORIZED
|
|
162
|
+
return custom.headers === undefined
|
|
163
|
+
? status(custom.status ?? 401, body)
|
|
164
|
+
: status(custom.status ?? 401, body, { headers: custom.headers })
|
|
125
165
|
}
|
|
126
166
|
|
|
127
167
|
const rejection = (options: RequireSessionOptions): ResponseResult => {
|
|
128
168
|
const to = options.redirectTo
|
|
129
|
-
if (to === undefined) return
|
|
169
|
+
if (to === undefined) return unauthenticatedResult(options.unauthenticated)
|
|
130
170
|
return status(302, undefined, { headers: { location: to } })
|
|
131
171
|
}
|
|
132
172
|
|
|
@@ -155,6 +195,7 @@ export async function requireSession<A extends BetterAuthLike>(
|
|
|
155
195
|
options: RequireSessionOptions = {},
|
|
156
196
|
): Promise<SessionOf<A>> {
|
|
157
197
|
assertSafeRedirectTo(options.redirectTo, "requireSession")
|
|
198
|
+
assertUnauthenticated(options)
|
|
158
199
|
const session = await getSession(auth, request)
|
|
159
200
|
if (session !== null) return session
|
|
160
201
|
throw rejection(options)
|
|
@@ -195,6 +236,8 @@ export interface AuthedOptions<User> {
|
|
|
195
236
|
readonly requireTenant?: boolean
|
|
196
237
|
/** Browser redirect target (`302`) for a missing session, instead of the default `401` JSON. Same-origin. */
|
|
197
238
|
readonly redirectTo?: string
|
|
239
|
+
/** The response for a missing session when there is no `redirectTo`. Default `401` JSON. */
|
|
240
|
+
readonly unauthenticated?: UnauthenticatedResponse
|
|
198
241
|
/** Resolve the tenant id from the user. Default: `user.tenantId ?? user.orgId` (string-valued only). */
|
|
199
242
|
readonly tenantOf?: (user: User) => string | undefined
|
|
200
243
|
}
|
|
@@ -305,11 +348,7 @@ export async function requirePrincipal<
|
|
|
305
348
|
options?: AuthedOptions<SessionUserOf<A>> & { readonly requireTenant?: RequireTenant },
|
|
306
349
|
): Promise<PrincipalFor<SessionUserOf<A>, RequireTenant>> {
|
|
307
350
|
// Reuse requireSession's throw path (302/redirect or 401) verbatim - one owner for the no-session gate.
|
|
308
|
-
const session = await requireSession(
|
|
309
|
-
auth,
|
|
310
|
-
request,
|
|
311
|
-
options?.redirectTo !== undefined ? { redirectTo: options.redirectTo } : {},
|
|
312
|
-
)
|
|
351
|
+
const session = await requireSession(auth, request, options ?? {})
|
|
313
352
|
// better-auth sessions are `{ user: { id: string, ... }, session: { id: string, ... } }`; view the
|
|
314
353
|
// fields we map. The concrete user type flows through `SessionUserOf<A>` for `principal.user`.
|
|
315
354
|
const mapped = principalFromSession(session, options)
|
|
@@ -346,6 +385,7 @@ export function authed<A extends BetterAuthLike, const RequireTenant extends boo
|
|
|
346
385
|
app: S,
|
|
347
386
|
) => WithPrincipal<S, PrincipalFor<SessionUserOf<A>, RequireTenant>> {
|
|
348
387
|
assertSafeRedirectTo(options?.redirectTo, "authed")
|
|
388
|
+
assertUnauthenticated(options ?? {})
|
|
349
389
|
const plugin = <S extends AnyServer>(app: S) =>
|
|
350
390
|
app.authenticate({
|
|
351
391
|
id: "better-auth",
|
|
@@ -354,12 +394,7 @@ export function authed<A extends BetterAuthLike, const RequireTenant extends boo
|
|
|
354
394
|
try {
|
|
355
395
|
const session = await getSession(auth, input.headers)
|
|
356
396
|
if (session === null) {
|
|
357
|
-
return rejected(
|
|
358
|
-
"unauthenticated",
|
|
359
|
-
rejection(
|
|
360
|
-
options?.redirectTo === undefined ? {} : { redirectTo: options.redirectTo },
|
|
361
|
-
),
|
|
362
|
-
)
|
|
397
|
+
return rejected("unauthenticated", rejection(options ?? {}))
|
|
363
398
|
}
|
|
364
399
|
const mapped = principalFromSession(session, options)
|
|
365
400
|
if (mapped.kind === "forbidden") return rejected("forbidden")
|