@aurabase/aurabase-js 0.3.1 → 0.4.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.
Files changed (86) hide show
  1. package/README.md +26 -795
  2. package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts → dist/AuraClient-BcFE8u1v.d.cts} +1 -1
  3. package/{packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts → dist/AuraClient-BcFE8u1v.d.ts} +1 -1
  4. package/{packages/aurabase/dist → dist}/index.d.cts +1 -1
  5. package/{packages/aurabase/dist → dist}/index.d.ts +1 -1
  6. package/{packages/aurabase/dist → dist}/ssr.d.cts +1 -1
  7. package/{packages/aurabase/dist → dist}/ssr.d.ts +1 -1
  8. package/package.json +72 -82
  9. package/.changeset/config.json +0 -11
  10. package/CHANGELOG.md +0 -30
  11. package/packages/ai/LICENSE +0 -21
  12. package/packages/ai/README.md +0 -106
  13. package/packages/ai/dist/index.cjs +0 -229
  14. package/packages/ai/dist/index.d.cts +0 -139
  15. package/packages/ai/dist/index.d.ts +0 -139
  16. package/packages/ai/dist/index.js +0 -226
  17. package/packages/ai/package.json +0 -61
  18. package/packages/aurabase/LICENSE +0 -21
  19. package/packages/aurabase/README.md +0 -64
  20. package/packages/aurabase/package.json +0 -80
  21. package/packages/aurabase/vitest.e2e.config.ts +0 -9
  22. package/packages/auth/LICENSE +0 -21
  23. package/packages/auth/README.md +0 -215
  24. package/packages/auth/dist/index.cjs +0 -1175
  25. package/packages/auth/dist/index.d.cts +0 -397
  26. package/packages/auth/dist/index.d.ts +0 -397
  27. package/packages/auth/dist/index.js +0 -1170
  28. package/packages/auth/package.json +0 -61
  29. package/packages/core/LICENSE +0 -21
  30. package/packages/core/README.md +0 -132
  31. package/packages/core/dist/index.cjs +0 -695
  32. package/packages/core/dist/index.d.cts +0 -1137
  33. package/packages/core/dist/index.d.ts +0 -1137
  34. package/packages/core/dist/index.js +0 -673
  35. package/packages/core/package.json +0 -57
  36. package/packages/db/LICENSE +0 -21
  37. package/packages/db/README.md +0 -152
  38. package/packages/db/VENDORED.md +0 -93
  39. package/packages/db/dist/index.cjs +0 -544
  40. package/packages/db/dist/index.d.cts +0 -1111
  41. package/packages/db/dist/index.d.ts +0 -1111
  42. package/packages/db/dist/index.js +0 -541
  43. package/packages/db/package.json +0 -62
  44. package/packages/functions/LICENSE +0 -21
  45. package/packages/functions/README.md +0 -96
  46. package/packages/functions/dist/index.cjs +0 -173
  47. package/packages/functions/dist/index.d.cts +0 -81
  48. package/packages/functions/dist/index.d.ts +0 -81
  49. package/packages/functions/dist/index.js +0 -168
  50. package/packages/functions/package.json +0 -60
  51. package/packages/gen/LICENSE +0 -21
  52. package/packages/gen/README.md +0 -84
  53. package/packages/gen/dist/cli.cjs +0 -334
  54. package/packages/gen/dist/cli.d.cts +0 -1
  55. package/packages/gen/dist/cli.d.ts +0 -1
  56. package/packages/gen/dist/cli.js +0 -332
  57. package/packages/gen/dist/index.cjs +0 -257
  58. package/packages/gen/dist/index.d.cts +0 -12
  59. package/packages/gen/dist/index.d.ts +0 -12
  60. package/packages/gen/dist/index.js +0 -255
  61. package/packages/gen/package.json +0 -66
  62. package/packages/notifications/LICENSE +0 -21
  63. package/packages/notifications/README.md +0 -134
  64. package/packages/notifications/dist/index.cjs +0 -128
  65. package/packages/notifications/dist/index.d.cts +0 -81
  66. package/packages/notifications/dist/index.d.ts +0 -81
  67. package/packages/notifications/dist/index.js +0 -125
  68. package/packages/notifications/package.json +0 -60
  69. package/packages/realtime/LICENSE +0 -21
  70. package/packages/realtime/README.md +0 -183
  71. package/packages/realtime/dist/index.cjs +0 -977
  72. package/packages/realtime/dist/index.d.cts +0 -259
  73. package/packages/realtime/dist/index.d.ts +0 -259
  74. package/packages/realtime/dist/index.js +0 -973
  75. package/packages/realtime/package.json +0 -60
  76. package/packages/storage/LICENSE +0 -21
  77. package/packages/storage/README.md +0 -141
  78. package/packages/storage/dist/index.cjs +0 -333
  79. package/packages/storage/dist/index.d.cts +0 -185
  80. package/packages/storage/dist/index.d.ts +0 -185
  81. package/packages/storage/dist/index.js +0 -329
  82. package/packages/storage/package.json +0 -60
  83. /package/{packages/aurabase/dist → dist}/index.cjs +0 -0
  84. /package/{packages/aurabase/dist → dist}/index.js +0 -0
  85. /package/{packages/aurabase/dist → dist}/ssr.cjs +0 -0
  86. /package/{packages/aurabase/dist → dist}/ssr.js +0 -0
@@ -1,1170 +0,0 @@
1
- import { BaseService, isTokenExpired, AuraError, refreshOnce, emitAuraEvent } from '@aurabase/core';
2
-
3
- // src/AuthService.ts
4
-
5
- // src/SessionManager.ts
6
- var SessionManager = class {
7
- opts;
8
- _interval = null;
9
- _channel = null;
10
- _active = false;
11
- _visHandler = null;
12
- constructor(opts) {
13
- this.opts = { tickMs: 3e4, ...opts };
14
- }
15
- get isActive() {
16
- return this._active;
17
- }
18
- start() {
19
- if (!this.opts.enabled) return;
20
- if (typeof BroadcastChannel !== "undefined" && !this._channel) {
21
- this._channel = new BroadcastChannel(this.opts.channelName);
22
- this._channel.onmessage = (e) => this.opts.onRemoteMessage(e.data);
23
- }
24
- if (typeof document !== "undefined" && !this._visHandler) {
25
- this._visHandler = () => {
26
- if (document.hidden) {
27
- this._stopInterval();
28
- } else if (this._active) {
29
- this._startInterval();
30
- if (this.opts.isRefreshDue()) this.opts.onTick();
31
- }
32
- };
33
- document.addEventListener("visibilitychange", this._visHandler);
34
- }
35
- }
36
- setActive(active) {
37
- this._active = active;
38
- if (!this.opts.enabled) return;
39
- const hidden = typeof document !== "undefined" && document.hidden;
40
- if (active && !hidden) this._startInterval();
41
- else this._stopInterval();
42
- }
43
- broadcast(data) {
44
- this._channel?.postMessage(data);
45
- }
46
- stop() {
47
- this._stopInterval();
48
- this._active = false;
49
- if (this._channel) {
50
- try {
51
- this._channel.close();
52
- } catch {
53
- }
54
- this._channel = null;
55
- }
56
- if (this._visHandler && typeof document !== "undefined") {
57
- document.removeEventListener("visibilitychange", this._visHandler);
58
- this._visHandler = null;
59
- }
60
- }
61
- _startInterval() {
62
- if (this._interval) return;
63
- this._interval = setInterval(() => {
64
- if (this.opts.isRefreshDue()) this.opts.onTick();
65
- }, this.opts.tickMs);
66
- this._interval.unref?.();
67
- }
68
- _stopInterval() {
69
- if (this._interval) {
70
- clearInterval(this._interval);
71
- this._interval = null;
72
- }
73
- }
74
- };
75
-
76
- // src/jwks.ts
77
- var MAX_AGE_CAP_MS = 864e5;
78
- var FORCED_REFETCH_COOLDOWN_MS = 3e4;
79
- var NEGATIVE_CACHE_TTL_MS = 1e4;
80
- var DEFAULT_MAX_AGE_MS = 6e5;
81
- var clientStates = /* @__PURE__ */ new WeakMap();
82
- function stateFor(scope) {
83
- let s = clientStates.get(scope);
84
- if (!s) {
85
- s = {
86
- cache: /* @__PURE__ */ new Map(),
87
- inflight: /* @__PURE__ */ new Map(),
88
- lastFailedAt: /* @__PURE__ */ new Map(),
89
- lastForcedFetchAt: /* @__PURE__ */ new Map()
90
- };
91
- clientStates.set(scope, s);
92
- }
93
- return s;
94
- }
95
- function b64urlToBytes(s) {
96
- const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4);
97
- const bin = atob(b64);
98
- const out = new Uint8Array(bin.length);
99
- for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
100
- return out;
101
- }
102
- function decodeJwt(jwt) {
103
- const [h, p, s] = jwt.split(".");
104
- if (!h || !p || !s) throw new Error("malformed jwt");
105
- const dec = new TextDecoder();
106
- return {
107
- header: JSON.parse(dec.decode(b64urlToBytes(h))),
108
- payload: JSON.parse(dec.decode(b64urlToBytes(p))),
109
- signingInput: `${h}.${p}`,
110
- signature: b64urlToBytes(s)
111
- };
112
- }
113
- function parseMaxAge(cc) {
114
- const m = cc?.match(/max-age=(\d+)/);
115
- return m ? Number(m[1]) * 1e3 : null;
116
- }
117
- function fetchJwksDeduped(state, fetchFn, baseUrl) {
118
- const existing = state.inflight.get(baseUrl);
119
- if (existing) return existing;
120
- const p = (async () => {
121
- try {
122
- const res = await fetchFn(`${baseUrl}/.well-known/jwks.json`);
123
- if (!res.ok) throw new Error("jwks_fetch_failed");
124
- const body = await res.json();
125
- const parsed = parseMaxAge(res.headers.get("Cache-Control")) ?? DEFAULT_MAX_AGE_MS;
126
- const maxAge = Math.min(parsed, MAX_AGE_CAP_MS);
127
- const keys = body.keys ?? [];
128
- state.cache.set(baseUrl, { keys, fetchedAt: Date.now(), maxAgeMs: maxAge });
129
- state.lastFailedAt.delete(baseUrl);
130
- return keys;
131
- } catch {
132
- state.lastFailedAt.set(baseUrl, Date.now());
133
- throw new Error("jwks_fetch_failed");
134
- }
135
- })().finally(() => state.inflight.delete(baseUrl));
136
- state.inflight.set(baseUrl, p);
137
- return p;
138
- }
139
- async function loadJwks(state, fetchFn, baseUrl) {
140
- const now = Date.now();
141
- const c = state.cache.get(baseUrl);
142
- if (c && now - c.fetchedAt < c.maxAgeMs) return c.keys;
143
- const failedAt = state.lastFailedAt.get(baseUrl);
144
- if (failedAt !== void 0 && now - failedAt < NEGATIVE_CACHE_TTL_MS) {
145
- throw new Error("jwks_fetch_failed");
146
- }
147
- return fetchJwksDeduped(state, fetchFn, baseUrl);
148
- }
149
- async function forcedRefetchJwks(state, fetchFn, baseUrl) {
150
- const now = Date.now();
151
- const last = state.lastForcedFetchAt.get(baseUrl);
152
- if (last !== void 0 && now - last < FORCED_REFETCH_COOLDOWN_MS) return null;
153
- state.lastForcedFetchAt.set(baseUrl, now);
154
- const failedAt = state.lastFailedAt.get(baseUrl);
155
- if (failedAt !== void 0 && now - failedAt < NEGATIVE_CACHE_TTL_MS) {
156
- throw new Error("jwks_fetch_failed");
157
- }
158
- return fetchJwksDeduped(state, fetchFn, baseUrl);
159
- }
160
- function algParams(alg) {
161
- if (alg === "RS256") {
162
- return {
163
- importAlg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
164
- verifyAlg: { name: "RSASSA-PKCS1-v1_5" }
165
- };
166
- }
167
- if (alg === "ES256") {
168
- return {
169
- importAlg: { name: "ECDSA", namedCurve: "P-256" },
170
- verifyAlg: { name: "ECDSA", hash: "SHA-256" }
171
- };
172
- }
173
- return null;
174
- }
175
- async function verifyJwtLocal(scope, fetchFn, baseUrl, jwt, opts = {}) {
176
- let d;
177
- try {
178
- d = decodeJwt(jwt);
179
- } catch {
180
- return { ok: false, code: "invalid_jwt" };
181
- }
182
- const params = algParams(d.header.alg);
183
- if (!params) return { ok: false, code: "unsupported_alg" };
184
- const state = stateFor(scope);
185
- const matchesAlg = (k) => d.header.alg === "RS256" ? k.kty === "RSA" : k.kty === "EC";
186
- const candidateKeys = (keys2) => d.header.kid ? keys2.filter((k) => k.kid === d.header.kid && matchesAlg(k)) : keys2.filter((k) => matchesAlg(k));
187
- let keys;
188
- try {
189
- keys = await loadJwks(state, fetchFn, baseUrl);
190
- } catch {
191
- try {
192
- opts.onEvent?.(baseUrl);
193
- } catch {
194
- }
195
- return { ok: false, code: "jwks_fetch_failed" };
196
- }
197
- let candidates = candidateKeys(keys);
198
- if (candidates.length === 0 && d.header.kid) {
199
- let refetched;
200
- try {
201
- refetched = await forcedRefetchJwks(state, fetchFn, baseUrl);
202
- } catch {
203
- try {
204
- opts.onEvent?.(baseUrl);
205
- } catch {
206
- }
207
- return { ok: false, code: "jwks_fetch_failed" };
208
- }
209
- if (refetched) {
210
- keys = refetched;
211
- candidates = candidateKeys(keys);
212
- }
213
- }
214
- if (candidates.length === 0) return { ok: false, code: "no_matching_key" };
215
- const signingBytes = new TextEncoder().encode(d.signingInput);
216
- let valid = false;
217
- for (const jwk of candidates) {
218
- try {
219
- const key = await crypto.subtle.importKey("jwk", jwk, params.importAlg, false, ["verify"]);
220
- if (await crypto.subtle.verify(
221
- params.verifyAlg,
222
- key,
223
- d.signature,
224
- signingBytes
225
- )) {
226
- valid = true;
227
- break;
228
- }
229
- } catch {
230
- }
231
- }
232
- if (!valid) return { ok: false, code: "signature_invalid" };
233
- const now = Math.floor(Date.now() / 1e3);
234
- if (!opts.allowExpired && typeof d.payload.exp === "number" && d.payload.exp < now) {
235
- return { ok: false, code: "token_expired" };
236
- }
237
- if (typeof d.payload.nbf === "number" && d.payload.nbf > now + 60) {
238
- return { ok: false, code: "token_not_yet_valid" };
239
- }
240
- return {
241
- ok: true,
242
- claims: d.payload,
243
- header: {
244
- alg: d.header.alg,
245
- kid: d.header.kid,
246
- typ: d.header.typ
247
- }
248
- };
249
- }
250
-
251
- // src/pkce.ts
252
- function b64url(bytes) {
253
- let s = "";
254
- for (const b of bytes) s += String.fromCharCode(b);
255
- return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
256
- }
257
- function generateCodeVerifier() {
258
- const bytes = new Uint8Array(64);
259
- crypto.getRandomValues(bytes);
260
- return b64url(bytes);
261
- }
262
- async function codeChallengeS256(verifier) {
263
- const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
264
- return b64url(new Uint8Array(digest));
265
- }
266
-
267
- // src/AuthService.ts
268
- function isMfaChallenge(v) {
269
- return typeof v === "object" && v !== null && v.mfa_required === true;
270
- }
271
- var AuthService = class extends BaseService {
272
- _stateListeners = /* @__PURE__ */ new Set();
273
- _sessionManager;
274
- _applyingRemote = false;
275
- _unsubStore = null;
276
- _disposed = false;
277
- constructor(client, options) {
278
- super(client);
279
- const browser = typeof window !== "undefined";
280
- const enabled = !!options?.forceEnabled || (options?.autoRefresh ?? true) && browser && (options?.persistSession ?? true);
281
- this._sessionManager = new SessionManager({
282
- channelName: `aura-${client.projectId}-auth`,
283
- enabled,
284
- onTick: () => {
285
- void this.refreshSession();
286
- },
287
- isRefreshDue: () => {
288
- const t = this.client.authStore.token;
289
- return !!t && !!this.client.authStore.refreshToken && isTokenExpired(t, 60);
290
- },
291
- onRemoteMessage: (data) => this._applyRemoteState(data)
292
- });
293
- this._sessionManager.start();
294
- this._unsubStore = this.client.authStore.onChange((token) => {
295
- this._sessionManager.setActive(!!token);
296
- if (!this._applyingRemote) {
297
- const state = this._broadcastState();
298
- if (!token || state !== null) {
299
- this._sessionManager.broadcast(state);
300
- }
301
- }
302
- });
303
- if (this.client.authStore.token) this._sessionManager.setActive(true);
304
- const detect = options?.detectSessionInUrl ?? browser;
305
- if (detect && browser) {
306
- void this._detectSessionInUrl();
307
- }
308
- }
309
- /**
310
- * Détecte et consomme une session présente dans l'URL courante au boot (callback
311
- * OAuth), à l'image de `detectSessionInUrl` de supabase-js :
312
- * - fragment `#access_token=…&refresh_token=…` (flux implicit) → `setSession`.
313
- * - query `?token=…&type=recovery` (lien de récupération de mot de passe) → émet
314
- * `PASSWORD_RECOVERY` avec `recoveryToken` (AUCUNE session posée, PAS de `SIGNED_IN`).
315
- * - query `?code=…` (flux PKCE) → `exchangeCodeForSession` (relit le verifier).
316
- * Puis nettoie l'URL via `history.replaceState` (retire tokens/code de la barre
317
- * d'adresse et de l'historique). Idempotent, no-op si aucun token/code n'est
318
- * présent, no-op hors navigateur. NE THROW JAMAIS (best-effort au boot).
319
- */
320
- async _detectSessionInUrl() {
321
- if (typeof window === "undefined") return;
322
- try {
323
- const loc = window.location;
324
- if (!loc) return;
325
- const hash = typeof loc.hash === "string" ? loc.hash : "";
326
- const search = typeof loc.search === "string" ? loc.search : "";
327
- if (hash.includes("access_token")) {
328
- const initiatedKey = this._oauthInitiatedKey;
329
- const initiated = this.client.authStore.getItem(initiatedKey);
330
- if (!initiated) return;
331
- this.client.authStore.removeItem(initiatedKey);
332
- const frag = new URLSearchParams(hash.replace(/^#/, ""));
333
- const accessToken = frag.get("access_token");
334
- const refreshToken = frag.get("refresh_token");
335
- if (accessToken && refreshToken) {
336
- await this.setSession(accessToken, refreshToken);
337
- this._cleanAuthParamsFromUrl();
338
- }
339
- return;
340
- }
341
- {
342
- const qs = new URLSearchParams(search.replace(/^\?/, ""));
343
- const recoveryToken = qs.get("token");
344
- if (qs.get("type") === "recovery" && recoveryToken) {
345
- this._emitEvent("PASSWORD_RECOVERY", null, null, { recoveryToken });
346
- this._cleanAuthParamsFromUrl();
347
- return;
348
- }
349
- }
350
- if (/[?&]code=/.test(search)) {
351
- const qs = new URLSearchParams(search.replace(/^\?/, ""));
352
- const code = qs.get("code");
353
- if (code) {
354
- await this.exchangeCodeForSession(code);
355
- this._cleanAuthParamsFromUrl();
356
- }
357
- return;
358
- }
359
- } catch {
360
- }
361
- }
362
- /**
363
- * Retire de l'URL courante les paramètres d'auth du callback OAuth : le fragment
364
- * entier (flux implicit) et les query params `code`/`state` (flux PKCE), tout en
365
- * préservant les autres query params. Via `history.replaceState` (pas de nouvelle
366
- * entrée d'historique). Best-effort, ne throw jamais.
367
- */
368
- _cleanAuthParamsFromUrl() {
369
- try {
370
- if (typeof window === "undefined") return;
371
- const h = window.history;
372
- const loc = window.location;
373
- if (!h || typeof h.replaceState !== "function" || !loc) return;
374
- const qs = new URLSearchParams(
375
- typeof loc.search === "string" ? loc.search.replace(/^\?/, "") : ""
376
- );
377
- qs.delete("code");
378
- qs.delete("state");
379
- qs.delete("token");
380
- qs.delete("project_id");
381
- qs.delete("type");
382
- const remaining = qs.toString();
383
- const path = loc.pathname ?? "/";
384
- const clean = path + (remaining ? `?${remaining}` : "");
385
- h.replaceState(null, "", clean || "/");
386
- } catch {
387
- }
388
- }
389
- _broadcastState() {
390
- const s = this.client.authStore;
391
- if (!s.token || !s.refreshToken || !s.user || typeof s.user !== "object" || !("id" in s.user) || !s.user.id) {
392
- return null;
393
- }
394
- return { token: s.token, refreshToken: s.refreshToken, user: s.user };
395
- }
396
- _applyRemoteState(data) {
397
- if (data !== null) {
398
- const d = data;
399
- if (typeof d.token !== "string" || typeof d.refreshToken !== "string" || !d.user || typeof d.user !== "object") {
400
- return;
401
- }
402
- }
403
- this._applyingRemote = true;
404
- try {
405
- const store = this.client.authStore;
406
- const prevToken = store.token;
407
- if (!data) {
408
- store.clear();
409
- this._emitEvent("SIGNED_OUT", null, null);
410
- } else {
411
- const s = data;
412
- store.save(s.token, s.refreshToken, s.user);
413
- const event = !prevToken ? "SIGNED_IN" : s.token !== prevToken ? "TOKEN_REFRESHED" : "USER_UPDATED";
414
- this._emitEvent(event, s.user, s.token);
415
- }
416
- } finally {
417
- this._applyingRemote = false;
418
- }
419
- }
420
- /** Démonte le cycle de vie proactif : ticker, visibilité, BroadcastChannel, listeners. */
421
- dispose() {
422
- if (this._disposed) return;
423
- this._disposed = true;
424
- this._sessionManager.stop();
425
- this._unsubStore?.();
426
- this._unsubStore = null;
427
- this._stateListeners.clear();
428
- }
429
- get prefix() {
430
- return `/v1/auth/${this.client.projectId}`;
431
- }
432
- /** Clé project-scoped du `code_verifier` PKCE (anti-collision multi-projets). */
433
- get _pkceVerifierKey() {
434
- return `aura-${this.client.projectId}-pkce-code-verifier`;
435
- }
436
- /**
437
- * Clé project-scoped du MARQUEUR d'initiation du flux OAuth implicit (SEC-F3).
438
- * Posé par `signInWithOAuth` (flux implicit) avant le redirect ; vérifié + purgé
439
- * par `_detectSessionInUrl` au retour. Un fragment `#access_token` forgé/injecté
440
- * par un attaquant (login-CSRF / session fixation) n'aura PAS ce marqueur → il ne
441
- * sera jamais adopté.
442
- */
443
- get _oauthInitiatedKey() {
444
- return `aura-${this.client.projectId}-oauth-implicit-initiated`;
445
- }
446
- // -- Auth state change ------------------------------------------------------
447
- onAuthStateChange(callback) {
448
- if (this._disposed) return { unsubscribe: () => {
449
- } };
450
- this._stateListeners.add(callback);
451
- const store = this.client.authStore;
452
- queueMicrotask(() => {
453
- if (this._stateListeners.has(callback)) {
454
- callback({ event: "INITIAL_SESSION", user: store.user, accessToken: store.token });
455
- }
456
- });
457
- return {
458
- unsubscribe: () => {
459
- this._stateListeners.delete(callback);
460
- }
461
- };
462
- }
463
- _emitEvent(event, user, accessToken, extra) {
464
- for (const cb of this._stateListeners) {
465
- try {
466
- cb({ event, user, accessToken, ...extra });
467
- } catch {
468
- }
469
- }
470
- }
471
- // -- Sign up / sign in / sign out -------------------------------------------
472
- async signUp(req) {
473
- const res = await this.request("POST", `${this.prefix}/register`, req);
474
- if (res.data && !isMfaChallenge(res.data)) {
475
- const tp = res.data;
476
- this.client.authStore.save(tp.access_token, tp.refresh_token, tp.user);
477
- this._emitEvent("SIGNED_IN", tp.user, tp.access_token);
478
- }
479
- if (res.data && isMfaChallenge(res.data)) {
480
- const mfa = res.data;
481
- this._emitEvent("MFA_CHALLENGE", null, null, {
482
- mfaToken: mfa.mfa_token,
483
- mfaSetupRequired: mfa.mfa_setup_required
484
- });
485
- }
486
- return res;
487
- }
488
- async signInWithPassword(req) {
489
- const res = await this.request("POST", `${this.prefix}/login`, req);
490
- if (res.data && !isMfaChallenge(res.data)) {
491
- const tp = res.data;
492
- this.client.authStore.save(tp.access_token, tp.refresh_token, tp.user);
493
- this._emitEvent("SIGNED_IN", tp.user, tp.access_token);
494
- }
495
- if (res.data && isMfaChallenge(res.data)) {
496
- const mfa = res.data;
497
- this._emitEvent("MFA_CHALLENGE", null, null, {
498
- mfaToken: mfa.mfa_token,
499
- mfaSetupRequired: mfa.mfa_setup_required
500
- });
501
- }
502
- return res;
503
- }
504
- async signInWithOAuth(provider, options) {
505
- const params = new URLSearchParams();
506
- params.set("project_id", this.client.projectId);
507
- if (options?.redirectTo) params.set("redirect_uri", options.redirectTo);
508
- if (options?.scopes) params.set("scopes", options.scopes);
509
- if (options?.queryParams) {
510
- for (const [k, v] of Object.entries(options.queryParams)) {
511
- params.set(k, v);
512
- }
513
- }
514
- if (options?.flowType === "pkce") {
515
- if (typeof window !== "undefined" && !this.client.authStore.persistent) {
516
- console.warn(
517
- `[aurabase] PKCE (flowType:"pkce") requiert un store persistant (LocalAuthStore) pour que le code_verifier survive au redirect OAuth ; le store courant est en m\xE9moire \u2014 l'\xE9change \xE9chouera au retour.`
518
- );
519
- }
520
- try {
521
- const verifier = generateCodeVerifier();
522
- const challenge = await codeChallengeS256(verifier);
523
- this.client.authStore.setItem(this._pkceVerifierKey, verifier);
524
- params.set("code_challenge", challenge);
525
- params.set("code_challenge_method", "S256");
526
- } catch {
527
- return {
528
- data: null,
529
- error: new AuraError(0, {
530
- error: {
531
- code: "pkce_generation_failed",
532
- message: "PKCE: g\xE9n\xE9ration du code_verifier/challenge impossible (WebCrypto indisponible)"
533
- }
534
- })
535
- };
536
- }
537
- } else {
538
- if (typeof window !== "undefined" && !this.client.authStore.persistent) {
539
- console.warn(
540
- '[aurabase] S\xC9CURIT\xC9 : signInWithOAuth en flux implicit avec un store NON persistant (m\xE9moire) \u2014 le marqueur anti session-fixation ne survivra pas au redirect OAuth et la session ne sera pas adopt\xE9e au retour. Utilisez un LocalAuthStore, et pr\xE9f\xE9rez flowType:"pkce" (recommand\xE9, immunis\xE9 contre la session fixation).'
541
- );
542
- }
543
- try {
544
- this.client.authStore.setItem(this._oauthInitiatedKey, "1");
545
- } catch {
546
- }
547
- }
548
- const url = `${this.client.baseUrl}/v1/auth/oauth/${encodeURIComponent(provider)}/start?${params.toString()}`;
549
- if (typeof window !== "undefined") {
550
- window.location.href = url;
551
- }
552
- return { data: { url }, error: null };
553
- }
554
- async signInWithMagicLink(req) {
555
- return this.request("POST", `${this.prefix}/magic-link`, req);
556
- }
557
- async signInAnonymously() {
558
- const res = await this.request("POST", `${this.prefix}/anonymous`);
559
- if (res.data) {
560
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
561
- this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
562
- }
563
- return res;
564
- }
565
- async signOut() {
566
- const refreshToken = this.client.authStore.refreshToken;
567
- if (!refreshToken) {
568
- this.client.authStore.clear();
569
- this._emitEvent("SIGNED_OUT", null, null);
570
- return { data: null, error: null };
571
- }
572
- const res = await this.request("POST", `${this.prefix}/logout`, {
573
- refresh_token: refreshToken
574
- }, { skipAuthRefresh: true });
575
- this.client.authStore.clear();
576
- this._emitEvent("SIGNED_OUT", null, null);
577
- return res;
578
- }
579
- // -- Session management -----------------------------------------------------
580
- async refreshSession() {
581
- const res = await refreshOnce(this.client);
582
- if (res.data) {
583
- this._emitEvent("TOKEN_REFRESHED", res.data.user, res.data.access_token);
584
- }
585
- return res;
586
- }
587
- /**
588
- * Session courante lue depuis le store (synchrone en pratique, pas d'appel réseau).
589
- *
590
- * ÉCART DE FORME AVEC SUPABASE (LOT 3, 3.5, documenté — pas de breaking change) :
591
- * `supabase-js` renvoie `AuraResponse`-like `{data:{session: Session|null}, error}`
592
- * où `Session` regroupe `access_token`/`refresh_token`/`expires_at`/`user`. Ici,
593
- * `getSession()` renvoie directement `{accessToken, user}` (pas d'enveloppe
594
- * `{data, error}`, pas de `refreshToken`/`expiresAt`). Ce choix est délibéré :
595
- * cette méthode ne fait aucune requête réseau et ne peut donc jamais échouer
596
- * (`error` serait toujours `null`) — l'envelopper aurait été un contrat menteur.
597
- * Le `refreshToken` n'est pas exposé pour éviter qu'un consommateur le manipule
598
- * hors du store (contrairement à `accessToken`, souvent nécessaire pour des appels
599
- * `fetch` manuels côté app). Pour la session complète, lire `client.authStore`
600
- * directement (`.token`, `.refreshToken`, `.user`, `.isValid`).
601
- */
602
- async getSession() {
603
- return {
604
- accessToken: this.client.authStore.token,
605
- user: this.client.authStore.user
606
- };
607
- }
608
- async setSession(accessToken, refreshToken) {
609
- const store = this.client.authStore;
610
- const prev = { token: store.token, refreshToken: store.refreshToken, user: store.user };
611
- const hadValidPrev = !!(prev.token && prev.refreshToken && prev.user && prev.user.id);
612
- const restore = () => {
613
- if (hadValidPrev) {
614
- store.save(prev.token, prev.refreshToken, prev.user);
615
- } else {
616
- store.clear();
617
- }
618
- };
619
- if (isTokenExpired(accessToken, 0)) {
620
- store.save(accessToken, refreshToken, {});
621
- const refreshed = await refreshOnce(this.client);
622
- if (refreshed.data) {
623
- this._emitEvent("SIGNED_IN", refreshed.data.user, refreshed.data.access_token);
624
- return { data: refreshed.data.user, error: null };
625
- }
626
- restore();
627
- return { data: null, error: refreshed.error };
628
- }
629
- store.save(accessToken, refreshToken, {});
630
- const res = await this.request("GET", `${this.prefix}/user`);
631
- if (res.data) {
632
- store.save(accessToken, refreshToken, res.data);
633
- this._emitEvent("SIGNED_IN", res.data, accessToken);
634
- return res;
635
- }
636
- restore();
637
- return res;
638
- }
639
- async exchangeCodeForSession(authCode, codeVerifier) {
640
- return this._exchangeCode(authCode, codeVerifier, "SIGNED_IN");
641
- }
642
- /**
643
- * Cœur de l'échange PKCE. `successEvent` détermine l'événement émis en cas de
644
- * succès : `SIGNED_IN` (connexion normale) ou `PASSWORD_RECOVERY` (callback de
645
- * récupération de mot de passe, `?code=…&type=recovery` — voir `_detectSessionInUrl`).
646
- */
647
- async _exchangeCode(authCode, codeVerifier, successEvent) {
648
- const verifierKey = this._pkceVerifierKey;
649
- const verifier = codeVerifier ?? this.client.authStore.getItem(verifierKey);
650
- if (!verifier) {
651
- return {
652
- data: null,
653
- error: new AuraError(0, {
654
- error: { code: "no_code_verifier", message: "No PKCE code_verifier available (not provided and none stored)" }
655
- })
656
- };
657
- }
658
- try {
659
- const res = await this.request("POST", `${this.prefix}/oauth/pkce`, {
660
- auth_code: authCode,
661
- code_verifier: verifier
662
- });
663
- if (res.data) {
664
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
665
- this._emitEvent(successEvent, res.data.user, res.data.access_token);
666
- }
667
- return res;
668
- } finally {
669
- this.client.authStore.removeItem(verifierKey);
670
- }
671
- }
672
- async listSessions() {
673
- const store = this.client.authStore;
674
- if (store.token && store.refreshToken && !store.isValid) {
675
- const refreshed = await this.refreshSession();
676
- if (refreshed.error) {
677
- return { data: null, error: refreshed.error };
678
- }
679
- }
680
- const refreshToken = store.refreshToken;
681
- return this.request("GET", `${this.prefix}/sessions`, void 0, {
682
- headers: refreshToken ? { "x-refresh-token": refreshToken } : void 0
683
- });
684
- }
685
- async revokeSession(sessionId) {
686
- return this.request("DELETE", `${this.prefix}/sessions/${sessionId}`);
687
- }
688
- async revokeAllSessions() {
689
- const rt = this.client.authStore.refreshToken;
690
- if (!rt) {
691
- return {
692
- data: null,
693
- error: new AuraError(0, { error: { code: "no_session", message: "No refresh token available" } })
694
- };
695
- }
696
- const res = await this.request(
697
- "POST",
698
- `${this.prefix}/logout`,
699
- { refresh_token: rt, scope: "global" },
700
- { skipAuthRefresh: true }
701
- );
702
- if (!res.error) {
703
- this.client.authStore.clear();
704
- this._emitEvent("SIGNED_OUT", null, null);
705
- }
706
- return res;
707
- }
708
- // -- User management --------------------------------------------------------
709
- /**
710
- * Récupère l'utilisateur validé côté serveur.
711
- *
712
- * Sans argument : valide le token de la SESSION courante (`store.token`),
713
- * avec refresh proactif — comportement historique.
714
- *
715
- * Avec `jwt` (LOT D) : valide le token ARBITRAIRE passé, via l'override
716
- * `authToken` (`Authorization: Bearer <jwt>`), SANS toucher la session
717
- * (`skipAuthRefresh`, aucun refresh ni retry-401 sur le store). Le backend
718
- * applique l'isolation projet native : un token d'un AUTRE projet → 403.
719
- */
720
- async getUser(jwt) {
721
- if (jwt !== void 0) {
722
- return this.request("GET", `${this.prefix}/user`, void 0, {
723
- authToken: jwt,
724
- skipAuthRefresh: true
725
- });
726
- }
727
- return this.request("GET", `${this.prefix}/user`);
728
- }
729
- /**
730
- * Vérifie et décode les claims d'un JWT.
731
- *
732
- * Chemin nominal : vérification LOCALE via JWKS + WebCrypto, uniquement pour
733
- * `alg ∈ {RS256, ES256}` (anti-alg-confusion : jamais de vérif locale HS256).
734
- * Chemin fallback : `getUser()` (validation serveur) quand la vérif locale
735
- * n'est pas applicable — `alg` non supporté (HS256), JWKS vide, aucune clé
736
- * correspondante, ou fetch JWKS en échec. C'est le chemin nominal sur les
737
- * déploiements HS256 (JWKS `{"keys":[]}`).
738
- *
739
- * FOREIGN-TOKEN (LOT D) : le fallback getUser valide le token RÉELLEMENT visé.
740
- * Pour le token de session (no-arg, ou `jwt === authStore.token`), c'est
741
- * `getUser()` (validation du token du store). Pour un `jwt` ÉTRANGER, c'est
742
- * `getUser(jwt)` qui présente CE token en `Authorization` (override `authToken`,
743
- * jamais celui du store) : le backend le valide et renvoie SON identité. Aucune
744
- * misattribution possible — on présente exactement le token à vérifier — et
745
- * l'isolation projet est native côté gateway (token d'un autre projet → 403,
746
- * remonté en AuraError, jamais fabriqué).
747
- *
748
- * NOTE (fallback) : les claims renvoyées par le chemin fallback sont dérivées
749
- * de `getUser()` (identité validée serveur) et NE contiennent PAS `exp`/`iat`
750
- * (ni les autres champs temporels du JWT) — un consommateur ne doit pas se
751
- * fier à `claims.exp` / `claims.iat` après un fallback. Seul le chemin de
752
- * vérification locale RS256/ES256 renvoie les claims JWT complètes.
753
- */
754
- async getClaims(jwt, options) {
755
- const token = jwt ?? this.client.authStore.token;
756
- const usingStoreToken = jwt === void 0 || jwt === this.client.authStore.token;
757
- if (!token) {
758
- return {
759
- data: null,
760
- error: new AuraError(0, { error: { code: "no_session", message: "No JWT available" } })
761
- };
762
- }
763
- const r = await verifyJwtLocal(this.client, this.client.fetchFn, this.client.baseUrl, token, {
764
- allowExpired: options?.allowExpired,
765
- // LOT 3 (I8) : relie le callback générique de jwks.ts au hook d'observabilité
766
- // du client (onEvent/debug). jwks.ts reste sans dépendance vers @aurabase/core.
767
- onEvent: (baseUrl) => emitAuraEvent(this.client.onEvent, this.client.debug, { kind: "jwks-fetch-failed", baseUrl })
768
- });
769
- const checkIssAud = (c) => {
770
- if (options?.issuer !== void 0 && c.iss !== options.issuer) {
771
- return new AuraError(0, {
772
- error: { code: "invalid_issuer", message: `getClaims: issuer mismatch` }
773
- });
774
- }
775
- if (options?.audience !== void 0) {
776
- const aud = c.aud;
777
- const audList = Array.isArray(aud) ? aud : aud !== void 0 ? [aud] : [];
778
- if (!audList.includes(options.audience)) {
779
- return new AuraError(0, {
780
- error: { code: "invalid_audience", message: `getClaims: audience mismatch` }
781
- });
782
- }
783
- }
784
- return null;
785
- };
786
- if (r.ok) {
787
- const claims = r.claims;
788
- const issAudErr = checkIssAud(claims);
789
- if (issAudErr) return { data: null, error: issAudErr };
790
- const claimProject = claims.project_id;
791
- if (typeof claimProject === "string" && claimProject !== this.client.projectId) {
792
- return {
793
- data: null,
794
- error: new AuraError(0, {
795
- error: {
796
- code: "project_isolation_violation",
797
- message: `getClaims: token project mismatch (isolation)`
798
- }
799
- })
800
- };
801
- }
802
- return { data: { claims, header: r.header }, error: null };
803
- }
804
- if (r.code === "unsupported_alg" || r.code === "no_matching_key" || r.code === "jwks_fetch_failed") {
805
- let localPayload = {};
806
- try {
807
- localPayload = decodeJwt(token).payload;
808
- } catch {
809
- }
810
- const issAudErr = checkIssAud(localPayload);
811
- if (issAudErr) return { data: null, error: issAudErr };
812
- if (options?.allowExpired && typeof localPayload.exp === "number" && localPayload.exp < Math.floor(Date.now() / 1e3)) {
813
- return {
814
- data: null,
815
- error: new AuraError(0, {
816
- error: {
817
- code: "allow_expired_unsupported_on_fallback",
818
- message: "getClaims: allowExpired ne peut pas \xEAtre honor\xE9 sur le chemin de validation serveur (fallback HS256/JWKS)"
819
- }
820
- })
821
- };
822
- }
823
- const u = usingStoreToken ? await this.getUser() : await this.getUser(token);
824
- if (u.data) {
825
- let realAlg = "unknown";
826
- try {
827
- realAlg = decodeJwt(token).header.alg ?? "unknown";
828
- } catch {
829
- }
830
- return {
831
- data: {
832
- // Claims dérivées de getUser : PAS de `exp`/`iat` (voir JwtClaims/JSDoc).
833
- // `exp`/`iat` étant optionnels, plus de `as unknown as` masquant l'absence.
834
- claims: { ...u.data, sub: u.data.id },
835
- header: { alg: realAlg }
836
- },
837
- error: null
838
- };
839
- }
840
- return { data: null, error: u.error };
841
- }
842
- return {
843
- data: null,
844
- error: new AuraError(0, { error: { code: r.code, message: `getClaims: ${r.code}` } })
845
- };
846
- }
847
- async deleteUser(password) {
848
- const res = await this.request("DELETE", `${this.prefix}/user`, { password });
849
- if (!res.error) {
850
- this.client.authStore.clear();
851
- this._emitEvent("SIGNED_OUT", null, null);
852
- }
853
- return res;
854
- }
855
- async updateUser(req) {
856
- const res = await this.request("PUT", `${this.prefix}/user`, req);
857
- if (res.data) {
858
- const store = this.client.authStore;
859
- if (store.token && store.refreshToken) {
860
- store.save(store.token, store.refreshToken, res.data);
861
- }
862
- this._emitEvent("USER_UPDATED", res.data, this.client.authStore.token);
863
- }
864
- return res;
865
- }
866
- // -- Password recovery ------------------------------------------------------
867
- async resetPasswordForEmail(req) {
868
- return this.request("POST", `${this.prefix}/forgot-password`, req);
869
- }
870
- async updatePassword(newPassword, currentPassword) {
871
- const res = await this.request("PUT", `${this.prefix}/user`, {
872
- password: newPassword,
873
- current_password: currentPassword
874
- });
875
- if (!res.error) {
876
- this.client.authStore.clear();
877
- this._emitEvent("SIGNED_OUT", null, null);
878
- }
879
- return res;
880
- }
881
- // -- MFA --------------------------------------------------------------------
882
- async mfaEnroll() {
883
- return this.request("POST", `${this.prefix}/mfa/setup`);
884
- }
885
- async mfaVerifySetup(code) {
886
- return this.request("POST", `${this.prefix}/mfa/setup/verify`, { code });
887
- }
888
- async mfaVerify(req) {
889
- const res = await this.request("POST", `${this.prefix}/mfa/verify-login`, req);
890
- if (res.data) {
891
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
892
- this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
893
- }
894
- return res;
895
- }
896
- async mfaUnenroll(password) {
897
- return this.request("POST", `${this.prefix}/mfa/disable`, { password });
898
- }
899
- async verifyOtp(params) {
900
- switch (params.type) {
901
- case "signup":
902
- return this._verifyToTokenPair(`${this.prefix}/verify-email`, { token: params.token });
903
- case "magiclink":
904
- return this._verifyToTokenPair(`${this.prefix}/magic-link/verify`, { token: params.token });
905
- case "email":
906
- return this._verifyToTokenPair(`${this.prefix}/email-otp/verify`, {
907
- email: params.email,
908
- code: params.token
909
- });
910
- case "sms":
911
- return this._verifyToTokenPair(`${this.prefix}/sms/verify`, {
912
- phone: params.phone,
913
- code: params.token
914
- });
915
- case "recovery": {
916
- if (!params.password) {
917
- return {
918
- data: null,
919
- error: new AuraError(0, {
920
- error: {
921
- code: "missing_password",
922
- message: "verifyOtp({ type: 'recovery' }) exige `password` : le backend fusionne consommation du token et changement de mot de passe."
923
- }
924
- })
925
- };
926
- }
927
- return this._verifyToTokenPair(`${this.prefix}/reset-password`, {
928
- token: params.token,
929
- password: params.password
930
- });
931
- }
932
- case "email_change": {
933
- const res = await this.request("POST", `${this.prefix}/user/confirm-email-change`, {
934
- token: params.token
935
- });
936
- if (res.data) {
937
- const store = this.client.authStore;
938
- if (store.token && store.refreshToken) {
939
- store.save(store.token, store.refreshToken, res.data);
940
- }
941
- this._emitEvent("USER_UPDATED", res.data, this.client.authStore.token);
942
- }
943
- return res;
944
- }
945
- case "invite":
946
- case "phone_change":
947
- return {
948
- data: null,
949
- error: new AuraError(0, {
950
- error: {
951
- code: "otp_type_unsupported",
952
- message: `verifyOtp: le type '${params.type}' n'est pas support\xE9 (aucun endpoint backend).`
953
- }
954
- })
955
- };
956
- default:
957
- return {
958
- data: null,
959
- error: new AuraError(0, {
960
- error: { code: "otp_type_unsupported", message: "verifyOtp: type OTP inconnu." }
961
- })
962
- };
963
- }
964
- }
965
- /**
966
- * Helper commun aux vérifications OTP renvoyant une `TokenPair` : appelle
967
- * l'endpoint, puis (succès) sauvegarde la session et émet `SIGNED_IN`.
968
- */
969
- async _verifyToTokenPair(path, body) {
970
- const res = await this.request("POST", path, body);
971
- if (res.data) {
972
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
973
- this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
974
- }
975
- return res;
976
- }
977
- // -- Email verification -----------------------------------------------------
978
- /** Rétrocompat : délègue à `verifyOtp({ type: 'signup' })` (POST /verify-email). */
979
- async verifyEmail(token) {
980
- return this.verifyOtp({ type: "signup", token });
981
- }
982
- async resendVerificationEmail(email) {
983
- const e = email ?? this.client.authStore.user?.email;
984
- if (!e) {
985
- return {
986
- data: null,
987
- error: new AuraError(0, { error: { code: "missing_email", message: "Email is required" } })
988
- };
989
- }
990
- return this.request("POST", `${this.prefix}/resend-verification`, { email: e });
991
- }
992
- // -- Email OTP (Phase 2) --------------------------------------------------
993
- async signInWithEmailOtp(email) {
994
- return this.request("POST", `${this.prefix}/email-otp/send`, { email });
995
- }
996
- /** Rétrocompat : délègue à `verifyOtp({ type: 'email' })` (POST /email-otp/verify). */
997
- async verifyEmailOtp(email, code) {
998
- return this.verifyOtp({ type: "email", email, token: code });
999
- }
1000
- // -- SMS OTP (Phase 2) ----------------------------------------------------
1001
- async signInWithSmsOtp(phone) {
1002
- return this.request("POST", `${this.prefix}/sms/send`, { phone });
1003
- }
1004
- /** Rétrocompat : délègue à `verifyOtp({ type: 'sms' })` (POST /sms/verify). */
1005
- async verifySmsOtp(phone, code) {
1006
- return this.verifyOtp({ type: "sms", phone, token: code });
1007
- }
1008
- // -- Identity linking (Phase 2) -------------------------------------------
1009
- async listIdentities() {
1010
- return this.request("GET", `${this.prefix}/user/identities`);
1011
- }
1012
- async linkIdentity(provider) {
1013
- return this.request("POST", `${this.prefix}/user/identities/link/${provider}`);
1014
- }
1015
- async unlinkIdentity(identityId) {
1016
- return this.request("DELETE", `${this.prefix}/user/identities/${identityId}`);
1017
- }
1018
- // -- Email change (Phase 2) -----------------------------------------------
1019
- /**
1020
- * Rétrocompat : délègue à `verifyOtp({ type: 'email_change' })`.
1021
- * CHANGEMENT ASSUMÉ (parité) : émet désormais `USER_UPDATED` (auparavant aucun
1022
- * événement) — voir CHANGELOG.
1023
- */
1024
- async confirmEmailChange(token) {
1025
- return this.verifyOtp({ type: "email_change", token });
1026
- }
1027
- // -- MFA recovery codes (Phase 2) -----------------------------------------
1028
- async regenerateRecoveryCodes(code) {
1029
- return this.request("POST", `${this.prefix}/mfa/recovery-codes/regenerate`, { code });
1030
- }
1031
- // -- Native mobile OAuth (Phase 2) ----------------------------------------
1032
- async signInWithIdToken(provider, idToken, accessToken) {
1033
- const res = await this.request("POST", `${this.prefix}/token`, {
1034
- provider,
1035
- id_token: idToken,
1036
- access_token: accessToken
1037
- });
1038
- if (res.data) {
1039
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
1040
- this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
1041
- }
1042
- return res;
1043
- }
1044
- // -- Project settings / public config --------------------------------------
1045
- /** GoTrue parity : GET /settings — config publique du projet (pas d'auth requise). */
1046
- async getSettings() {
1047
- return this.request("GET", `${this.prefix}/settings`);
1048
- }
1049
- /** Clé publiable (anon key) du projet — GET /anon-key (public, à l'image du modèle Supabase). */
1050
- async getAnonKey() {
1051
- return this.request("GET", `${this.prefix}/anon-key`);
1052
- }
1053
- // -- Password recovery (token consumption) ----------------------------------
1054
- /**
1055
- * Consomme le token de récupération de mot de passe et applique le nouveau mot de passe.
1056
- * Body confirmé au handler (`ResetPasswordRequest` — services/aura-auth/src/handlers/auth.rs) :
1057
- * `{ token, password }`. Révoque les anciennes sessions et renvoie une nouvelle TokenPair.
1058
- */
1059
- async resetPassword(token, newPassword) {
1060
- return this.verifyOtp({ type: "recovery", token, password: newPassword });
1061
- }
1062
- // -- Anonymous promotion -----------------------------------------------------
1063
- /**
1064
- * Convertit l'utilisateur anonyme courant (JWT requis) en compte régulier email+password.
1065
- * Body confirmé au handler (`PromoteAnonymousRequest`) : `{ email, password }`.
1066
- */
1067
- async promoteAnonymous(credentials) {
1068
- const res = await this.request("POST", `${this.prefix}/anonymous/promote`, credentials);
1069
- if (res.data) {
1070
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
1071
- this._emitEvent("USER_UPDATED", res.data.user, res.data.access_token);
1072
- }
1073
- return res;
1074
- }
1075
- // -- Magic link verification -------------------------------------------------
1076
- /**
1077
- * Consomme le token de magic link et renvoie une TokenPair.
1078
- * Body confirmé au handler (`VerifyMagicLinkRequest`) : `{ token }`.
1079
- */
1080
- async verifyMagicLink(token) {
1081
- return this.verifyOtp({ type: "magiclink", token });
1082
- }
1083
- };
1084
- var AuthAdminService = class extends BaseService {
1085
- get prefix() {
1086
- return `/v1/auth/${this.client.projectId}/admin`;
1087
- }
1088
- // -- Users ----------------------------------------------------------------
1089
- async listUsers(params) {
1090
- const query = new URLSearchParams();
1091
- if (params?.limit !== void 0) query.set("limit", String(params.limit));
1092
- if (params?.offset !== void 0) query.set("offset", String(params.offset));
1093
- if (params?.search) query.set("search", params.search);
1094
- const qs = query.toString();
1095
- return this.request("GET", `${this.prefix}/users${qs ? "?" + qs : ""}`);
1096
- }
1097
- async getUser(userId) {
1098
- return this.request("GET", `${this.prefix}/users/${userId}`);
1099
- }
1100
- async createUser(data) {
1101
- return this.request("POST", `${this.prefix}/users`, data);
1102
- }
1103
- async updateUser(userId, data) {
1104
- return this.request("PUT", `${this.prefix}/users/${userId}`, data);
1105
- }
1106
- async deleteUser(userId) {
1107
- return this.request("DELETE", `${this.prefix}/users/${userId}`);
1108
- }
1109
- async banUser(userId) {
1110
- return this.request("POST", `${this.prefix}/users/${userId}/ban`, {});
1111
- }
1112
- async unbanUser(userId) {
1113
- return this.request("POST", `${this.prefix}/users/${userId}/unban`, {});
1114
- }
1115
- async generateLink(userId, type) {
1116
- return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
1117
- }
1118
- async getUserStats() {
1119
- return this.request("GET", `${this.prefix}/users/stats`);
1120
- }
1121
- async listUserIdentities(userId) {
1122
- return this.request("GET", `${this.prefix}/users/${userId}/identities`);
1123
- }
1124
- // -- Sessions ---------------------------------------------------------------
1125
- async listSessions(params) {
1126
- const query = new URLSearchParams();
1127
- if (params?.userId !== void 0) query.set("user_id", params.userId);
1128
- if (params?.limit !== void 0) query.set("limit", String(params.limit));
1129
- if (params?.offset !== void 0) query.set("offset", String(params.offset));
1130
- const qs = query.toString();
1131
- return this.request("GET", `${this.prefix}/sessions${qs ? "?" + qs : ""}`);
1132
- }
1133
- async revokeSession(sessionId) {
1134
- return this.request("DELETE", `${this.prefix}/sessions/${sessionId}`);
1135
- }
1136
- // -- Audit log ------------------------------------------------------------
1137
- async getAuditLog(params) {
1138
- const query = new URLSearchParams();
1139
- if (params?.userId !== void 0) query.set("user_id", params.userId);
1140
- if (params?.limit !== void 0) query.set("limit", String(params.limit));
1141
- if (params?.offset !== void 0) query.set("offset", String(params.offset));
1142
- const qs = query.toString();
1143
- return this.request("GET", `${this.prefix}/audit-log${qs ? "?" + qs : ""}`);
1144
- }
1145
- // -- Settings -------------------------------------------------------------
1146
- async getSettings() {
1147
- return this.request("GET", `${this.prefix}/settings`);
1148
- }
1149
- async updateSettings(data) {
1150
- return this.request("PUT", `${this.prefix}/settings`, data);
1151
- }
1152
- // -- Email templates ------------------------------------------------------
1153
- async listEmailTemplates() {
1154
- return this.request("GET", `${this.prefix}/email-templates`);
1155
- }
1156
- async getEmailTemplate(type) {
1157
- return this.request("GET", `${this.prefix}/email-templates/${type}`);
1158
- }
1159
- async updateEmailTemplate(type, data) {
1160
- return this.request("PUT", `${this.prefix}/email-templates/${type}`, data);
1161
- }
1162
- async deleteEmailTemplate(type) {
1163
- return this.request("DELETE", `${this.prefix}/email-templates/${type}`);
1164
- }
1165
- async testEmailTemplate(type, body) {
1166
- return this.request("POST", `${this.prefix}/email-templates/${type}/test`, body);
1167
- }
1168
- };
1169
-
1170
- export { AuthAdminService, AuthService, SessionManager, isMfaChallenge };