@fluixi/auth 1.0.0-alpha.91 → 1.0.0-alpha.93
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 +13 -1
- package/dist/client.cjs +2 -2
- package/dist/client.js +1 -1
- package/dist/client.mjs +2 -2
- package/dist/cookies.cjs +8 -37
- package/dist/cookies.d.ts +2 -15
- package/dist/cookies.d.ts.map +1 -1
- package/dist/cookies.js +4 -47
- package/dist/cookies.mjs +4 -33
- package/dist/remote.cjs +52 -0
- package/dist/remote.d.ts +20 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +30 -0
- package/dist/remote.mjs +31 -0
- package/dist/server.cjs +94 -39
- package/dist/server.d.ts +78 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +86 -2
- package/dist/server.mjs +91 -36
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +14 -11
package/dist/server.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* AND server functions (same request context).
|
|
9
9
|
*/
|
|
10
10
|
import type { Middleware } from '@fluixi/start';
|
|
11
|
-
import { type AuthUser, type BetterAuthServer, type SessionData } from './types.js';
|
|
11
|
+
import { type AuthSession, type AuthUser, type BetterAuthServer, type SessionData } from './types.js';
|
|
12
12
|
export interface FluixiAuth<U = AuthUser> {
|
|
13
13
|
/** The underlying Better Auth instance. */
|
|
14
14
|
raw: BetterAuthServer;
|
|
@@ -27,11 +27,15 @@ export declare function mountAuthRoutes(): (request: Request) => Promise<Respons
|
|
|
27
27
|
* Place it in `src/middleware.ts`: `export default defineMiddleware([ authMiddleware() ])`.
|
|
28
28
|
*/
|
|
29
29
|
export declare function authMiddleware(): Middleware;
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Thrown by the require* helpers and the guards; carries the HTTP status, an optional redirect
|
|
32
|
+
* target, and a `code` when the refusal is more precise than 401 or 403 alone.
|
|
33
|
+
*/
|
|
31
34
|
export declare class AuthError extends Error {
|
|
32
35
|
status: 401 | 403;
|
|
33
36
|
redirectTo?: string | undefined;
|
|
34
|
-
|
|
37
|
+
code?: string | undefined;
|
|
38
|
+
constructor(status: 401 | 403, redirectTo?: string | undefined, message?: string, code?: string | undefined);
|
|
35
39
|
}
|
|
36
40
|
/** The current user or null: never throws. */
|
|
37
41
|
export declare function optionalUser<U = AuthUser>(): Promise<U | null>;
|
|
@@ -52,6 +56,76 @@ export declare function requireRole<U extends {
|
|
|
52
56
|
export declare function requirePermission(check: boolean | (() => boolean), opts?: {
|
|
53
57
|
redirectTo?: string;
|
|
54
58
|
}): Promise<void>;
|
|
59
|
+
export interface GuardOptions {
|
|
60
|
+
/** The security scheme the API description gives, when the cookie is renamed or a token is used. */
|
|
61
|
+
scheme?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
/** A signed-in caller's session, as a guard hands it to the handler. */
|
|
64
|
+
export interface SignedIn<U = AuthUser> {
|
|
65
|
+
user: U;
|
|
66
|
+
session: AuthSession;
|
|
67
|
+
}
|
|
68
|
+
/** The caller's membership of the organisation their session has active. */
|
|
69
|
+
export interface ActiveMember {
|
|
70
|
+
id: string;
|
|
71
|
+
organizationId: string;
|
|
72
|
+
userId: string;
|
|
73
|
+
/** One role, or several comma-separated, as Better Auth stores them. */
|
|
74
|
+
role: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* An `@fluixi/api` guard: the caller must be signed in. The handler gets `auth.user` and
|
|
78
|
+
* `auth.session`. Refuses with 401.
|
|
79
|
+
*
|
|
80
|
+
* ```ts
|
|
81
|
+
* export const GET = route({ guard: signedIn(), handler: ({ auth }) => auth.user });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export declare function signedIn<U = AuthUser>(options?: GuardOptions): {
|
|
85
|
+
check(request: Request): Promise<SignedIn<U>>;
|
|
86
|
+
security: {
|
|
87
|
+
name: string;
|
|
88
|
+
scheme: {
|
|
89
|
+
type: string;
|
|
90
|
+
in: string;
|
|
91
|
+
name: string;
|
|
92
|
+
description: string;
|
|
93
|
+
} | Record<string, unknown>;
|
|
94
|
+
};
|
|
95
|
+
refuses: readonly [401];
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* An `@fluixi/api` guard: the caller must be a member of the organisation their session has
|
|
99
|
+
* active, with one of `roles` if given, and every one of `permissions` if given (checked by
|
|
100
|
+
* Better Auth's access control). The handler gets `auth.member.organizationId`, which is the
|
|
101
|
+
* tenant every query of that route is scoped to.
|
|
102
|
+
*
|
|
103
|
+
* Refuses with 401 when signed out, else 403: `no_active_organization` when none is active,
|
|
104
|
+
* `not_a_member` when the membership is gone, `forbidden` for a role or permission.
|
|
105
|
+
*
|
|
106
|
+
* ```ts
|
|
107
|
+
* export const DELETE = route({ guard: member({ roles: ['owner', 'admin'] }), handler });
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare function member<U = AuthUser>(options?: GuardOptions & {
|
|
111
|
+
roles?: readonly string[];
|
|
112
|
+
permissions?: Record<string, string[]>;
|
|
113
|
+
}): {
|
|
114
|
+
check(request: Request): Promise<SignedIn<U> & {
|
|
115
|
+
member: ActiveMember;
|
|
116
|
+
}>;
|
|
117
|
+
security: {
|
|
118
|
+
name: string;
|
|
119
|
+
scheme: {
|
|
120
|
+
type: string;
|
|
121
|
+
in: string;
|
|
122
|
+
name: string;
|
|
123
|
+
description: string;
|
|
124
|
+
} | Record<string, unknown>;
|
|
125
|
+
};
|
|
126
|
+
refuses: readonly [401, 403];
|
|
127
|
+
};
|
|
55
128
|
export { parseCookies, serializeCookie, deleteCookie, getCookie, getCookies, type CookieOptions, } from './cookies.js';
|
|
56
|
-
export
|
|
129
|
+
export { remoteAuth, type RemoteAuthOptions } from './remote.js';
|
|
130
|
+
export type { SessionData, AuthSession, AuthUser, BetterAuthServer } from './types.js';
|
|
57
131
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,QAAQ;IACtC,2CAA2C;IAC3C,GAAG,EAAE,gBAAgB,CAAC;IACtB,8BAA8B;IAC9B,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjD,kEAAkE;IAClE,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3D;AAMD,wBAAgB,UAAU,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,GAAG,UAAU,CAAC,CAAC,CAAC,CAWlF;AAED,wBAAgB,OAAO,CAAC,CAAC,GAAG,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,CAKrD;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,UAAU,CAM3C;AAED
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,QAAQ;IACtC,2CAA2C;IAC3C,GAAG,EAAE,gBAAgB,CAAC;IACtB,8BAA8B;IAC9B,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjD,kEAAkE;IAClE,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;CAC3D;AAMD,wBAAgB,UAAU,CAAC,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,gBAAgB,GAAG,UAAU,CAAC,CAAC,CAAC,CAWlF;AAED,wBAAgB,OAAO,CAAC,CAAC,GAAG,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAC,CAKrD;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAEzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,IAAI,UAAU,CAM3C;AAED;;;GAGG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAEzB,MAAM,EAAE,GAAG,GAAG,GAAG;IACjB,UAAU,CAAC,EAAE,MAAM;IAEnB,IAAI,CAAC,EAAE,MAAM;gBAHb,MAAM,EAAE,GAAG,GAAG,GAAG,EACjB,UAAU,CAAC,EAAE,MAAM,YAAA,EAC1B,OAAO,CAAC,EAAE,MAAM,EACT,IAAI,CAAC,EAAE,MAAM,YAAA;CAKvB;AAMD,8CAA8C;AAC9C,wBAAsB,YAAY,CAAC,CAAC,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAEpE;AAED,+DAA+D;AAC/D,wBAAsB,WAAW,CAAC,CAAC,GAAG,QAAQ,EAAE,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAI1F;AAED,0DAA0D;AAC1D,wBAAsB,WAAW,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,EACtE,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,CAAC,CAAC,CAIZ;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,OAAO,GAAG,CAAC,MAAM,OAAO,CAAC,EAChC,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7B,OAAO,CAAC,IAAI,CAAC,CAIf;AAUD,MAAM,WAAW,YAAY;IAC3B,oGAAoG;IACpG,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,wEAAwE;AACxE,MAAM,WAAW,QAAQ,CAAC,CAAC,GAAG,QAAQ;IACpC,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,WAAW,CAAC;CACtB;AAED,4EAA4E;AAC5E,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;CACd;AAcD;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,QAAQ,EAAE,OAAO,GAAE,YAAiB;mBAExC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;EAQtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,CAAC,GAAG,QAAQ,EACjC,OAAO,GAAE,YAAY,GAAG;IAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;CAAO;mBAI3E,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;QAAE,MAAM,EAAE,YAAY,CAAA;KAAE,CAAC;;;;;;;;;;;EA2BjF;AAGD,OAAO,EACL,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,SAAS,EACT,UAAU,EACV,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEjE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -37,14 +37,19 @@ export function authMiddleware() {
|
|
|
37
37
|
return next();
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
/**
|
|
40
|
+
/**
|
|
41
|
+
* Thrown by the require* helpers and the guards; carries the HTTP status, an optional redirect
|
|
42
|
+
* target, and a `code` when the refusal is more precise than 401 or 403 alone.
|
|
43
|
+
*/
|
|
41
44
|
export class AuthError extends Error {
|
|
42
45
|
status;
|
|
43
46
|
redirectTo;
|
|
44
|
-
|
|
47
|
+
code;
|
|
48
|
+
constructor(status, redirectTo, message, code) {
|
|
45
49
|
super(message ?? (status === 401 ? 'Unauthorized' : 'Forbidden'));
|
|
46
50
|
this.status = status;
|
|
47
51
|
this.redirectTo = redirectTo;
|
|
52
|
+
this.code = code;
|
|
48
53
|
this.name = 'AuthError';
|
|
49
54
|
}
|
|
50
55
|
}
|
|
@@ -79,5 +84,84 @@ export async function requirePermission(check, opts) {
|
|
|
79
84
|
if (!ok)
|
|
80
85
|
throw new AuthError(403, opts?.redirectTo);
|
|
81
86
|
}
|
|
87
|
+
/** Better Auth's session cookie, as an API description names it. */
|
|
88
|
+
const SESSION_COOKIE = {
|
|
89
|
+
type: 'apiKey',
|
|
90
|
+
in: 'cookie',
|
|
91
|
+
name: 'better-auth.session_token',
|
|
92
|
+
description: 'Set by sign-in. Named `__Secure-better-auth.session_token` over HTTPS.',
|
|
93
|
+
};
|
|
94
|
+
/** The session for this request: from `authMiddleware()` when it ran, else resolved here. */
|
|
95
|
+
async function sessionFor(request) {
|
|
96
|
+
const resolved = getLocals()[AUTH_LOCALS_KEY];
|
|
97
|
+
return resolved ?? getAuth().getSession(request);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* An `@fluixi/api` guard: the caller must be signed in. The handler gets `auth.user` and
|
|
101
|
+
* `auth.session`. Refuses with 401.
|
|
102
|
+
*
|
|
103
|
+
* ```ts
|
|
104
|
+
* export const GET = route({ guard: signedIn(), handler: ({ auth }) => auth.user });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export function signedIn(options = {}) {
|
|
108
|
+
return {
|
|
109
|
+
async check(request) {
|
|
110
|
+
const { user, session } = await sessionFor(request);
|
|
111
|
+
if (!user || !session)
|
|
112
|
+
throw new AuthError(401);
|
|
113
|
+
return { user, session };
|
|
114
|
+
},
|
|
115
|
+
security: { name: 'session', scheme: options.scheme ?? SESSION_COOKIE },
|
|
116
|
+
refuses: [401],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* An `@fluixi/api` guard: the caller must be a member of the organisation their session has
|
|
121
|
+
* active, with one of `roles` if given, and every one of `permissions` if given (checked by
|
|
122
|
+
* Better Auth's access control). The handler gets `auth.member.organizationId`, which is the
|
|
123
|
+
* tenant every query of that route is scoped to.
|
|
124
|
+
*
|
|
125
|
+
* Refuses with 401 when signed out, else 403: `no_active_organization` when none is active,
|
|
126
|
+
* `not_a_member` when the membership is gone, `forbidden` for a role or permission.
|
|
127
|
+
*
|
|
128
|
+
* ```ts
|
|
129
|
+
* export const DELETE = route({ guard: member({ roles: ['owner', 'admin'] }), handler });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export function member(options = {}) {
|
|
133
|
+
const signed = signedIn(options);
|
|
134
|
+
return {
|
|
135
|
+
async check(request) {
|
|
136
|
+
const auth = await signed.check(request);
|
|
137
|
+
if (!auth.session.activeOrganizationId) {
|
|
138
|
+
throw new AuthError(403, undefined, 'Choose an organisation first.', 'no_active_organization');
|
|
139
|
+
}
|
|
140
|
+
const api = getAuth().raw.api;
|
|
141
|
+
if (!api.getActiveMember || !api.hasPermission) {
|
|
142
|
+
throw new Error("[@fluixi/auth] member() needs Better Auth's organization() plugin.");
|
|
143
|
+
}
|
|
144
|
+
const membership = await api.getActiveMember({ headers: request.headers });
|
|
145
|
+
if (!membership) {
|
|
146
|
+
throw new AuthError(403, undefined, 'You are not a member of this organisation.', 'not_a_member');
|
|
147
|
+
}
|
|
148
|
+
if (options.roles) {
|
|
149
|
+
const held = membership.role.split(',').map((role) => role.trim());
|
|
150
|
+
if (!options.roles.some((role) => held.includes(role)))
|
|
151
|
+
throw new AuthError(403);
|
|
152
|
+
}
|
|
153
|
+
if (options.permissions) {
|
|
154
|
+
const { success } = await api.hasPermission({ headers: request.headers, body: { permissions: options.permissions } });
|
|
155
|
+
if (!success)
|
|
156
|
+
throw new AuthError(403);
|
|
157
|
+
}
|
|
158
|
+
const { id, organizationId, userId, role } = membership;
|
|
159
|
+
return { ...auth, member: { id, organizationId, userId, role } };
|
|
160
|
+
},
|
|
161
|
+
security: signed.security,
|
|
162
|
+
refuses: [401, 403],
|
|
163
|
+
};
|
|
164
|
+
}
|
|
82
165
|
// Cookie API: proper, secure-by-default cookie management (parse/serialize/request reads).
|
|
83
166
|
export { parseCookies, serializeCookie, deleteCookie, getCookie, getCookies, } from './cookies.js';
|
|
167
|
+
export { remoteAuth } from './remote.js';
|
package/dist/server.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @fluixi/auth v1.0.0-alpha.
|
|
1
|
+
/*! @fluixi/auth v1.0.0-alpha.93 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import { getLocals, getRequestLocals } from "@fluixi/server";
|
|
@@ -8,38 +8,8 @@ var AUTH_LOCALS_KEY = "auth";
|
|
|
8
8
|
|
|
9
9
|
// src/cookies.ts
|
|
10
10
|
import { getRequestEvent } from "@fluixi/server";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!header) return out;
|
|
14
|
-
for (const part of header.split(";")) {
|
|
15
|
-
const eq = part.indexOf("=");
|
|
16
|
-
if (eq < 0) continue;
|
|
17
|
-
const k = part.slice(0, eq).trim();
|
|
18
|
-
if (k) out[k] = decodeURIComponent(part.slice(eq + 1).trim());
|
|
19
|
-
}
|
|
20
|
-
return out;
|
|
21
|
-
}
|
|
22
|
-
function serializeCookie(name, value, opts = {}) {
|
|
23
|
-
const o = {
|
|
24
|
-
path: "/",
|
|
25
|
-
httpOnly: true,
|
|
26
|
-
secure: true,
|
|
27
|
-
sameSite: "lax",
|
|
28
|
-
...opts
|
|
29
|
-
};
|
|
30
|
-
let s = `${name}=${encodeURIComponent(value)}`;
|
|
31
|
-
if (o.maxAge != null) s += `; Max-Age=${Math.floor(o.maxAge)}`;
|
|
32
|
-
if (o.expires) s += `; Expires=${o.expires.toUTCString()}`;
|
|
33
|
-
if (o.domain) s += `; Domain=${o.domain}`;
|
|
34
|
-
s += `; Path=${o.path}`;
|
|
35
|
-
if (o.httpOnly) s += "; HttpOnly";
|
|
36
|
-
if (o.secure) s += "; Secure";
|
|
37
|
-
s += `; SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`;
|
|
38
|
-
return s;
|
|
39
|
-
}
|
|
40
|
-
function deleteCookie(name, opts = {}) {
|
|
41
|
-
return serializeCookie(name, "", { ...opts, maxAge: 0 });
|
|
42
|
-
}
|
|
11
|
+
import { parseCookies } from "@fluixi/utils/cookie";
|
|
12
|
+
import { parseCookies as parseCookies2, serializeCookie, deleteCookie, isValidCookieName } from "@fluixi/utils/cookie";
|
|
43
13
|
function requestHeaders() {
|
|
44
14
|
const req = getRequestEvent()?.request;
|
|
45
15
|
return req?.headers ?? null;
|
|
@@ -51,6 +21,35 @@ function getCookies() {
|
|
|
51
21
|
return parseCookies(requestHeaders()?.get("cookie"));
|
|
52
22
|
}
|
|
53
23
|
|
|
24
|
+
// src/remote.ts
|
|
25
|
+
function remoteAuth(options) {
|
|
26
|
+
const base = new URL((options.basePath ?? "/api/auth").replace(/\/$/, "") + "/", options.url);
|
|
27
|
+
const call = (input, init) => (options.fetch ?? globalThis.fetch)(input, init);
|
|
28
|
+
return {
|
|
29
|
+
async handler(request) {
|
|
30
|
+
const url = new URL(request.url);
|
|
31
|
+
const hasBody = request.method !== "GET" && request.method !== "HEAD";
|
|
32
|
+
return call(new URL(url.pathname + url.search, base), {
|
|
33
|
+
method: request.method,
|
|
34
|
+
headers: request.headers,
|
|
35
|
+
body: hasBody ? request.body : void 0,
|
|
36
|
+
redirect: "manual",
|
|
37
|
+
...hasBody ? { duplex: "half" } : {}
|
|
38
|
+
});
|
|
39
|
+
},
|
|
40
|
+
api: {
|
|
41
|
+
async getSession({ headers }) {
|
|
42
|
+
const cookie = headers.get("cookie");
|
|
43
|
+
if (!cookie) return null;
|
|
44
|
+
const response = await call(new URL("get-session", base), { headers: { cookie, accept: "application/json" } });
|
|
45
|
+
if (!response.ok) return null;
|
|
46
|
+
const body = await response.json().catch(() => null);
|
|
47
|
+
return body && body.user ? { user: body.user, session: body.session ?? null } : null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
54
53
|
// src/server.ts
|
|
55
54
|
var _auth = null;
|
|
56
55
|
function defineAuth(instance) {
|
|
@@ -82,10 +81,11 @@ function authMiddleware() {
|
|
|
82
81
|
};
|
|
83
82
|
}
|
|
84
83
|
var AuthError = class extends Error {
|
|
85
|
-
constructor(status, redirectTo, message) {
|
|
84
|
+
constructor(status, redirectTo, message, code) {
|
|
86
85
|
super(message ?? (status === 401 ? "Unauthorized" : "Forbidden"));
|
|
87
86
|
this.status = status;
|
|
88
87
|
this.redirectTo = redirectTo;
|
|
88
|
+
this.code = code;
|
|
89
89
|
this.name = "AuthError";
|
|
90
90
|
}
|
|
91
91
|
};
|
|
@@ -110,6 +110,58 @@ async function requirePermission(check, opts) {
|
|
|
110
110
|
const ok = typeof check === "function" ? check() : check;
|
|
111
111
|
if (!ok) throw new AuthError(403, opts?.redirectTo);
|
|
112
112
|
}
|
|
113
|
+
var SESSION_COOKIE = {
|
|
114
|
+
type: "apiKey",
|
|
115
|
+
in: "cookie",
|
|
116
|
+
name: "better-auth.session_token",
|
|
117
|
+
description: "Set by sign-in. Named `__Secure-better-auth.session_token` over HTTPS."
|
|
118
|
+
};
|
|
119
|
+
async function sessionFor(request) {
|
|
120
|
+
const resolved = getLocals()[AUTH_LOCALS_KEY];
|
|
121
|
+
return resolved ?? getAuth().getSession(request);
|
|
122
|
+
}
|
|
123
|
+
function signedIn(options = {}) {
|
|
124
|
+
return {
|
|
125
|
+
async check(request) {
|
|
126
|
+
const { user, session } = await sessionFor(request);
|
|
127
|
+
if (!user || !session) throw new AuthError(401);
|
|
128
|
+
return { user, session };
|
|
129
|
+
},
|
|
130
|
+
security: { name: "session", scheme: options.scheme ?? SESSION_COOKIE },
|
|
131
|
+
refuses: [401]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function member(options = {}) {
|
|
135
|
+
const signed = signedIn(options);
|
|
136
|
+
return {
|
|
137
|
+
async check(request) {
|
|
138
|
+
const auth = await signed.check(request);
|
|
139
|
+
if (!auth.session.activeOrganizationId) {
|
|
140
|
+
throw new AuthError(403, void 0, "Choose an organisation first.", "no_active_organization");
|
|
141
|
+
}
|
|
142
|
+
const api = getAuth().raw.api;
|
|
143
|
+
if (!api.getActiveMember || !api.hasPermission) {
|
|
144
|
+
throw new Error("[@fluixi/auth] member() needs Better Auth's organization() plugin.");
|
|
145
|
+
}
|
|
146
|
+
const membership = await api.getActiveMember({ headers: request.headers });
|
|
147
|
+
if (!membership) {
|
|
148
|
+
throw new AuthError(403, void 0, "You are not a member of this organisation.", "not_a_member");
|
|
149
|
+
}
|
|
150
|
+
if (options.roles) {
|
|
151
|
+
const held = membership.role.split(",").map((role2) => role2.trim());
|
|
152
|
+
if (!options.roles.some((role2) => held.includes(role2))) throw new AuthError(403);
|
|
153
|
+
}
|
|
154
|
+
if (options.permissions) {
|
|
155
|
+
const { success } = await api.hasPermission({ headers: request.headers, body: { permissions: options.permissions } });
|
|
156
|
+
if (!success) throw new AuthError(403);
|
|
157
|
+
}
|
|
158
|
+
const { id, organizationId, userId, role } = membership;
|
|
159
|
+
return { ...auth, member: { id, organizationId, userId, role } };
|
|
160
|
+
},
|
|
161
|
+
security: signed.security,
|
|
162
|
+
refuses: [401, 403]
|
|
163
|
+
};
|
|
164
|
+
}
|
|
113
165
|
export {
|
|
114
166
|
AuthError,
|
|
115
167
|
authMiddleware,
|
|
@@ -118,11 +170,14 @@ export {
|
|
|
118
170
|
getAuth,
|
|
119
171
|
getCookie,
|
|
120
172
|
getCookies,
|
|
173
|
+
member,
|
|
121
174
|
mountAuthRoutes,
|
|
122
175
|
optionalUser,
|
|
123
|
-
parseCookies,
|
|
176
|
+
parseCookies2 as parseCookies,
|
|
177
|
+
remoteAuth,
|
|
124
178
|
requirePermission,
|
|
125
179
|
requireRole,
|
|
126
180
|
requireUser,
|
|
127
|
-
serializeCookie
|
|
181
|
+
serializeCookie,
|
|
182
|
+
signedIn
|
|
128
183
|
};
|