@aurabase/auth 0.4.1 → 0.5.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/dist/index.cjs CHANGED
@@ -2,76 +2,110 @@
2
2
 
3
3
  var core = require('@aurabase/core');
4
4
 
5
- // src/AuthService.ts
6
-
7
- // src/SessionManager.ts
8
- var SessionManager = class {
9
- opts;
10
- _interval = null;
11
- _channel = null;
12
- _active = false;
13
- _visHandler = null;
14
- constructor(opts) {
15
- this.opts = { tickMs: 3e4, ...opts };
5
+ // src/AuthAdminService.ts
6
+ var AuthAdminService = class extends core.BaseService {
7
+ /**
8
+ * Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
9
+ * la passerelle depuis le sous-domaine du projet (`inject_project_id`,
10
+ * `services/aura-gateway/src/middleware/project_subdomain.rs`).
11
+ */
12
+ get prefix() {
13
+ return "/v1/auth/admin";
16
14
  }
17
- get isActive() {
18
- return this._active;
15
+ // -- Users ----------------------------------------------------------------
16
+ async listUsers(params) {
17
+ const query = new URLSearchParams();
18
+ if (params?.limit !== void 0) query.set("limit", String(params.limit));
19
+ if (params?.offset !== void 0) query.set("offset", String(params.offset));
20
+ if (params?.search) query.set("search", params.search);
21
+ const qs = query.toString();
22
+ return this.request("GET", `${this.prefix}/users${qs ? "?" + qs : ""}`);
19
23
  }
20
- start() {
21
- if (!this.opts.enabled) return;
22
- if (typeof BroadcastChannel !== "undefined" && !this._channel) {
23
- this._channel = new BroadcastChannel(this.opts.channelName);
24
- this._channel.onmessage = (e) => this.opts.onRemoteMessage(e.data);
25
- }
26
- if (typeof document !== "undefined" && !this._visHandler) {
27
- this._visHandler = () => {
28
- if (document.hidden) {
29
- this._stopInterval();
30
- } else if (this._active) {
31
- this._startInterval();
32
- if (this.opts.isRefreshDue()) this.opts.onTick();
33
- }
34
- };
35
- document.addEventListener("visibilitychange", this._visHandler);
36
- }
24
+ async getUser(userId) {
25
+ return this.request("GET", `${this.prefix}/users/${userId}`);
37
26
  }
38
- setActive(active) {
39
- this._active = active;
40
- if (!this.opts.enabled) return;
41
- const hidden = typeof document !== "undefined" && document.hidden;
42
- if (active && !hidden) this._startInterval();
43
- else this._stopInterval();
27
+ async createUser(data) {
28
+ return this.request("POST", `${this.prefix}/users`, data);
44
29
  }
45
- broadcast(data) {
46
- this._channel?.postMessage(data);
30
+ async updateUser(userId, data) {
31
+ return this.request("PUT", `${this.prefix}/users/${userId}`, data);
47
32
  }
48
- stop() {
49
- this._stopInterval();
50
- this._active = false;
51
- if (this._channel) {
52
- try {
53
- this._channel.close();
54
- } catch {
55
- }
56
- this._channel = null;
57
- }
58
- if (this._visHandler && typeof document !== "undefined") {
59
- document.removeEventListener("visibilitychange", this._visHandler);
60
- this._visHandler = null;
61
- }
33
+ async deleteUser(userId) {
34
+ return this.request("DELETE", `${this.prefix}/users/${userId}`);
62
35
  }
63
- _startInterval() {
64
- if (this._interval) return;
65
- this._interval = setInterval(() => {
66
- if (this.opts.isRefreshDue()) this.opts.onTick();
67
- }, this.opts.tickMs);
68
- this._interval.unref?.();
36
+ async banUser(userId) {
37
+ return this.request("POST", `${this.prefix}/users/${userId}/ban`, {});
69
38
  }
70
- _stopInterval() {
71
- if (this._interval) {
72
- clearInterval(this._interval);
73
- this._interval = null;
74
- }
39
+ async unbanUser(userId) {
40
+ return this.request("POST", `${this.prefix}/users/${userId}/unban`, {});
41
+ }
42
+ async generateLink(userId, type) {
43
+ return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
44
+ }
45
+ async getUserStats() {
46
+ return this.request("GET", `${this.prefix}/users/stats`);
47
+ }
48
+ async listUserIdentities(userId) {
49
+ return this.request("GET", `${this.prefix}/users/${userId}/identities`);
50
+ }
51
+ // -- Sessions ---------------------------------------------------------------
52
+ async listSessions(params) {
53
+ const query = new URLSearchParams();
54
+ if (params?.userId !== void 0) query.set("user_id", params.userId);
55
+ if (params?.limit !== void 0) query.set("limit", String(params.limit));
56
+ if (params?.offset !== void 0) query.set("offset", String(params.offset));
57
+ const qs = query.toString();
58
+ return this.request("GET", `${this.prefix}/sessions${qs ? "?" + qs : ""}`);
59
+ }
60
+ async revokeSession(sessionId) {
61
+ return this.request("DELETE", `${this.prefix}/sessions/${sessionId}`);
62
+ }
63
+ // -- Audit log ------------------------------------------------------------
64
+ async getAuditLog(params) {
65
+ const query = new URLSearchParams();
66
+ if (params?.userId !== void 0) query.set("user_id", params.userId);
67
+ if (params?.limit !== void 0) query.set("limit", String(params.limit));
68
+ if (params?.offset !== void 0) query.set("offset", String(params.offset));
69
+ const qs = query.toString();
70
+ return this.request("GET", `${this.prefix}/audit-log${qs ? "?" + qs : ""}`);
71
+ }
72
+ // -- Settings -------------------------------------------------------------
73
+ async getSettings() {
74
+ return this.request("GET", `${this.prefix}/settings`);
75
+ }
76
+ /**
77
+ * Met à jour les réglages auth du projet (fusion partielle : seuls les champs fournis
78
+ * sont écrits, `COALESCE` côté backend).
79
+ *
80
+ * Le corps est un {@link AuthSettingsUpdate}, PAS un `Partial<AuthSettings>` : la vue de
81
+ * lecture ne sait pas porter les secrets write-only (`smtp_password`, `webhook_secret`,
82
+ * `captcha_secret_key`, `sms_api_key`/`sms_api_secret`, `hook_secret`) ni les drapeaux
83
+ * d'effacement (`smtp_clear`, `sms_clear`, `oauth_credentials_clear`), et porte à l'inverse
84
+ * des projections (`smtp_password_set`, `hook_secret_set`, `updated_at`…) que le backend
85
+ * REFUSE.
86
+ *
87
+ * Le serveur refuse aussi bien un champ inconnu qu'un `null` explicite — `400` dans les deux
88
+ * cas, le champ fautif est nommé. Ce n'est plus seulement un contrôle TypeScript : ne
89
+ * renvoyez donc pas tel quel un objet lu par {@link getSettings}.
90
+ */
91
+ async updateSettings(data) {
92
+ return this.request("PUT", `${this.prefix}/settings`, data);
93
+ }
94
+ // -- Email templates ------------------------------------------------------
95
+ async listEmailTemplates() {
96
+ return this.request("GET", `${this.prefix}/email-templates`);
97
+ }
98
+ async getEmailTemplate(type) {
99
+ return this.request("GET", `${this.prefix}/email-templates/${type}`);
100
+ }
101
+ async updateEmailTemplate(type, data) {
102
+ return this.request("PUT", `${this.prefix}/email-templates/${type}`, data);
103
+ }
104
+ async deleteEmailTemplate(type) {
105
+ return this.request("DELETE", `${this.prefix}/email-templates/${type}`);
106
+ }
107
+ async testEmailTemplate(type, body) {
108
+ return this.request("POST", `${this.prefix}/email-templates/${type}/test`, body);
75
109
  }
76
110
  };
77
111
 
@@ -286,10 +320,103 @@ async function codeChallengeS256(verifier) {
286
320
  return b64url(new Uint8Array(digest));
287
321
  }
288
322
 
323
+ // src/SessionManager.ts
324
+ var SessionManager = class {
325
+ opts;
326
+ _interval = null;
327
+ _channel = null;
328
+ _active = false;
329
+ _visHandler = null;
330
+ constructor(opts) {
331
+ this.opts = { tickMs: 3e4, ...opts };
332
+ }
333
+ get isActive() {
334
+ return this._active;
335
+ }
336
+ start() {
337
+ if (!this.opts.enabled) return;
338
+ if (typeof BroadcastChannel !== "undefined" && !this._channel) {
339
+ this._channel = new BroadcastChannel(this.opts.channelName);
340
+ this._channel.onmessage = (e) => this.opts.onRemoteMessage(e.data);
341
+ }
342
+ if (typeof document !== "undefined" && !this._visHandler) {
343
+ this._visHandler = () => {
344
+ if (document.hidden) {
345
+ this._stopInterval();
346
+ } else if (this._active) {
347
+ this._startInterval();
348
+ if (this.opts.isRefreshDue()) this.opts.onTick();
349
+ }
350
+ };
351
+ document.addEventListener("visibilitychange", this._visHandler);
352
+ }
353
+ }
354
+ setActive(active) {
355
+ this._active = active;
356
+ if (!this.opts.enabled) return;
357
+ const hidden = typeof document !== "undefined" && document.hidden;
358
+ if (active && !hidden) this._startInterval();
359
+ else this._stopInterval();
360
+ }
361
+ broadcast(data) {
362
+ this._channel?.postMessage(data);
363
+ }
364
+ stop() {
365
+ this._stopInterval();
366
+ this._active = false;
367
+ if (this._channel) {
368
+ try {
369
+ this._channel.close();
370
+ } catch {
371
+ }
372
+ this._channel = null;
373
+ }
374
+ if (this._visHandler && typeof document !== "undefined") {
375
+ document.removeEventListener("visibilitychange", this._visHandler);
376
+ this._visHandler = null;
377
+ }
378
+ }
379
+ _startInterval() {
380
+ if (this._interval) return;
381
+ this._interval = setInterval(() => {
382
+ if (this.opts.isRefreshDue()) this.opts.onTick();
383
+ }, this.opts.tickMs);
384
+ this._interval.unref?.();
385
+ }
386
+ _stopInterval() {
387
+ if (this._interval) {
388
+ clearInterval(this._interval);
389
+ this._interval = null;
390
+ }
391
+ }
392
+ };
393
+
289
394
  // src/AuthService.ts
290
395
  function isMfaChallenge(v) {
291
396
  return typeof v === "object" && v !== null && v.mfa_required === true;
292
397
  }
398
+ var OAUTH_FORWARDABLE_QUERY_PARAMS = /* @__PURE__ */ new Set([
399
+ "access_type",
400
+ "acr_values",
401
+ "claims_locales",
402
+ "display",
403
+ "hd",
404
+ "include_granted_scopes",
405
+ "login_hint",
406
+ "max_age",
407
+ "prompt",
408
+ "ui_locales"
409
+ ]);
410
+ var OAUTH_SDK_OWNED_QUERY_PARAMS = /* @__PURE__ */ new Set([
411
+ "project_id",
412
+ "redirect_uri",
413
+ // Alias de `redirect_uri` accepté par le backend (nom GoTrue, employé par les SDK
414
+ // Dart/Python/Rust) : il ne doit pas non plus arriver par `queryParams`.
415
+ "redirect_to",
416
+ "scopes",
417
+ "code_challenge",
418
+ "code_challenge_method"
419
+ ]);
293
420
  var AuthService = class extends core.BaseService {
294
421
  _stateListeners = /* @__PURE__ */ new Set();
295
422
  _sessionManager;
@@ -301,7 +428,7 @@ var AuthService = class extends core.BaseService {
301
428
  const browser = typeof window !== "undefined";
302
429
  const enabled = !!options?.forceEnabled || (options?.autoRefresh ?? true) && browser && (options?.persistSession ?? true);
303
430
  this._sessionManager = new SessionManager({
304
- channelName: `aura-${client.projectId}-auth`,
431
+ channelName: `aura-${core.clientScope(client.baseUrl)}-auth`,
305
432
  enabled,
306
433
  onTick: () => {
307
434
  void this.refreshSession();
@@ -332,6 +459,9 @@ var AuthService = class extends core.BaseService {
332
459
  * Détecte et consomme une session présente dans l'URL courante au boot (callback
333
460
  * OAuth), à l'image de `detectSessionInUrl` de supabase-js :
334
461
  * - fragment `#access_token=…&refresh_token=…` (flux implicit) → `setSession`.
462
+ * - fragment `#mfa_token=…` (atterrissage `{redirectTo}/oauth/mfa` d'un utilisateur MFA au
463
+ * retour d'OAuth) → émet `MFA_CHALLENGE` avec `mfaToken` (AUCUNE session posée : le défi
464
+ * reste à relever via `mfaVerify`).
335
465
  * - query `?token=…&type=recovery` (lien de récupération de mot de passe) → émet
336
466
  * `PASSWORD_RECOVERY` avec `recoveryToken` (AUCUNE session posée, PAS de `SIGNED_IN`).
337
467
  * - query `?code=…` (flux PKCE) → `exchangeCodeForSession` (relit le verifier).
@@ -360,6 +490,18 @@ var AuthService = class extends core.BaseService {
360
490
  }
361
491
  return;
362
492
  }
493
+ if (hash.includes("mfa_token")) {
494
+ const initiatedKey = this._oauthInitiatedKey;
495
+ if (!this.client.authStore.getItem(initiatedKey)) return;
496
+ this.client.authStore.removeItem(initiatedKey);
497
+ const frag = new URLSearchParams(hash.replace(/^#/, ""));
498
+ const mfaToken = frag.get("mfa_token");
499
+ if (mfaToken) {
500
+ this._emitEvent("MFA_CHALLENGE", null, null, { mfaToken });
501
+ this._cleanAuthParamsFromUrl();
502
+ }
503
+ return;
504
+ }
363
505
  {
364
506
  const qs = new URLSearchParams(search.replace(/^\?/, ""));
365
507
  const recoveryToken = qs.get("token");
@@ -448,12 +590,36 @@ var AuthService = class extends core.BaseService {
448
590
  this._unsubStore = null;
449
591
  this._stateListeners.clear();
450
592
  }
593
+ /**
594
+ * Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
595
+ * la passerelle depuis le sous-domaine du projet (`inject_project_id`,
596
+ * `services/aura-gateway/src/middleware/project_subdomain.rs`).
597
+ */
451
598
  get prefix() {
452
- return `/v1/auth/${this.client.projectId}`;
599
+ return "/v1/auth";
600
+ }
601
+ /**
602
+ * `project_id` de la SESSION en cours, lu dans le token stocké — seule référence de projet
603
+ * dont dispose encore le client (voir SEC-F2 dans `getClaims`). `null` s'il n'y a pas de
604
+ * session, si le token est illisible, ou s'il ne porte pas ce claim.
605
+ */
606
+ _sessionProjectId() {
607
+ const token = this.client.authStore.token;
608
+ if (!token) return null;
609
+ try {
610
+ const pid = decodeJwt(token).payload.project_id;
611
+ return typeof pid === "string" && pid !== "" ? pid : null;
612
+ } catch {
613
+ return null;
614
+ }
453
615
  }
454
- /** Clé project-scoped du `code_verifier` PKCE (anti-collision multi-projets). */
616
+ /**
617
+ * Clé du `code_verifier` PKCE, scopée par la PORTÉE du client (`clientScope`, dérivée de
618
+ * l'URL du projet) : deux clients de deux projets instanciés dans la MÊME page partagent
619
+ * un seul `localStorage`, et ne doivent pas se marcher dessus.
620
+ */
455
621
  get _pkceVerifierKey() {
456
- return `aura-${this.client.projectId}-pkce-code-verifier`;
622
+ return `aura-${core.clientScope(this.client.baseUrl)}-pkce-code-verifier`;
457
623
  }
458
624
  /**
459
625
  * Clé project-scoped du MARQUEUR d'initiation du flux OAuth implicit (SEC-F3).
@@ -463,7 +629,7 @@ var AuthService = class extends core.BaseService {
463
629
  * sera jamais adopté.
464
630
  */
465
631
  get _oauthInitiatedKey() {
466
- return `aura-${this.client.projectId}-oauth-implicit-initiated`;
632
+ return `aura-${core.clientScope(this.client.baseUrl)}-oauth-implicit-initiated`;
467
633
  }
468
634
  // -- Auth state change ------------------------------------------------------
469
635
  onAuthStateChange(callback) {
@@ -523,14 +689,56 @@ var AuthService = class extends core.BaseService {
523
689
  }
524
690
  return res;
525
691
  }
692
+ /**
693
+ * Démarre un flux OAuth : renvoie toujours `{ url }` (et redirige en plus le navigateur
694
+ * si un `window` existe).
695
+ *
696
+ * ### `options.queryParams` — contrat (N22)
697
+ *
698
+ * Les clés listées par {@link OAUTH_FORWARDABLE_QUERY_PARAMS} — `prompt`, `login_hint`,
699
+ * `display`, `ui_locales`, `max_age`, `acr_values`, `claims_locales`, `access_type`,
700
+ * `include_granted_scopes`, `hd` — sont **réellement transmises au provider**, jusque
701
+ * dans l'URL d'autorisation.
702
+ *
703
+ * Toute autre clé est **refusée** : la méthode renvoie
704
+ * `error.code === 'invalid_oauth_query_param'` et n'amorce aucune redirection. Rien
705
+ * n'est jamais accepté puis jeté en silence. Sont notamment refusés :
706
+ * - les paramètres du protocole (`state`, `nonce`, `client_id`, `response_type`,
707
+ * `scope`, `code_challenge`…) — ils sont calculés par le serveur ;
708
+ * - les paramètres construits par cette méthode (`redirect_uri`, `scopes`) — passez par
709
+ * `redirectTo`, `scopes` ou `flowType` ;
710
+ * - `project_id` : le projet est porté par l'URL du client, jamais par un paramètre ;
711
+ * - les variantes de casse (`Prompt`, `State`) et les clés inconnues.
712
+ *
713
+ * L'autorité est le **backend** (`OAUTH_FORWARDABLE_PARAMS` de
714
+ * `services/aura-auth/src/handlers/oauth.rs`), qui répond `400` sur les mêmes règles :
715
+ * l'endpoint de démarrage est public et appelable sans SDK. Ce filtre-ci n'est que son
716
+ * miroir, pour échouer avant la redirection ; un test de parité interdit qu'ils divergent.
717
+ *
718
+ * Les **valeurs**, elles, ne sont validées que par le backend (non vide, sans caractère
719
+ * de contrôle, 512 caractères au plus, jamais deux fois la même clé) — un manquement
720
+ * donne un `400` au démarrage du flux, pas une erreur de cette méthode.
721
+ */
526
722
  async signInWithOAuth(provider, options) {
527
723
  const params = new URLSearchParams();
528
- params.set("project_id", this.client.projectId);
529
724
  if (options?.redirectTo) params.set("redirect_uri", options.redirectTo);
530
725
  if (options?.scopes) params.set("scopes", options.scopes);
531
726
  if (options?.queryParams) {
532
727
  for (const [k, v] of Object.entries(options.queryParams)) {
533
- params.set(k, v);
728
+ if (OAUTH_FORWARDABLE_QUERY_PARAMS.has(k)) {
729
+ params.set(k, v);
730
+ continue;
731
+ }
732
+ const raison = OAUTH_SDK_OWNED_QUERY_PARAMS.has(k) ? `il est construit par signInWithOAuth (utilisez l'option d\xE9di\xE9e : ${k === "redirect_uri" || k === "redirect_to" ? "redirectTo" : k === "scopes" ? "scopes" : k === "project_id" ? "l'URL du projet pass\xE9e \xE0 createClient()" : "flowType:'pkce'"})` : `il n'est pas transmis au provider. Seuls ces param\xE8tres d'exp\xE9rience de connexion le sont : ${[...OAUTH_FORWARDABLE_QUERY_PARAMS].join(", ")}`;
733
+ return {
734
+ data: null,
735
+ error: new core.AuraError(400, {
736
+ error: {
737
+ code: "invalid_oauth_query_param",
738
+ message: `signInWithOAuth: queryParams.${k} refus\xE9 \u2014 ${raison}.`
739
+ }
740
+ })
741
+ };
534
742
  }
535
743
  }
536
744
  if (options?.flowType === "pkce") {
@@ -591,9 +799,14 @@ var AuthService = class extends core.BaseService {
591
799
  this._emitEvent("SIGNED_OUT", null, null);
592
800
  return { data: null, error: null };
593
801
  }
594
- const res = await this.request("POST", `${this.prefix}/logout`, {
595
- refresh_token: refreshToken
596
- }, { skipAuthRefresh: true });
802
+ const res = await this.request(
803
+ "POST",
804
+ `${this.prefix}/logout`,
805
+ {
806
+ refresh_token: refreshToken
807
+ },
808
+ { skipAuthRefresh: true }
809
+ );
597
810
  this.client.authStore.clear();
598
811
  this._emitEvent("SIGNED_OUT", null, null);
599
812
  return res;
@@ -621,11 +834,11 @@ var AuthService = class extends core.BaseService {
621
834
  * `fetch` manuels côté app). Pour la session complète, lire `client.authStore`
622
835
  * directement (`.token`, `.refreshToken`, `.user`, `.isValid`).
623
836
  */
624
- async getSession() {
625
- return {
837
+ getSession() {
838
+ return Promise.resolve({
626
839
  accessToken: this.client.authStore.token,
627
840
  user: this.client.authStore.user
628
- };
841
+ });
629
842
  }
630
843
  async setSession(accessToken, refreshToken) {
631
844
  const store = this.client.authStore;
@@ -658,6 +871,16 @@ var AuthService = class extends core.BaseService {
658
871
  restore();
659
872
  return res;
660
873
  }
874
+ /**
875
+ * Échange le code d'autorisation PKCE contre une session.
876
+ *
877
+ * Renvoie un `LoginResponse`, PAS un `TokenPair` : si l'utilisateur a le MFA actif, le
878
+ * backend répond `{ mfa_required: true, mfa_token }` (gate « C1-bis » de
879
+ * `POST /oauth/pkce`) au lieu d'une paire de jetons. Dans ce cas AUCUNE session n'est
880
+ * enregistrée et `MFA_CHALLENGE` est émis avec le `mfaToken` : l'application demande le
881
+ * code TOTP puis appelle `mfaVerify({ mfa_token, code })`. Discriminez avec
882
+ * {@link isMfaChallenge}.
883
+ */
661
884
  async exchangeCodeForSession(authCode, codeVerifier) {
662
885
  return this._exchangeCode(authCode, codeVerifier, "SIGNED_IN");
663
886
  }
@@ -673,7 +896,10 @@ var AuthService = class extends core.BaseService {
673
896
  return {
674
897
  data: null,
675
898
  error: new core.AuraError(0, {
676
- error: { code: "no_code_verifier", message: "No PKCE code_verifier available (not provided and none stored)" }
899
+ error: {
900
+ code: "no_code_verifier",
901
+ message: "No PKCE code_verifier available (not provided and none stored)"
902
+ }
677
903
  })
678
904
  };
679
905
  }
@@ -682,9 +908,18 @@ var AuthService = class extends core.BaseService {
682
908
  auth_code: authCode,
683
909
  code_verifier: verifier
684
910
  });
911
+ if (res.data && isMfaChallenge(res.data)) {
912
+ const mfa = res.data;
913
+ this._emitEvent("MFA_CHALLENGE", null, null, {
914
+ mfaToken: mfa.mfa_token,
915
+ mfaSetupRequired: mfa.mfa_setup_required
916
+ });
917
+ return res;
918
+ }
685
919
  if (res.data) {
686
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
687
- this._emitEvent(successEvent, res.data.user, res.data.access_token);
920
+ const tp = res.data;
921
+ this.client.authStore.save(tp.access_token, tp.refresh_token, tp.user);
922
+ this._emitEvent(successEvent, tp.user, tp.access_token);
688
923
  }
689
924
  return res;
690
925
  } finally {
@@ -712,7 +947,9 @@ var AuthService = class extends core.BaseService {
712
947
  if (!rt) {
713
948
  return {
714
949
  data: null,
715
- error: new core.AuraError(0, { error: { code: "no_session", message: "No refresh token available" } })
950
+ error: new core.AuraError(0, {
951
+ error: { code: "no_session", message: "No refresh token available" }
952
+ })
716
953
  };
717
954
  }
718
955
  const res = await this.request(
@@ -786,7 +1023,10 @@ var AuthService = class extends core.BaseService {
786
1023
  allowExpired: options?.allowExpired,
787
1024
  // LOT 3 (I8) : relie le callback générique de jwks.ts au hook d'observabilité
788
1025
  // du client (onEvent/debug). jwks.ts reste sans dépendance vers @aurabase/core.
789
- onEvent: (baseUrl) => core.emitAuraEvent(this.client.onEvent, this.client.debug, { kind: "jwks-fetch-failed", baseUrl }),
1026
+ onEvent: (baseUrl) => core.emitAuraEvent(this.client.onEvent, this.client.debug, {
1027
+ kind: "jwks-fetch-failed",
1028
+ baseUrl
1029
+ }),
790
1030
  // P2-1 : borne le fetch JWKS (et la lecture de son corps) sur le même
791
1031
  // budget que les autres requêtes du client. Sans elle, un serveur JWKS
792
1032
  // muet figeait `getClaims` définitivement (single-flight `inflight`).
@@ -814,7 +1054,8 @@ var AuthService = class extends core.BaseService {
814
1054
  const issAudErr = checkIssAud(claims);
815
1055
  if (issAudErr) return { data: null, error: issAudErr };
816
1056
  const claimProject = claims.project_id;
817
- if (typeof claimProject === "string" && claimProject !== this.client.projectId) {
1057
+ const sessionProject = this._sessionProjectId();
1058
+ if (typeof claimProject === "string" && sessionProject !== null && claimProject !== sessionProject) {
818
1059
  return {
819
1060
  data: null,
820
1061
  error: new core.AuraError(0, {
@@ -979,17 +1220,6 @@ var AuthService = class extends core.BaseService {
979
1220
  }
980
1221
  return res;
981
1222
  }
982
- case "invite":
983
- case "phone_change":
984
- return {
985
- data: null,
986
- error: new core.AuraError(0, {
987
- error: {
988
- code: "otp_type_unsupported",
989
- message: `verifyOtp: le type '${params.type}' n'est pas support\xE9 (aucun endpoint backend).`
990
- }
991
- })
992
- };
993
1223
  default:
994
1224
  return {
995
1225
  data: null,
@@ -1012,10 +1242,6 @@ var AuthService = class extends core.BaseService {
1012
1242
  return res;
1013
1243
  }
1014
1244
  // -- Email verification -----------------------------------------------------
1015
- /** Rétrocompat : délègue à `verifyOtp({ type: 'signup' })` (POST /verify-email). */
1016
- async verifyEmail(token) {
1017
- return this.verifyOtp({ type: "signup", token });
1018
- }
1019
1245
  async resendVerificationEmail(email) {
1020
1246
  const e = email ?? this.client.authStore.user?.email;
1021
1247
  if (!e) {
@@ -1030,18 +1256,10 @@ var AuthService = class extends core.BaseService {
1030
1256
  async signInWithEmailOtp(email) {
1031
1257
  return this.request("POST", `${this.prefix}/email-otp/send`, { email });
1032
1258
  }
1033
- /** Rétrocompat : délègue à `verifyOtp({ type: 'email' })` (POST /email-otp/verify). */
1034
- async verifyEmailOtp(email, code) {
1035
- return this.verifyOtp({ type: "email", email, token: code });
1036
- }
1037
1259
  // -- SMS OTP (Phase 2) ----------------------------------------------------
1038
1260
  async signInWithSmsOtp(phone) {
1039
1261
  return this.request("POST", `${this.prefix}/sms/send`, { phone });
1040
1262
  }
1041
- /** Rétrocompat : délègue à `verifyOtp({ type: 'sms' })` (POST /sms/verify). */
1042
- async verifySmsOtp(phone, code) {
1043
- return this.verifyOtp({ type: "sms", phone, token: code });
1044
- }
1045
1263
  // -- Identity linking (Phase 2) -------------------------------------------
1046
1264
  async listIdentities() {
1047
1265
  return this.request("GET", `${this.prefix}/user/identities`);
@@ -1052,29 +1270,37 @@ var AuthService = class extends core.BaseService {
1052
1270
  async unlinkIdentity(identityId) {
1053
1271
  return this.request("DELETE", `${this.prefix}/user/identities/${identityId}`);
1054
1272
  }
1055
- // -- Email change (Phase 2) -----------------------------------------------
1056
- /**
1057
- * Rétrocompat : délègue à `verifyOtp({ type: 'email_change' })`.
1058
- * CHANGEMENT ASSUMÉ (parité) : émet désormais `USER_UPDATED` (auparavant aucun
1059
- * événement) — voir CHANGELOG.
1060
- */
1061
- async confirmEmailChange(token) {
1062
- return this.verifyOtp({ type: "email_change", token });
1063
- }
1064
1273
  // -- MFA recovery codes (Phase 2) -----------------------------------------
1065
1274
  async regenerateRecoveryCodes(code) {
1066
1275
  return this.request("POST", `${this.prefix}/mfa/recovery-codes/regenerate`, { code });
1067
1276
  }
1068
1277
  // -- Native mobile OAuth (Phase 2) ----------------------------------------
1278
+ /**
1279
+ * Échange un ID token de provider (Google/Apple, mobile natif) contre une session.
1280
+ *
1281
+ * Renvoie un `LoginResponse`, PAS un `TokenPair` : comme partout ailleurs, un utilisateur
1282
+ * avec MFA actif reçoit `{ mfa_required: true, mfa_token }` et AUCUNE session n'est
1283
+ * enregistrée — `MFA_CHALLENGE` est émis, à relever par `mfaVerify`. Discriminez avec
1284
+ * {@link isMfaChallenge}.
1285
+ */
1069
1286
  async signInWithIdToken(provider, idToken, accessToken) {
1070
1287
  const res = await this.request("POST", `${this.prefix}/token`, {
1071
1288
  provider,
1072
1289
  id_token: idToken,
1073
1290
  access_token: accessToken
1074
1291
  });
1292
+ if (res.data && isMfaChallenge(res.data)) {
1293
+ const mfa = res.data;
1294
+ this._emitEvent("MFA_CHALLENGE", null, null, {
1295
+ mfaToken: mfa.mfa_token,
1296
+ mfaSetupRequired: mfa.mfa_setup_required
1297
+ });
1298
+ return res;
1299
+ }
1075
1300
  if (res.data) {
1076
- this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
1077
- this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
1301
+ const tp = res.data;
1302
+ this.client.authStore.save(tp.access_token, tp.refresh_token, tp.user);
1303
+ this._emitEvent("SIGNED_IN", tp.user, tp.access_token);
1078
1304
  }
1079
1305
  return res;
1080
1306
  }
@@ -1102,7 +1328,11 @@ var AuthService = class extends core.BaseService {
1102
1328
  * Body confirmé au handler (`PromoteAnonymousRequest`) : `{ email, password }`.
1103
1329
  */
1104
1330
  async promoteAnonymous(credentials) {
1105
- const res = await this.request("POST", `${this.prefix}/anonymous/promote`, credentials);
1331
+ const res = await this.request(
1332
+ "POST",
1333
+ `${this.prefix}/anonymous/promote`,
1334
+ credentials
1335
+ );
1106
1336
  if (res.data) {
1107
1337
  this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
1108
1338
  this._emitEvent("USER_UPDATED", res.data.user, res.data.access_token);
@@ -1118,91 +1348,6 @@ var AuthService = class extends core.BaseService {
1118
1348
  return this.verifyOtp({ type: "magiclink", token });
1119
1349
  }
1120
1350
  };
1121
- var AuthAdminService = class extends core.BaseService {
1122
- get prefix() {
1123
- return `/v1/auth/${this.client.projectId}/admin`;
1124
- }
1125
- // -- Users ----------------------------------------------------------------
1126
- async listUsers(params) {
1127
- const query = new URLSearchParams();
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
- if (params?.search) query.set("search", params.search);
1131
- const qs = query.toString();
1132
- return this.request("GET", `${this.prefix}/users${qs ? "?" + qs : ""}`);
1133
- }
1134
- async getUser(userId) {
1135
- return this.request("GET", `${this.prefix}/users/${userId}`);
1136
- }
1137
- async createUser(data) {
1138
- return this.request("POST", `${this.prefix}/users`, data);
1139
- }
1140
- async updateUser(userId, data) {
1141
- return this.request("PUT", `${this.prefix}/users/${userId}`, data);
1142
- }
1143
- async deleteUser(userId) {
1144
- return this.request("DELETE", `${this.prefix}/users/${userId}`);
1145
- }
1146
- async banUser(userId) {
1147
- return this.request("POST", `${this.prefix}/users/${userId}/ban`, {});
1148
- }
1149
- async unbanUser(userId) {
1150
- return this.request("POST", `${this.prefix}/users/${userId}/unban`, {});
1151
- }
1152
- async generateLink(userId, type) {
1153
- return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
1154
- }
1155
- async getUserStats() {
1156
- return this.request("GET", `${this.prefix}/users/stats`);
1157
- }
1158
- async listUserIdentities(userId) {
1159
- return this.request("GET", `${this.prefix}/users/${userId}/identities`);
1160
- }
1161
- // -- Sessions ---------------------------------------------------------------
1162
- async listSessions(params) {
1163
- const query = new URLSearchParams();
1164
- if (params?.userId !== void 0) query.set("user_id", params.userId);
1165
- if (params?.limit !== void 0) query.set("limit", String(params.limit));
1166
- if (params?.offset !== void 0) query.set("offset", String(params.offset));
1167
- const qs = query.toString();
1168
- return this.request("GET", `${this.prefix}/sessions${qs ? "?" + qs : ""}`);
1169
- }
1170
- async revokeSession(sessionId) {
1171
- return this.request("DELETE", `${this.prefix}/sessions/${sessionId}`);
1172
- }
1173
- // -- Audit log ------------------------------------------------------------
1174
- async getAuditLog(params) {
1175
- const query = new URLSearchParams();
1176
- if (params?.userId !== void 0) query.set("user_id", params.userId);
1177
- if (params?.limit !== void 0) query.set("limit", String(params.limit));
1178
- if (params?.offset !== void 0) query.set("offset", String(params.offset));
1179
- const qs = query.toString();
1180
- return this.request("GET", `${this.prefix}/audit-log${qs ? "?" + qs : ""}`);
1181
- }
1182
- // -- Settings -------------------------------------------------------------
1183
- async getSettings() {
1184
- return this.request("GET", `${this.prefix}/settings`);
1185
- }
1186
- async updateSettings(data) {
1187
- return this.request("PUT", `${this.prefix}/settings`, data);
1188
- }
1189
- // -- Email templates ------------------------------------------------------
1190
- async listEmailTemplates() {
1191
- return this.request("GET", `${this.prefix}/email-templates`);
1192
- }
1193
- async getEmailTemplate(type) {
1194
- return this.request("GET", `${this.prefix}/email-templates/${type}`);
1195
- }
1196
- async updateEmailTemplate(type, data) {
1197
- return this.request("PUT", `${this.prefix}/email-templates/${type}`, data);
1198
- }
1199
- async deleteEmailTemplate(type) {
1200
- return this.request("DELETE", `${this.prefix}/email-templates/${type}`);
1201
- }
1202
- async testEmailTemplate(type, body) {
1203
- return this.request("POST", `${this.prefix}/email-templates/${type}/test`, body);
1204
- }
1205
- };
1206
1351
 
1207
1352
  exports.AuthAdminService = AuthAdminService;
1208
1353
  exports.AuthService = AuthService;