@authyon/auth 0.1.5 → 0.2.0-beta.1
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 +301 -158
- package/dist/ability-CVplsuzO.d.cts +615 -0
- package/dist/ability-CVplsuzO.d.ts +615 -0
- package/dist/chunk-OKPSF4LZ.js +303 -0
- package/dist/index.cjs +727 -60
- package/dist/index.d.cts +57 -466
- package/dist/index.d.ts +57 -466
- package/dist/index.js +429 -58
- package/dist/react/index.cjs +406 -0
- package/dist/react/index.d.cts +38 -0
- package/dist/react/index.d.ts +38 -0
- package/dist/react/index.js +106 -0
- package/package.json +71 -37
package/dist/index.cjs
CHANGED
|
@@ -20,39 +20,140 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
AuthyonAbility: () => AuthyonAbility,
|
|
24
|
+
AuthyonAbilityBuilder: () => AuthyonAbilityBuilder,
|
|
23
25
|
AuthyonClient: () => AuthyonClient,
|
|
26
|
+
AuthyonClientBuilder: () => AuthyonClientBuilder,
|
|
24
27
|
AuthyonError: () => AuthyonError,
|
|
28
|
+
AuthyonSessionController: () => AuthyonSessionController,
|
|
25
29
|
ErrorCodes: () => ErrorCodes,
|
|
30
|
+
FetchHttpAdapter: () => FetchHttpAdapter,
|
|
31
|
+
LoggingHttpAdapter: () => LoggingHttpAdapter,
|
|
32
|
+
createAuthyonAbility: () => createAuthyonAbility,
|
|
33
|
+
createAuthyonRules: () => createAuthyonRules,
|
|
26
34
|
createClient: () => createClient,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
createDefaultStorage: () => createDefaultStorage,
|
|
36
|
+
createLocalStorage: () => createLocalStorage,
|
|
37
|
+
createMemoryStorage: () => createMemoryStorage
|
|
30
38
|
});
|
|
31
39
|
module.exports = __toCommonJS(index_exports);
|
|
32
40
|
|
|
33
|
-
//
|
|
41
|
+
// ../../internal/core/errors/authyonError.ts
|
|
42
|
+
var ErrorCodes = {
|
|
43
|
+
Unknown: "unknown",
|
|
44
|
+
NetworkError: "request.network_error",
|
|
45
|
+
Timeout: "request.timeout",
|
|
46
|
+
SessionMalformed: "session.malformed",
|
|
47
|
+
NotAuthenticated: "auth.not_authenticated",
|
|
48
|
+
InvalidToken: "auth.invalid_token",
|
|
49
|
+
MissingToken: "auth.missing_token",
|
|
50
|
+
EmailTaken: "user.email_taken",
|
|
51
|
+
PasswordWeak: "user.password_weak",
|
|
52
|
+
PasswordPwned: "user.password_pwned",
|
|
53
|
+
RateLimited: "rate_limited"
|
|
54
|
+
};
|
|
34
55
|
var AuthyonError = class extends Error {
|
|
35
|
-
constructor(status, body) {
|
|
56
|
+
constructor(status, body, options = {}) {
|
|
36
57
|
super(body.detail ?? body.title ?? `Authyon request failed with status ${status}`);
|
|
37
58
|
this.name = "AuthyonError";
|
|
38
59
|
this.status = status;
|
|
39
60
|
this.code = body.code ?? "unknown";
|
|
40
61
|
this.title = body.title ?? "Error";
|
|
41
62
|
this.detail = body.detail;
|
|
63
|
+
this.requestId = options.requestId;
|
|
64
|
+
this.retryAfter = options.retryAfter;
|
|
65
|
+
this.cause = options.cause;
|
|
42
66
|
}
|
|
43
67
|
is(code) {
|
|
44
68
|
return this.code === code;
|
|
45
69
|
}
|
|
70
|
+
isAny(...codes) {
|
|
71
|
+
return codes.includes(this.code);
|
|
72
|
+
}
|
|
73
|
+
hasPrefix(prefix) {
|
|
74
|
+
return this.code.startsWith(prefix);
|
|
75
|
+
}
|
|
76
|
+
isStatus(...statuses) {
|
|
77
|
+
return statuses.includes(this.status);
|
|
78
|
+
}
|
|
79
|
+
/** Stable interpretation for UI decisions, retries, telemetry and support flows. */
|
|
80
|
+
interpret() {
|
|
81
|
+
const category = classifyError(this.status, this.code);
|
|
82
|
+
return {
|
|
83
|
+
category,
|
|
84
|
+
action: actionFor(category),
|
|
85
|
+
retryable: isRetryable(category),
|
|
86
|
+
...this.retryAfter !== void 0 ? { retryAfter: this.retryAfter } : {},
|
|
87
|
+
...this.requestId !== void 0 ? { requestId: this.requestId } : {}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
get category() {
|
|
91
|
+
return this.interpret().category;
|
|
92
|
+
}
|
|
93
|
+
get retryable() {
|
|
94
|
+
return this.interpret().retryable;
|
|
95
|
+
}
|
|
96
|
+
toJSON() {
|
|
97
|
+
return {
|
|
98
|
+
name: this.name,
|
|
99
|
+
message: this.message,
|
|
100
|
+
status: this.status,
|
|
101
|
+
code: this.code,
|
|
102
|
+
title: this.title,
|
|
103
|
+
detail: this.detail,
|
|
104
|
+
requestId: this.requestId,
|
|
105
|
+
retryAfter: this.retryAfter,
|
|
106
|
+
...this.interpret()
|
|
107
|
+
};
|
|
108
|
+
}
|
|
46
109
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
110
|
+
function classifyError(status, code) {
|
|
111
|
+
if (code === ErrorCodes.NetworkError) return "network";
|
|
112
|
+
if (code === ErrorCodes.Timeout || status === 408) return "timeout";
|
|
113
|
+
if (code === ErrorCodes.RateLimited || status === 429) return "rate_limit";
|
|
114
|
+
if (code === ErrorCodes.EmailTaken || status === 409) return "conflict";
|
|
115
|
+
if (code === ErrorCodes.PasswordWeak || code === ErrorCodes.PasswordPwned) return "validation";
|
|
116
|
+
if (code === ErrorCodes.SessionMalformed) return "server";
|
|
117
|
+
if (code.startsWith("auth.") || status === 401) return "authentication";
|
|
118
|
+
if (status === 403) return "authorization";
|
|
119
|
+
if (status === 404) return "not_found";
|
|
120
|
+
if (status === 400 || status === 422) return "validation";
|
|
121
|
+
if (status >= 500) return "server";
|
|
122
|
+
return "unknown";
|
|
123
|
+
}
|
|
124
|
+
function actionFor(category) {
|
|
125
|
+
switch (category) {
|
|
126
|
+
case "network":
|
|
127
|
+
case "timeout":
|
|
128
|
+
case "rate_limit":
|
|
129
|
+
case "server":
|
|
130
|
+
return "retry";
|
|
131
|
+
case "authentication":
|
|
132
|
+
return "reauthenticate";
|
|
133
|
+
case "authorization":
|
|
134
|
+
return "request_access";
|
|
135
|
+
case "validation":
|
|
136
|
+
return "fix_input";
|
|
137
|
+
case "not_found":
|
|
138
|
+
return "not_found";
|
|
139
|
+
case "conflict":
|
|
140
|
+
return "resolve_conflict";
|
|
141
|
+
case "unknown":
|
|
142
|
+
return "contact_support";
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function isRetryable(category) {
|
|
146
|
+
return category === "network" || category === "timeout" || category === "rate_limit" || category === "server";
|
|
147
|
+
}
|
|
52
148
|
|
|
53
|
-
// src/storage.ts
|
|
149
|
+
// src/session/storage.ts
|
|
54
150
|
var STORAGE_KEY = "authyon.session";
|
|
55
|
-
function
|
|
151
|
+
function isSession(value) {
|
|
152
|
+
if (!value || typeof value !== "object") return false;
|
|
153
|
+
const candidate = value;
|
|
154
|
+
return typeof candidate.accessToken === "string" && candidate.accessToken.length > 0 && typeof candidate.refreshToken === "string" && candidate.refreshToken.length > 0 && typeof candidate.expiresIn === "number" && Number.isFinite(candidate.expiresIn) && candidate.expiresIn > 0 && typeof candidate.expiresAt === "number" && Number.isFinite(candidate.expiresAt);
|
|
155
|
+
}
|
|
156
|
+
function createMemoryStorage() {
|
|
56
157
|
let session = null;
|
|
57
158
|
return {
|
|
58
159
|
get: () => session,
|
|
@@ -64,12 +165,16 @@ function memoryStorage() {
|
|
|
64
165
|
}
|
|
65
166
|
};
|
|
66
167
|
}
|
|
67
|
-
function
|
|
168
|
+
function createLocalStorage(key = STORAGE_KEY) {
|
|
68
169
|
return {
|
|
69
170
|
get() {
|
|
70
171
|
try {
|
|
71
172
|
const raw = window.localStorage.getItem(key);
|
|
72
|
-
|
|
173
|
+
if (!raw) return null;
|
|
174
|
+
const parsed = JSON.parse(raw);
|
|
175
|
+
if (isSession(parsed)) return parsed;
|
|
176
|
+
window.localStorage.removeItem(key);
|
|
177
|
+
return null;
|
|
73
178
|
} catch {
|
|
74
179
|
return null;
|
|
75
180
|
}
|
|
@@ -88,22 +193,217 @@ function localStorageAdapter(key = STORAGE_KEY) {
|
|
|
88
193
|
}
|
|
89
194
|
};
|
|
90
195
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
return localStorageAdapter();
|
|
94
|
-
}
|
|
95
|
-
return memoryStorage();
|
|
196
|
+
function createDefaultStorage() {
|
|
197
|
+
return createMemoryStorage();
|
|
96
198
|
}
|
|
97
199
|
|
|
98
|
-
//
|
|
200
|
+
// ../../internal/core/config/defaults.ts
|
|
99
201
|
var DEFAULT_BASE_URL = "https://api.authyon.com";
|
|
100
|
-
var
|
|
202
|
+
var DEFAULT_EXPIRY_SKEW_MS = 3e4;
|
|
203
|
+
|
|
204
|
+
// ../../internal/core/http/query.ts
|
|
205
|
+
function appendQuery(path, params) {
|
|
206
|
+
if (!params) return path;
|
|
207
|
+
const query = new URLSearchParams();
|
|
208
|
+
for (const [key, value2] of Object.entries(params)) {
|
|
209
|
+
if (value2 !== void 0) query.set(key, String(value2));
|
|
210
|
+
}
|
|
211
|
+
const value = query.toString();
|
|
212
|
+
return value ? `${path}${path.includes("?") ? "&" : "?"}${value}` : path;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ../../internal/core/http/httpAdapter.ts
|
|
216
|
+
var FetchHttpAdapter = class {
|
|
217
|
+
constructor(fetchImpl = fetch.bind(globalThis)) {
|
|
218
|
+
this.fetchImpl = fetchImpl;
|
|
219
|
+
}
|
|
220
|
+
request(request) {
|
|
221
|
+
return this.fetchImpl(request.url, {
|
|
222
|
+
method: request.method,
|
|
223
|
+
headers: request.headers,
|
|
224
|
+
body: request.body,
|
|
225
|
+
signal: request.signal
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var LoggingHttpAdapter = class {
|
|
230
|
+
constructor(adapter, options) {
|
|
231
|
+
this.adapter = adapter;
|
|
232
|
+
this.options = options;
|
|
233
|
+
}
|
|
234
|
+
async request(request) {
|
|
235
|
+
const startedAt = Date.now();
|
|
236
|
+
const eventBase = {
|
|
237
|
+
method: request.method,
|
|
238
|
+
url: sanitizeUrl(request.url)
|
|
239
|
+
};
|
|
240
|
+
this.log({ type: "request", ...eventBase, timestamp: startedAt });
|
|
241
|
+
try {
|
|
242
|
+
const response = await this.adapter.request(request);
|
|
243
|
+
this.log({
|
|
244
|
+
type: "response",
|
|
245
|
+
...eventBase,
|
|
246
|
+
status: response.status,
|
|
247
|
+
durationMs: Date.now() - startedAt,
|
|
248
|
+
requestId: response.headers.get("x-request-id") ?? response.headers.get("trace-id") ?? void 0,
|
|
249
|
+
timestamp: Date.now()
|
|
250
|
+
});
|
|
251
|
+
return response;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
this.log({
|
|
254
|
+
type: "error",
|
|
255
|
+
...eventBase,
|
|
256
|
+
errorName: error instanceof Error ? error.name : "UnknownError",
|
|
257
|
+
durationMs: Date.now() - startedAt,
|
|
258
|
+
timestamp: Date.now()
|
|
259
|
+
});
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
log(event) {
|
|
264
|
+
if (!this.options.enabled) return;
|
|
265
|
+
const logger = this.options.logger ?? defaultHttpLogger;
|
|
266
|
+
try {
|
|
267
|
+
logger(event);
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
function defaultHttpLogger(event) {
|
|
273
|
+
console.debug("[Authyon HTTP]", event);
|
|
274
|
+
}
|
|
275
|
+
function sanitizeUrl(value) {
|
|
276
|
+
const url = new URL(value);
|
|
277
|
+
const queryKeys = /* @__PURE__ */ new Set();
|
|
278
|
+
url.searchParams.forEach((_value, key) => queryKeys.add(key));
|
|
279
|
+
url.search = queryKeys.size > 0 ? [...queryKeys].map((key) => `${encodeURIComponent(key)}=REDACTED`).join("&") : "";
|
|
280
|
+
return url.toString();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ../../internal/core/http/transport.ts
|
|
284
|
+
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
285
|
+
var SharedTransportError = class extends Error {
|
|
286
|
+
constructor(code, cause) {
|
|
287
|
+
super(code === "request.timeout" ? "Request timed out" : "Network request failed");
|
|
288
|
+
this.name = "SharedTransportError";
|
|
289
|
+
this.code = code;
|
|
290
|
+
this.cause = cause;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
function createSharedTransport(options) {
|
|
294
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl, options.allowInsecureHttp ?? false);
|
|
295
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
296
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
|
|
297
|
+
throw new Error("Authyon: `timeoutMs` must be a non-negative finite number");
|
|
298
|
+
}
|
|
299
|
+
if (options.httpAdapter && options.fetch) {
|
|
300
|
+
throw new Error("Authyon: use either `httpAdapter` or `fetch`, not both");
|
|
301
|
+
}
|
|
302
|
+
const baseAdapter = options.httpAdapter ?? new FetchHttpAdapter(options.fetch);
|
|
303
|
+
const httpAdapter = options.httpLogger ? new LoggingHttpAdapter(baseAdapter, options.httpLogger) : baseAdapter;
|
|
304
|
+
return {
|
|
305
|
+
baseUrl,
|
|
306
|
+
async request(path, init) {
|
|
307
|
+
const controller = new AbortController();
|
|
308
|
+
const timeout = timeoutMs > 0 ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
|
|
309
|
+
try {
|
|
310
|
+
return await httpAdapter.request({
|
|
311
|
+
url: `${baseUrl}${path}`,
|
|
312
|
+
method: init.method ?? "GET",
|
|
313
|
+
headers: normalizeHeaders(init.headers),
|
|
314
|
+
body: init.body,
|
|
315
|
+
signal: controller.signal
|
|
316
|
+
});
|
|
317
|
+
} catch (cause) {
|
|
318
|
+
throw new SharedTransportError(
|
|
319
|
+
controller.signal.aborted ? ErrorCodes.Timeout : ErrorCodes.NetworkError,
|
|
320
|
+
cause
|
|
321
|
+
);
|
|
322
|
+
} finally {
|
|
323
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function normalizeHeaders(headers) {
|
|
329
|
+
const normalized = {};
|
|
330
|
+
new Headers(headers).forEach((value, key) => {
|
|
331
|
+
normalized[key] = value;
|
|
332
|
+
});
|
|
333
|
+
return normalized;
|
|
334
|
+
}
|
|
335
|
+
function responseMetadata(response) {
|
|
336
|
+
const retryAfterHeader = response.headers.get("retry-after");
|
|
337
|
+
const retryAfter = retryAfterHeader ? Number(retryAfterHeader) : void 0;
|
|
338
|
+
return {
|
|
339
|
+
requestId: response.headers.get("x-request-id") ?? response.headers.get("trace-id") ?? void 0,
|
|
340
|
+
retryAfter: Number.isFinite(retryAfter) ? retryAfter : void 0
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function normalizeBaseUrl(value, allowInsecureHttp) {
|
|
344
|
+
let url;
|
|
345
|
+
try {
|
|
346
|
+
url = new URL(value);
|
|
347
|
+
} catch {
|
|
348
|
+
throw new Error("Authyon: `baseUrl` must be an absolute URL");
|
|
349
|
+
}
|
|
350
|
+
const loopback = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
351
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && (loopback || allowInsecureHttp))) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
"Authyon: `baseUrl` must use HTTPS (HTTP is allowed only for loopback or with `allowInsecureHttp`)"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
357
|
+
throw new Error(
|
|
358
|
+
"Authyon: `baseUrl` cannot contain credentials, query parameters, or fragments"
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
return url.toString().replace(/\/+$/, "");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ../../internal/core/http/jsonHttpClient.ts
|
|
365
|
+
var JsonHttpClient = class {
|
|
366
|
+
constructor(transport) {
|
|
367
|
+
this.transport = transport;
|
|
368
|
+
}
|
|
369
|
+
async send(path, options = {}) {
|
|
370
|
+
const headers = { ...options.headers };
|
|
371
|
+
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
372
|
+
try {
|
|
373
|
+
return await this.transport.request(appendQuery(path, options.query), {
|
|
374
|
+
method: options.method ?? "GET",
|
|
375
|
+
headers,
|
|
376
|
+
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
377
|
+
});
|
|
378
|
+
} catch (cause) {
|
|
379
|
+
if (!(cause instanceof SharedTransportError)) throw cause;
|
|
380
|
+
throw new AuthyonError(0, { code: cause.code, title: cause.message }, { cause: cause.cause });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async parse(response) {
|
|
384
|
+
if (!response.ok) {
|
|
385
|
+
let body = {};
|
|
386
|
+
try {
|
|
387
|
+
body = await response.json();
|
|
388
|
+
} catch {
|
|
389
|
+
}
|
|
390
|
+
throw new AuthyonError(response.status, body, responseMetadata(response));
|
|
391
|
+
}
|
|
392
|
+
if (response.status === 204) return void 0;
|
|
393
|
+
return await response.json();
|
|
394
|
+
}
|
|
395
|
+
async request(path, options = {}) {
|
|
396
|
+
return this.parse(await this.send(path, options));
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// src/client/authyonClient.ts
|
|
101
401
|
var FALLBACK_EXPIRES_IN = 1800;
|
|
102
402
|
function readTokens(raw) {
|
|
103
403
|
const tokens = raw.tokens ?? raw;
|
|
104
404
|
if (!tokens.accessToken || !tokens.refreshToken) {
|
|
105
405
|
throw new AuthyonError(502, {
|
|
106
|
-
code:
|
|
406
|
+
code: ErrorCodes.SessionMalformed,
|
|
107
407
|
title: "Malformed session response",
|
|
108
408
|
detail: "The session response carried no access/refresh token pair."
|
|
109
409
|
});
|
|
@@ -159,7 +459,7 @@ var AuthyonClient = class {
|
|
|
159
459
|
/** GET /auth/sessions — active refresh-token sessions with device/IP data. */
|
|
160
460
|
sessions: () => this.request("/auth/sessions", { bearer: true }),
|
|
161
461
|
/** GET /auth/me/activities — paginated recent account activity for the current user. */
|
|
162
|
-
activities: (params = {}) => this.request(
|
|
462
|
+
activities: (params = {}) => this.request(appendQuery("/auth/me/activities", params), { bearer: true }),
|
|
163
463
|
/**
|
|
164
464
|
* Revokes a single session by id (e.g. one entry from `sessions()`),
|
|
165
465
|
* signing that device out without affecting the current one.
|
|
@@ -215,11 +515,11 @@ var AuthyonClient = class {
|
|
|
215
515
|
/**
|
|
216
516
|
* GET /auth/tenants/{organizationId}/members — paginated list of an
|
|
217
517
|
* organization's members. Consistent with the confirmed-live
|
|
218
|
-
* `
|
|
518
|
+
* `Paged<T>` envelope every other `skip`/`take` endpoint returns
|
|
219
519
|
* (`user.activities()`, `@authyon/server`'s `environment.users.list()`).
|
|
220
520
|
*/
|
|
221
521
|
list: (organizationId, params = {}) => this.request(
|
|
222
|
-
`/auth/tenants/${encodeURIComponent(organizationId)}/members
|
|
522
|
+
appendQuery(`/auth/tenants/${encodeURIComponent(organizationId)}/members`, params),
|
|
223
523
|
{ bearer: true }
|
|
224
524
|
),
|
|
225
525
|
/** POST /auth/tenants/{organizationId}/members — invite a member by e-mail. */
|
|
@@ -312,10 +612,18 @@ var AuthyonClient = class {
|
|
|
312
612
|
if (!options.envKey)
|
|
313
613
|
throw new Error("Authyon: `envKey` is required (pk_live_... / pk_test_...)");
|
|
314
614
|
this.envKey = options.envKey;
|
|
315
|
-
this.
|
|
316
|
-
|
|
615
|
+
this.transport = createSharedTransport({
|
|
616
|
+
baseUrl: options.baseUrl ?? DEFAULT_BASE_URL,
|
|
617
|
+
allowInsecureHttp: options.allowInsecureHttp,
|
|
618
|
+
timeoutMs: options.timeoutMs,
|
|
619
|
+
httpAdapter: options.httpAdapter,
|
|
620
|
+
httpLogger: options.httpLogger,
|
|
621
|
+
fetch: options.fetch
|
|
622
|
+
});
|
|
623
|
+
this.baseUrl = this.transport.baseUrl;
|
|
624
|
+
this.http = new JsonHttpClient(this.transport);
|
|
625
|
+
this.storage = options.storage ?? createDefaultStorage();
|
|
317
626
|
this.autoRefresh = options.autoRefresh ?? true;
|
|
318
|
-
this.fetchImpl = options.fetch ?? fetch.bind(globalThis);
|
|
319
627
|
}
|
|
320
628
|
// ── Session state ────────────────────────────────────────────────────────
|
|
321
629
|
/** Current persisted session, or null when signed out. */
|
|
@@ -323,7 +631,13 @@ var AuthyonClient = class {
|
|
|
323
631
|
return this.storage.get();
|
|
324
632
|
}
|
|
325
633
|
isAuthenticated() {
|
|
326
|
-
return this.
|
|
634
|
+
return this.getAuthState() === "authenticated";
|
|
635
|
+
}
|
|
636
|
+
/** Synchronous snapshot of the locally available authentication state. */
|
|
637
|
+
getAuthState() {
|
|
638
|
+
const session = this.getSession();
|
|
639
|
+
if (!session) return "signed_out";
|
|
640
|
+
return Date.now() < session.expiresAt ? "authenticated" : "expired";
|
|
327
641
|
}
|
|
328
642
|
/**
|
|
329
643
|
* Returns a valid access token, refreshing it transparently when it is
|
|
@@ -332,15 +646,41 @@ var AuthyonClient = class {
|
|
|
332
646
|
async getAccessToken() {
|
|
333
647
|
const session = this.getSession();
|
|
334
648
|
if (!session) return null;
|
|
335
|
-
if (this.autoRefresh && Date.now() >= session.expiresAt -
|
|
649
|
+
if (this.autoRefresh && Date.now() >= session.expiresAt - DEFAULT_EXPIRY_SKEW_MS) {
|
|
336
650
|
try {
|
|
337
651
|
return (await this.refresh()).accessToken;
|
|
338
|
-
} catch {
|
|
339
|
-
|
|
652
|
+
} catch (error) {
|
|
653
|
+
if (error instanceof AuthyonError && (error.status === 401 || error.status === 403)) {
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
throw error;
|
|
340
657
|
}
|
|
341
658
|
}
|
|
342
659
|
return session.accessToken;
|
|
343
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Refreshes when needed, validates the server-side session through `GET /auth/me`,
|
|
663
|
+
* and stores the fresh user profile. Returns null when the session is no longer valid.
|
|
664
|
+
*/
|
|
665
|
+
async validateSession() {
|
|
666
|
+
const accessToken = await this.getAccessToken();
|
|
667
|
+
if (!accessToken) return null;
|
|
668
|
+
try {
|
|
669
|
+
const user = await this.user.me();
|
|
670
|
+
const current = this.getSession();
|
|
671
|
+
if (!current) return null;
|
|
672
|
+
const session = { ...current, user };
|
|
673
|
+
this.storage.set(session);
|
|
674
|
+
this.emit({ type: "session_validated", session });
|
|
675
|
+
return session;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
if (error instanceof AuthyonError && (error.status === 401 || error.status === 403)) {
|
|
678
|
+
this.clearSession();
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
throw error;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
344
684
|
/** Subscribe to sign-in / refresh / sign-out events. Returns an unsubscribe fn. */
|
|
345
685
|
onAuthStateChange(listener) {
|
|
346
686
|
this.listeners.add(listener);
|
|
@@ -386,38 +726,26 @@ var AuthyonClient = class {
|
|
|
386
726
|
"X-Authyon-Environment": this.envKey,
|
|
387
727
|
...options.headers
|
|
388
728
|
};
|
|
389
|
-
if (options.body !== void 0) headers["Content-Type"] = "application/json";
|
|
390
729
|
if (options.bearer) {
|
|
391
730
|
const token = await this.getAccessToken();
|
|
392
731
|
if (!token)
|
|
393
|
-
throw new AuthyonError(401, {
|
|
732
|
+
throw new AuthyonError(401, {
|
|
733
|
+
code: ErrorCodes.NotAuthenticated,
|
|
734
|
+
title: "Not authenticated"
|
|
735
|
+
});
|
|
394
736
|
headers.Authorization = `Bearer ${token}`;
|
|
395
737
|
}
|
|
396
|
-
const response = await this.
|
|
397
|
-
method: options.method ?? "GET",
|
|
398
|
-
headers,
|
|
399
|
-
body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
400
|
-
});
|
|
738
|
+
const response = await this.http.send(path, { ...options, headers });
|
|
401
739
|
if (response.status === 401 && options.bearer && this.autoRefresh && !isRetry && this.getSession()) {
|
|
402
740
|
try {
|
|
403
741
|
await this.refresh();
|
|
404
742
|
} catch {
|
|
405
743
|
this.clearSession();
|
|
406
|
-
|
|
744
|
+
return this.http.parse(response);
|
|
407
745
|
}
|
|
408
746
|
return this.request(path, options, true);
|
|
409
747
|
}
|
|
410
|
-
|
|
411
|
-
if (response.status === 204) return void 0;
|
|
412
|
-
return await response.json();
|
|
413
|
-
}
|
|
414
|
-
async toError(response) {
|
|
415
|
-
let body = {};
|
|
416
|
-
try {
|
|
417
|
-
body = await response.json();
|
|
418
|
-
} catch {
|
|
419
|
-
}
|
|
420
|
-
return new AuthyonError(response.status, body);
|
|
748
|
+
return this.http.parse(response);
|
|
421
749
|
}
|
|
422
750
|
// ── Auth flows ───────────────────────────────────────────────────────────
|
|
423
751
|
/** POST /auth/register — creates a new user (rate-limited to 20/hour per IP). */
|
|
@@ -453,7 +781,10 @@ var AuthyonClient = class {
|
|
|
453
781
|
if (this.refreshInFlight) return this.refreshInFlight;
|
|
454
782
|
const current = this.getSession();
|
|
455
783
|
if (!current)
|
|
456
|
-
throw new AuthyonError(401, {
|
|
784
|
+
throw new AuthyonError(401, {
|
|
785
|
+
code: ErrorCodes.NotAuthenticated,
|
|
786
|
+
title: "Not authenticated"
|
|
787
|
+
});
|
|
457
788
|
this.refreshInFlight = this.request("/auth/refresh", {
|
|
458
789
|
method: "POST",
|
|
459
790
|
body: { refreshToken: current.refreshToken }
|
|
@@ -503,6 +834,8 @@ var AuthyonClient = class {
|
|
|
503
834
|
* A browser app has no client secret to present, so this will fail from
|
|
504
835
|
* `@authyon/auth` in practice; call it from your backend via
|
|
505
836
|
* `@authyon/server` instead.
|
|
837
|
+
*
|
|
838
|
+
* @deprecated Use `@authyon/server.introspect()` from a trusted backend.
|
|
506
839
|
*/
|
|
507
840
|
async introspect(token) {
|
|
508
841
|
const accessToken = token ?? await this.getAccessToken();
|
|
@@ -512,6 +845,8 @@ var AuthyonClient = class {
|
|
|
512
845
|
* POST /auth/validate — recommended: cross-checks DB state, catches
|
|
513
846
|
* revocation immediately. Same caller-authentication requirement (and
|
|
514
847
|
* the same practical limitation from the browser) as `introspect()`.
|
|
848
|
+
*
|
|
849
|
+
* @deprecated Use `@authyon/server.validate()` from a trusted backend.
|
|
515
850
|
*/
|
|
516
851
|
async validate(token) {
|
|
517
852
|
const accessToken = token ?? await this.getAccessToken();
|
|
@@ -534,20 +869,352 @@ function normalizeUser(raw) {
|
|
|
534
869
|
function createClient(options) {
|
|
535
870
|
return new AuthyonClient(options);
|
|
536
871
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
872
|
+
|
|
873
|
+
// src/client/authyonClientBuilder.ts
|
|
874
|
+
var AuthyonClientBuilder = class {
|
|
875
|
+
constructor(envKey) {
|
|
876
|
+
this.options = { envKey };
|
|
877
|
+
}
|
|
878
|
+
withBaseUrl(baseUrl, allowInsecureHttp = false) {
|
|
879
|
+
this.options.baseUrl = baseUrl;
|
|
880
|
+
this.options.allowInsecureHttp = allowInsecureHttp;
|
|
881
|
+
return this;
|
|
882
|
+
}
|
|
883
|
+
withStorage(storage) {
|
|
884
|
+
this.options.storage = storage;
|
|
885
|
+
return this;
|
|
886
|
+
}
|
|
887
|
+
withAutomaticRefresh(enabled = true) {
|
|
888
|
+
this.options.autoRefresh = enabled;
|
|
889
|
+
return this;
|
|
890
|
+
}
|
|
891
|
+
withTimeout(timeoutMs) {
|
|
892
|
+
this.options.timeoutMs = timeoutMs;
|
|
893
|
+
return this;
|
|
894
|
+
}
|
|
895
|
+
withHttpAdapter(httpAdapter) {
|
|
896
|
+
this.options.httpAdapter = httpAdapter;
|
|
897
|
+
return this;
|
|
898
|
+
}
|
|
899
|
+
withHttpLogger(httpLogger) {
|
|
900
|
+
this.options.httpLogger = httpLogger;
|
|
901
|
+
return this;
|
|
902
|
+
}
|
|
903
|
+
build() {
|
|
904
|
+
return new AuthyonClient({ ...this.options });
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/session/sessionController.ts
|
|
909
|
+
var SERVER_SNAPSHOT = {
|
|
910
|
+
status: "unauthenticated",
|
|
911
|
+
session: null,
|
|
912
|
+
user: null,
|
|
913
|
+
error: null
|
|
914
|
+
};
|
|
915
|
+
var AuthyonSessionController = class {
|
|
916
|
+
constructor(client, options = {}) {
|
|
917
|
+
this.client = client;
|
|
918
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
919
|
+
this.getSnapshot = () => this.snapshot;
|
|
920
|
+
this.getServerSnapshot = () => SERVER_SNAPSHOT;
|
|
921
|
+
this.subscribe = (listener) => {
|
|
922
|
+
this.listeners.add(listener);
|
|
923
|
+
return () => this.listeners.delete(listener);
|
|
924
|
+
};
|
|
925
|
+
this.refreshAheadMs = options.refreshAheadMs ?? 3e4;
|
|
926
|
+
if (!Number.isFinite(this.refreshAheadMs) || this.refreshAheadMs < 0) {
|
|
927
|
+
throw new Error("Authyon: `refreshAheadMs` must be a non-negative finite number");
|
|
928
|
+
}
|
|
929
|
+
const session = client.getSession();
|
|
930
|
+
this.snapshot = {
|
|
931
|
+
status: session ? "validating" : "unauthenticated",
|
|
932
|
+
session,
|
|
933
|
+
user: session?.user ?? null,
|
|
934
|
+
error: null
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
start() {
|
|
938
|
+
if (!this.unsubscribeAuth) {
|
|
939
|
+
this.unsubscribeAuth = this.client.onAuthStateChange((event) => {
|
|
940
|
+
if (event.type === "signed_out") {
|
|
941
|
+
this.cancelRefresh();
|
|
942
|
+
this.setSnapshot({
|
|
943
|
+
status: "unauthenticated",
|
|
944
|
+
session: null,
|
|
945
|
+
user: null,
|
|
946
|
+
error: null
|
|
947
|
+
});
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
if (event.type === "session_validated") {
|
|
951
|
+
this.acceptSession(event.session);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
this.setSnapshot({
|
|
955
|
+
status: "validating",
|
|
956
|
+
session: event.session,
|
|
957
|
+
user: event.session.user ?? null,
|
|
958
|
+
error: null
|
|
959
|
+
});
|
|
960
|
+
void this.validate();
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
void this.validate();
|
|
964
|
+
return () => this.stop();
|
|
965
|
+
}
|
|
966
|
+
stop() {
|
|
967
|
+
this.unsubscribeAuth?.();
|
|
968
|
+
this.unsubscribeAuth = void 0;
|
|
969
|
+
this.cancelRefresh();
|
|
970
|
+
}
|
|
971
|
+
validate() {
|
|
972
|
+
if (this.validation) return this.validation;
|
|
973
|
+
const localSession = this.client.getSession();
|
|
974
|
+
if (!localSession) {
|
|
975
|
+
this.setSnapshot({
|
|
976
|
+
status: "unauthenticated",
|
|
977
|
+
session: null,
|
|
978
|
+
user: null,
|
|
979
|
+
error: null
|
|
980
|
+
});
|
|
981
|
+
return Promise.resolve(this.snapshot);
|
|
982
|
+
}
|
|
983
|
+
this.setSnapshot({ ...this.snapshot, status: "validating", error: null });
|
|
984
|
+
this.validation = this.client.validateSession().then((session) => {
|
|
985
|
+
if (session) this.acceptSession(session);
|
|
986
|
+
else {
|
|
987
|
+
this.setSnapshot({
|
|
988
|
+
status: "unauthenticated",
|
|
989
|
+
session: null,
|
|
990
|
+
user: null,
|
|
991
|
+
error: null
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
return this.snapshot;
|
|
995
|
+
}).catch((error) => {
|
|
996
|
+
this.setSnapshot({ ...this.snapshot, status: "error", error });
|
|
997
|
+
return this.snapshot;
|
|
998
|
+
}).finally(() => {
|
|
999
|
+
this.validation = void 0;
|
|
1000
|
+
});
|
|
1001
|
+
return this.validation;
|
|
1002
|
+
}
|
|
1003
|
+
async refreshNow() {
|
|
1004
|
+
if (!this.client.getSession()) return this.validate();
|
|
1005
|
+
try {
|
|
1006
|
+
await this.client.refresh();
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
if (!this.client.getSession()) return this.validate();
|
|
1009
|
+
this.setSnapshot({ ...this.snapshot, status: "error", error });
|
|
1010
|
+
return this.snapshot;
|
|
1011
|
+
}
|
|
1012
|
+
return this.validate();
|
|
1013
|
+
}
|
|
1014
|
+
acceptSession(session) {
|
|
1015
|
+
this.setSnapshot({
|
|
1016
|
+
status: "authenticated",
|
|
1017
|
+
session,
|
|
1018
|
+
user: session.user ?? null,
|
|
1019
|
+
error: null
|
|
1020
|
+
});
|
|
1021
|
+
this.scheduleRefresh(session);
|
|
1022
|
+
}
|
|
1023
|
+
scheduleRefresh(session) {
|
|
1024
|
+
this.cancelRefresh();
|
|
1025
|
+
const delay = Math.max(1e3, session.expiresAt - Date.now() - this.refreshAheadMs);
|
|
1026
|
+
this.refreshTimer = setTimeout(() => void this.refreshNow(), delay);
|
|
1027
|
+
}
|
|
1028
|
+
cancelRefresh() {
|
|
1029
|
+
if (this.refreshTimer !== void 0) clearTimeout(this.refreshTimer);
|
|
1030
|
+
this.refreshTimer = void 0;
|
|
1031
|
+
}
|
|
1032
|
+
setSnapshot(snapshot) {
|
|
1033
|
+
this.snapshot = snapshot;
|
|
1034
|
+
for (const listener of this.listeners) listener();
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
// ../../internal/core/authorization/ability.ts
|
|
1039
|
+
var AuthyonAbility = class {
|
|
1040
|
+
constructor(rules = [], detectSubjectType = defaultSubjectType) {
|
|
1041
|
+
this.detectSubjectType = detectSubjectType;
|
|
1042
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1043
|
+
this.currentRules = rules.map(cloneRule);
|
|
1044
|
+
}
|
|
1045
|
+
get rules() {
|
|
1046
|
+
return this.currentRules;
|
|
1047
|
+
}
|
|
1048
|
+
can(action, subject, field) {
|
|
1049
|
+
const subjectType = typeof subject === "string" ? subject : this.detectSubjectType(subject);
|
|
1050
|
+
for (let index = this.currentRules.length - 1; index >= 0; index -= 1) {
|
|
1051
|
+
const rule = this.currentRules[index];
|
|
1052
|
+
if (!matchesToken(rule.action, action, "manage")) continue;
|
|
1053
|
+
if (!matchesToken(rule.subject, subjectType, "all")) continue;
|
|
1054
|
+
if (field && rule.fields && !rule.fields.some((value) => matchesField(value, field)))
|
|
1055
|
+
continue;
|
|
1056
|
+
if (rule.conditions) {
|
|
1057
|
+
if (typeof subject === "string" || !matchesConditions(subject, rule.conditions)) continue;
|
|
1058
|
+
}
|
|
1059
|
+
return !rule.inverted;
|
|
1060
|
+
}
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
cannot(action, subject, field) {
|
|
1064
|
+
return !this.can(action, subject, field);
|
|
1065
|
+
}
|
|
1066
|
+
rulesFor(action, subject) {
|
|
1067
|
+
return this.currentRules.filter(
|
|
1068
|
+
(rule) => matchesToken(rule.action, action, "manage") && matchesToken(rule.subject, subject, "all")
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
update(rules) {
|
|
1072
|
+
this.currentRules = rules.map(cloneRule);
|
|
1073
|
+
for (const listener of this.listeners) listener(this.rules);
|
|
1074
|
+
}
|
|
1075
|
+
on(event, listener) {
|
|
1076
|
+
if (event !== "updated") return () => void 0;
|
|
1077
|
+
this.listeners.add(listener);
|
|
1078
|
+
return () => this.listeners.delete(listener);
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
var AuthyonAbilityBuilder = class {
|
|
1082
|
+
constructor() {
|
|
1083
|
+
this.rules = [];
|
|
1084
|
+
}
|
|
1085
|
+
can(action, subject, conditions, fields) {
|
|
1086
|
+
this.rules.push({ action, subject, conditions, fields });
|
|
1087
|
+
return this;
|
|
541
1088
|
}
|
|
542
|
-
|
|
1089
|
+
cannot(action, subject, conditions, fields, reason) {
|
|
1090
|
+
this.rules.push({ action, subject, conditions, fields, reason, inverted: true });
|
|
1091
|
+
return this;
|
|
1092
|
+
}
|
|
1093
|
+
build(options = {}) {
|
|
1094
|
+
return new AuthyonAbility(this.rules, options.detectSubjectType);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
function createAuthyonAbility(source = {}, options = {}) {
|
|
1098
|
+
return new AuthyonAbility(createAuthyonRules(source, options), options.detectSubjectType);
|
|
1099
|
+
}
|
|
1100
|
+
function createAuthyonRules(source = {}, options = {}) {
|
|
1101
|
+
const permissions = /* @__PURE__ */ new Set([
|
|
1102
|
+
...source.permissions ?? [],
|
|
1103
|
+
...source.scope?.split(/\s+/).filter(Boolean) ?? []
|
|
1104
|
+
]);
|
|
1105
|
+
const rules = [...permissions].map(permissionToRule).filter(isAbilityRule);
|
|
1106
|
+
for (const role of /* @__PURE__ */ new Set([...source.roles ?? [], ...options.roles ?? []])) {
|
|
1107
|
+
rules.push(...(options.roleRules?.[role] ?? []).map(cloneRule));
|
|
1108
|
+
}
|
|
1109
|
+
rules.push(...(options.rules ?? []).map(cloneRule));
|
|
1110
|
+
return rules;
|
|
1111
|
+
}
|
|
1112
|
+
function permissionToRule(permission) {
|
|
1113
|
+
const normalized = permission.trim();
|
|
1114
|
+
if (!normalized) return null;
|
|
1115
|
+
if (normalized === "*" || normalized === "*:*" || normalized === "all:manage") {
|
|
1116
|
+
return { action: "manage", subject: "all" };
|
|
1117
|
+
}
|
|
1118
|
+
const separator = normalized.lastIndexOf(":");
|
|
1119
|
+
if (separator <= 0 || separator === normalized.length - 1) return null;
|
|
1120
|
+
const subject = normalized.slice(0, separator);
|
|
1121
|
+
const action = normalized.slice(separator + 1);
|
|
1122
|
+
return {
|
|
1123
|
+
action: action === "*" ? "manage" : action,
|
|
1124
|
+
subject: subject === "*" ? "all" : subject
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
function matchesToken(value, expected, wildcard) {
|
|
1128
|
+
return (Array.isArray(value) ? value : [value]).some(
|
|
1129
|
+
(candidate) => candidate === expected || candidate === wildcard || candidate === "*"
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
function matchesField(pattern, field) {
|
|
1133
|
+
if (pattern === "*" || pattern === field) return true;
|
|
1134
|
+
return pattern.endsWith(".*") && field.startsWith(pattern.slice(0, -1));
|
|
1135
|
+
}
|
|
1136
|
+
function matchesConditions(subject, conditions) {
|
|
1137
|
+
return Object.entries(conditions).every(([path, expected]) => {
|
|
1138
|
+
if (path === "$and" && Array.isArray(expected)) {
|
|
1139
|
+
return expected.every(
|
|
1140
|
+
(condition) => matchesConditions(subject, condition)
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
if (path === "$or" && Array.isArray(expected)) {
|
|
1144
|
+
return expected.some(
|
|
1145
|
+
(condition) => matchesConditions(subject, condition)
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
return matchesValue(readPath(subject, path), expected);
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
function matchesValue(actual, expected) {
|
|
1152
|
+
if (!isRecord(expected) || !Object.keys(expected).some((key) => key.startsWith("$"))) {
|
|
1153
|
+
return isRecord(expected) && isRecord(actual) ? matchesConditions(actual, expected) : Object.is(actual, expected);
|
|
1154
|
+
}
|
|
1155
|
+
return Object.entries(expected).every(([operator, operand]) => {
|
|
1156
|
+
switch (operator) {
|
|
1157
|
+
case "$eq":
|
|
1158
|
+
return Object.is(actual, operand);
|
|
1159
|
+
case "$ne":
|
|
1160
|
+
return !Object.is(actual, operand);
|
|
1161
|
+
case "$in":
|
|
1162
|
+
return Array.isArray(operand) && operand.some((value) => Object.is(actual, value));
|
|
1163
|
+
case "$nin":
|
|
1164
|
+
return Array.isArray(operand) && !operand.some((value) => Object.is(actual, value));
|
|
1165
|
+
case "$gt":
|
|
1166
|
+
return typeof actual === "number" && typeof operand === "number" && actual > operand;
|
|
1167
|
+
case "$gte":
|
|
1168
|
+
return typeof actual === "number" && typeof operand === "number" && actual >= operand;
|
|
1169
|
+
case "$lt":
|
|
1170
|
+
return typeof actual === "number" && typeof operand === "number" && actual < operand;
|
|
1171
|
+
case "$lte":
|
|
1172
|
+
return typeof actual === "number" && typeof operand === "number" && actual <= operand;
|
|
1173
|
+
case "$exists":
|
|
1174
|
+
return operand ? actual !== void 0 : actual === void 0;
|
|
1175
|
+
default:
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
function readPath(value, path) {
|
|
1181
|
+
return path.split(".").reduce((current, part) => isRecord(current) ? current[part] : void 0, value);
|
|
1182
|
+
}
|
|
1183
|
+
function defaultSubjectType(subject) {
|
|
1184
|
+
const explicit = subject.__type ?? subject.type ?? subject.kind;
|
|
1185
|
+
if (typeof explicit === "string") return explicit;
|
|
1186
|
+
const constructorName = subject.constructor?.name;
|
|
1187
|
+
return typeof constructorName === "string" ? constructorName : "Object";
|
|
1188
|
+
}
|
|
1189
|
+
function cloneRule(rule) {
|
|
1190
|
+
return {
|
|
1191
|
+
...rule,
|
|
1192
|
+
action: Array.isArray(rule.action) ? [...rule.action] : rule.action,
|
|
1193
|
+
subject: Array.isArray(rule.subject) ? [...rule.subject] : rule.subject,
|
|
1194
|
+
fields: rule.fields ? [...rule.fields] : void 0
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
function isRecord(value) {
|
|
1198
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1199
|
+
}
|
|
1200
|
+
function isAbilityRule(value) {
|
|
1201
|
+
return value !== null;
|
|
543
1202
|
}
|
|
544
1203
|
// Annotate the CommonJS export names for ESM import in node:
|
|
545
1204
|
0 && (module.exports = {
|
|
1205
|
+
AuthyonAbility,
|
|
1206
|
+
AuthyonAbilityBuilder,
|
|
546
1207
|
AuthyonClient,
|
|
1208
|
+
AuthyonClientBuilder,
|
|
547
1209
|
AuthyonError,
|
|
1210
|
+
AuthyonSessionController,
|
|
548
1211
|
ErrorCodes,
|
|
1212
|
+
FetchHttpAdapter,
|
|
1213
|
+
LoggingHttpAdapter,
|
|
1214
|
+
createAuthyonAbility,
|
|
1215
|
+
createAuthyonRules,
|
|
549
1216
|
createClient,
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
1217
|
+
createDefaultStorage,
|
|
1218
|
+
createLocalStorage,
|
|
1219
|
+
createMemoryStorage
|
|
553
1220
|
});
|