@fluixi/auth 1.0.0-alpha.83 → 1.0.0-alpha.85
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.cjs +136 -1
- package/dist/client.d.ts +17 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -8
- package/dist/client.mjs +116 -1
- package/dist/cookies.cjs +72 -1
- package/dist/cookies.js +1 -1
- package/dist/cookies.mjs +51 -1
- package/dist/server.cjs +148 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.mjs +128 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.cjs +26 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.mjs +5 -1
- package/package.json +14 -14
package/dist/client.cjs
CHANGED
|
@@ -1 +1,136 @@
|
|
|
1
|
-
|
|
1
|
+
/*! @fluixi/auth v1.0.0-alpha.85 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/client.ts
|
|
22
|
+
var client_exports = {};
|
|
23
|
+
__export(client_exports, {
|
|
24
|
+
createAuthHooks: () => createAuthHooks
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(client_exports);
|
|
27
|
+
var import_core = require("@fluixi/core");
|
|
28
|
+
var import_dom = require("@fluixi/dom");
|
|
29
|
+
|
|
30
|
+
// src/types.ts
|
|
31
|
+
var AUTH_LOCALS_KEY = "auth";
|
|
32
|
+
|
|
33
|
+
// src/client.ts
|
|
34
|
+
function createAuthHooks(options) {
|
|
35
|
+
const sess = options.session;
|
|
36
|
+
const store = (0, import_core.createIsomorphicStore)("auth", {
|
|
37
|
+
// Server: read what authMiddleware put on locals → serialized for the client to seed.
|
|
38
|
+
server: () => (0, import_dom.getLocals)()[AUTH_LOCALS_KEY] ?? { user: null, session: null },
|
|
39
|
+
// SPA cold load: from the provided session, else the Better Auth client.
|
|
40
|
+
client: async () => sess ? { user: sess.user(), session: null } : normalize(await options.client.getSession()),
|
|
41
|
+
fallback: { user: null, session: null }
|
|
42
|
+
});
|
|
43
|
+
if (sess) {
|
|
44
|
+
const apply = () => store.set({ user: sess.user(), session: null });
|
|
45
|
+
apply();
|
|
46
|
+
(0, import_core.createRoot)(() => (0, import_core.createEffect)(apply));
|
|
47
|
+
}
|
|
48
|
+
const refresh = async () => {
|
|
49
|
+
store.set(sess ? { user: sess.user(), session: null } : normalize(await options.client.getSession()));
|
|
50
|
+
};
|
|
51
|
+
const signIn = sess ? ((...args) => sess.login?.(...args) ?? Promise.resolve()) : async (creds) => {
|
|
52
|
+
await options.client.signIn.email(creds);
|
|
53
|
+
await refresh();
|
|
54
|
+
};
|
|
55
|
+
const signOut = sess ? () => sess.logout?.() ?? Promise.resolve() : async () => {
|
|
56
|
+
await options.client.signOut();
|
|
57
|
+
store.set({ user: null, session: null });
|
|
58
|
+
};
|
|
59
|
+
const go = (to, opts) => {
|
|
60
|
+
const fromRoute = typeof opts?.handler === "function" ? opts.handler() : void 0;
|
|
61
|
+
const fromHooks = typeof options.navigate === "function" ? options.navigate : typeof options.navigate?.handler === "function" ? (
|
|
62
|
+
// Called, not passed along. Handing back the factory made `fn(to)` build a
|
|
63
|
+
// navigate function and return it, so the redirect silently did nothing.
|
|
64
|
+
options.navigate.handler()
|
|
65
|
+
) : void 0;
|
|
66
|
+
const fn = fromRoute ?? fromHooks;
|
|
67
|
+
if (fn) {
|
|
68
|
+
const navigateOptions = opts?.options ?? (typeof options.navigate === "object" ? options.navigate?.options : void 0);
|
|
69
|
+
if (navigateOptions) fn(to, navigateOptions);
|
|
70
|
+
else fn(to);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
import_core.env.window?.location.assign(to);
|
|
74
|
+
};
|
|
75
|
+
const permissions = () => sess?.permissions?.();
|
|
76
|
+
const can = (query) => sess?.can?.(query) ?? false;
|
|
77
|
+
const roleList = () => {
|
|
78
|
+
if (sess?.roles) return sess.roles();
|
|
79
|
+
const r = store.fields.user()?.role;
|
|
80
|
+
return r ? [r] : [];
|
|
81
|
+
};
|
|
82
|
+
const passesRole = (opts) => {
|
|
83
|
+
if (opts?.role && !roleList().includes(opts.role)) return false;
|
|
84
|
+
if (opts?.roles && !opts.roles.some((r) => roleList().includes(r))) return false;
|
|
85
|
+
if (opts?.permission !== void 0 && !can(opts.permission)) return false;
|
|
86
|
+
return true;
|
|
87
|
+
};
|
|
88
|
+
function Can(props) {
|
|
89
|
+
return () => can(props.perform) ? props.children : props.fallback ?? null;
|
|
90
|
+
}
|
|
91
|
+
function Protect(props) {
|
|
92
|
+
return () => {
|
|
93
|
+
if (!store.fields.user()) return props.fallback ?? null;
|
|
94
|
+
return passesRole(props) ? props.children : props.fallback ?? null;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function protectedRoute(Comp, opts) {
|
|
98
|
+
return (props) => {
|
|
99
|
+
const user = store.fields.user();
|
|
100
|
+
if (opts?.guestOnly) {
|
|
101
|
+
if (user) return go(options.homePath ?? "/", opts?.navigator), null;
|
|
102
|
+
} else {
|
|
103
|
+
if (!user) return go(options.loginPath ?? "/login", opts?.navigator), null;
|
|
104
|
+
if (!passesRole(opts)) return go(options.forbiddenPath ?? "/403"), null;
|
|
105
|
+
}
|
|
106
|
+
return Comp(props);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
useUser: () => store.fields.user,
|
|
111
|
+
useSession: () => store.fields.session,
|
|
112
|
+
useAuth: () => ({
|
|
113
|
+
user: store.fields.user,
|
|
114
|
+
session: store.fields.session,
|
|
115
|
+
isAuthenticated: () => store.fields.user() !== null,
|
|
116
|
+
roles: roleList,
|
|
117
|
+
can,
|
|
118
|
+
signIn,
|
|
119
|
+
signOut
|
|
120
|
+
}),
|
|
121
|
+
useSignIn: () => signIn,
|
|
122
|
+
useSignOut: () => signOut,
|
|
123
|
+
/** Reactive accessor for the active permission set (UI affordance only). */
|
|
124
|
+
usePermissions: () => permissions,
|
|
125
|
+
/** Returns `can(check)`: `false` unless the session provides RBAC. */
|
|
126
|
+
useCan: () => can,
|
|
127
|
+
Can,
|
|
128
|
+
Protect,
|
|
129
|
+
refresh,
|
|
130
|
+
protectedRoute,
|
|
131
|
+
store
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function normalize(r) {
|
|
135
|
+
return { user: r.data?.user ?? null, session: r.data?.session ?? null };
|
|
136
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -5,12 +5,13 @@ export interface ProtectedRouteOptions<Q = (p: any) => boolean> {
|
|
|
5
5
|
role?: string;
|
|
6
6
|
/** Require ANY of these roles. */
|
|
7
7
|
roles?: string[];
|
|
8
|
-
/** Require a permission
|
|
8
|
+
/** Require a permission: your access matcher's query (default: a predicate). */
|
|
9
9
|
permission?: Q;
|
|
10
|
+
navigator?: Partial<Navigator>;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* Any reactive session with a `user` accessor (e.g. @fluixi/session's createSession).
|
|
13
|
-
* The RBAC accessors (`roles` / `permissions` / `can`) are optional
|
|
14
|
+
* The RBAC accessors (`roles` / `permissions` / `can`) are optional, provide them (as
|
|
14
15
|
* createSession's `access` config does) to unlock `<Can>`, `usePermissions`, and
|
|
15
16
|
* permission-gated routes. They are a UI affordance only; the server enforces authz.
|
|
16
17
|
*/
|
|
@@ -22,13 +23,24 @@ export interface SessionLike<U = AuthUser, P = unknown, Q = (p: P) => boolean> {
|
|
|
22
23
|
permissions?: () => P | undefined;
|
|
23
24
|
can?: (query: Q) => boolean;
|
|
24
25
|
}
|
|
26
|
+
export interface NavigateOptions {
|
|
27
|
+
/** Replace the current history entry instead of pushing a new one. */
|
|
28
|
+
replace?: boolean;
|
|
29
|
+
/** Opaque history state to attach. */
|
|
30
|
+
state?: unknown;
|
|
31
|
+
transition?: unknown;
|
|
32
|
+
}
|
|
33
|
+
export interface Navigator {
|
|
34
|
+
handler: () => (to: string, opts?: any) => void;
|
|
35
|
+
options?: NavigateOptions;
|
|
36
|
+
}
|
|
25
37
|
export interface AuthHooksOptions<U = AuthUser, P = unknown, Q = (p: P) => boolean> {
|
|
26
38
|
/** A Better Auth client (`createAuthClient(...)`). Mutually exclusive with `session`. */
|
|
27
39
|
client?: BetterAuthClient;
|
|
28
|
-
/** An existing reactive session
|
|
40
|
+
/** An existing reactive session: the framework syncs it into the hooks for you. */
|
|
29
41
|
session?: SessionLike<U, P, Q>;
|
|
30
42
|
/** Navigate for `protectedRoute` redirects (default: `window.location.assign`). */
|
|
31
|
-
navigate?: (to: string) => void;
|
|
43
|
+
navigate?: ((to: string, opts?: NavigateOptions) => void) | Navigator;
|
|
32
44
|
loginPath?: string;
|
|
33
45
|
homePath?: string;
|
|
34
46
|
forbiddenPath?: string;
|
|
@@ -57,7 +69,7 @@ export declare function createAuthHooks<U = AuthUser, P = unknown, Q = (p: P) =>
|
|
|
57
69
|
useSignOut: () => () => Promise<unknown>;
|
|
58
70
|
/** Reactive accessor for the active permission set (UI affordance only). */
|
|
59
71
|
usePermissions: () => () => P | undefined;
|
|
60
|
-
/** Returns `can(check)
|
|
72
|
+
/** Returns `can(check)`: `false` unless the session provides RBAC. */
|
|
61
73
|
useCan: () => (query: Q) => boolean;
|
|
62
74
|
Can: (props: {
|
|
63
75
|
perform: Q;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAeA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fluixi/auth/client
|
|
2
|
+
* @fluixi/auth/client: the reactive client hooks.
|
|
3
3
|
*
|
|
4
4
|
* The session is an isomorphic store: resolved on the server from `locals.auth`
|
|
5
5
|
* (serialized into the page) and seeded synchronously on the client → no auth flash, no
|
|
6
6
|
* post-hydration refetch. Two ways to drive it:
|
|
7
|
-
* • `{ client }
|
|
8
|
-
* • `{ session }
|
|
7
|
+
* • `{ client }`: a Better Auth client.
|
|
8
|
+
* • `{ session }`: an existing reactive session (e.g. @fluixi/session's createSession or
|
|
9
9
|
* any `{ user(), login?, logout? }`). The framework OWNS the reactive sync, so apps stop
|
|
10
10
|
* hand-writing a `createEffect` to mirror their store into the auth hooks.
|
|
11
11
|
*
|
|
12
|
-
* Client-only deps: fluixi + @fluixi/dom's pure `getLocals
|
|
12
|
+
* Client-only deps: fluixi + @fluixi/dom's pure `getLocals`, never @fluixi/server.
|
|
13
13
|
*/
|
|
14
14
|
import { createIsomorphicStore, createEffect, createRoot, env } from '@fluixi/core';
|
|
15
15
|
import { getLocals } from '@fluixi/dom';
|
|
@@ -46,7 +46,42 @@ export function createAuthHooks(options) {
|
|
|
46
46
|
await options.client.signOut();
|
|
47
47
|
store.set({ user: null, session: null });
|
|
48
48
|
};
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Send the visitor somewhere else.
|
|
51
|
+
*
|
|
52
|
+
* The route's own `navigator` wins, then the one given to `createAuthHooks`, then a
|
|
53
|
+
* document load. A handler is a factory rather than a function: `useNavigate` has to
|
|
54
|
+
* run inside the component being rendered, so it is called here, at the redirect,
|
|
55
|
+
* instead of when the hooks were built.
|
|
56
|
+
*
|
|
57
|
+
* Without a handler this assigns `location`, which loads a document and rebuilds the
|
|
58
|
+
* whole app. That is right for an app with no client router and wrong for one that
|
|
59
|
+
* has it, which is what the option exists to fix.
|
|
60
|
+
*/
|
|
61
|
+
const go = (to, opts) => {
|
|
62
|
+
const fromRoute = typeof opts?.handler === 'function' ? opts.handler() : undefined;
|
|
63
|
+
const fromHooks = typeof options.navigate === 'function'
|
|
64
|
+
? options.navigate
|
|
65
|
+
: typeof options.navigate?.handler === 'function'
|
|
66
|
+
? // Called, not passed along. Handing back the factory made `fn(to)` build a
|
|
67
|
+
// navigate function and return it, so the redirect silently did nothing.
|
|
68
|
+
options.navigate.handler()
|
|
69
|
+
: undefined;
|
|
70
|
+
const fn = fromRoute ?? fromHooks;
|
|
71
|
+
if (fn) {
|
|
72
|
+
const navigateOptions = opts?.options ??
|
|
73
|
+
(typeof options.navigate === 'object' ? options.navigate?.options : undefined);
|
|
74
|
+
// Only when there are some. Deciding on the handler's arity instead dropped them
|
|
75
|
+
// for anything declaring the parameter optional, which `useNavigate` does; passing
|
|
76
|
+
// `undefined` regardless would change the call every handler sees.
|
|
77
|
+
if (navigateOptions)
|
|
78
|
+
fn(to, navigateOptions);
|
|
79
|
+
else
|
|
80
|
+
fn(to);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
env.window?.location.assign(to);
|
|
84
|
+
};
|
|
50
85
|
// ── RBAC surface (driven by the bound session's access config) ────────────────
|
|
51
86
|
const permissions = () => sess?.permissions?.();
|
|
52
87
|
const can = (query) => sess?.can?.(query) ?? false;
|
|
@@ -82,11 +117,11 @@ export function createAuthHooks(options) {
|
|
|
82
117
|
const user = store.fields.user();
|
|
83
118
|
if (opts?.guestOnly) {
|
|
84
119
|
if (user)
|
|
85
|
-
return go(options.homePath ?? '/'), null;
|
|
120
|
+
return go(options.homePath ?? '/', opts?.navigator), null;
|
|
86
121
|
}
|
|
87
122
|
else {
|
|
88
123
|
if (!user)
|
|
89
|
-
return go(options.loginPath ?? '/login'), null;
|
|
124
|
+
return go(options.loginPath ?? '/login', opts?.navigator), null;
|
|
90
125
|
if (!passesRole(opts))
|
|
91
126
|
return go(options.forbiddenPath ?? '/403'), null;
|
|
92
127
|
}
|
|
@@ -109,7 +144,7 @@ export function createAuthHooks(options) {
|
|
|
109
144
|
useSignOut: () => signOut,
|
|
110
145
|
/** Reactive accessor for the active permission set (UI affordance only). */
|
|
111
146
|
usePermissions: () => permissions,
|
|
112
|
-
/** Returns `can(check)
|
|
147
|
+
/** Returns `can(check)`: `false` unless the session provides RBAC. */
|
|
113
148
|
useCan: () => can,
|
|
114
149
|
Can,
|
|
115
150
|
Protect,
|
package/dist/client.mjs
CHANGED
|
@@ -1 +1,116 @@
|
|
|
1
|
-
|
|
1
|
+
/*! @fluixi/auth v1.0.0-alpha.85 | (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 = () => store.set({ user: sess.user(), session: null });
|
|
22
|
+
apply();
|
|
23
|
+
createRoot(() => createEffect(apply));
|
|
24
|
+
}
|
|
25
|
+
const refresh = async () => {
|
|
26
|
+
store.set(sess ? { user: sess.user(), session: null } : normalize(await options.client.getSession()));
|
|
27
|
+
};
|
|
28
|
+
const signIn = sess ? ((...args) => sess.login?.(...args) ?? Promise.resolve()) : async (creds) => {
|
|
29
|
+
await options.client.signIn.email(creds);
|
|
30
|
+
await refresh();
|
|
31
|
+
};
|
|
32
|
+
const signOut = sess ? () => sess.logout?.() ?? Promise.resolve() : async () => {
|
|
33
|
+
await options.client.signOut();
|
|
34
|
+
store.set({ user: null, session: null });
|
|
35
|
+
};
|
|
36
|
+
const go = (to, opts) => {
|
|
37
|
+
const fromRoute = typeof opts?.handler === "function" ? opts.handler() : void 0;
|
|
38
|
+
const fromHooks = typeof options.navigate === "function" ? options.navigate : typeof options.navigate?.handler === "function" ? (
|
|
39
|
+
// Called, not passed along. Handing back the factory made `fn(to)` build a
|
|
40
|
+
// navigate function and return it, so the redirect silently did nothing.
|
|
41
|
+
options.navigate.handler()
|
|
42
|
+
) : void 0;
|
|
43
|
+
const fn = fromRoute ?? fromHooks;
|
|
44
|
+
if (fn) {
|
|
45
|
+
const navigateOptions = opts?.options ?? (typeof options.navigate === "object" ? options.navigate?.options : void 0);
|
|
46
|
+
if (navigateOptions) fn(to, navigateOptions);
|
|
47
|
+
else fn(to);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
env.window?.location.assign(to);
|
|
51
|
+
};
|
|
52
|
+
const permissions = () => sess?.permissions?.();
|
|
53
|
+
const can = (query) => sess?.can?.(query) ?? false;
|
|
54
|
+
const roleList = () => {
|
|
55
|
+
if (sess?.roles) return sess.roles();
|
|
56
|
+
const r = store.fields.user()?.role;
|
|
57
|
+
return r ? [r] : [];
|
|
58
|
+
};
|
|
59
|
+
const passesRole = (opts) => {
|
|
60
|
+
if (opts?.role && !roleList().includes(opts.role)) return false;
|
|
61
|
+
if (opts?.roles && !opts.roles.some((r) => roleList().includes(r))) return false;
|
|
62
|
+
if (opts?.permission !== void 0 && !can(opts.permission)) return false;
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
65
|
+
function Can(props) {
|
|
66
|
+
return () => can(props.perform) ? props.children : props.fallback ?? null;
|
|
67
|
+
}
|
|
68
|
+
function Protect(props) {
|
|
69
|
+
return () => {
|
|
70
|
+
if (!store.fields.user()) return props.fallback ?? null;
|
|
71
|
+
return passesRole(props) ? props.children : props.fallback ?? null;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function protectedRoute(Comp, opts) {
|
|
75
|
+
return (props) => {
|
|
76
|
+
const user = store.fields.user();
|
|
77
|
+
if (opts?.guestOnly) {
|
|
78
|
+
if (user) return go(options.homePath ?? "/", opts?.navigator), null;
|
|
79
|
+
} else {
|
|
80
|
+
if (!user) return go(options.loginPath ?? "/login", opts?.navigator), null;
|
|
81
|
+
if (!passesRole(opts)) return go(options.forbiddenPath ?? "/403"), null;
|
|
82
|
+
}
|
|
83
|
+
return Comp(props);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
useUser: () => store.fields.user,
|
|
88
|
+
useSession: () => store.fields.session,
|
|
89
|
+
useAuth: () => ({
|
|
90
|
+
user: store.fields.user,
|
|
91
|
+
session: store.fields.session,
|
|
92
|
+
isAuthenticated: () => store.fields.user() !== null,
|
|
93
|
+
roles: roleList,
|
|
94
|
+
can,
|
|
95
|
+
signIn,
|
|
96
|
+
signOut
|
|
97
|
+
}),
|
|
98
|
+
useSignIn: () => signIn,
|
|
99
|
+
useSignOut: () => signOut,
|
|
100
|
+
/** Reactive accessor for the active permission set (UI affordance only). */
|
|
101
|
+
usePermissions: () => permissions,
|
|
102
|
+
/** Returns `can(check)`: `false` unless the session provides RBAC. */
|
|
103
|
+
useCan: () => can,
|
|
104
|
+
Can,
|
|
105
|
+
Protect,
|
|
106
|
+
refresh,
|
|
107
|
+
protectedRoute,
|
|
108
|
+
store
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function normalize(r) {
|
|
112
|
+
return { user: r.data?.user ?? null, session: r.data?.session ?? null };
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
createAuthHooks
|
|
116
|
+
};
|
package/dist/cookies.cjs
CHANGED
|
@@ -1 +1,72 @@
|
|
|
1
|
-
"use strict";
|
|
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: () => deleteCookie,
|
|
24
|
+
getCookie: () => getCookie,
|
|
25
|
+
getCookies: () => getCookies,
|
|
26
|
+
parseCookies: () => parseCookies,
|
|
27
|
+
serializeCookie: () => serializeCookie
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(cookies_exports);
|
|
30
|
+
var import_server = require("@fluixi/server");
|
|
31
|
+
function parseCookies(header) {
|
|
32
|
+
const out = {};
|
|
33
|
+
if (!header) return out;
|
|
34
|
+
for (const part of header.split(";")) {
|
|
35
|
+
const eq = part.indexOf("=");
|
|
36
|
+
if (eq < 0) continue;
|
|
37
|
+
const k = part.slice(0, eq).trim();
|
|
38
|
+
if (k) out[k] = decodeURIComponent(part.slice(eq + 1).trim());
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
function serializeCookie(name, value, opts = {}) {
|
|
43
|
+
const o = {
|
|
44
|
+
path: "/",
|
|
45
|
+
httpOnly: true,
|
|
46
|
+
secure: true,
|
|
47
|
+
sameSite: "lax",
|
|
48
|
+
...opts
|
|
49
|
+
};
|
|
50
|
+
let s = `${name}=${encodeURIComponent(value)}`;
|
|
51
|
+
if (o.maxAge != null) s += `; Max-Age=${Math.floor(o.maxAge)}`;
|
|
52
|
+
if (o.expires) s += `; Expires=${o.expires.toUTCString()}`;
|
|
53
|
+
if (o.domain) s += `; Domain=${o.domain}`;
|
|
54
|
+
s += `; Path=${o.path}`;
|
|
55
|
+
if (o.httpOnly) s += "; HttpOnly";
|
|
56
|
+
if (o.secure) s += "; Secure";
|
|
57
|
+
s += `; SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`;
|
|
58
|
+
return s;
|
|
59
|
+
}
|
|
60
|
+
function deleteCookie(name, opts = {}) {
|
|
61
|
+
return serializeCookie(name, "", { ...opts, maxAge: 0 });
|
|
62
|
+
}
|
|
63
|
+
function requestHeaders() {
|
|
64
|
+
const req = (0, import_server.getRequestEvent)()?.request;
|
|
65
|
+
return req?.headers ?? null;
|
|
66
|
+
}
|
|
67
|
+
function getCookie(name) {
|
|
68
|
+
return parseCookies(requestHeaders()?.get("cookie"))[name] ?? null;
|
|
69
|
+
}
|
|
70
|
+
function getCookies() {
|
|
71
|
+
return parseCookies(requestHeaders()?.get("cookie"));
|
|
72
|
+
}
|
package/dist/cookies.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cookie API
|
|
2
|
+
* Cookie API: proper, secure-by-default cookie management for auth (and anything else).
|
|
3
3
|
* Parsing + request-scoped reads work standalone; `serializeCookie` builds a spec-compliant
|
|
4
4
|
* `Set-Cookie` string with safe defaults (HttpOnly, Secure, SameSite=Lax, Path=/) that
|
|
5
5
|
* middleware appends to its Response (`response.headers.append('set-cookie', …)`).
|
package/dist/cookies.mjs
CHANGED
|
@@ -1 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
// src/cookies.ts
|
|
2
|
+
import { getRequestEvent } from "@fluixi/server";
|
|
3
|
+
function parseCookies(header) {
|
|
4
|
+
const out = {};
|
|
5
|
+
if (!header) return out;
|
|
6
|
+
for (const part of header.split(";")) {
|
|
7
|
+
const eq = part.indexOf("=");
|
|
8
|
+
if (eq < 0) continue;
|
|
9
|
+
const k = part.slice(0, eq).trim();
|
|
10
|
+
if (k) out[k] = decodeURIComponent(part.slice(eq + 1).trim());
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
function serializeCookie(name, value, opts = {}) {
|
|
15
|
+
const o = {
|
|
16
|
+
path: "/",
|
|
17
|
+
httpOnly: true,
|
|
18
|
+
secure: true,
|
|
19
|
+
sameSite: "lax",
|
|
20
|
+
...opts
|
|
21
|
+
};
|
|
22
|
+
let s = `${name}=${encodeURIComponent(value)}`;
|
|
23
|
+
if (o.maxAge != null) s += `; Max-Age=${Math.floor(o.maxAge)}`;
|
|
24
|
+
if (o.expires) s += `; Expires=${o.expires.toUTCString()}`;
|
|
25
|
+
if (o.domain) s += `; Domain=${o.domain}`;
|
|
26
|
+
s += `; Path=${o.path}`;
|
|
27
|
+
if (o.httpOnly) s += "; HttpOnly";
|
|
28
|
+
if (o.secure) s += "; Secure";
|
|
29
|
+
s += `; SameSite=${o.sameSite[0].toUpperCase()}${o.sameSite.slice(1)}`;
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
function deleteCookie(name, opts = {}) {
|
|
33
|
+
return serializeCookie(name, "", { ...opts, maxAge: 0 });
|
|
34
|
+
}
|
|
35
|
+
function requestHeaders() {
|
|
36
|
+
const req = getRequestEvent()?.request;
|
|
37
|
+
return req?.headers ?? null;
|
|
38
|
+
}
|
|
39
|
+
function getCookie(name) {
|
|
40
|
+
return parseCookies(requestHeaders()?.get("cookie"))[name] ?? null;
|
|
41
|
+
}
|
|
42
|
+
function getCookies() {
|
|
43
|
+
return parseCookies(requestHeaders()?.get("cookie"));
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
deleteCookie,
|
|
47
|
+
getCookie,
|
|
48
|
+
getCookies,
|
|
49
|
+
parseCookies,
|
|
50
|
+
serializeCookie
|
|
51
|
+
};
|