@aurabase/auth 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -10
- package/dist/index.cjs +391 -211
- package/dist/index.d.cts +160 -88
- package/dist/index.d.ts +160 -88
- package/dist/index.js +392 -212
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -1,75 +1,109 @@
|
|
|
1
|
-
import { BaseService, isTokenExpired, AuraError, refreshOnce, emitAuraEvent } from '@aurabase/core';
|
|
1
|
+
import { BaseService, isTokenExpired, clientScope, AuraError, refreshOnce, DEFAULT_REQUEST_TIMEOUT_MS, emitAuraEvent } from '@aurabase/core';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
constructor(opts) {
|
|
13
|
-
this.opts = { tickMs: 3e4, ...opts };
|
|
3
|
+
// src/AuthAdminService.ts
|
|
4
|
+
var AuthAdminService = class extends BaseService {
|
|
5
|
+
/**
|
|
6
|
+
* Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
|
|
7
|
+
* la passerelle depuis le sous-domaine du projet (`inject_project_id`,
|
|
8
|
+
* `services/aura-gateway/src/middleware/project_subdomain.rs`).
|
|
9
|
+
*/
|
|
10
|
+
get prefix() {
|
|
11
|
+
return "/v1/auth/admin";
|
|
14
12
|
}
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
// -- Users ----------------------------------------------------------------
|
|
14
|
+
async listUsers(params) {
|
|
15
|
+
const query = new URLSearchParams();
|
|
16
|
+
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
17
|
+
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
18
|
+
if (params?.search) query.set("search", params.search);
|
|
19
|
+
const qs = query.toString();
|
|
20
|
+
return this.request("GET", `${this.prefix}/users${qs ? "?" + qs : ""}`);
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
}
|
|
22
|
+
async getUser(userId) {
|
|
23
|
+
return this.request("GET", `${this.prefix}/users/${userId}`);
|
|
35
24
|
}
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
-
if (!this.opts.enabled) return;
|
|
39
|
-
const hidden = typeof document !== "undefined" && document.hidden;
|
|
40
|
-
if (active && !hidden) this._startInterval();
|
|
41
|
-
else this._stopInterval();
|
|
25
|
+
async createUser(data) {
|
|
26
|
+
return this.request("POST", `${this.prefix}/users`, data);
|
|
42
27
|
}
|
|
43
|
-
|
|
44
|
-
this.
|
|
28
|
+
async updateUser(userId, data) {
|
|
29
|
+
return this.request("PUT", `${this.prefix}/users/${userId}`, data);
|
|
45
30
|
}
|
|
46
|
-
|
|
47
|
-
this.
|
|
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
|
-
}
|
|
31
|
+
async deleteUser(userId) {
|
|
32
|
+
return this.request("DELETE", `${this.prefix}/users/${userId}`);
|
|
60
33
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this._interval = setInterval(() => {
|
|
64
|
-
if (this.opts.isRefreshDue()) this.opts.onTick();
|
|
65
|
-
}, this.opts.tickMs);
|
|
66
|
-
this._interval.unref?.();
|
|
34
|
+
async banUser(userId) {
|
|
35
|
+
return this.request("POST", `${this.prefix}/users/${userId}/ban`, {});
|
|
67
36
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
37
|
+
async unbanUser(userId) {
|
|
38
|
+
return this.request("POST", `${this.prefix}/users/${userId}/unban`, {});
|
|
39
|
+
}
|
|
40
|
+
async generateLink(userId, type) {
|
|
41
|
+
return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
|
|
42
|
+
}
|
|
43
|
+
async getUserStats() {
|
|
44
|
+
return this.request("GET", `${this.prefix}/users/stats`);
|
|
45
|
+
}
|
|
46
|
+
async listUserIdentities(userId) {
|
|
47
|
+
return this.request("GET", `${this.prefix}/users/${userId}/identities`);
|
|
48
|
+
}
|
|
49
|
+
// -- Sessions ---------------------------------------------------------------
|
|
50
|
+
async listSessions(params) {
|
|
51
|
+
const query = new URLSearchParams();
|
|
52
|
+
if (params?.userId !== void 0) query.set("user_id", params.userId);
|
|
53
|
+
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
54
|
+
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
55
|
+
const qs = query.toString();
|
|
56
|
+
return this.request("GET", `${this.prefix}/sessions${qs ? "?" + qs : ""}`);
|
|
57
|
+
}
|
|
58
|
+
async revokeSession(sessionId) {
|
|
59
|
+
return this.request("DELETE", `${this.prefix}/sessions/${sessionId}`);
|
|
60
|
+
}
|
|
61
|
+
// -- Audit log ------------------------------------------------------------
|
|
62
|
+
async getAuditLog(params) {
|
|
63
|
+
const query = new URLSearchParams();
|
|
64
|
+
if (params?.userId !== void 0) query.set("user_id", params.userId);
|
|
65
|
+
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
66
|
+
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
67
|
+
const qs = query.toString();
|
|
68
|
+
return this.request("GET", `${this.prefix}/audit-log${qs ? "?" + qs : ""}`);
|
|
69
|
+
}
|
|
70
|
+
// -- Settings -------------------------------------------------------------
|
|
71
|
+
async getSettings() {
|
|
72
|
+
return this.request("GET", `${this.prefix}/settings`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Met à jour les réglages auth du projet (fusion partielle : seuls les champs fournis
|
|
76
|
+
* sont écrits, `COALESCE` côté backend).
|
|
77
|
+
*
|
|
78
|
+
* Le corps est un {@link AuthSettingsUpdate}, PAS un `Partial<AuthSettings>` : la vue de
|
|
79
|
+
* lecture ne sait pas porter les secrets write-only (`smtp_password`, `webhook_secret`,
|
|
80
|
+
* `captcha_secret_key`, `sms_api_key`/`sms_api_secret`, `hook_secret`) ni les drapeaux
|
|
81
|
+
* d'effacement (`smtp_clear`, `sms_clear`, `oauth_credentials_clear`), et porte à l'inverse
|
|
82
|
+
* des projections (`smtp_password_set`, `hook_secret_set`, `updated_at`…) que le backend
|
|
83
|
+
* REFUSE.
|
|
84
|
+
*
|
|
85
|
+
* Le serveur refuse aussi bien un champ inconnu qu'un `null` explicite — `400` dans les deux
|
|
86
|
+
* cas, le champ fautif est nommé. Ce n'est plus seulement un contrôle TypeScript : ne
|
|
87
|
+
* renvoyez donc pas tel quel un objet lu par {@link getSettings}.
|
|
88
|
+
*/
|
|
89
|
+
async updateSettings(data) {
|
|
90
|
+
return this.request("PUT", `${this.prefix}/settings`, data);
|
|
91
|
+
}
|
|
92
|
+
// -- Email templates ------------------------------------------------------
|
|
93
|
+
async listEmailTemplates() {
|
|
94
|
+
return this.request("GET", `${this.prefix}/email-templates`);
|
|
95
|
+
}
|
|
96
|
+
async getEmailTemplate(type) {
|
|
97
|
+
return this.request("GET", `${this.prefix}/email-templates/${type}`);
|
|
98
|
+
}
|
|
99
|
+
async updateEmailTemplate(type, data) {
|
|
100
|
+
return this.request("PUT", `${this.prefix}/email-templates/${type}`, data);
|
|
101
|
+
}
|
|
102
|
+
async deleteEmailTemplate(type) {
|
|
103
|
+
return this.request("DELETE", `${this.prefix}/email-templates/${type}`);
|
|
104
|
+
}
|
|
105
|
+
async testEmailTemplate(type, body) {
|
|
106
|
+
return this.request("POST", `${this.prefix}/email-templates/${type}/test`, body);
|
|
73
107
|
}
|
|
74
108
|
};
|
|
75
109
|
|
|
@@ -114,14 +148,30 @@ function parseMaxAge(cc) {
|
|
|
114
148
|
const m = cc?.match(/max-age=(\d+)/);
|
|
115
149
|
return m ? Number(m[1]) * 1e3 : null;
|
|
116
150
|
}
|
|
117
|
-
function fetchJwksDeduped(state, fetchFn, baseUrl) {
|
|
151
|
+
function fetchJwksDeduped(state, fetchFn, baseUrl, timeoutMs) {
|
|
118
152
|
const existing = state.inflight.get(baseUrl);
|
|
119
153
|
if (existing) return existing;
|
|
120
154
|
const p = (async () => {
|
|
155
|
+
let timer;
|
|
156
|
+
let bodyTimer;
|
|
121
157
|
try {
|
|
122
|
-
|
|
158
|
+
let signal;
|
|
159
|
+
if (timeoutMs > 0 && typeof AbortController !== "undefined") {
|
|
160
|
+
const ctrl = new AbortController();
|
|
161
|
+
timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
162
|
+
timer.unref?.();
|
|
163
|
+
signal = ctrl.signal;
|
|
164
|
+
}
|
|
165
|
+
const res = await fetchFn(`${baseUrl}/.well-known/jwks.json`, signal ? { signal } : {});
|
|
123
166
|
if (!res.ok) throw new Error("jwks_fetch_failed");
|
|
124
|
-
const body = await
|
|
167
|
+
const body = await Promise.race([
|
|
168
|
+
res.json(),
|
|
169
|
+
new Promise((_resolve, reject) => {
|
|
170
|
+
if (!(timeoutMs > 0)) return;
|
|
171
|
+
bodyTimer = setTimeout(() => reject(new Error("jwks_body_timeout")), timeoutMs);
|
|
172
|
+
bodyTimer.unref?.();
|
|
173
|
+
})
|
|
174
|
+
]);
|
|
125
175
|
const parsed = parseMaxAge(res.headers.get("Cache-Control")) ?? DEFAULT_MAX_AGE_MS;
|
|
126
176
|
const maxAge = Math.min(parsed, MAX_AGE_CAP_MS);
|
|
127
177
|
const keys = body.keys ?? [];
|
|
@@ -131,12 +181,15 @@ function fetchJwksDeduped(state, fetchFn, baseUrl) {
|
|
|
131
181
|
} catch {
|
|
132
182
|
state.lastFailedAt.set(baseUrl, Date.now());
|
|
133
183
|
throw new Error("jwks_fetch_failed");
|
|
184
|
+
} finally {
|
|
185
|
+
if (timer) clearTimeout(timer);
|
|
186
|
+
if (bodyTimer) clearTimeout(bodyTimer);
|
|
134
187
|
}
|
|
135
188
|
})().finally(() => state.inflight.delete(baseUrl));
|
|
136
189
|
state.inflight.set(baseUrl, p);
|
|
137
190
|
return p;
|
|
138
191
|
}
|
|
139
|
-
async function loadJwks(state, fetchFn, baseUrl) {
|
|
192
|
+
async function loadJwks(state, fetchFn, baseUrl, timeoutMs) {
|
|
140
193
|
const now = Date.now();
|
|
141
194
|
const c = state.cache.get(baseUrl);
|
|
142
195
|
if (c && now - c.fetchedAt < c.maxAgeMs) return c.keys;
|
|
@@ -144,9 +197,9 @@ async function loadJwks(state, fetchFn, baseUrl) {
|
|
|
144
197
|
if (failedAt !== void 0 && now - failedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
145
198
|
throw new Error("jwks_fetch_failed");
|
|
146
199
|
}
|
|
147
|
-
return fetchJwksDeduped(state, fetchFn, baseUrl);
|
|
200
|
+
return fetchJwksDeduped(state, fetchFn, baseUrl, timeoutMs);
|
|
148
201
|
}
|
|
149
|
-
async function forcedRefetchJwks(state, fetchFn, baseUrl) {
|
|
202
|
+
async function forcedRefetchJwks(state, fetchFn, baseUrl, timeoutMs) {
|
|
150
203
|
const now = Date.now();
|
|
151
204
|
const last = state.lastForcedFetchAt.get(baseUrl);
|
|
152
205
|
if (last !== void 0 && now - last < FORCED_REFETCH_COOLDOWN_MS) return null;
|
|
@@ -155,7 +208,7 @@ async function forcedRefetchJwks(state, fetchFn, baseUrl) {
|
|
|
155
208
|
if (failedAt !== void 0 && now - failedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
156
209
|
throw new Error("jwks_fetch_failed");
|
|
157
210
|
}
|
|
158
|
-
return fetchJwksDeduped(state, fetchFn, baseUrl);
|
|
211
|
+
return fetchJwksDeduped(state, fetchFn, baseUrl, timeoutMs);
|
|
159
212
|
}
|
|
160
213
|
function algParams(alg) {
|
|
161
214
|
if (alg === "RS256") {
|
|
@@ -182,11 +235,12 @@ async function verifyJwtLocal(scope, fetchFn, baseUrl, jwt, opts = {}) {
|
|
|
182
235
|
const params = algParams(d.header.alg);
|
|
183
236
|
if (!params) return { ok: false, code: "unsupported_alg" };
|
|
184
237
|
const state = stateFor(scope);
|
|
238
|
+
const timeoutMs = opts.timeoutMs ?? 0;
|
|
185
239
|
const matchesAlg = (k) => d.header.alg === "RS256" ? k.kty === "RSA" : k.kty === "EC";
|
|
186
240
|
const candidateKeys = (keys2) => d.header.kid ? keys2.filter((k) => k.kid === d.header.kid && matchesAlg(k)) : keys2.filter((k) => matchesAlg(k));
|
|
187
241
|
let keys;
|
|
188
242
|
try {
|
|
189
|
-
keys = await loadJwks(state, fetchFn, baseUrl);
|
|
243
|
+
keys = await loadJwks(state, fetchFn, baseUrl, timeoutMs);
|
|
190
244
|
} catch {
|
|
191
245
|
try {
|
|
192
246
|
opts.onEvent?.(baseUrl);
|
|
@@ -198,7 +252,7 @@ async function verifyJwtLocal(scope, fetchFn, baseUrl, jwt, opts = {}) {
|
|
|
198
252
|
if (candidates.length === 0 && d.header.kid) {
|
|
199
253
|
let refetched;
|
|
200
254
|
try {
|
|
201
|
-
refetched = await forcedRefetchJwks(state, fetchFn, baseUrl);
|
|
255
|
+
refetched = await forcedRefetchJwks(state, fetchFn, baseUrl, timeoutMs);
|
|
202
256
|
} catch {
|
|
203
257
|
try {
|
|
204
258
|
opts.onEvent?.(baseUrl);
|
|
@@ -264,10 +318,103 @@ async function codeChallengeS256(verifier) {
|
|
|
264
318
|
return b64url(new Uint8Array(digest));
|
|
265
319
|
}
|
|
266
320
|
|
|
321
|
+
// src/SessionManager.ts
|
|
322
|
+
var SessionManager = class {
|
|
323
|
+
opts;
|
|
324
|
+
_interval = null;
|
|
325
|
+
_channel = null;
|
|
326
|
+
_active = false;
|
|
327
|
+
_visHandler = null;
|
|
328
|
+
constructor(opts) {
|
|
329
|
+
this.opts = { tickMs: 3e4, ...opts };
|
|
330
|
+
}
|
|
331
|
+
get isActive() {
|
|
332
|
+
return this._active;
|
|
333
|
+
}
|
|
334
|
+
start() {
|
|
335
|
+
if (!this.opts.enabled) return;
|
|
336
|
+
if (typeof BroadcastChannel !== "undefined" && !this._channel) {
|
|
337
|
+
this._channel = new BroadcastChannel(this.opts.channelName);
|
|
338
|
+
this._channel.onmessage = (e) => this.opts.onRemoteMessage(e.data);
|
|
339
|
+
}
|
|
340
|
+
if (typeof document !== "undefined" && !this._visHandler) {
|
|
341
|
+
this._visHandler = () => {
|
|
342
|
+
if (document.hidden) {
|
|
343
|
+
this._stopInterval();
|
|
344
|
+
} else if (this._active) {
|
|
345
|
+
this._startInterval();
|
|
346
|
+
if (this.opts.isRefreshDue()) this.opts.onTick();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
document.addEventListener("visibilitychange", this._visHandler);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
setActive(active) {
|
|
353
|
+
this._active = active;
|
|
354
|
+
if (!this.opts.enabled) return;
|
|
355
|
+
const hidden = typeof document !== "undefined" && document.hidden;
|
|
356
|
+
if (active && !hidden) this._startInterval();
|
|
357
|
+
else this._stopInterval();
|
|
358
|
+
}
|
|
359
|
+
broadcast(data) {
|
|
360
|
+
this._channel?.postMessage(data);
|
|
361
|
+
}
|
|
362
|
+
stop() {
|
|
363
|
+
this._stopInterval();
|
|
364
|
+
this._active = false;
|
|
365
|
+
if (this._channel) {
|
|
366
|
+
try {
|
|
367
|
+
this._channel.close();
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
this._channel = null;
|
|
371
|
+
}
|
|
372
|
+
if (this._visHandler && typeof document !== "undefined") {
|
|
373
|
+
document.removeEventListener("visibilitychange", this._visHandler);
|
|
374
|
+
this._visHandler = null;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
_startInterval() {
|
|
378
|
+
if (this._interval) return;
|
|
379
|
+
this._interval = setInterval(() => {
|
|
380
|
+
if (this.opts.isRefreshDue()) this.opts.onTick();
|
|
381
|
+
}, this.opts.tickMs);
|
|
382
|
+
this._interval.unref?.();
|
|
383
|
+
}
|
|
384
|
+
_stopInterval() {
|
|
385
|
+
if (this._interval) {
|
|
386
|
+
clearInterval(this._interval);
|
|
387
|
+
this._interval = null;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
267
392
|
// src/AuthService.ts
|
|
268
393
|
function isMfaChallenge(v) {
|
|
269
394
|
return typeof v === "object" && v !== null && v.mfa_required === true;
|
|
270
395
|
}
|
|
396
|
+
var OAUTH_FORWARDABLE_QUERY_PARAMS = /* @__PURE__ */ new Set([
|
|
397
|
+
"access_type",
|
|
398
|
+
"acr_values",
|
|
399
|
+
"claims_locales",
|
|
400
|
+
"display",
|
|
401
|
+
"hd",
|
|
402
|
+
"include_granted_scopes",
|
|
403
|
+
"login_hint",
|
|
404
|
+
"max_age",
|
|
405
|
+
"prompt",
|
|
406
|
+
"ui_locales"
|
|
407
|
+
]);
|
|
408
|
+
var OAUTH_SDK_OWNED_QUERY_PARAMS = /* @__PURE__ */ new Set([
|
|
409
|
+
"project_id",
|
|
410
|
+
"redirect_uri",
|
|
411
|
+
// Alias de `redirect_uri` accepté par le backend (nom GoTrue, employé par les SDK
|
|
412
|
+
// Dart/Python/Rust) : il ne doit pas non plus arriver par `queryParams`.
|
|
413
|
+
"redirect_to",
|
|
414
|
+
"scopes",
|
|
415
|
+
"code_challenge",
|
|
416
|
+
"code_challenge_method"
|
|
417
|
+
]);
|
|
271
418
|
var AuthService = class extends BaseService {
|
|
272
419
|
_stateListeners = /* @__PURE__ */ new Set();
|
|
273
420
|
_sessionManager;
|
|
@@ -279,7 +426,7 @@ var AuthService = class extends BaseService {
|
|
|
279
426
|
const browser = typeof window !== "undefined";
|
|
280
427
|
const enabled = !!options?.forceEnabled || (options?.autoRefresh ?? true) && browser && (options?.persistSession ?? true);
|
|
281
428
|
this._sessionManager = new SessionManager({
|
|
282
|
-
channelName: `aura-${client.
|
|
429
|
+
channelName: `aura-${clientScope(client.baseUrl)}-auth`,
|
|
283
430
|
enabled,
|
|
284
431
|
onTick: () => {
|
|
285
432
|
void this.refreshSession();
|
|
@@ -310,6 +457,9 @@ var AuthService = class extends BaseService {
|
|
|
310
457
|
* Détecte et consomme une session présente dans l'URL courante au boot (callback
|
|
311
458
|
* OAuth), à l'image de `detectSessionInUrl` de supabase-js :
|
|
312
459
|
* - fragment `#access_token=…&refresh_token=…` (flux implicit) → `setSession`.
|
|
460
|
+
* - fragment `#mfa_token=…` (atterrissage `{redirectTo}/oauth/mfa` d'un utilisateur MFA au
|
|
461
|
+
* retour d'OAuth) → émet `MFA_CHALLENGE` avec `mfaToken` (AUCUNE session posée : le défi
|
|
462
|
+
* reste à relever via `mfaVerify`).
|
|
313
463
|
* - query `?token=…&type=recovery` (lien de récupération de mot de passe) → émet
|
|
314
464
|
* `PASSWORD_RECOVERY` avec `recoveryToken` (AUCUNE session posée, PAS de `SIGNED_IN`).
|
|
315
465
|
* - query `?code=…` (flux PKCE) → `exchangeCodeForSession` (relit le verifier).
|
|
@@ -338,6 +488,18 @@ var AuthService = class extends BaseService {
|
|
|
338
488
|
}
|
|
339
489
|
return;
|
|
340
490
|
}
|
|
491
|
+
if (hash.includes("mfa_token")) {
|
|
492
|
+
const initiatedKey = this._oauthInitiatedKey;
|
|
493
|
+
if (!this.client.authStore.getItem(initiatedKey)) return;
|
|
494
|
+
this.client.authStore.removeItem(initiatedKey);
|
|
495
|
+
const frag = new URLSearchParams(hash.replace(/^#/, ""));
|
|
496
|
+
const mfaToken = frag.get("mfa_token");
|
|
497
|
+
if (mfaToken) {
|
|
498
|
+
this._emitEvent("MFA_CHALLENGE", null, null, { mfaToken });
|
|
499
|
+
this._cleanAuthParamsFromUrl();
|
|
500
|
+
}
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
341
503
|
{
|
|
342
504
|
const qs = new URLSearchParams(search.replace(/^\?/, ""));
|
|
343
505
|
const recoveryToken = qs.get("token");
|
|
@@ -426,12 +588,36 @@ var AuthService = class extends BaseService {
|
|
|
426
588
|
this._unsubStore = null;
|
|
427
589
|
this._stateListeners.clear();
|
|
428
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Préfixe des routes de ce service. Aucun identifiant de projet : il est injecté par
|
|
593
|
+
* la passerelle depuis le sous-domaine du projet (`inject_project_id`,
|
|
594
|
+
* `services/aura-gateway/src/middleware/project_subdomain.rs`).
|
|
595
|
+
*/
|
|
429
596
|
get prefix() {
|
|
430
|
-
return
|
|
597
|
+
return "/v1/auth";
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* `project_id` de la SESSION en cours, lu dans le token stocké — seule référence de projet
|
|
601
|
+
* dont dispose encore le client (voir SEC-F2 dans `getClaims`). `null` s'il n'y a pas de
|
|
602
|
+
* session, si le token est illisible, ou s'il ne porte pas ce claim.
|
|
603
|
+
*/
|
|
604
|
+
_sessionProjectId() {
|
|
605
|
+
const token = this.client.authStore.token;
|
|
606
|
+
if (!token) return null;
|
|
607
|
+
try {
|
|
608
|
+
const pid = decodeJwt(token).payload.project_id;
|
|
609
|
+
return typeof pid === "string" && pid !== "" ? pid : null;
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
431
613
|
}
|
|
432
|
-
/**
|
|
614
|
+
/**
|
|
615
|
+
* Clé du `code_verifier` PKCE, scopée par la PORTÉE du client (`clientScope`, dérivée de
|
|
616
|
+
* l'URL du projet) : deux clients de deux projets instanciés dans la MÊME page partagent
|
|
617
|
+
* un seul `localStorage`, et ne doivent pas se marcher dessus.
|
|
618
|
+
*/
|
|
433
619
|
get _pkceVerifierKey() {
|
|
434
|
-
return `aura-${this.client.
|
|
620
|
+
return `aura-${clientScope(this.client.baseUrl)}-pkce-code-verifier`;
|
|
435
621
|
}
|
|
436
622
|
/**
|
|
437
623
|
* Clé project-scoped du MARQUEUR d'initiation du flux OAuth implicit (SEC-F3).
|
|
@@ -441,7 +627,7 @@ var AuthService = class extends BaseService {
|
|
|
441
627
|
* sera jamais adopté.
|
|
442
628
|
*/
|
|
443
629
|
get _oauthInitiatedKey() {
|
|
444
|
-
return `aura-${this.client.
|
|
630
|
+
return `aura-${clientScope(this.client.baseUrl)}-oauth-implicit-initiated`;
|
|
445
631
|
}
|
|
446
632
|
// -- Auth state change ------------------------------------------------------
|
|
447
633
|
onAuthStateChange(callback) {
|
|
@@ -501,14 +687,56 @@ var AuthService = class extends BaseService {
|
|
|
501
687
|
}
|
|
502
688
|
return res;
|
|
503
689
|
}
|
|
690
|
+
/**
|
|
691
|
+
* Démarre un flux OAuth : renvoie toujours `{ url }` (et redirige en plus le navigateur
|
|
692
|
+
* si un `window` existe).
|
|
693
|
+
*
|
|
694
|
+
* ### `options.queryParams` — contrat (N22)
|
|
695
|
+
*
|
|
696
|
+
* Les clés listées par {@link OAUTH_FORWARDABLE_QUERY_PARAMS} — `prompt`, `login_hint`,
|
|
697
|
+
* `display`, `ui_locales`, `max_age`, `acr_values`, `claims_locales`, `access_type`,
|
|
698
|
+
* `include_granted_scopes`, `hd` — sont **réellement transmises au provider**, jusque
|
|
699
|
+
* dans l'URL d'autorisation.
|
|
700
|
+
*
|
|
701
|
+
* Toute autre clé est **refusée** : la méthode renvoie
|
|
702
|
+
* `error.code === 'invalid_oauth_query_param'` et n'amorce aucune redirection. Rien
|
|
703
|
+
* n'est jamais accepté puis jeté en silence. Sont notamment refusés :
|
|
704
|
+
* - les paramètres du protocole (`state`, `nonce`, `client_id`, `response_type`,
|
|
705
|
+
* `scope`, `code_challenge`…) — ils sont calculés par le serveur ;
|
|
706
|
+
* - les paramètres construits par cette méthode (`redirect_uri`, `scopes`) — passez par
|
|
707
|
+
* `redirectTo`, `scopes` ou `flowType` ;
|
|
708
|
+
* - `project_id` : le projet est porté par l'URL du client, jamais par un paramètre ;
|
|
709
|
+
* - les variantes de casse (`Prompt`, `State`) et les clés inconnues.
|
|
710
|
+
*
|
|
711
|
+
* L'autorité est le **backend** (`OAUTH_FORWARDABLE_PARAMS` de
|
|
712
|
+
* `services/aura-auth/src/handlers/oauth.rs`), qui répond `400` sur les mêmes règles :
|
|
713
|
+
* l'endpoint de démarrage est public et appelable sans SDK. Ce filtre-ci n'est que son
|
|
714
|
+
* miroir, pour échouer avant la redirection ; un test de parité interdit qu'ils divergent.
|
|
715
|
+
*
|
|
716
|
+
* Les **valeurs**, elles, ne sont validées que par le backend (non vide, sans caractère
|
|
717
|
+
* de contrôle, 512 caractères au plus, jamais deux fois la même clé) — un manquement
|
|
718
|
+
* donne un `400` au démarrage du flux, pas une erreur de cette méthode.
|
|
719
|
+
*/
|
|
504
720
|
async signInWithOAuth(provider, options) {
|
|
505
721
|
const params = new URLSearchParams();
|
|
506
|
-
params.set("project_id", this.client.projectId);
|
|
507
722
|
if (options?.redirectTo) params.set("redirect_uri", options.redirectTo);
|
|
508
723
|
if (options?.scopes) params.set("scopes", options.scopes);
|
|
509
724
|
if (options?.queryParams) {
|
|
510
725
|
for (const [k, v] of Object.entries(options.queryParams)) {
|
|
511
|
-
|
|
726
|
+
if (OAUTH_FORWARDABLE_QUERY_PARAMS.has(k)) {
|
|
727
|
+
params.set(k, v);
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
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(", ")}`;
|
|
731
|
+
return {
|
|
732
|
+
data: null,
|
|
733
|
+
error: new AuraError(400, {
|
|
734
|
+
error: {
|
|
735
|
+
code: "invalid_oauth_query_param",
|
|
736
|
+
message: `signInWithOAuth: queryParams.${k} refus\xE9 \u2014 ${raison}.`
|
|
737
|
+
}
|
|
738
|
+
})
|
|
739
|
+
};
|
|
512
740
|
}
|
|
513
741
|
}
|
|
514
742
|
if (options?.flowType === "pkce") {
|
|
@@ -569,9 +797,14 @@ var AuthService = class extends BaseService {
|
|
|
569
797
|
this._emitEvent("SIGNED_OUT", null, null);
|
|
570
798
|
return { data: null, error: null };
|
|
571
799
|
}
|
|
572
|
-
const res = await this.request(
|
|
573
|
-
|
|
574
|
-
|
|
800
|
+
const res = await this.request(
|
|
801
|
+
"POST",
|
|
802
|
+
`${this.prefix}/logout`,
|
|
803
|
+
{
|
|
804
|
+
refresh_token: refreshToken
|
|
805
|
+
},
|
|
806
|
+
{ skipAuthRefresh: true }
|
|
807
|
+
);
|
|
575
808
|
this.client.authStore.clear();
|
|
576
809
|
this._emitEvent("SIGNED_OUT", null, null);
|
|
577
810
|
return res;
|
|
@@ -599,11 +832,11 @@ var AuthService = class extends BaseService {
|
|
|
599
832
|
* `fetch` manuels côté app). Pour la session complète, lire `client.authStore`
|
|
600
833
|
* directement (`.token`, `.refreshToken`, `.user`, `.isValid`).
|
|
601
834
|
*/
|
|
602
|
-
|
|
603
|
-
return {
|
|
835
|
+
getSession() {
|
|
836
|
+
return Promise.resolve({
|
|
604
837
|
accessToken: this.client.authStore.token,
|
|
605
838
|
user: this.client.authStore.user
|
|
606
|
-
};
|
|
839
|
+
});
|
|
607
840
|
}
|
|
608
841
|
async setSession(accessToken, refreshToken) {
|
|
609
842
|
const store = this.client.authStore;
|
|
@@ -636,6 +869,16 @@ var AuthService = class extends BaseService {
|
|
|
636
869
|
restore();
|
|
637
870
|
return res;
|
|
638
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* Échange le code d'autorisation PKCE contre une session.
|
|
874
|
+
*
|
|
875
|
+
* Renvoie un `LoginResponse`, PAS un `TokenPair` : si l'utilisateur a le MFA actif, le
|
|
876
|
+
* backend répond `{ mfa_required: true, mfa_token }` (gate « C1-bis » de
|
|
877
|
+
* `POST /oauth/pkce`) au lieu d'une paire de jetons. Dans ce cas AUCUNE session n'est
|
|
878
|
+
* enregistrée et `MFA_CHALLENGE` est émis avec le `mfaToken` : l'application demande le
|
|
879
|
+
* code TOTP puis appelle `mfaVerify({ mfa_token, code })`. Discriminez avec
|
|
880
|
+
* {@link isMfaChallenge}.
|
|
881
|
+
*/
|
|
639
882
|
async exchangeCodeForSession(authCode, codeVerifier) {
|
|
640
883
|
return this._exchangeCode(authCode, codeVerifier, "SIGNED_IN");
|
|
641
884
|
}
|
|
@@ -651,7 +894,10 @@ var AuthService = class extends BaseService {
|
|
|
651
894
|
return {
|
|
652
895
|
data: null,
|
|
653
896
|
error: new AuraError(0, {
|
|
654
|
-
error: {
|
|
897
|
+
error: {
|
|
898
|
+
code: "no_code_verifier",
|
|
899
|
+
message: "No PKCE code_verifier available (not provided and none stored)"
|
|
900
|
+
}
|
|
655
901
|
})
|
|
656
902
|
};
|
|
657
903
|
}
|
|
@@ -660,9 +906,18 @@ var AuthService = class extends BaseService {
|
|
|
660
906
|
auth_code: authCode,
|
|
661
907
|
code_verifier: verifier
|
|
662
908
|
});
|
|
909
|
+
if (res.data && isMfaChallenge(res.data)) {
|
|
910
|
+
const mfa = res.data;
|
|
911
|
+
this._emitEvent("MFA_CHALLENGE", null, null, {
|
|
912
|
+
mfaToken: mfa.mfa_token,
|
|
913
|
+
mfaSetupRequired: mfa.mfa_setup_required
|
|
914
|
+
});
|
|
915
|
+
return res;
|
|
916
|
+
}
|
|
663
917
|
if (res.data) {
|
|
664
|
-
|
|
665
|
-
this.
|
|
918
|
+
const tp = res.data;
|
|
919
|
+
this.client.authStore.save(tp.access_token, tp.refresh_token, tp.user);
|
|
920
|
+
this._emitEvent(successEvent, tp.user, tp.access_token);
|
|
666
921
|
}
|
|
667
922
|
return res;
|
|
668
923
|
} finally {
|
|
@@ -690,7 +945,9 @@ var AuthService = class extends BaseService {
|
|
|
690
945
|
if (!rt) {
|
|
691
946
|
return {
|
|
692
947
|
data: null,
|
|
693
|
-
error: new AuraError(0, {
|
|
948
|
+
error: new AuraError(0, {
|
|
949
|
+
error: { code: "no_session", message: "No refresh token available" }
|
|
950
|
+
})
|
|
694
951
|
};
|
|
695
952
|
}
|
|
696
953
|
const res = await this.request(
|
|
@@ -764,7 +1021,14 @@ var AuthService = class extends BaseService {
|
|
|
764
1021
|
allowExpired: options?.allowExpired,
|
|
765
1022
|
// LOT 3 (I8) : relie le callback générique de jwks.ts au hook d'observabilité
|
|
766
1023
|
// du client (onEvent/debug). jwks.ts reste sans dépendance vers @aurabase/core.
|
|
767
|
-
onEvent: (baseUrl) => emitAuraEvent(this.client.onEvent, this.client.debug, {
|
|
1024
|
+
onEvent: (baseUrl) => emitAuraEvent(this.client.onEvent, this.client.debug, {
|
|
1025
|
+
kind: "jwks-fetch-failed",
|
|
1026
|
+
baseUrl
|
|
1027
|
+
}),
|
|
1028
|
+
// P2-1 : borne le fetch JWKS (et la lecture de son corps) sur le même
|
|
1029
|
+
// budget que les autres requêtes du client. Sans elle, un serveur JWKS
|
|
1030
|
+
// muet figeait `getClaims` définitivement (single-flight `inflight`).
|
|
1031
|
+
timeoutMs: this.client.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS
|
|
768
1032
|
});
|
|
769
1033
|
const checkIssAud = (c) => {
|
|
770
1034
|
if (options?.issuer !== void 0 && c.iss !== options.issuer) {
|
|
@@ -788,7 +1052,8 @@ var AuthService = class extends BaseService {
|
|
|
788
1052
|
const issAudErr = checkIssAud(claims);
|
|
789
1053
|
if (issAudErr) return { data: null, error: issAudErr };
|
|
790
1054
|
const claimProject = claims.project_id;
|
|
791
|
-
|
|
1055
|
+
const sessionProject = this._sessionProjectId();
|
|
1056
|
+
if (typeof claimProject === "string" && sessionProject !== null && claimProject !== sessionProject) {
|
|
792
1057
|
return {
|
|
793
1058
|
data: null,
|
|
794
1059
|
error: new AuraError(0, {
|
|
@@ -837,6 +1102,17 @@ var AuthService = class extends BaseService {
|
|
|
837
1102
|
error: null
|
|
838
1103
|
};
|
|
839
1104
|
}
|
|
1105
|
+
if (u.error === null) {
|
|
1106
|
+
return {
|
|
1107
|
+
data: null,
|
|
1108
|
+
error: new AuraError(0, {
|
|
1109
|
+
error: {
|
|
1110
|
+
code: "invalid_response",
|
|
1111
|
+
message: "getClaims: la validation serveur du token (getUser) a r\xE9pondu sans contenu \u2014 claims ind\xE9terminables"
|
|
1112
|
+
}
|
|
1113
|
+
})
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
840
1116
|
return { data: null, error: u.error };
|
|
841
1117
|
}
|
|
842
1118
|
return {
|
|
@@ -942,17 +1218,6 @@ var AuthService = class extends BaseService {
|
|
|
942
1218
|
}
|
|
943
1219
|
return res;
|
|
944
1220
|
}
|
|
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
1221
|
default:
|
|
957
1222
|
return {
|
|
958
1223
|
data: null,
|
|
@@ -975,10 +1240,6 @@ var AuthService = class extends BaseService {
|
|
|
975
1240
|
return res;
|
|
976
1241
|
}
|
|
977
1242
|
// -- 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
1243
|
async resendVerificationEmail(email) {
|
|
983
1244
|
const e = email ?? this.client.authStore.user?.email;
|
|
984
1245
|
if (!e) {
|
|
@@ -993,18 +1254,10 @@ var AuthService = class extends BaseService {
|
|
|
993
1254
|
async signInWithEmailOtp(email) {
|
|
994
1255
|
return this.request("POST", `${this.prefix}/email-otp/send`, { email });
|
|
995
1256
|
}
|
|
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
1257
|
// -- SMS OTP (Phase 2) ----------------------------------------------------
|
|
1001
1258
|
async signInWithSmsOtp(phone) {
|
|
1002
1259
|
return this.request("POST", `${this.prefix}/sms/send`, { phone });
|
|
1003
1260
|
}
|
|
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
1261
|
// -- Identity linking (Phase 2) -------------------------------------------
|
|
1009
1262
|
async listIdentities() {
|
|
1010
1263
|
return this.request("GET", `${this.prefix}/user/identities`);
|
|
@@ -1015,29 +1268,37 @@ var AuthService = class extends BaseService {
|
|
|
1015
1268
|
async unlinkIdentity(identityId) {
|
|
1016
1269
|
return this.request("DELETE", `${this.prefix}/user/identities/${identityId}`);
|
|
1017
1270
|
}
|
|
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
1271
|
// -- MFA recovery codes (Phase 2) -----------------------------------------
|
|
1028
1272
|
async regenerateRecoveryCodes(code) {
|
|
1029
1273
|
return this.request("POST", `${this.prefix}/mfa/recovery-codes/regenerate`, { code });
|
|
1030
1274
|
}
|
|
1031
1275
|
// -- Native mobile OAuth (Phase 2) ----------------------------------------
|
|
1276
|
+
/**
|
|
1277
|
+
* Échange un ID token de provider (Google/Apple, mobile natif) contre une session.
|
|
1278
|
+
*
|
|
1279
|
+
* Renvoie un `LoginResponse`, PAS un `TokenPair` : comme partout ailleurs, un utilisateur
|
|
1280
|
+
* avec MFA actif reçoit `{ mfa_required: true, mfa_token }` et AUCUNE session n'est
|
|
1281
|
+
* enregistrée — `MFA_CHALLENGE` est émis, à relever par `mfaVerify`. Discriminez avec
|
|
1282
|
+
* {@link isMfaChallenge}.
|
|
1283
|
+
*/
|
|
1032
1284
|
async signInWithIdToken(provider, idToken, accessToken) {
|
|
1033
1285
|
const res = await this.request("POST", `${this.prefix}/token`, {
|
|
1034
1286
|
provider,
|
|
1035
1287
|
id_token: idToken,
|
|
1036
1288
|
access_token: accessToken
|
|
1037
1289
|
});
|
|
1290
|
+
if (res.data && isMfaChallenge(res.data)) {
|
|
1291
|
+
const mfa = res.data;
|
|
1292
|
+
this._emitEvent("MFA_CHALLENGE", null, null, {
|
|
1293
|
+
mfaToken: mfa.mfa_token,
|
|
1294
|
+
mfaSetupRequired: mfa.mfa_setup_required
|
|
1295
|
+
});
|
|
1296
|
+
return res;
|
|
1297
|
+
}
|
|
1038
1298
|
if (res.data) {
|
|
1039
|
-
|
|
1040
|
-
this.
|
|
1299
|
+
const tp = res.data;
|
|
1300
|
+
this.client.authStore.save(tp.access_token, tp.refresh_token, tp.user);
|
|
1301
|
+
this._emitEvent("SIGNED_IN", tp.user, tp.access_token);
|
|
1041
1302
|
}
|
|
1042
1303
|
return res;
|
|
1043
1304
|
}
|
|
@@ -1065,7 +1326,11 @@ var AuthService = class extends BaseService {
|
|
|
1065
1326
|
* Body confirmé au handler (`PromoteAnonymousRequest`) : `{ email, password }`.
|
|
1066
1327
|
*/
|
|
1067
1328
|
async promoteAnonymous(credentials) {
|
|
1068
|
-
const res = await this.request(
|
|
1329
|
+
const res = await this.request(
|
|
1330
|
+
"POST",
|
|
1331
|
+
`${this.prefix}/anonymous/promote`,
|
|
1332
|
+
credentials
|
|
1333
|
+
);
|
|
1069
1334
|
if (res.data) {
|
|
1070
1335
|
this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
|
|
1071
1336
|
this._emitEvent("USER_UPDATED", res.data.user, res.data.access_token);
|
|
@@ -1081,90 +1346,5 @@ var AuthService = class extends BaseService {
|
|
|
1081
1346
|
return this.verifyOtp({ type: "magiclink", token });
|
|
1082
1347
|
}
|
|
1083
1348
|
};
|
|
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
1349
|
|
|
1170
1350
|
export { AuthAdminService, AuthService, SessionManager, isMfaChallenge };
|