@authyon/auth 0.2.0-beta.0 → 0.2.0-beta.2
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/LICENSE +21 -0
- package/README.md +177 -14
- package/dist/ability-g6nBpOQM.d.cts +617 -0
- package/dist/ability-g6nBpOQM.d.ts +617 -0
- package/dist/chunk-EHZEUM47.js +323 -0
- package/dist/index.cjs +748 -60
- package/dist/index.d.cts +57 -466
- package/dist/index.d.ts +57 -466
- package/dist/index.js +431 -58
- package/dist/react/index.cjs +428 -0
- package/dist/react/index.d.cts +47 -0
- package/dist/react/index.d.ts +47 -0
- package/dist/react/index.js +120 -0
- package/package.json +71 -37
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
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/react/index.tsx
|
|
22
|
+
var react_exports = {};
|
|
23
|
+
__export(react_exports, {
|
|
24
|
+
AuthyonProvider: () => AuthyonProvider,
|
|
25
|
+
AuthyonSessionController: () => AuthyonSessionController,
|
|
26
|
+
PermissionGuard: () => PermissionGuard,
|
|
27
|
+
SessionGuard: () => SessionGuard,
|
|
28
|
+
useAuthyon: () => useAuthyon,
|
|
29
|
+
useAuthyonAbility: () => useAuthyonAbility,
|
|
30
|
+
useCan: () => useCan
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(react_exports);
|
|
33
|
+
var import_react = require("react");
|
|
34
|
+
|
|
35
|
+
// ../../internal/core/authorization/ability.ts
|
|
36
|
+
var AuthyonAbility = class {
|
|
37
|
+
constructor(rules = [], detectSubjectType = defaultSubjectType) {
|
|
38
|
+
this.detectSubjectType = detectSubjectType;
|
|
39
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
40
|
+
this.currentRules = rules.map(cloneRule);
|
|
41
|
+
}
|
|
42
|
+
get rules() {
|
|
43
|
+
return this.currentRules;
|
|
44
|
+
}
|
|
45
|
+
can(action, subject, field) {
|
|
46
|
+
const subjectType = typeof subject === "string" ? subject : this.detectSubjectType(subject);
|
|
47
|
+
for (let index = this.currentRules.length - 1; index >= 0; index -= 1) {
|
|
48
|
+
const rule = this.currentRules[index];
|
|
49
|
+
if (!matchesToken(rule.action, action, "manage")) continue;
|
|
50
|
+
if (!matchesToken(rule.subject, subjectType, "all")) continue;
|
|
51
|
+
if (field && rule.fields && !rule.fields.some((value) => matchesField(value, field)))
|
|
52
|
+
continue;
|
|
53
|
+
if (rule.conditions) {
|
|
54
|
+
if (typeof subject === "string" || !matchesConditions(subject, rule.conditions)) continue;
|
|
55
|
+
}
|
|
56
|
+
return !rule.inverted;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
cannot(action, subject, field) {
|
|
61
|
+
return !this.can(action, subject, field);
|
|
62
|
+
}
|
|
63
|
+
rulesFor(action, subject) {
|
|
64
|
+
return this.currentRules.filter(
|
|
65
|
+
(rule) => matchesToken(rule.action, action, "manage") && matchesToken(rule.subject, subject, "all")
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
update(rules) {
|
|
69
|
+
this.currentRules = rules.map(cloneRule);
|
|
70
|
+
for (const listener of this.listeners) listener(this.rules);
|
|
71
|
+
}
|
|
72
|
+
on(event, listener) {
|
|
73
|
+
if (event !== "updated") return () => void 0;
|
|
74
|
+
this.listeners.add(listener);
|
|
75
|
+
return () => this.listeners.delete(listener);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
function createAuthyonAbility(source = {}, options = {}) {
|
|
79
|
+
return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
|
|
80
|
+
}
|
|
81
|
+
function createAuthyonRules(source = {}, options = {}) {
|
|
82
|
+
const permissions = /* @__PURE__ */ new Set([
|
|
83
|
+
...source.permissions ?? [],
|
|
84
|
+
...source.scope?.split(/\s+/).filter(Boolean) ?? []
|
|
85
|
+
]);
|
|
86
|
+
const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
|
|
87
|
+
for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
|
|
88
|
+
rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
|
|
89
|
+
}
|
|
90
|
+
rules.push(...(options.rules ?? []).map(cloneRule));
|
|
91
|
+
return rules;
|
|
92
|
+
}
|
|
93
|
+
function permissionToRule(permission) {
|
|
94
|
+
const normalized = permission.trim();
|
|
95
|
+
if (!normalized) return null;
|
|
96
|
+
if (normalized === "*" || normalized === "*:*" || normalized === "all:manage") {
|
|
97
|
+
return { action: "manage", subject: "all" };
|
|
98
|
+
}
|
|
99
|
+
const separator = normalized.lastIndexOf(":");
|
|
100
|
+
if (separator <= 0 || separator === normalized.length - 1) return null;
|
|
101
|
+
const subject = normalized.slice(0, separator);
|
|
102
|
+
const action = normalized.slice(separator + 1);
|
|
103
|
+
return {
|
|
104
|
+
action: action === "*" ? "manage" : action,
|
|
105
|
+
subject: subject === "*" ? "all" : subject
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function matchesToken(value, expected, wildcard) {
|
|
109
|
+
return (Array.isArray(value) ? value : [value]).some(
|
|
110
|
+
(candidate) => candidate === expected || candidate === wildcard || candidate === "*" || matchesSegments(candidate, expected)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
function matchesSegments(pattern, value) {
|
|
114
|
+
const patternSegments = pattern.split(":");
|
|
115
|
+
const valueSegments = value.split(":");
|
|
116
|
+
return patternSegments.length === valueSegments.length && patternSegments.every((segment, index) => segment === "*" || segment === valueSegments[index]);
|
|
117
|
+
}
|
|
118
|
+
function matchesField(pattern, field) {
|
|
119
|
+
if (pattern === "*" || pattern === field) return true;
|
|
120
|
+
return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
|
|
121
|
+
}
|
|
122
|
+
function matchesConditions(subject, conditions) {
|
|
123
|
+
return Object.entries(conditions).every(([path, expected]) => {
|
|
124
|
+
if (path === "$and" && Array.isArray(expected)) {
|
|
125
|
+
return expected.every(
|
|
126
|
+
(condition) => matchesConditions(subject, condition)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (path === "$or" && Array.isArray(expected)) {
|
|
130
|
+
return expected.some(
|
|
131
|
+
(condition) => matchesConditions(subject, condition)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return matchesValue(readPath(subject, path), expected);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function matchesValue(actual, expected) {
|
|
138
|
+
if (!isRecord(expected) || !Object.keys(expected).some((key) => key.startsWith("$"))) {
|
|
139
|
+
return isRecord(expected) && isRecord(actual) ? matchesConditions(actual, expected) : Object.is(actual, expected);
|
|
140
|
+
}
|
|
141
|
+
return Object.entries(expected).every(([operator, operand]) => {
|
|
142
|
+
switch (operator) {
|
|
143
|
+
case "$eq":
|
|
144
|
+
return Object.is(actual, operand);
|
|
145
|
+
case "$ne":
|
|
146
|
+
return !Object.is(actual, operand);
|
|
147
|
+
case "$in":
|
|
148
|
+
return Array.isArray(operand) && operand.some((value) => Object.is(actual, value));
|
|
149
|
+
case "$nin":
|
|
150
|
+
return Array.isArray(operand) && !operand.some((value) => Object.is(actual, value));
|
|
151
|
+
case "$gt":
|
|
152
|
+
return typeof actual === "number" && typeof operand === "number" && actual > operand;
|
|
153
|
+
case "$gte":
|
|
154
|
+
return typeof actual === "number" && typeof operand === "number" && actual >= operand;
|
|
155
|
+
case "$lt":
|
|
156
|
+
return typeof actual === "number" && typeof operand === "number" && actual < operand;
|
|
157
|
+
case "$lte":
|
|
158
|
+
return typeof actual === "number" && typeof operand === "number" && actual <= operand;
|
|
159
|
+
case "$exists":
|
|
160
|
+
return operand ? actual !== void 0 : actual === void 0;
|
|
161
|
+
default:
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function readPath(value, path) {
|
|
167
|
+
return path.split(".").reduce((current, part) => isRecord(current) ? current[part] : void 0, value);
|
|
168
|
+
}
|
|
169
|
+
function defaultSubjectType(subject) {
|
|
170
|
+
const explicit = subject.__type ?? subject.type ?? subject.kind;
|
|
171
|
+
if (typeof explicit === "string") return explicit;
|
|
172
|
+
const constructorName = subject.constructor?.name;
|
|
173
|
+
return typeof constructorName === "string" ? constructorName : "Object";
|
|
174
|
+
}
|
|
175
|
+
function cloneRule(rule) {
|
|
176
|
+
return {
|
|
177
|
+
...rule,
|
|
178
|
+
action: Array.isArray(rule.action) ? [...rule.action] : rule.action,
|
|
179
|
+
subject: Array.isArray(rule.subject) ? [...rule.subject] : rule.subject,
|
|
180
|
+
fields: rule.fields ? [...rule.fields] : void 0
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function isRecord(value) {
|
|
184
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
185
|
+
}
|
|
186
|
+
function isAbilityRule(value) {
|
|
187
|
+
return value !== null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/session/sessionController.ts
|
|
191
|
+
var SERVER_SNAPSHOT = {
|
|
192
|
+
// The server cannot inspect browser storage. Reporting unauthenticated here
|
|
193
|
+
// makes guards redirect during hydration before a persisted session can be
|
|
194
|
+
// restored and validated on the client.
|
|
195
|
+
status: "validating",
|
|
196
|
+
session: null,
|
|
197
|
+
user: null,
|
|
198
|
+
error: null
|
|
199
|
+
};
|
|
200
|
+
var AuthyonSessionController = class {
|
|
201
|
+
constructor(client, options = {}) {
|
|
202
|
+
this.client = client;
|
|
203
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
204
|
+
this.getSnapshot = () => this.snapshot;
|
|
205
|
+
this.getServerSnapshot = () => SERVER_SNAPSHOT;
|
|
206
|
+
this.subscribe = (listener) => {
|
|
207
|
+
this.listeners.add(listener);
|
|
208
|
+
return () => this.listeners.delete(listener);
|
|
209
|
+
};
|
|
210
|
+
this.refreshAheadMs = options.refreshAheadMs ?? 3e4;
|
|
211
|
+
if (!Number.isFinite(this.refreshAheadMs) || this.refreshAheadMs < 0) {
|
|
212
|
+
throw new Error("Authyon: `refreshAheadMs` must be a non-negative finite number");
|
|
213
|
+
}
|
|
214
|
+
const session = client.getSession();
|
|
215
|
+
this.snapshot = {
|
|
216
|
+
status: session ? "validating" : "unauthenticated",
|
|
217
|
+
session,
|
|
218
|
+
user: session?.user ?? null,
|
|
219
|
+
error: null
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
start() {
|
|
223
|
+
if (!this.unsubscribeAuth) {
|
|
224
|
+
this.unsubscribeAuth = this.client.onAuthStateChange((event) => {
|
|
225
|
+
if (event.type === "signed_out") {
|
|
226
|
+
this.cancelRefresh();
|
|
227
|
+
this.setSnapshot({
|
|
228
|
+
status: "unauthenticated",
|
|
229
|
+
session: null,
|
|
230
|
+
user: null,
|
|
231
|
+
error: null
|
|
232
|
+
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (event.type === "session_validated") {
|
|
236
|
+
this.acceptSession(event.session);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.setSnapshot({
|
|
240
|
+
status: "validating",
|
|
241
|
+
session: event.session,
|
|
242
|
+
user: event.session.user ?? null,
|
|
243
|
+
error: null
|
|
244
|
+
});
|
|
245
|
+
void this.validate();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
void this.validate();
|
|
249
|
+
return () => this.stop();
|
|
250
|
+
}
|
|
251
|
+
stop() {
|
|
252
|
+
this.unsubscribeAuth?.();
|
|
253
|
+
this.unsubscribeAuth = void 0;
|
|
254
|
+
this.cancelRefresh();
|
|
255
|
+
}
|
|
256
|
+
validate() {
|
|
257
|
+
if (this.validation) return this.validation;
|
|
258
|
+
const localSession = this.client.getSession();
|
|
259
|
+
if (!localSession) {
|
|
260
|
+
this.setSnapshot({
|
|
261
|
+
status: "unauthenticated",
|
|
262
|
+
session: null,
|
|
263
|
+
user: null,
|
|
264
|
+
error: null
|
|
265
|
+
});
|
|
266
|
+
return Promise.resolve(this.snapshot);
|
|
267
|
+
}
|
|
268
|
+
this.setSnapshot({ ...this.snapshot, status: "validating", error: null });
|
|
269
|
+
this.validation = this.client.validateSession().then((session) => {
|
|
270
|
+
if (session) this.acceptSession(session);
|
|
271
|
+
else {
|
|
272
|
+
this.setSnapshot({
|
|
273
|
+
status: "unauthenticated",
|
|
274
|
+
session: null,
|
|
275
|
+
user: null,
|
|
276
|
+
error: null
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return this.snapshot;
|
|
280
|
+
}).catch((error) => {
|
|
281
|
+
this.setSnapshot({ ...this.snapshot, status: "error", error });
|
|
282
|
+
return this.snapshot;
|
|
283
|
+
}).finally(() => {
|
|
284
|
+
this.validation = void 0;
|
|
285
|
+
});
|
|
286
|
+
return this.validation;
|
|
287
|
+
}
|
|
288
|
+
async refreshNow() {
|
|
289
|
+
if (!this.client.getSession()) return this.validate();
|
|
290
|
+
try {
|
|
291
|
+
await this.client.refresh();
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (!this.client.getSession()) return this.validate();
|
|
294
|
+
this.setSnapshot({ ...this.snapshot, status: "error", error });
|
|
295
|
+
return this.snapshot;
|
|
296
|
+
}
|
|
297
|
+
return this.validate();
|
|
298
|
+
}
|
|
299
|
+
acceptSession(session) {
|
|
300
|
+
this.setSnapshot({
|
|
301
|
+
status: "authenticated",
|
|
302
|
+
session,
|
|
303
|
+
user: session.user ?? null,
|
|
304
|
+
error: null
|
|
305
|
+
});
|
|
306
|
+
this.scheduleRefresh(session);
|
|
307
|
+
}
|
|
308
|
+
scheduleRefresh(session) {
|
|
309
|
+
this.cancelRefresh();
|
|
310
|
+
const delay = Math.max(1e3, session.expiresAt - Date.now() - this.refreshAheadMs);
|
|
311
|
+
this.refreshTimer = setTimeout(() => void this.refreshNow(), delay);
|
|
312
|
+
}
|
|
313
|
+
cancelRefresh() {
|
|
314
|
+
if (this.refreshTimer !== void 0) clearTimeout(this.refreshTimer);
|
|
315
|
+
this.refreshTimer = void 0;
|
|
316
|
+
}
|
|
317
|
+
setSnapshot(snapshot) {
|
|
318
|
+
this.snapshot = snapshot;
|
|
319
|
+
for (const listener of this.listeners) listener();
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
// src/react/index.tsx
|
|
324
|
+
var AuthyonReactContext = (0, import_react.createContext)(null);
|
|
325
|
+
function AuthyonProvider({
|
|
326
|
+
client,
|
|
327
|
+
children,
|
|
328
|
+
refreshAheadMs,
|
|
329
|
+
validateOnFocus = true,
|
|
330
|
+
transformUser
|
|
331
|
+
}) {
|
|
332
|
+
const controllerRef = (0, import_react.useRef)(null);
|
|
333
|
+
if (!controllerRef.current || controllerRef.current.client !== client) {
|
|
334
|
+
controllerRef.current = new AuthyonSessionController(client, { refreshAheadMs });
|
|
335
|
+
}
|
|
336
|
+
const controller = controllerRef.current;
|
|
337
|
+
const snapshot = (0, import_react.useSyncExternalStore)(
|
|
338
|
+
controller.subscribe,
|
|
339
|
+
controller.getSnapshot,
|
|
340
|
+
controller.getServerSnapshot
|
|
341
|
+
);
|
|
342
|
+
(0, import_react.useEffect)(() => controller.start(), [controller]);
|
|
343
|
+
(0, import_react.useEffect)(() => {
|
|
344
|
+
if (!validateOnFocus) return;
|
|
345
|
+
const validateWhenVisible = () => {
|
|
346
|
+
if (document.visibilityState === "visible") void controller.validate();
|
|
347
|
+
};
|
|
348
|
+
document.addEventListener("visibilitychange", validateWhenVisible);
|
|
349
|
+
return () => document.removeEventListener("visibilitychange", validateWhenVisible);
|
|
350
|
+
}, [controller, validateOnFocus]);
|
|
351
|
+
const exposedSnapshot = (0, import_react.useMemo)(
|
|
352
|
+
() => transformSnapshot(snapshot, transformUser),
|
|
353
|
+
[snapshot, transformUser]
|
|
354
|
+
);
|
|
355
|
+
const value = (0, import_react.useMemo)(
|
|
356
|
+
() => ({
|
|
357
|
+
...exposedSnapshot,
|
|
358
|
+
client,
|
|
359
|
+
validateSession: async () => transformSnapshot(await controller.validate(), transformUser),
|
|
360
|
+
refreshSession: async () => transformSnapshot(await controller.refreshNow(), transformUser)
|
|
361
|
+
}),
|
|
362
|
+
[client, controller, exposedSnapshot, transformUser]
|
|
363
|
+
);
|
|
364
|
+
return (0, import_react.createElement)(AuthyonReactContext.Provider, { value }, children);
|
|
365
|
+
}
|
|
366
|
+
function useAuthyon() {
|
|
367
|
+
const context = (0, import_react.useContext)(AuthyonReactContext);
|
|
368
|
+
if (!context) throw new Error("Authyon: `useAuthyon` must be used inside `AuthyonProvider`");
|
|
369
|
+
return context;
|
|
370
|
+
}
|
|
371
|
+
function transformSnapshot(snapshot, transformUser) {
|
|
372
|
+
const mapUser = (user2) => transformUser ? transformUser(user2) : user2;
|
|
373
|
+
const user = snapshot.user ? mapUser(snapshot.user) : null;
|
|
374
|
+
const session = snapshot.session ? {
|
|
375
|
+
...snapshot.session,
|
|
376
|
+
user: snapshot.session.user ? snapshot.session.user === snapshot.user && user ? user : mapUser(snapshot.session.user) : void 0
|
|
377
|
+
} : null;
|
|
378
|
+
return { ...snapshot, session, user };
|
|
379
|
+
}
|
|
380
|
+
function useAuthyonAbility(options = {}) {
|
|
381
|
+
const { user } = useAuthyon();
|
|
382
|
+
return (0, import_react.useMemo)(() => createAuthyonAbility(user ?? {}, options), [user, options]);
|
|
383
|
+
}
|
|
384
|
+
function useCan(action, subject, field, options) {
|
|
385
|
+
const ability = useAuthyonAbility(options);
|
|
386
|
+
return ability.can(action, subject, field);
|
|
387
|
+
}
|
|
388
|
+
function SessionGuard({
|
|
389
|
+
children,
|
|
390
|
+
loadingFallback = null,
|
|
391
|
+
unauthenticatedFallback = null,
|
|
392
|
+
errorFallback = null,
|
|
393
|
+
onUnauthenticated
|
|
394
|
+
}) {
|
|
395
|
+
const { status } = useAuthyon();
|
|
396
|
+
(0, import_react.useEffect)(() => {
|
|
397
|
+
if (status === "unauthenticated") onUnauthenticated?.();
|
|
398
|
+
}, [onUnauthenticated, status]);
|
|
399
|
+
if (status === "validating") return loadingFallback;
|
|
400
|
+
if (status === "error") return errorFallback;
|
|
401
|
+
if (status !== "authenticated") return unauthenticatedFallback;
|
|
402
|
+
return children;
|
|
403
|
+
}
|
|
404
|
+
function PermissionGuard({
|
|
405
|
+
action,
|
|
406
|
+
subject,
|
|
407
|
+
field,
|
|
408
|
+
forbiddenFallback = null,
|
|
409
|
+
abilityOptions,
|
|
410
|
+
children,
|
|
411
|
+
...sessionProps
|
|
412
|
+
}) {
|
|
413
|
+
const allowed = useCan(action, subject, field, abilityOptions);
|
|
414
|
+
return (0, import_react.createElement)(SessionGuard, {
|
|
415
|
+
...sessionProps,
|
|
416
|
+
children: allowed ? children : forbiddenFallback
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
420
|
+
0 && (module.exports = {
|
|
421
|
+
AuthyonProvider,
|
|
422
|
+
AuthyonSessionController,
|
|
423
|
+
PermissionGuard,
|
|
424
|
+
SessionGuard,
|
|
425
|
+
useAuthyon,
|
|
426
|
+
useAuthyonAbility,
|
|
427
|
+
useCan
|
|
428
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { U as User, B as SessionControllerOptions, A as AuthyonClient, E as SessionSnapshot, S as Session, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-g6nBpOQM.cjs';
|
|
4
|
+
export { q as AuthyonSessionController, G as SessionSnapshotListener, J as SessionStatus } from '../ability-g6nBpOQM.cjs';
|
|
5
|
+
|
|
6
|
+
interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
|
|
7
|
+
client: AuthyonClient;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
/** Revalidate with `/auth/me` when the tab becomes visible. Defaults to true. */
|
|
10
|
+
validateOnFocus?: boolean;
|
|
11
|
+
/** Derives the user exposed by `useAuthyon` without mutating the stored Authyon session. */
|
|
12
|
+
transformUser?: (user: User) => TUser;
|
|
13
|
+
}
|
|
14
|
+
type AuthyonSession<TUser extends User = User> = Omit<Session, "user"> & {
|
|
15
|
+
user?: TUser;
|
|
16
|
+
};
|
|
17
|
+
type AuthyonSessionSnapshot<TUser extends User = User> = Omit<SessionSnapshot, "session" | "user"> & {
|
|
18
|
+
session: AuthyonSession<TUser> | null;
|
|
19
|
+
user: TUser | null;
|
|
20
|
+
};
|
|
21
|
+
interface AuthyonReactContextValue<TUser extends User = User> extends AuthyonSessionSnapshot<TUser> {
|
|
22
|
+
client: AuthyonClient;
|
|
23
|
+
validateSession(): Promise<AuthyonSessionSnapshot<TUser>>;
|
|
24
|
+
refreshSession(): Promise<AuthyonSessionSnapshot<TUser>>;
|
|
25
|
+
}
|
|
26
|
+
declare function AuthyonProvider<TUser extends User = User>({ client, children, refreshAheadMs, validateOnFocus, transformUser, }: AuthyonProviderProps<TUser>): react.FunctionComponentElement<react.ProviderProps<AuthyonReactContextValue<User> | null>>;
|
|
27
|
+
declare function useAuthyon<TUser extends User = User>(): AuthyonReactContextValue<TUser>;
|
|
28
|
+
declare function useAuthyonAbility(options?: AuthyonAbilityOptions): AuthyonAbility;
|
|
29
|
+
declare function useCan(action: string, subject: AbilitySubject, field?: string, options?: AuthyonAbilityOptions): boolean;
|
|
30
|
+
interface SessionGuardProps {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
loadingFallback?: ReactNode;
|
|
33
|
+
unauthenticatedFallback?: ReactNode;
|
|
34
|
+
errorFallback?: ReactNode;
|
|
35
|
+
onUnauthenticated?: () => void;
|
|
36
|
+
}
|
|
37
|
+
declare function SessionGuard({ children, loadingFallback, unauthenticatedFallback, errorFallback, onUnauthenticated, }: SessionGuardProps): ReactNode;
|
|
38
|
+
interface PermissionGuardProps extends SessionGuardProps {
|
|
39
|
+
action: string;
|
|
40
|
+
subject: AbilitySubject;
|
|
41
|
+
field?: string;
|
|
42
|
+
forbiddenFallback?: ReactNode;
|
|
43
|
+
abilityOptions?: AuthyonAbilityOptions;
|
|
44
|
+
}
|
|
45
|
+
declare function PermissionGuard({ action, subject, field, forbiddenFallback, abilityOptions, children, ...sessionProps }: PermissionGuardProps): react.FunctionComponentElement<SessionGuardProps>;
|
|
46
|
+
|
|
47
|
+
export { AuthyonProvider, type AuthyonProviderProps, type AuthyonReactContextValue, type AuthyonSession, type AuthyonSessionSnapshot, PermissionGuard, type PermissionGuardProps, SessionControllerOptions, SessionGuard, type SessionGuardProps, SessionSnapshot, useAuthyon, useAuthyonAbility, useCan };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { U as User, B as SessionControllerOptions, A as AuthyonClient, E as SessionSnapshot, S as Session, f as AbilitySubject, n as AuthyonAbilityOptions, l as AuthyonAbility } from '../ability-g6nBpOQM.js';
|
|
4
|
+
export { q as AuthyonSessionController, G as SessionSnapshotListener, J as SessionStatus } from '../ability-g6nBpOQM.js';
|
|
5
|
+
|
|
6
|
+
interface AuthyonProviderProps<TUser extends User = User> extends SessionControllerOptions {
|
|
7
|
+
client: AuthyonClient;
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
/** Revalidate with `/auth/me` when the tab becomes visible. Defaults to true. */
|
|
10
|
+
validateOnFocus?: boolean;
|
|
11
|
+
/** Derives the user exposed by `useAuthyon` without mutating the stored Authyon session. */
|
|
12
|
+
transformUser?: (user: User) => TUser;
|
|
13
|
+
}
|
|
14
|
+
type AuthyonSession<TUser extends User = User> = Omit<Session, "user"> & {
|
|
15
|
+
user?: TUser;
|
|
16
|
+
};
|
|
17
|
+
type AuthyonSessionSnapshot<TUser extends User = User> = Omit<SessionSnapshot, "session" | "user"> & {
|
|
18
|
+
session: AuthyonSession<TUser> | null;
|
|
19
|
+
user: TUser | null;
|
|
20
|
+
};
|
|
21
|
+
interface AuthyonReactContextValue<TUser extends User = User> extends AuthyonSessionSnapshot<TUser> {
|
|
22
|
+
client: AuthyonClient;
|
|
23
|
+
validateSession(): Promise<AuthyonSessionSnapshot<TUser>>;
|
|
24
|
+
refreshSession(): Promise<AuthyonSessionSnapshot<TUser>>;
|
|
25
|
+
}
|
|
26
|
+
declare function AuthyonProvider<TUser extends User = User>({ client, children, refreshAheadMs, validateOnFocus, transformUser, }: AuthyonProviderProps<TUser>): react.FunctionComponentElement<react.ProviderProps<AuthyonReactContextValue<User> | null>>;
|
|
27
|
+
declare function useAuthyon<TUser extends User = User>(): AuthyonReactContextValue<TUser>;
|
|
28
|
+
declare function useAuthyonAbility(options?: AuthyonAbilityOptions): AuthyonAbility;
|
|
29
|
+
declare function useCan(action: string, subject: AbilitySubject, field?: string, options?: AuthyonAbilityOptions): boolean;
|
|
30
|
+
interface SessionGuardProps {
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
loadingFallback?: ReactNode;
|
|
33
|
+
unauthenticatedFallback?: ReactNode;
|
|
34
|
+
errorFallback?: ReactNode;
|
|
35
|
+
onUnauthenticated?: () => void;
|
|
36
|
+
}
|
|
37
|
+
declare function SessionGuard({ children, loadingFallback, unauthenticatedFallback, errorFallback, onUnauthenticated, }: SessionGuardProps): ReactNode;
|
|
38
|
+
interface PermissionGuardProps extends SessionGuardProps {
|
|
39
|
+
action: string;
|
|
40
|
+
subject: AbilitySubject;
|
|
41
|
+
field?: string;
|
|
42
|
+
forbiddenFallback?: ReactNode;
|
|
43
|
+
abilityOptions?: AuthyonAbilityOptions;
|
|
44
|
+
}
|
|
45
|
+
declare function PermissionGuard({ action, subject, field, forbiddenFallback, abilityOptions, children, ...sessionProps }: PermissionGuardProps): react.FunctionComponentElement<SessionGuardProps>;
|
|
46
|
+
|
|
47
|
+
export { AuthyonProvider, type AuthyonProviderProps, type AuthyonReactContextValue, type AuthyonSession, type AuthyonSessionSnapshot, PermissionGuard, type PermissionGuardProps, SessionControllerOptions, SessionGuard, type SessionGuardProps, SessionSnapshot, useAuthyon, useAuthyonAbility, useCan };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
AuthyonSessionController,
|
|
4
|
+
createAuthyonAbility
|
|
5
|
+
} from "../chunk-EHZEUM47.js";
|
|
6
|
+
|
|
7
|
+
// src/react/index.tsx
|
|
8
|
+
import {
|
|
9
|
+
createContext,
|
|
10
|
+
createElement,
|
|
11
|
+
useContext,
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useRef,
|
|
15
|
+
useSyncExternalStore
|
|
16
|
+
} from "react";
|
|
17
|
+
var AuthyonReactContext = createContext(null);
|
|
18
|
+
function AuthyonProvider({
|
|
19
|
+
client,
|
|
20
|
+
children,
|
|
21
|
+
refreshAheadMs,
|
|
22
|
+
validateOnFocus = true,
|
|
23
|
+
transformUser
|
|
24
|
+
}) {
|
|
25
|
+
const controllerRef = useRef(null);
|
|
26
|
+
if (!controllerRef.current || controllerRef.current.client !== client) {
|
|
27
|
+
controllerRef.current = new AuthyonSessionController(client, { refreshAheadMs });
|
|
28
|
+
}
|
|
29
|
+
const controller = controllerRef.current;
|
|
30
|
+
const snapshot = useSyncExternalStore(
|
|
31
|
+
controller.subscribe,
|
|
32
|
+
controller.getSnapshot,
|
|
33
|
+
controller.getServerSnapshot
|
|
34
|
+
);
|
|
35
|
+
useEffect(() => controller.start(), [controller]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!validateOnFocus) return;
|
|
38
|
+
const validateWhenVisible = () => {
|
|
39
|
+
if (document.visibilityState === "visible") void controller.validate();
|
|
40
|
+
};
|
|
41
|
+
document.addEventListener("visibilitychange", validateWhenVisible);
|
|
42
|
+
return () => document.removeEventListener("visibilitychange", validateWhenVisible);
|
|
43
|
+
}, [controller, validateOnFocus]);
|
|
44
|
+
const exposedSnapshot = useMemo(
|
|
45
|
+
() => transformSnapshot(snapshot, transformUser),
|
|
46
|
+
[snapshot, transformUser]
|
|
47
|
+
);
|
|
48
|
+
const value = useMemo(
|
|
49
|
+
() => ({
|
|
50
|
+
...exposedSnapshot,
|
|
51
|
+
client,
|
|
52
|
+
validateSession: async () => transformSnapshot(await controller.validate(), transformUser),
|
|
53
|
+
refreshSession: async () => transformSnapshot(await controller.refreshNow(), transformUser)
|
|
54
|
+
}),
|
|
55
|
+
[client, controller, exposedSnapshot, transformUser]
|
|
56
|
+
);
|
|
57
|
+
return createElement(AuthyonReactContext.Provider, { value }, children);
|
|
58
|
+
}
|
|
59
|
+
function useAuthyon() {
|
|
60
|
+
const context = useContext(AuthyonReactContext);
|
|
61
|
+
if (!context) throw new Error("Authyon: `useAuthyon` must be used inside `AuthyonProvider`");
|
|
62
|
+
return context;
|
|
63
|
+
}
|
|
64
|
+
function transformSnapshot(snapshot, transformUser) {
|
|
65
|
+
const mapUser = (user2) => transformUser ? transformUser(user2) : user2;
|
|
66
|
+
const user = snapshot.user ? mapUser(snapshot.user) : null;
|
|
67
|
+
const session = snapshot.session ? {
|
|
68
|
+
...snapshot.session,
|
|
69
|
+
user: snapshot.session.user ? snapshot.session.user === snapshot.user && user ? user : mapUser(snapshot.session.user) : void 0
|
|
70
|
+
} : null;
|
|
71
|
+
return { ...snapshot, session, user };
|
|
72
|
+
}
|
|
73
|
+
function useAuthyonAbility(options = {}) {
|
|
74
|
+
const { user } = useAuthyon();
|
|
75
|
+
return useMemo(() => createAuthyonAbility(user ?? {}, options), [user, options]);
|
|
76
|
+
}
|
|
77
|
+
function useCan(action, subject, field, options) {
|
|
78
|
+
const ability = useAuthyonAbility(options);
|
|
79
|
+
return ability.can(action, subject, field);
|
|
80
|
+
}
|
|
81
|
+
function SessionGuard({
|
|
82
|
+
children,
|
|
83
|
+
loadingFallback = null,
|
|
84
|
+
unauthenticatedFallback = null,
|
|
85
|
+
errorFallback = null,
|
|
86
|
+
onUnauthenticated
|
|
87
|
+
}) {
|
|
88
|
+
const { status } = useAuthyon();
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (status === "unauthenticated") onUnauthenticated?.();
|
|
91
|
+
}, [onUnauthenticated, status]);
|
|
92
|
+
if (status === "validating") return loadingFallback;
|
|
93
|
+
if (status === "error") return errorFallback;
|
|
94
|
+
if (status !== "authenticated") return unauthenticatedFallback;
|
|
95
|
+
return children;
|
|
96
|
+
}
|
|
97
|
+
function PermissionGuard({
|
|
98
|
+
action,
|
|
99
|
+
subject,
|
|
100
|
+
field,
|
|
101
|
+
forbiddenFallback = null,
|
|
102
|
+
abilityOptions,
|
|
103
|
+
children,
|
|
104
|
+
...sessionProps
|
|
105
|
+
}) {
|
|
106
|
+
const allowed = useCan(action, subject, field, abilityOptions);
|
|
107
|
+
return createElement(SessionGuard, {
|
|
108
|
+
...sessionProps,
|
|
109
|
+
children: allowed ? children : forbiddenFallback
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
AuthyonProvider,
|
|
114
|
+
AuthyonSessionController,
|
|
115
|
+
PermissionGuard,
|
|
116
|
+
SessionGuard,
|
|
117
|
+
useAuthyon,
|
|
118
|
+
useAuthyonAbility,
|
|
119
|
+
useCan
|
|
120
|
+
};
|