@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/README.md +89 -7
- package/dist/index.cjs +347 -202
- package/dist/index.d.cts +160 -88
- package/dist/index.d.ts +160 -88
- package/dist/index.js +348 -203
- package/package.json +16 -14
package/dist/index.cjs
CHANGED
|
@@ -2,76 +2,110 @@
|
|
|
2
2
|
|
|
3
3
|
var core = require('@aurabase/core');
|
|
4
4
|
|
|
5
|
-
// src/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
39
|
-
this.
|
|
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
|
-
|
|
46
|
-
this.
|
|
30
|
+
async updateUser(userId, data) {
|
|
31
|
+
return this.request("PUT", `${this.prefix}/users/${userId}`, data);
|
|
47
32
|
}
|
|
48
|
-
|
|
49
|
-
this.
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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.
|
|
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
|
|
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
|
-
/**
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
687
|
-
this.
|
|
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, {
|
|
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, {
|
|
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
|
-
|
|
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
|
-
|
|
1077
|
-
this.
|
|
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(
|
|
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;
|