@fluixi/auth 1.0.0-alpha.100

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/client.js ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * @fluixi/auth/client: the reactive client hooks.
3
+ *
4
+ * The session is an isomorphic store: resolved on the server from `locals.auth`
5
+ * (serialized into the page) and seeded synchronously on the client → no auth flash, no
6
+ * post-hydration refetch. Two ways to drive it:
7
+ * • `{ client }`: a Better Auth client.
8
+ * • `{ session }`: an existing reactive session (e.g. @fluixi/session's createSession or
9
+ * any `{ user(), login?, logout? }`). The framework OWNS the reactive sync, so apps stop
10
+ * hand-writing a `createEffect` to mirror their store into the auth hooks.
11
+ *
12
+ * Client-only deps: fluixi + @fluixi/dom's pure `getLocals`, never @fluixi/server.
13
+ */
14
+ import { createIsomorphicStore, createEffect, createRoot, env } from '@fluixi/core';
15
+ import { getLocals } from '@fluixi/dom';
16
+ import { AUTH_LOCALS_KEY, } from './types.js';
17
+ export function createAuthHooks(options) {
18
+ const sess = options.session;
19
+ const store = createIsomorphicStore('auth', {
20
+ // Server: read what authMiddleware put on locals → serialized for the client to seed.
21
+ server: () => getLocals()[AUTH_LOCALS_KEY] ?? { user: null, session: null },
22
+ // SPA cold load: from the provided session, else the Better Auth client.
23
+ client: async () => sess
24
+ ? { user: sess.user(), session: null }
25
+ : normalize(await options.client.getSession()),
26
+ fallback: { user: null, session: null },
27
+ });
28
+ // `{ session }` path: the framework owns the reactive bridge (no app-level effect).
29
+ if (sess) {
30
+ const apply = () => {
31
+ if (options.ready && !options.ready())
32
+ return;
33
+ store.set({ user: sess.user(), session: null });
34
+ };
35
+ apply(); // immediate, so a first protectedRoute read isn't stale
36
+ createRoot(() => createEffect(apply, undefined, { name: 'auth ← session' }));
37
+ }
38
+ const refresh = async () => {
39
+ store.set(sess ? { user: sess.user(), session: null } : normalize(await options.client.getSession()));
40
+ };
41
+ const signIn = sess
42
+ ? ((...args) => sess.login?.(...args) ?? Promise.resolve())
43
+ : async (creds) => {
44
+ await options.client.signIn.email(creds);
45
+ await refresh();
46
+ };
47
+ const signOut = sess
48
+ ? () => sess.logout?.() ?? Promise.resolve()
49
+ : async () => {
50
+ await options.client.signOut();
51
+ store.set({ user: null, session: null });
52
+ };
53
+ /**
54
+ * Send the visitor somewhere else.
55
+ *
56
+ * The route's own `navigator` wins, then the one given to `createAuthHooks`, then a
57
+ * document load. A handler is a factory rather than a function: `useNavigate` has to
58
+ * run inside the component being rendered, so it is called here, at the redirect,
59
+ * instead of when the hooks were built.
60
+ *
61
+ * Without a handler this assigns `location`, which loads a document and rebuilds the
62
+ * whole app. That is right for an app with no client router and wrong for one that
63
+ * has it, which is what the option exists to fix.
64
+ */
65
+ const go = (to, opts) => {
66
+ const fromRoute = typeof opts?.handler === 'function' ? opts.handler() : undefined;
67
+ const fromHooks = typeof options.navigate === 'function'
68
+ ? options.navigate
69
+ : typeof options.navigate?.handler === 'function'
70
+ ? // Called, not passed along. Handing back the factory made `fn(to)` build a
71
+ // navigate function and return it, so the redirect silently did nothing.
72
+ options.navigate.handler()
73
+ : undefined;
74
+ const fn = fromRoute ?? fromHooks;
75
+ if (fn) {
76
+ const navigateOptions = opts?.options ??
77
+ (typeof options.navigate === 'object' ? options.navigate?.options : undefined);
78
+ // Only when there are some. Deciding on the handler's arity instead dropped them
79
+ // for anything declaring the parameter optional, which `useNavigate` does; passing
80
+ // `undefined` regardless would change the call every handler sees.
81
+ if (navigateOptions)
82
+ fn(to, navigateOptions);
83
+ else
84
+ fn(to);
85
+ return;
86
+ }
87
+ env.window?.location.assign(to);
88
+ };
89
+ // ── RBAC surface (driven by the bound session's access config) ────────────────
90
+ const permissions = () => sess?.permissions?.();
91
+ const can = (query) => sess?.can?.(query) ?? false;
92
+ const roleList = () => {
93
+ if (sess?.roles)
94
+ return sess.roles();
95
+ const r = store.fields.user()?.role;
96
+ return r ? [r] : [];
97
+ };
98
+ const passesRole = (opts) => {
99
+ if (opts?.role && !roleList().includes(opts.role))
100
+ return false;
101
+ if (opts?.roles && !opts.roles.some((r) => roleList().includes(r)))
102
+ return false;
103
+ if (opts?.permission !== undefined && !can(opts.permission))
104
+ return false;
105
+ return true;
106
+ };
107
+ /**
108
+ * Show something only when the access matcher allows it.
109
+ *
110
+ * Asks a permission question, which is the finer of the two: `Protect` asks who
111
+ * someone is, this asks what they may do. Reactive, so it follows a role change
112
+ * without a reload.
113
+ *
114
+ * @example
115
+ * ```tsx
116
+ * <Can perform={(p) => p.invoices.refund} fallback={<Tooltip>Ask an admin</Tooltip>}>
117
+ * <button onClick={refund}>Refund</button>
118
+ * </Can>
119
+ * ```
120
+ */
121
+ function Can(props) {
122
+ return () => (can(props.perform) ? props.children : (props.fallback ?? null));
123
+ }
124
+ /**
125
+ * Show a section of a page to some people and not others.
126
+ *
127
+ * Renders `fallback` for a signed-out visitor, and for a signed-in one who lacks the
128
+ * role. Nothing navigates: the rest of the page stays exactly as it is, which is the
129
+ * difference from `protectedRoute`.
130
+ *
131
+ * Reach for it inside a page. Reach for `protectedRoute` when the whole page should
132
+ * not have been reachable.
133
+ *
134
+ * @example
135
+ * ```tsx
136
+ * <Protect roles={['admin', 'support']} fallback={null}>
137
+ * <DangerZone />
138
+ * </Protect>
139
+ * ```
140
+ */
141
+ function Protect(props) {
142
+ return () => {
143
+ if (!store.fields.user())
144
+ return props.fallback ?? null;
145
+ return passesRole(props) ? props.children : (props.fallback ?? null);
146
+ };
147
+ }
148
+ /**
149
+ * Wrap a page so the wrong visitor never reaches it.
150
+ *
151
+ * Unlike `Protect`, this **navigates**: a signed-out visitor goes to `loginPath`, one
152
+ * lacking the role goes to `forbiddenPath`, and with `guestOnly` a signed-in visitor
153
+ * goes to `homePath`. The component never runs, so a page that loads data on mount
154
+ * does not load it for someone who should not see it.
155
+ *
156
+ * This is a client-side guard and a rendering decision. It is not authorization: the
157
+ * server has to refuse the request as well, because a client can be made to skip this.
158
+ *
159
+ * @example
160
+ * ```tsx
161
+ * // the whole page, behind a role
162
+ * export default protectedRoute(AdminPage, { role: 'admin' });
163
+ *
164
+ * // a login page, which a signed-in visitor has no use for
165
+ * export default protectedRoute(LoginPage, { guestOnly: true });
166
+ * ```
167
+ */
168
+ function protectedRoute(Comp, opts) {
169
+ return (props) => {
170
+ const user = store.fields.user();
171
+ if (opts?.guestOnly) {
172
+ if (user)
173
+ return go(options.homePath ?? '/', opts?.navigator), null;
174
+ }
175
+ else {
176
+ if (!user)
177
+ return go(options.loginPath ?? '/login', opts?.navigator), null;
178
+ if (!passesRole(opts))
179
+ return go(options.forbiddenPath ?? '/403'), null;
180
+ }
181
+ return Comp(props);
182
+ };
183
+ }
184
+ return {
185
+ /**
186
+ * The signed-in user, or null.
187
+ *
188
+ * Returns the accessor rather than the value, so a component reads it where it
189
+ * renders and re-renders when it changes.
190
+ *
191
+ * @example
192
+ * ```tsx
193
+ * const user = useUser();
194
+ * <span>{() => user()?.email ?? 'Signed out'}</span>
195
+ * ```
196
+ */
197
+ useUser: () => store.fields.user,
198
+ /**
199
+ * The session record beside the user: when it expires, and whatever else the
200
+ * backend attached. Null when signed out.
201
+ */
202
+ useSession: () => store.fields.session,
203
+ /**
204
+ * Everything at once, for a component that needs more than one of them.
205
+ *
206
+ * @example
207
+ * ```tsx
208
+ * const { user, isAuthenticated, can, signOut } = useAuth();
209
+ * ```
210
+ */
211
+ useAuth: () => ({
212
+ user: store.fields.user,
213
+ session: store.fields.session,
214
+ isAuthenticated: () => store.fields.user() !== null,
215
+ roles: roleList,
216
+ can,
217
+ signIn,
218
+ signOut,
219
+ }),
220
+ /**
221
+ * Start a sign-in.
222
+ *
223
+ * What it takes depends on what the hooks were given: credentials for a Better Auth
224
+ * client, and for a redirect flow whatever that session's `login` expects.
225
+ *
226
+ * @example
227
+ * ```tsx
228
+ * const signIn = useSignIn();
229
+ * <button onClick={() => signIn({ email, password })}>Sign in</button>
230
+ * ```
231
+ */
232
+ useSignIn: () => signIn,
233
+ /**
234
+ * End the session.
235
+ *
236
+ * @example
237
+ * ```tsx
238
+ * const signOut = useSignOut();
239
+ * <button onClick={() => signOut()}>Sign out</button>
240
+ * ```
241
+ */
242
+ useSignOut: () => signOut,
243
+ /**
244
+ * The permission set for the current roles, reactively.
245
+ *
246
+ * A rendering hint and nothing more: a client can claim any permission, so the
247
+ * server decides what is allowed on every request.
248
+ */
249
+ usePermissions: () => permissions,
250
+ /**
251
+ * Ask whether the current user may do something.
252
+ *
253
+ * `false` when the session supplies no RBAC, so a missing `access` config hides a
254
+ * control rather than revealing it.
255
+ *
256
+ * @example
257
+ * ```tsx
258
+ * const can = useCan();
259
+ * <button disabled={() => !can((p) => p.users.delete)}>Delete</button>
260
+ * ```
261
+ */
262
+ useCan: () => can,
263
+ Can,
264
+ Protect,
265
+ /**
266
+ * Re-read the session from the backend.
267
+ *
268
+ * For after something changed outside this tab: a role granted elsewhere, or a
269
+ * return from a flow that altered the account.
270
+ *
271
+ * @example
272
+ * ```ts
273
+ * await refresh();
274
+ * ```
275
+ */
276
+ refresh,
277
+ protectedRoute,
278
+ /**
279
+ * The underlying isomorphic store.
280
+ *
281
+ * Escape hatch. It is here for a test that wants to seed a user, and for the rare
282
+ * caller that needs to write what the hooks read. Prefer the accessors above:
283
+ * writing to the store directly puts the interface out of step with the session
284
+ * that owns it.
285
+ */
286
+ store,
287
+ };
288
+ }
289
+ function normalize(r) {
290
+ return { user: r.data?.user ?? null, session: r.data?.session ?? null };
291
+ }
@@ -0,0 +1,199 @@
1
+ /*! @fluixi/auth v1.0.0-alpha.100 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+
3
+ // src/client.ts
4
+ import { createIsomorphicStore, createEffect, createRoot, env } from "@fluixi/core";
5
+ import { getLocals } from "@fluixi/dom";
6
+
7
+ // src/types.ts
8
+ var AUTH_LOCALS_KEY = "auth";
9
+
10
+ // src/client.ts
11
+ function createAuthHooks(options) {
12
+ const sess = options.session;
13
+ const store = createIsomorphicStore("auth", {
14
+ // Server: read what authMiddleware put on locals → serialized for the client to seed.
15
+ server: () => getLocals()[AUTH_LOCALS_KEY] ?? { user: null, session: null },
16
+ // SPA cold load: from the provided session, else the Better Auth client.
17
+ client: async () => sess ? { user: sess.user(), session: null } : normalize(await options.client.getSession()),
18
+ fallback: { user: null, session: null }
19
+ });
20
+ if (sess) {
21
+ const apply = () => {
22
+ if (options.ready && !options.ready()) return;
23
+ store.set({ user: sess.user(), session: null });
24
+ };
25
+ apply();
26
+ createRoot(() => createEffect(apply, void 0, { name: "auth ← session" }));
27
+ }
28
+ const refresh = async () => {
29
+ store.set(sess ? { user: sess.user(), session: null } : normalize(await options.client.getSession()));
30
+ };
31
+ const signIn = sess ? ((...args) => sess.login?.(...args) ?? Promise.resolve()) : async (creds) => {
32
+ await options.client.signIn.email(creds);
33
+ await refresh();
34
+ };
35
+ const signOut = sess ? () => sess.logout?.() ?? Promise.resolve() : async () => {
36
+ await options.client.signOut();
37
+ store.set({ user: null, session: null });
38
+ };
39
+ const go = (to, opts) => {
40
+ const fromRoute = typeof opts?.handler === "function" ? opts.handler() : void 0;
41
+ const fromHooks = typeof options.navigate === "function" ? options.navigate : typeof options.navigate?.handler === "function" ? (
42
+ // Called, not passed along. Handing back the factory made `fn(to)` build a
43
+ // navigate function and return it, so the redirect silently did nothing.
44
+ options.navigate.handler()
45
+ ) : void 0;
46
+ const fn = fromRoute ?? fromHooks;
47
+ if (fn) {
48
+ const navigateOptions = opts?.options ?? (typeof options.navigate === "object" ? options.navigate?.options : void 0);
49
+ if (navigateOptions) fn(to, navigateOptions);
50
+ else fn(to);
51
+ return;
52
+ }
53
+ env.window?.location.assign(to);
54
+ };
55
+ const permissions = () => sess?.permissions?.();
56
+ const can = (query) => sess?.can?.(query) ?? false;
57
+ const roleList = () => {
58
+ if (sess?.roles) return sess.roles();
59
+ const r = store.fields.user()?.role;
60
+ return r ? [r] : [];
61
+ };
62
+ const passesRole = (opts) => {
63
+ if (opts?.role && !roleList().includes(opts.role)) return false;
64
+ if (opts?.roles && !opts.roles.some((r) => roleList().includes(r))) return false;
65
+ if (opts?.permission !== void 0 && !can(opts.permission)) return false;
66
+ return true;
67
+ };
68
+ function Can(props) {
69
+ return () => can(props.perform) ? props.children : props.fallback ?? null;
70
+ }
71
+ function Protect(props) {
72
+ return () => {
73
+ if (!store.fields.user()) return props.fallback ?? null;
74
+ return passesRole(props) ? props.children : props.fallback ?? null;
75
+ };
76
+ }
77
+ function protectedRoute(Comp, opts) {
78
+ return (props) => {
79
+ const user = store.fields.user();
80
+ if (opts?.guestOnly) {
81
+ if (user) return go(options.homePath ?? "/", opts?.navigator), null;
82
+ } else {
83
+ if (!user) return go(options.loginPath ?? "/login", opts?.navigator), null;
84
+ if (!passesRole(opts)) return go(options.forbiddenPath ?? "/403"), null;
85
+ }
86
+ return Comp(props);
87
+ };
88
+ }
89
+ return {
90
+ /**
91
+ * The signed-in user, or null.
92
+ *
93
+ * Returns the accessor rather than the value, so a component reads it where it
94
+ * renders and re-renders when it changes.
95
+ *
96
+ * @example
97
+ * ```tsx
98
+ * const user = useUser();
99
+ * <span>{() => user()?.email ?? 'Signed out'}</span>
100
+ * ```
101
+ */
102
+ useUser: () => store.fields.user,
103
+ /**
104
+ * The session record beside the user: when it expires, and whatever else the
105
+ * backend attached. Null when signed out.
106
+ */
107
+ useSession: () => store.fields.session,
108
+ /**
109
+ * Everything at once, for a component that needs more than one of them.
110
+ *
111
+ * @example
112
+ * ```tsx
113
+ * const { user, isAuthenticated, can, signOut } = useAuth();
114
+ * ```
115
+ */
116
+ useAuth: () => ({
117
+ user: store.fields.user,
118
+ session: store.fields.session,
119
+ isAuthenticated: () => store.fields.user() !== null,
120
+ roles: roleList,
121
+ can,
122
+ signIn,
123
+ signOut
124
+ }),
125
+ /**
126
+ * Start a sign-in.
127
+ *
128
+ * What it takes depends on what the hooks were given: credentials for a Better Auth
129
+ * client, and for a redirect flow whatever that session's `login` expects.
130
+ *
131
+ * @example
132
+ * ```tsx
133
+ * const signIn = useSignIn();
134
+ * <button onClick={() => signIn({ email, password })}>Sign in</button>
135
+ * ```
136
+ */
137
+ useSignIn: () => signIn,
138
+ /**
139
+ * End the session.
140
+ *
141
+ * @example
142
+ * ```tsx
143
+ * const signOut = useSignOut();
144
+ * <button onClick={() => signOut()}>Sign out</button>
145
+ * ```
146
+ */
147
+ useSignOut: () => signOut,
148
+ /**
149
+ * The permission set for the current roles, reactively.
150
+ *
151
+ * A rendering hint and nothing more: a client can claim any permission, so the
152
+ * server decides what is allowed on every request.
153
+ */
154
+ usePermissions: () => permissions,
155
+ /**
156
+ * Ask whether the current user may do something.
157
+ *
158
+ * `false` when the session supplies no RBAC, so a missing `access` config hides a
159
+ * control rather than revealing it.
160
+ *
161
+ * @example
162
+ * ```tsx
163
+ * const can = useCan();
164
+ * <button disabled={() => !can((p) => p.users.delete)}>Delete</button>
165
+ * ```
166
+ */
167
+ useCan: () => can,
168
+ Can,
169
+ Protect,
170
+ /**
171
+ * Re-read the session from the backend.
172
+ *
173
+ * For after something changed outside this tab: a role granted elsewhere, or a
174
+ * return from a flow that altered the account.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * await refresh();
179
+ * ```
180
+ */
181
+ refresh,
182
+ protectedRoute,
183
+ /**
184
+ * The underlying isomorphic store.
185
+ *
186
+ * Escape hatch. It is here for a test that wants to seed a user, and for the rare
187
+ * caller that needs to write what the hooks read. Prefer the accessors above:
188
+ * writing to the store directly puts the interface out of step with the session
189
+ * that owns it.
190
+ */
191
+ store
192
+ };
193
+ }
194
+ function normalize(r) {
195
+ return { user: r.data?.user ?? null, session: r.data?.session ?? null };
196
+ }
197
+ export {
198
+ createAuthHooks
199
+ };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cookies.ts
21
+ var cookies_exports = {};
22
+ __export(cookies_exports, {
23
+ deleteCookie: () => import_cookie2.deleteCookie,
24
+ getCookie: () => getCookie,
25
+ getCookies: () => getCookies,
26
+ isValidCookieName: () => import_cookie2.isValidCookieName,
27
+ parseCookies: () => import_cookie2.parseCookies,
28
+ serializeCookie: () => import_cookie2.serializeCookie
29
+ });
30
+ module.exports = __toCommonJS(cookies_exports);
31
+ var import_server = require("@fluixi/server");
32
+ var import_cookie = require("@fluixi/utils/cookie");
33
+ var import_cookie2 = require("@fluixi/utils/cookie");
34
+ function requestHeaders() {
35
+ const req = (0, import_server.getRequestEvent)()?.request;
36
+ return req?.headers ?? null;
37
+ }
38
+ function getCookie(name) {
39
+ return (0, import_cookie.parseCookies)(requestHeaders()?.get("cookie"))[name] ?? null;
40
+ }
41
+ function getCookies() {
42
+ return (0, import_cookie.parseCookies)(requestHeaders()?.get("cookie"));
43
+ }
@@ -0,0 +1,7 @@
1
+ export { parseCookies, serializeCookie, deleteCookie, isValidCookieName } from '@fluixi/utils/cookie';
2
+ export type { CookieOptions } from '@fluixi/utils/cookie';
3
+ /** Read one cookie from the CURRENT request (request-scoped). */
4
+ export declare function getCookie(name: string): string | null;
5
+ /** All cookies on the current request. */
6
+ export declare function getCookies(): Record<string, string>;
7
+ //# sourceMappingURL=cookies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../src/cookies.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACtG,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAO1D,iEAAiE;AACjE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAErD;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEnD"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Request-scoped cookie reads. The codec lives in `@fluixi/utils/cookie` and is re-exported
3
+ * here; middleware appends `serializeCookie` output to its Response's `set-cookie` header.
4
+ */
5
+ import { getRequestEvent } from '@fluixi/server';
6
+ import { parseCookies } from '@fluixi/utils/cookie';
7
+ export { parseCookies, serializeCookie, deleteCookie, isValidCookieName } from '@fluixi/utils/cookie';
8
+ function requestHeaders() {
9
+ const req = getRequestEvent()?.request;
10
+ return req?.headers ?? null;
11
+ }
12
+ /** Read one cookie from the CURRENT request (request-scoped). */
13
+ export function getCookie(name) {
14
+ return parseCookies(requestHeaders()?.get('cookie'))[name] ?? null;
15
+ }
16
+ /** All cookies on the current request. */
17
+ export function getCookies() {
18
+ return parseCookies(requestHeaders()?.get('cookie'));
19
+ }
@@ -0,0 +1,22 @@
1
+ // src/cookies.ts
2
+ import { getRequestEvent } from "@fluixi/server";
3
+ import { parseCookies } from "@fluixi/utils/cookie";
4
+ import { parseCookies as parseCookies2, serializeCookie, deleteCookie, isValidCookieName } from "@fluixi/utils/cookie";
5
+ function requestHeaders() {
6
+ const req = getRequestEvent()?.request;
7
+ return req?.headers ?? null;
8
+ }
9
+ function getCookie(name) {
10
+ return parseCookies(requestHeaders()?.get("cookie"))[name] ?? null;
11
+ }
12
+ function getCookies() {
13
+ return parseCookies(requestHeaders()?.get("cookie"));
14
+ }
15
+ export {
16
+ deleteCookie,
17
+ getCookie,
18
+ getCookies,
19
+ isValidCookieName,
20
+ parseCookies2 as parseCookies,
21
+ serializeCookie
22
+ };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/remote.ts
21
+ var remote_exports = {};
22
+ __export(remote_exports, {
23
+ remoteAuth: () => remoteAuth
24
+ });
25
+ module.exports = __toCommonJS(remote_exports);
26
+ function remoteAuth(options) {
27
+ const base = new URL((options.basePath ?? "/api/auth").replace(/\/$/, "") + "/", options.url);
28
+ const call = (input, init) => (options.fetch ?? globalThis.fetch)(input, init);
29
+ return {
30
+ async handler(request) {
31
+ const url = new URL(request.url);
32
+ const hasBody = request.method !== "GET" && request.method !== "HEAD";
33
+ return call(new URL(url.pathname + url.search, base), {
34
+ method: request.method,
35
+ headers: request.headers,
36
+ body: hasBody ? request.body : void 0,
37
+ redirect: "manual",
38
+ ...hasBody ? { duplex: "half" } : {}
39
+ });
40
+ },
41
+ api: {
42
+ async getSession({ headers }) {
43
+ const cookie = headers.get("cookie");
44
+ if (!cookie) return null;
45
+ const response = await call(new URL("get-session", base), { headers: { cookie, accept: "application/json" } });
46
+ if (!response.ok) return null;
47
+ const body = await response.json().catch(() => null);
48
+ return body && body.user ? { user: body.user, session: body.session ?? null } : null;
49
+ }
50
+ }
51
+ };
52
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * An auth engine that lives in another service. A web app whose Better Auth runs in its API
3
+ * registers this with `defineAuth(remoteAuth({ url }))`; `authMiddleware()`, `requireUser()` and
4
+ * the client hooks then work as they would with the engine in-process, each session read being
5
+ * one request to the API with the visitor's cookie.
6
+ *
7
+ * The API's guards (`member()`, `hasPermission`) call the engine directly and stay there: a web
8
+ * app reads the session, the API decides what it may do.
9
+ */
10
+ import type { BetterAuthServer } from './types.js';
11
+ export interface RemoteAuthOptions {
12
+ /** The API's origin, `http://localhost:3301`. */
13
+ url: string;
14
+ /** Where the API mounts its auth routes. `/api/auth` by default. */
15
+ basePath?: string;
16
+ /** `globalThis.fetch` by default. */
17
+ fetch?: typeof fetch;
18
+ }
19
+ export declare function remoteAuth(options: RemoteAuthOptions): BetterAuthServer;
20
+ //# sourceMappingURL=remote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../src/remote.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CA4BvE"}