@aurabase/core 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/dist/index.cjs +183 -92
- package/dist/index.d.cts +609 -72
- package/dist/index.d.ts +609 -72
- package/dist/index.js +182 -93
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,70 @@ function isAuraError(error) {
|
|
|
31
31
|
return error instanceof AuraError || typeof error === "object" && error !== null && error.__isAuraError === true;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// src/retry.ts
|
|
35
|
+
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
36
|
+
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
37
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
38
|
+
var BASE_MS = 500;
|
|
39
|
+
var CAP_MS = 1e4;
|
|
40
|
+
function isRetryableStatus(status) {
|
|
41
|
+
return RETRYABLE_STATUS.has(status);
|
|
42
|
+
}
|
|
43
|
+
function isRetryableMethod(method) {
|
|
44
|
+
return RETRYABLE_METHODS.has(method.toUpperCase());
|
|
45
|
+
}
|
|
46
|
+
function computeBackoff(attempt, rand = Math.random) {
|
|
47
|
+
const bound = Math.min(BASE_MS * 2 ** attempt, CAP_MS);
|
|
48
|
+
return Math.floor(rand() * bound);
|
|
49
|
+
}
|
|
50
|
+
function parseRetryAfter(header, now = Date.now()) {
|
|
51
|
+
if (header === null) return null;
|
|
52
|
+
const trimmed = header.trim();
|
|
53
|
+
if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10) * 1e3;
|
|
54
|
+
const dateMs = Date.parse(trimmed);
|
|
55
|
+
if (Number.isNaN(dateMs)) return null;
|
|
56
|
+
return Math.max(0, dateMs - now);
|
|
57
|
+
}
|
|
58
|
+
function abortableSleep(ms, signal) {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
if (signal?.aborted) {
|
|
61
|
+
resolve();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const onAbort = () => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
resolve();
|
|
67
|
+
};
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
signal?.removeEventListener("abort", onAbort);
|
|
70
|
+
resolve();
|
|
71
|
+
}, ms);
|
|
72
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function isAbortError(err) {
|
|
76
|
+
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/scope.ts
|
|
80
|
+
function clientScope(baseUrl) {
|
|
81
|
+
let host;
|
|
82
|
+
try {
|
|
83
|
+
host = new URL(baseUrl).host;
|
|
84
|
+
} catch {
|
|
85
|
+
host = baseUrl;
|
|
86
|
+
}
|
|
87
|
+
return host.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/serialize.ts
|
|
91
|
+
function serializeBody(body) {
|
|
92
|
+
return JSON.stringify(
|
|
93
|
+
body,
|
|
94
|
+
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
34
98
|
// src/types.ts
|
|
35
99
|
function redactUrl(u) {
|
|
36
100
|
const cut = u.search(/[?#]/);
|
|
@@ -50,6 +114,60 @@ function emitAuraEvent(onEvent, debug, event) {
|
|
|
50
114
|
} catch {
|
|
51
115
|
}
|
|
52
116
|
}
|
|
117
|
+
var AUTH_SETTINGS_WRITABLE_FIELDS = [
|
|
118
|
+
"allow_signup",
|
|
119
|
+
"email_confirmation_required",
|
|
120
|
+
"password_min_length",
|
|
121
|
+
"mfa_enabled",
|
|
122
|
+
"allow_anonymous_users",
|
|
123
|
+
"jwt_expiry_seconds",
|
|
124
|
+
"refresh_token_expiry_days",
|
|
125
|
+
"site_url",
|
|
126
|
+
"oauth_providers",
|
|
127
|
+
"new_device_alerts_enabled",
|
|
128
|
+
"webhook_url",
|
|
129
|
+
"webhook_secret",
|
|
130
|
+
"smtp_host",
|
|
131
|
+
"smtp_port",
|
|
132
|
+
"smtp_username",
|
|
133
|
+
"smtp_password",
|
|
134
|
+
"smtp_from_email",
|
|
135
|
+
"smtp_from_name",
|
|
136
|
+
"smtp_clear",
|
|
137
|
+
"oauth_credentials",
|
|
138
|
+
"oauth_credentials_clear",
|
|
139
|
+
"captcha_enabled",
|
|
140
|
+
"captcha_provider",
|
|
141
|
+
"captcha_site_key",
|
|
142
|
+
"captcha_secret_key",
|
|
143
|
+
"password_check_hibp",
|
|
144
|
+
"sms_provider",
|
|
145
|
+
"sms_api_key",
|
|
146
|
+
"sms_api_secret",
|
|
147
|
+
"sms_from_number",
|
|
148
|
+
"sms_clear",
|
|
149
|
+
"redirect_urls",
|
|
150
|
+
"session_inactivity_timeout_seconds",
|
|
151
|
+
"hook_send_sms_url",
|
|
152
|
+
"hook_send_email_url",
|
|
153
|
+
"hook_mfa_verification_url",
|
|
154
|
+
"hook_password_verification_url",
|
|
155
|
+
"auto_link_by_email",
|
|
156
|
+
"rate_limit_login_per_hour",
|
|
157
|
+
"rate_limit_emails_per_hour",
|
|
158
|
+
"rate_limit_signup_per_hour",
|
|
159
|
+
"revoke_on_new_device",
|
|
160
|
+
"enforce_ip_binding",
|
|
161
|
+
"allowed_email_domains",
|
|
162
|
+
"blocked_email_domains",
|
|
163
|
+
"password_require_uppercase",
|
|
164
|
+
"password_require_number",
|
|
165
|
+
"password_require_special",
|
|
166
|
+
"hook_before_signup_url",
|
|
167
|
+
"hook_after_signup_url",
|
|
168
|
+
"hook_custom_access_token_url",
|
|
169
|
+
"hook_secret"
|
|
170
|
+
];
|
|
53
171
|
|
|
54
172
|
// src/stores/BaseAuthStore.ts
|
|
55
173
|
function decodeJwtPayload(token) {
|
|
@@ -170,59 +288,6 @@ var BaseAuthStore = class {
|
|
|
170
288
|
}
|
|
171
289
|
};
|
|
172
290
|
|
|
173
|
-
// src/retry.ts
|
|
174
|
-
var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
175
|
-
var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
|
|
176
|
-
var DEFAULT_MAX_RETRIES = 3;
|
|
177
|
-
var BASE_MS = 500;
|
|
178
|
-
var CAP_MS = 1e4;
|
|
179
|
-
function isRetryableStatus(status) {
|
|
180
|
-
return RETRYABLE_STATUS.has(status);
|
|
181
|
-
}
|
|
182
|
-
function isRetryableMethod(method) {
|
|
183
|
-
return RETRYABLE_METHODS.has(method.toUpperCase());
|
|
184
|
-
}
|
|
185
|
-
function computeBackoff(attempt, rand = Math.random) {
|
|
186
|
-
const bound = Math.min(BASE_MS * 2 ** attempt, CAP_MS);
|
|
187
|
-
return Math.floor(rand() * bound);
|
|
188
|
-
}
|
|
189
|
-
function parseRetryAfter(header, now = Date.now()) {
|
|
190
|
-
if (header === null) return null;
|
|
191
|
-
const trimmed = header.trim();
|
|
192
|
-
if (/^\d+$/.test(trimmed)) return parseInt(trimmed, 10) * 1e3;
|
|
193
|
-
const dateMs = Date.parse(trimmed);
|
|
194
|
-
if (Number.isNaN(dateMs)) return null;
|
|
195
|
-
return Math.max(0, dateMs - now);
|
|
196
|
-
}
|
|
197
|
-
function abortableSleep(ms, signal) {
|
|
198
|
-
return new Promise((resolve) => {
|
|
199
|
-
if (signal?.aborted) {
|
|
200
|
-
resolve();
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
const onAbort = () => {
|
|
204
|
-
clearTimeout(timer);
|
|
205
|
-
resolve();
|
|
206
|
-
};
|
|
207
|
-
const timer = setTimeout(() => {
|
|
208
|
-
signal?.removeEventListener("abort", onAbort);
|
|
209
|
-
resolve();
|
|
210
|
-
}, ms);
|
|
211
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
function isAbortError(err) {
|
|
215
|
-
return typeof err === "object" && err !== null && err.name === "AbortError";
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/serialize.ts
|
|
219
|
-
function serializeBody(body) {
|
|
220
|
-
return JSON.stringify(
|
|
221
|
-
body,
|
|
222
|
-
(_key, value) => typeof value === "bigint" ? value.toString() : value
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
291
|
// src/fetch.ts
|
|
227
292
|
var REFRESH_MARGIN_SEC = 30;
|
|
228
293
|
var RETRY_AFTER_CAP_MS = 6e4;
|
|
@@ -290,7 +355,9 @@ async function doRefresh(client) {
|
|
|
290
355
|
if (!rt) {
|
|
291
356
|
return {
|
|
292
357
|
data: null,
|
|
293
|
-
error: new AuraError(0, {
|
|
358
|
+
error: new AuraError(0, {
|
|
359
|
+
error: { code: "no_session", message: "No refresh token available" }
|
|
360
|
+
})
|
|
294
361
|
};
|
|
295
362
|
}
|
|
296
363
|
const epoch = store.removalEpoch;
|
|
@@ -317,7 +384,7 @@ async function doRefresh(client) {
|
|
|
317
384
|
};
|
|
318
385
|
let res;
|
|
319
386
|
try {
|
|
320
|
-
res = await client.fetchFn(`${client.baseUrl}/v1/auth
|
|
387
|
+
res = await client.fetchFn(`${client.baseUrl}/v1/auth/refresh`, {
|
|
321
388
|
method: "POST",
|
|
322
389
|
headers,
|
|
323
390
|
body: serializeBody({ refresh_token: rt }),
|
|
@@ -328,13 +395,16 @@ async function doRefresh(client) {
|
|
|
328
395
|
if (timedOut) {
|
|
329
396
|
emitAuraEvent(client.onEvent, client.debug, {
|
|
330
397
|
kind: "timeout",
|
|
331
|
-
url: `${client.baseUrl}/v1/auth
|
|
398
|
+
url: `${client.baseUrl}/v1/auth/refresh`
|
|
332
399
|
});
|
|
333
400
|
}
|
|
334
401
|
return {
|
|
335
402
|
data: null,
|
|
336
403
|
error: new AuraError(0, {
|
|
337
|
-
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
404
|
+
error: timedOut ? { code: "timeout", message: `Refresh timed out after ${timeoutMs}ms` } : {
|
|
405
|
+
code: "network_error",
|
|
406
|
+
message: err instanceof Error ? err.message : "Network request failed"
|
|
407
|
+
}
|
|
338
408
|
})
|
|
339
409
|
};
|
|
340
410
|
}
|
|
@@ -363,7 +433,7 @@ async function doRefresh(client) {
|
|
|
363
433
|
if (okRead.timedOut) {
|
|
364
434
|
emitAuraEvent(client.onEvent, client.debug, {
|
|
365
435
|
kind: "timeout",
|
|
366
|
-
url: `${client.baseUrl}/v1/auth
|
|
436
|
+
url: `${client.baseUrl}/v1/auth/refresh`
|
|
367
437
|
});
|
|
368
438
|
}
|
|
369
439
|
return {
|
|
@@ -378,7 +448,9 @@ async function doRefresh(client) {
|
|
|
378
448
|
} catch {
|
|
379
449
|
return {
|
|
380
450
|
data: null,
|
|
381
|
-
error: new AuraError(0, {
|
|
451
|
+
error: new AuraError(0, {
|
|
452
|
+
error: { code: "invalid_response", message: "Invalid refresh response (non-JSON body)" }
|
|
453
|
+
})
|
|
382
454
|
};
|
|
383
455
|
}
|
|
384
456
|
const data = unwrapEnvelope(body);
|
|
@@ -390,7 +462,9 @@ async function doRefresh(client) {
|
|
|
390
462
|
});
|
|
391
463
|
return {
|
|
392
464
|
data: null,
|
|
393
|
-
error: new AuraError(409, {
|
|
465
|
+
error: new AuraError(409, {
|
|
466
|
+
error: { code: "refresh_discarded", message: "Session changed during refresh" }
|
|
467
|
+
})
|
|
394
468
|
};
|
|
395
469
|
}
|
|
396
470
|
store.save(data.access_token, data.refresh_token, data.user);
|
|
@@ -399,7 +473,9 @@ async function doRefresh(client) {
|
|
|
399
473
|
}
|
|
400
474
|
return {
|
|
401
475
|
data: null,
|
|
402
|
-
error: new AuraError(0, {
|
|
476
|
+
error: new AuraError(0, {
|
|
477
|
+
error: { code: "invalid_response", message: "Invalid refresh response" }
|
|
478
|
+
})
|
|
403
479
|
};
|
|
404
480
|
}
|
|
405
481
|
function withRefreshLock(client, fn) {
|
|
@@ -410,7 +486,7 @@ function withRefreshLock(client, fn) {
|
|
|
410
486
|
return fn();
|
|
411
487
|
}
|
|
412
488
|
const request = locks.request;
|
|
413
|
-
const lockName = `aura-refresh-${client.
|
|
489
|
+
const lockName = `aura-refresh-${clientScope(client.baseUrl)}`;
|
|
414
490
|
const timeoutMs = (client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS) || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
415
491
|
let timer;
|
|
416
492
|
const options = {};
|
|
@@ -526,7 +602,10 @@ async function fetchWithAuth(url, init, client) {
|
|
|
526
602
|
return {
|
|
527
603
|
data: null,
|
|
528
604
|
error: new AuraError(0, {
|
|
529
|
-
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
605
|
+
error: timedOut ? { code: "timeout", message: `Request timed out after ${timeoutMs}ms` } : {
|
|
606
|
+
code: "network_error",
|
|
607
|
+
message: err instanceof Error ? err.message : "Network request failed"
|
|
608
|
+
}
|
|
530
609
|
})
|
|
531
610
|
};
|
|
532
611
|
}
|
|
@@ -539,7 +618,12 @@ async function fetchWithAuth(url, init, client) {
|
|
|
539
618
|
await res.body?.cancel().catch(() => {
|
|
540
619
|
});
|
|
541
620
|
const nextAttempt = attempt + 1;
|
|
542
|
-
emitAuraEvent(client.onEvent, client.debug, {
|
|
621
|
+
emitAuraEvent(client.onEvent, client.debug, {
|
|
622
|
+
kind: "retry",
|
|
623
|
+
attempt: nextAttempt,
|
|
624
|
+
status: res.status,
|
|
625
|
+
url
|
|
626
|
+
});
|
|
543
627
|
await abortableSleep(delay, init.signal);
|
|
544
628
|
attempt = nextAttempt;
|
|
545
629
|
continue;
|
|
@@ -645,12 +729,40 @@ var BaseService = class {
|
|
|
645
729
|
}
|
|
646
730
|
};
|
|
647
731
|
|
|
732
|
+
// src/cookie-utils.ts
|
|
733
|
+
function parseCookieHeader(header) {
|
|
734
|
+
const result = {};
|
|
735
|
+
if (!header) return result;
|
|
736
|
+
for (const pair of header.split(";")) {
|
|
737
|
+
const idx = pair.indexOf("=");
|
|
738
|
+
if (idx === -1) continue;
|
|
739
|
+
const key = pair.slice(0, idx).trim();
|
|
740
|
+
const value = pair.slice(idx + 1).trim();
|
|
741
|
+
if (key) result[key] = decodeURIComponent(value);
|
|
742
|
+
}
|
|
743
|
+
return result;
|
|
744
|
+
}
|
|
745
|
+
function serializeCookieHeader(name, value, options = {}) {
|
|
746
|
+
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
747
|
+
if (options.path) parts.push(`Path=${options.path}`);
|
|
748
|
+
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
749
|
+
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
750
|
+
if (options.secure) parts.push("Secure");
|
|
751
|
+
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
752
|
+
if (options.httpOnly) parts.push("HttpOnly");
|
|
753
|
+
return parts.join("; ");
|
|
754
|
+
}
|
|
755
|
+
|
|
648
756
|
// src/stores/LocalAuthStore.ts
|
|
649
757
|
var LocalAuthStore = class extends BaseAuthStore {
|
|
650
758
|
/** Persiste en localStorage → survit au redirect pleine page (cf. PKCE). */
|
|
651
759
|
persistent = true;
|
|
652
760
|
key;
|
|
653
|
-
|
|
761
|
+
/**
|
|
762
|
+
* @param key Nom de l'entrée `localStorage` (et du cookie SSR). Scopez-le par projet :
|
|
763
|
+
* `aura-${clientScope(baseUrl)}-auth`.
|
|
764
|
+
*/
|
|
765
|
+
constructor(key) {
|
|
654
766
|
super();
|
|
655
767
|
this.key = key;
|
|
656
768
|
this._loadFromStorage();
|
|
@@ -667,11 +779,12 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
667
779
|
}
|
|
668
780
|
/**
|
|
669
781
|
* Supprime de localStorage tous les artefacts d'auth éphémères sur disque :
|
|
670
|
-
* - `code_verifier` PKCE (`aura-<
|
|
671
|
-
* - marqueur d'initiation OAuth implicit (`aura-<
|
|
782
|
+
* - `code_verifier` PKCE (`aura-<portée>-pkce-code-verifier`),
|
|
783
|
+
* - marqueur d'initiation OAuth implicit (`aura-<portée>-oauth-implicit-initiated`,
|
|
672
784
|
* SEC-F3). Un `signOut`/`clear()` ne doit laisser aucun résidu réutilisable (parité
|
|
673
785
|
* `@supabase/ssr` : ces artefacts sont éphémères). On itère les clés préfixées pour
|
|
674
|
-
* couvrir un store partagé entre plusieurs projets, sans dépendre
|
|
786
|
+
* couvrir un store partagé entre plusieurs projets, sans dépendre de la portée courante
|
|
787
|
+
* (`clientScope`, dérivée de l'URL du projet).
|
|
675
788
|
*/
|
|
676
789
|
_removePkceVerifiers() {
|
|
677
790
|
if (typeof localStorage === "undefined") return;
|
|
@@ -792,28 +905,4 @@ var LocalAuthStore = class extends BaseAuthStore {
|
|
|
792
905
|
}
|
|
793
906
|
};
|
|
794
907
|
|
|
795
|
-
|
|
796
|
-
function parseCookieHeader(header) {
|
|
797
|
-
const result = {};
|
|
798
|
-
if (!header) return result;
|
|
799
|
-
for (const pair of header.split(";")) {
|
|
800
|
-
const idx = pair.indexOf("=");
|
|
801
|
-
if (idx === -1) continue;
|
|
802
|
-
const key = pair.slice(0, idx).trim();
|
|
803
|
-
const value = pair.slice(idx + 1).trim();
|
|
804
|
-
if (key) result[key] = decodeURIComponent(value);
|
|
805
|
-
}
|
|
806
|
-
return result;
|
|
807
|
-
}
|
|
808
|
-
function serializeCookieHeader(name, value, options = {}) {
|
|
809
|
-
const parts = [`${name}=${encodeURIComponent(value)}`];
|
|
810
|
-
if (options.path) parts.push(`Path=${options.path}`);
|
|
811
|
-
if (options.domain) parts.push(`Domain=${options.domain}`);
|
|
812
|
-
if (options.maxAge !== void 0) parts.push(`Max-Age=${options.maxAge}`);
|
|
813
|
-
if (options.secure) parts.push("Secure");
|
|
814
|
-
if (options.sameSite) parts.push(`SameSite=${options.sameSite}`);
|
|
815
|
-
if (options.httpOnly) parts.push("HttpOnly");
|
|
816
|
-
return parts.join("; ");
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
export { AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, LocalAuthStore, RETRYABLE_METHODS, RETRYABLE_STATUS, abortableSleep, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
|
|
908
|
+
export { AUTH_SETTINGS_WRITABLE_FIELDS, AuraError, BaseAuthStore, BaseService, DEFAULT_MAX_RETRIES, DEFAULT_REQUEST_TIMEOUT_MS, LocalAuthStore, RETRYABLE_METHODS, RETRYABLE_STATUS, abortableSleep, clientScope, computeBackoff, emitAuraEvent, fetchWithAuth, isAbortError, isAuraError, isRetryableMethod, isRetryableStatus, isTokenExpired, parseCookieHeader, parseRetryAfter, refreshOnce, serializeBody, serializeCookieHeader };
|
package/package.json
CHANGED