@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.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
|
|
|
@@ -116,14 +150,30 @@ function parseMaxAge(cc) {
|
|
|
116
150
|
const m = cc?.match(/max-age=(\d+)/);
|
|
117
151
|
return m ? Number(m[1]) * 1e3 : null;
|
|
118
152
|
}
|
|
119
|
-
function fetchJwksDeduped(state, fetchFn, baseUrl) {
|
|
153
|
+
function fetchJwksDeduped(state, fetchFn, baseUrl, timeoutMs) {
|
|
120
154
|
const existing = state.inflight.get(baseUrl);
|
|
121
155
|
if (existing) return existing;
|
|
122
156
|
const p = (async () => {
|
|
157
|
+
let timer;
|
|
158
|
+
let bodyTimer;
|
|
123
159
|
try {
|
|
124
|
-
|
|
160
|
+
let signal;
|
|
161
|
+
if (timeoutMs > 0 && typeof AbortController !== "undefined") {
|
|
162
|
+
const ctrl = new AbortController();
|
|
163
|
+
timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
164
|
+
timer.unref?.();
|
|
165
|
+
signal = ctrl.signal;
|
|
166
|
+
}
|
|
167
|
+
const res = await fetchFn(`${baseUrl}/.well-known/jwks.json`, signal ? { signal } : {});
|
|
125
168
|
if (!res.ok) throw new Error("jwks_fetch_failed");
|
|
126
|
-
const body = await
|
|
169
|
+
const body = await Promise.race([
|
|
170
|
+
res.json(),
|
|
171
|
+
new Promise((_resolve, reject) => {
|
|
172
|
+
if (!(timeoutMs > 0)) return;
|
|
173
|
+
bodyTimer = setTimeout(() => reject(new Error("jwks_body_timeout")), timeoutMs);
|
|
174
|
+
bodyTimer.unref?.();
|
|
175
|
+
})
|
|
176
|
+
]);
|
|
127
177
|
const parsed = parseMaxAge(res.headers.get("Cache-Control")) ?? DEFAULT_MAX_AGE_MS;
|
|
128
178
|
const maxAge = Math.min(parsed, MAX_AGE_CAP_MS);
|
|
129
179
|
const keys = body.keys ?? [];
|
|
@@ -133,12 +183,15 @@ function fetchJwksDeduped(state, fetchFn, baseUrl) {
|
|
|
133
183
|
} catch {
|
|
134
184
|
state.lastFailedAt.set(baseUrl, Date.now());
|
|
135
185
|
throw new Error("jwks_fetch_failed");
|
|
186
|
+
} finally {
|
|
187
|
+
if (timer) clearTimeout(timer);
|
|
188
|
+
if (bodyTimer) clearTimeout(bodyTimer);
|
|
136
189
|
}
|
|
137
190
|
})().finally(() => state.inflight.delete(baseUrl));
|
|
138
191
|
state.inflight.set(baseUrl, p);
|
|
139
192
|
return p;
|
|
140
193
|
}
|
|
141
|
-
async function loadJwks(state, fetchFn, baseUrl) {
|
|
194
|
+
async function loadJwks(state, fetchFn, baseUrl, timeoutMs) {
|
|
142
195
|
const now = Date.now();
|
|
143
196
|
const c = state.cache.get(baseUrl);
|
|
144
197
|
if (c && now - c.fetchedAt < c.maxAgeMs) return c.keys;
|
|
@@ -146,9 +199,9 @@ async function loadJwks(state, fetchFn, baseUrl) {
|
|
|
146
199
|
if (failedAt !== void 0 && now - failedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
147
200
|
throw new Error("jwks_fetch_failed");
|
|
148
201
|
}
|
|
149
|
-
return fetchJwksDeduped(state, fetchFn, baseUrl);
|
|
202
|
+
return fetchJwksDeduped(state, fetchFn, baseUrl, timeoutMs);
|
|
150
203
|
}
|
|
151
|
-
async function forcedRefetchJwks(state, fetchFn, baseUrl) {
|
|
204
|
+
async function forcedRefetchJwks(state, fetchFn, baseUrl, timeoutMs) {
|
|
152
205
|
const now = Date.now();
|
|
153
206
|
const last = state.lastForcedFetchAt.get(baseUrl);
|
|
154
207
|
if (last !== void 0 && now - last < FORCED_REFETCH_COOLDOWN_MS) return null;
|
|
@@ -157,7 +210,7 @@ async function forcedRefetchJwks(state, fetchFn, baseUrl) {
|
|
|
157
210
|
if (failedAt !== void 0 && now - failedAt < NEGATIVE_CACHE_TTL_MS) {
|
|
158
211
|
throw new Error("jwks_fetch_failed");
|
|
159
212
|
}
|
|
160
|
-
return fetchJwksDeduped(state, fetchFn, baseUrl);
|
|
213
|
+
return fetchJwksDeduped(state, fetchFn, baseUrl, timeoutMs);
|
|
161
214
|
}
|
|
162
215
|
function algParams(alg) {
|
|
163
216
|
if (alg === "RS256") {
|
|
@@ -184,11 +237,12 @@ async function verifyJwtLocal(scope, fetchFn, baseUrl, jwt, opts = {}) {
|
|
|
184
237
|
const params = algParams(d.header.alg);
|
|
185
238
|
if (!params) return { ok: false, code: "unsupported_alg" };
|
|
186
239
|
const state = stateFor(scope);
|
|
240
|
+
const timeoutMs = opts.timeoutMs ?? 0;
|
|
187
241
|
const matchesAlg = (k) => d.header.alg === "RS256" ? k.kty === "RSA" : k.kty === "EC";
|
|
188
242
|
const candidateKeys = (keys2) => d.header.kid ? keys2.filter((k) => k.kid === d.header.kid && matchesAlg(k)) : keys2.filter((k) => matchesAlg(k));
|
|
189
243
|
let keys;
|
|
190
244
|
try {
|
|
191
|
-
keys = await loadJwks(state, fetchFn, baseUrl);
|
|
245
|
+
keys = await loadJwks(state, fetchFn, baseUrl, timeoutMs);
|
|
192
246
|
} catch {
|
|
193
247
|
try {
|
|
194
248
|
opts.onEvent?.(baseUrl);
|
|
@@ -200,7 +254,7 @@ async function verifyJwtLocal(scope, fetchFn, baseUrl, jwt, opts = {}) {
|
|
|
200
254
|
if (candidates.length === 0 && d.header.kid) {
|
|
201
255
|
let refetched;
|
|
202
256
|
try {
|
|
203
|
-
refetched = await forcedRefetchJwks(state, fetchFn, baseUrl);
|
|
257
|
+
refetched = await forcedRefetchJwks(state, fetchFn, baseUrl, timeoutMs);
|
|
204
258
|
} catch {
|
|
205
259
|
try {
|
|
206
260
|
opts.onEvent?.(baseUrl);
|
|
@@ -266,10 +320,103 @@ async function codeChallengeS256(verifier) {
|
|
|
266
320
|
return b64url(new Uint8Array(digest));
|
|
267
321
|
}
|
|
268
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
|
+
|
|
269
394
|
// src/AuthService.ts
|
|
270
395
|
function isMfaChallenge(v) {
|
|
271
396
|
return typeof v === "object" && v !== null && v.mfa_required === true;
|
|
272
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
|
+
]);
|
|
273
420
|
var AuthService = class extends core.BaseService {
|
|
274
421
|
_stateListeners = /* @__PURE__ */ new Set();
|
|
275
422
|
_sessionManager;
|
|
@@ -281,7 +428,7 @@ var AuthService = class extends core.BaseService {
|
|
|
281
428
|
const browser = typeof window !== "undefined";
|
|
282
429
|
const enabled = !!options?.forceEnabled || (options?.autoRefresh ?? true) && browser && (options?.persistSession ?? true);
|
|
283
430
|
this._sessionManager = new SessionManager({
|
|
284
|
-
channelName: `aura-${client.
|
|
431
|
+
channelName: `aura-${core.clientScope(client.baseUrl)}-auth`,
|
|
285
432
|
enabled,
|
|
286
433
|
onTick: () => {
|
|
287
434
|
void this.refreshSession();
|
|
@@ -312,6 +459,9 @@ var AuthService = class extends core.BaseService {
|
|
|
312
459
|
* Détecte et consomme une session présente dans l'URL courante au boot (callback
|
|
313
460
|
* OAuth), à l'image de `detectSessionInUrl` de supabase-js :
|
|
314
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`).
|
|
315
465
|
* - query `?token=…&type=recovery` (lien de récupération de mot de passe) → émet
|
|
316
466
|
* `PASSWORD_RECOVERY` avec `recoveryToken` (AUCUNE session posée, PAS de `SIGNED_IN`).
|
|
317
467
|
* - query `?code=…` (flux PKCE) → `exchangeCodeForSession` (relit le verifier).
|
|
@@ -340,6 +490,18 @@ var AuthService = class extends core.BaseService {
|
|
|
340
490
|
}
|
|
341
491
|
return;
|
|
342
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
|
+
}
|
|
343
505
|
{
|
|
344
506
|
const qs = new URLSearchParams(search.replace(/^\?/, ""));
|
|
345
507
|
const recoveryToken = qs.get("token");
|
|
@@ -428,12 +590,36 @@ var AuthService = class extends core.BaseService {
|
|
|
428
590
|
this._unsubStore = null;
|
|
429
591
|
this._stateListeners.clear();
|
|
430
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
|
+
*/
|
|
431
598
|
get prefix() {
|
|
432
|
-
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
|
+
}
|
|
433
615
|
}
|
|
434
|
-
/**
|
|
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
|
+
*/
|
|
435
621
|
get _pkceVerifierKey() {
|
|
436
|
-
return `aura-${this.client.
|
|
622
|
+
return `aura-${core.clientScope(this.client.baseUrl)}-pkce-code-verifier`;
|
|
437
623
|
}
|
|
438
624
|
/**
|
|
439
625
|
* Clé project-scoped du MARQUEUR d'initiation du flux OAuth implicit (SEC-F3).
|
|
@@ -443,7 +629,7 @@ var AuthService = class extends core.BaseService {
|
|
|
443
629
|
* sera jamais adopté.
|
|
444
630
|
*/
|
|
445
631
|
get _oauthInitiatedKey() {
|
|
446
|
-
return `aura-${this.client.
|
|
632
|
+
return `aura-${core.clientScope(this.client.baseUrl)}-oauth-implicit-initiated`;
|
|
447
633
|
}
|
|
448
634
|
// -- Auth state change ------------------------------------------------------
|
|
449
635
|
onAuthStateChange(callback) {
|
|
@@ -503,14 +689,56 @@ var AuthService = class extends core.BaseService {
|
|
|
503
689
|
}
|
|
504
690
|
return res;
|
|
505
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
|
+
*/
|
|
506
722
|
async signInWithOAuth(provider, options) {
|
|
507
723
|
const params = new URLSearchParams();
|
|
508
|
-
params.set("project_id", this.client.projectId);
|
|
509
724
|
if (options?.redirectTo) params.set("redirect_uri", options.redirectTo);
|
|
510
725
|
if (options?.scopes) params.set("scopes", options.scopes);
|
|
511
726
|
if (options?.queryParams) {
|
|
512
727
|
for (const [k, v] of Object.entries(options.queryParams)) {
|
|
513
|
-
|
|
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
|
+
};
|
|
514
742
|
}
|
|
515
743
|
}
|
|
516
744
|
if (options?.flowType === "pkce") {
|
|
@@ -571,9 +799,14 @@ var AuthService = class extends core.BaseService {
|
|
|
571
799
|
this._emitEvent("SIGNED_OUT", null, null);
|
|
572
800
|
return { data: null, error: null };
|
|
573
801
|
}
|
|
574
|
-
const res = await this.request(
|
|
575
|
-
|
|
576
|
-
|
|
802
|
+
const res = await this.request(
|
|
803
|
+
"POST",
|
|
804
|
+
`${this.prefix}/logout`,
|
|
805
|
+
{
|
|
806
|
+
refresh_token: refreshToken
|
|
807
|
+
},
|
|
808
|
+
{ skipAuthRefresh: true }
|
|
809
|
+
);
|
|
577
810
|
this.client.authStore.clear();
|
|
578
811
|
this._emitEvent("SIGNED_OUT", null, null);
|
|
579
812
|
return res;
|
|
@@ -601,11 +834,11 @@ var AuthService = class extends core.BaseService {
|
|
|
601
834
|
* `fetch` manuels côté app). Pour la session complète, lire `client.authStore`
|
|
602
835
|
* directement (`.token`, `.refreshToken`, `.user`, `.isValid`).
|
|
603
836
|
*/
|
|
604
|
-
|
|
605
|
-
return {
|
|
837
|
+
getSession() {
|
|
838
|
+
return Promise.resolve({
|
|
606
839
|
accessToken: this.client.authStore.token,
|
|
607
840
|
user: this.client.authStore.user
|
|
608
|
-
};
|
|
841
|
+
});
|
|
609
842
|
}
|
|
610
843
|
async setSession(accessToken, refreshToken) {
|
|
611
844
|
const store = this.client.authStore;
|
|
@@ -638,6 +871,16 @@ var AuthService = class extends core.BaseService {
|
|
|
638
871
|
restore();
|
|
639
872
|
return res;
|
|
640
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
|
+
*/
|
|
641
884
|
async exchangeCodeForSession(authCode, codeVerifier) {
|
|
642
885
|
return this._exchangeCode(authCode, codeVerifier, "SIGNED_IN");
|
|
643
886
|
}
|
|
@@ -653,7 +896,10 @@ var AuthService = class extends core.BaseService {
|
|
|
653
896
|
return {
|
|
654
897
|
data: null,
|
|
655
898
|
error: new core.AuraError(0, {
|
|
656
|
-
error: {
|
|
899
|
+
error: {
|
|
900
|
+
code: "no_code_verifier",
|
|
901
|
+
message: "No PKCE code_verifier available (not provided and none stored)"
|
|
902
|
+
}
|
|
657
903
|
})
|
|
658
904
|
};
|
|
659
905
|
}
|
|
@@ -662,9 +908,18 @@ var AuthService = class extends core.BaseService {
|
|
|
662
908
|
auth_code: authCode,
|
|
663
909
|
code_verifier: verifier
|
|
664
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
|
+
}
|
|
665
919
|
if (res.data) {
|
|
666
|
-
|
|
667
|
-
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);
|
|
668
923
|
}
|
|
669
924
|
return res;
|
|
670
925
|
} finally {
|
|
@@ -692,7 +947,9 @@ var AuthService = class extends core.BaseService {
|
|
|
692
947
|
if (!rt) {
|
|
693
948
|
return {
|
|
694
949
|
data: null,
|
|
695
|
-
error: new core.AuraError(0, {
|
|
950
|
+
error: new core.AuraError(0, {
|
|
951
|
+
error: { code: "no_session", message: "No refresh token available" }
|
|
952
|
+
})
|
|
696
953
|
};
|
|
697
954
|
}
|
|
698
955
|
const res = await this.request(
|
|
@@ -766,7 +1023,14 @@ var AuthService = class extends core.BaseService {
|
|
|
766
1023
|
allowExpired: options?.allowExpired,
|
|
767
1024
|
// LOT 3 (I8) : relie le callback générique de jwks.ts au hook d'observabilité
|
|
768
1025
|
// du client (onEvent/debug). jwks.ts reste sans dépendance vers @aurabase/core.
|
|
769
|
-
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
|
+
}),
|
|
1030
|
+
// P2-1 : borne le fetch JWKS (et la lecture de son corps) sur le même
|
|
1031
|
+
// budget que les autres requêtes du client. Sans elle, un serveur JWKS
|
|
1032
|
+
// muet figeait `getClaims` définitivement (single-flight `inflight`).
|
|
1033
|
+
timeoutMs: this.client.requestTimeoutMs ?? core.DEFAULT_REQUEST_TIMEOUT_MS
|
|
770
1034
|
});
|
|
771
1035
|
const checkIssAud = (c) => {
|
|
772
1036
|
if (options?.issuer !== void 0 && c.iss !== options.issuer) {
|
|
@@ -790,7 +1054,8 @@ var AuthService = class extends core.BaseService {
|
|
|
790
1054
|
const issAudErr = checkIssAud(claims);
|
|
791
1055
|
if (issAudErr) return { data: null, error: issAudErr };
|
|
792
1056
|
const claimProject = claims.project_id;
|
|
793
|
-
|
|
1057
|
+
const sessionProject = this._sessionProjectId();
|
|
1058
|
+
if (typeof claimProject === "string" && sessionProject !== null && claimProject !== sessionProject) {
|
|
794
1059
|
return {
|
|
795
1060
|
data: null,
|
|
796
1061
|
error: new core.AuraError(0, {
|
|
@@ -839,6 +1104,17 @@ var AuthService = class extends core.BaseService {
|
|
|
839
1104
|
error: null
|
|
840
1105
|
};
|
|
841
1106
|
}
|
|
1107
|
+
if (u.error === null) {
|
|
1108
|
+
return {
|
|
1109
|
+
data: null,
|
|
1110
|
+
error: new core.AuraError(0, {
|
|
1111
|
+
error: {
|
|
1112
|
+
code: "invalid_response",
|
|
1113
|
+
message: "getClaims: la validation serveur du token (getUser) a r\xE9pondu sans contenu \u2014 claims ind\xE9terminables"
|
|
1114
|
+
}
|
|
1115
|
+
})
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
842
1118
|
return { data: null, error: u.error };
|
|
843
1119
|
}
|
|
844
1120
|
return {
|
|
@@ -944,17 +1220,6 @@ var AuthService = class extends core.BaseService {
|
|
|
944
1220
|
}
|
|
945
1221
|
return res;
|
|
946
1222
|
}
|
|
947
|
-
case "invite":
|
|
948
|
-
case "phone_change":
|
|
949
|
-
return {
|
|
950
|
-
data: null,
|
|
951
|
-
error: new core.AuraError(0, {
|
|
952
|
-
error: {
|
|
953
|
-
code: "otp_type_unsupported",
|
|
954
|
-
message: `verifyOtp: le type '${params.type}' n'est pas support\xE9 (aucun endpoint backend).`
|
|
955
|
-
}
|
|
956
|
-
})
|
|
957
|
-
};
|
|
958
1223
|
default:
|
|
959
1224
|
return {
|
|
960
1225
|
data: null,
|
|
@@ -977,10 +1242,6 @@ var AuthService = class extends core.BaseService {
|
|
|
977
1242
|
return res;
|
|
978
1243
|
}
|
|
979
1244
|
// -- Email verification -----------------------------------------------------
|
|
980
|
-
/** Rétrocompat : délègue à `verifyOtp({ type: 'signup' })` (POST /verify-email). */
|
|
981
|
-
async verifyEmail(token) {
|
|
982
|
-
return this.verifyOtp({ type: "signup", token });
|
|
983
|
-
}
|
|
984
1245
|
async resendVerificationEmail(email) {
|
|
985
1246
|
const e = email ?? this.client.authStore.user?.email;
|
|
986
1247
|
if (!e) {
|
|
@@ -995,18 +1256,10 @@ var AuthService = class extends core.BaseService {
|
|
|
995
1256
|
async signInWithEmailOtp(email) {
|
|
996
1257
|
return this.request("POST", `${this.prefix}/email-otp/send`, { email });
|
|
997
1258
|
}
|
|
998
|
-
/** Rétrocompat : délègue à `verifyOtp({ type: 'email' })` (POST /email-otp/verify). */
|
|
999
|
-
async verifyEmailOtp(email, code) {
|
|
1000
|
-
return this.verifyOtp({ type: "email", email, token: code });
|
|
1001
|
-
}
|
|
1002
1259
|
// -- SMS OTP (Phase 2) ----------------------------------------------------
|
|
1003
1260
|
async signInWithSmsOtp(phone) {
|
|
1004
1261
|
return this.request("POST", `${this.prefix}/sms/send`, { phone });
|
|
1005
1262
|
}
|
|
1006
|
-
/** Rétrocompat : délègue à `verifyOtp({ type: 'sms' })` (POST /sms/verify). */
|
|
1007
|
-
async verifySmsOtp(phone, code) {
|
|
1008
|
-
return this.verifyOtp({ type: "sms", phone, token: code });
|
|
1009
|
-
}
|
|
1010
1263
|
// -- Identity linking (Phase 2) -------------------------------------------
|
|
1011
1264
|
async listIdentities() {
|
|
1012
1265
|
return this.request("GET", `${this.prefix}/user/identities`);
|
|
@@ -1017,29 +1270,37 @@ var AuthService = class extends core.BaseService {
|
|
|
1017
1270
|
async unlinkIdentity(identityId) {
|
|
1018
1271
|
return this.request("DELETE", `${this.prefix}/user/identities/${identityId}`);
|
|
1019
1272
|
}
|
|
1020
|
-
// -- Email change (Phase 2) -----------------------------------------------
|
|
1021
|
-
/**
|
|
1022
|
-
* Rétrocompat : délègue à `verifyOtp({ type: 'email_change' })`.
|
|
1023
|
-
* CHANGEMENT ASSUMÉ (parité) : émet désormais `USER_UPDATED` (auparavant aucun
|
|
1024
|
-
* événement) — voir CHANGELOG.
|
|
1025
|
-
*/
|
|
1026
|
-
async confirmEmailChange(token) {
|
|
1027
|
-
return this.verifyOtp({ type: "email_change", token });
|
|
1028
|
-
}
|
|
1029
1273
|
// -- MFA recovery codes (Phase 2) -----------------------------------------
|
|
1030
1274
|
async regenerateRecoveryCodes(code) {
|
|
1031
1275
|
return this.request("POST", `${this.prefix}/mfa/recovery-codes/regenerate`, { code });
|
|
1032
1276
|
}
|
|
1033
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
|
+
*/
|
|
1034
1286
|
async signInWithIdToken(provider, idToken, accessToken) {
|
|
1035
1287
|
const res = await this.request("POST", `${this.prefix}/token`, {
|
|
1036
1288
|
provider,
|
|
1037
1289
|
id_token: idToken,
|
|
1038
1290
|
access_token: accessToken
|
|
1039
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
|
+
}
|
|
1040
1300
|
if (res.data) {
|
|
1041
|
-
|
|
1042
|
-
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);
|
|
1043
1304
|
}
|
|
1044
1305
|
return res;
|
|
1045
1306
|
}
|
|
@@ -1067,7 +1328,11 @@ var AuthService = class extends core.BaseService {
|
|
|
1067
1328
|
* Body confirmé au handler (`PromoteAnonymousRequest`) : `{ email, password }`.
|
|
1068
1329
|
*/
|
|
1069
1330
|
async promoteAnonymous(credentials) {
|
|
1070
|
-
const res = await this.request(
|
|
1331
|
+
const res = await this.request(
|
|
1332
|
+
"POST",
|
|
1333
|
+
`${this.prefix}/anonymous/promote`,
|
|
1334
|
+
credentials
|
|
1335
|
+
);
|
|
1071
1336
|
if (res.data) {
|
|
1072
1337
|
this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
|
|
1073
1338
|
this._emitEvent("USER_UPDATED", res.data.user, res.data.access_token);
|
|
@@ -1083,91 +1348,6 @@ var AuthService = class extends core.BaseService {
|
|
|
1083
1348
|
return this.verifyOtp({ type: "magiclink", token });
|
|
1084
1349
|
}
|
|
1085
1350
|
};
|
|
1086
|
-
var AuthAdminService = class extends core.BaseService {
|
|
1087
|
-
get prefix() {
|
|
1088
|
-
return `/v1/auth/${this.client.projectId}/admin`;
|
|
1089
|
-
}
|
|
1090
|
-
// -- Users ----------------------------------------------------------------
|
|
1091
|
-
async listUsers(params) {
|
|
1092
|
-
const query = new URLSearchParams();
|
|
1093
|
-
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
1094
|
-
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
1095
|
-
if (params?.search) query.set("search", params.search);
|
|
1096
|
-
const qs = query.toString();
|
|
1097
|
-
return this.request("GET", `${this.prefix}/users${qs ? "?" + qs : ""}`);
|
|
1098
|
-
}
|
|
1099
|
-
async getUser(userId) {
|
|
1100
|
-
return this.request("GET", `${this.prefix}/users/${userId}`);
|
|
1101
|
-
}
|
|
1102
|
-
async createUser(data) {
|
|
1103
|
-
return this.request("POST", `${this.prefix}/users`, data);
|
|
1104
|
-
}
|
|
1105
|
-
async updateUser(userId, data) {
|
|
1106
|
-
return this.request("PUT", `${this.prefix}/users/${userId}`, data);
|
|
1107
|
-
}
|
|
1108
|
-
async deleteUser(userId) {
|
|
1109
|
-
return this.request("DELETE", `${this.prefix}/users/${userId}`);
|
|
1110
|
-
}
|
|
1111
|
-
async banUser(userId) {
|
|
1112
|
-
return this.request("POST", `${this.prefix}/users/${userId}/ban`, {});
|
|
1113
|
-
}
|
|
1114
|
-
async unbanUser(userId) {
|
|
1115
|
-
return this.request("POST", `${this.prefix}/users/${userId}/unban`, {});
|
|
1116
|
-
}
|
|
1117
|
-
async generateLink(userId, type) {
|
|
1118
|
-
return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
|
|
1119
|
-
}
|
|
1120
|
-
async getUserStats() {
|
|
1121
|
-
return this.request("GET", `${this.prefix}/users/stats`);
|
|
1122
|
-
}
|
|
1123
|
-
async listUserIdentities(userId) {
|
|
1124
|
-
return this.request("GET", `${this.prefix}/users/${userId}/identities`);
|
|
1125
|
-
}
|
|
1126
|
-
// -- Sessions ---------------------------------------------------------------
|
|
1127
|
-
async listSessions(params) {
|
|
1128
|
-
const query = new URLSearchParams();
|
|
1129
|
-
if (params?.userId !== void 0) query.set("user_id", params.userId);
|
|
1130
|
-
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
1131
|
-
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
1132
|
-
const qs = query.toString();
|
|
1133
|
-
return this.request("GET", `${this.prefix}/sessions${qs ? "?" + qs : ""}`);
|
|
1134
|
-
}
|
|
1135
|
-
async revokeSession(sessionId) {
|
|
1136
|
-
return this.request("DELETE", `${this.prefix}/sessions/${sessionId}`);
|
|
1137
|
-
}
|
|
1138
|
-
// -- Audit log ------------------------------------------------------------
|
|
1139
|
-
async getAuditLog(params) {
|
|
1140
|
-
const query = new URLSearchParams();
|
|
1141
|
-
if (params?.userId !== void 0) query.set("user_id", params.userId);
|
|
1142
|
-
if (params?.limit !== void 0) query.set("limit", String(params.limit));
|
|
1143
|
-
if (params?.offset !== void 0) query.set("offset", String(params.offset));
|
|
1144
|
-
const qs = query.toString();
|
|
1145
|
-
return this.request("GET", `${this.prefix}/audit-log${qs ? "?" + qs : ""}`);
|
|
1146
|
-
}
|
|
1147
|
-
// -- Settings -------------------------------------------------------------
|
|
1148
|
-
async getSettings() {
|
|
1149
|
-
return this.request("GET", `${this.prefix}/settings`);
|
|
1150
|
-
}
|
|
1151
|
-
async updateSettings(data) {
|
|
1152
|
-
return this.request("PUT", `${this.prefix}/settings`, data);
|
|
1153
|
-
}
|
|
1154
|
-
// -- Email templates ------------------------------------------------------
|
|
1155
|
-
async listEmailTemplates() {
|
|
1156
|
-
return this.request("GET", `${this.prefix}/email-templates`);
|
|
1157
|
-
}
|
|
1158
|
-
async getEmailTemplate(type) {
|
|
1159
|
-
return this.request("GET", `${this.prefix}/email-templates/${type}`);
|
|
1160
|
-
}
|
|
1161
|
-
async updateEmailTemplate(type, data) {
|
|
1162
|
-
return this.request("PUT", `${this.prefix}/email-templates/${type}`, data);
|
|
1163
|
-
}
|
|
1164
|
-
async deleteEmailTemplate(type) {
|
|
1165
|
-
return this.request("DELETE", `${this.prefix}/email-templates/${type}`);
|
|
1166
|
-
}
|
|
1167
|
-
async testEmailTemplate(type, body) {
|
|
1168
|
-
return this.request("POST", `${this.prefix}/email-templates/${type}/test`, body);
|
|
1169
|
-
}
|
|
1170
|
-
};
|
|
1171
1351
|
|
|
1172
1352
|
exports.AuthAdminService = AuthAdminService;
|
|
1173
1353
|
exports.AuthService = AuthService;
|