@aurabase/auth 0.6.1 → 0.7.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 +12 -7
- package/dist/index.cjs +284 -55
- package/dist/index.d.cts +185 -17
- package/dist/index.d.ts +185 -17
- package/dist/index.js +279 -56
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -279,7 +279,7 @@ Réservé au serveur (clé `service_role`). Deux façons d'y accéder :
|
|
|
279
279
|
// Via le client admin (recommandé)
|
|
280
280
|
import { createAdminClient } from '@aurabase/aurabase-js'
|
|
281
281
|
|
|
282
|
-
const admin = createAdminClient('https://mon-projet.aurabase.cloud', process.env.
|
|
282
|
+
const admin = createAdminClient('https://mon-projet.aurabase.cloud', process.env.AURABASE_SERVICE_ROLE_KEY!)
|
|
283
283
|
await admin.authAdmin.listUsers({ limit: 50 })
|
|
284
284
|
```
|
|
285
285
|
|
|
@@ -288,14 +288,19 @@ await admin.authAdmin.listUsers({ limit: 50 })
|
|
|
288
288
|
import { AuthAdminService } from '@aurabase/auth'
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
-
Méthodes clés : `listUsers`, `getUser`, `createUser`, `updateUser`, `deleteUser`, `banUser`, `unbanUser`, `generateLink`, `getUserStats`, `listUserIdentities`, `listSessions`, `revokeSession`, `getAuditLog`, `getSettings`, `updateSettings`, `listEmailTemplates`/`getEmailTemplate`/`updateEmailTemplate`/`deleteEmailTemplate`, `testEmailTemplate`.
|
|
291
|
+
Méthodes clés : `listUsers`, `getUser`, `createUser`, `updateUser`, `deleteUser`, `banUser`, `unbanUser`, `generateLink`, `getUserStats`, `listUserIdentities`, `listSessions`, `revokeSession`, `impersonate` (RFC 8693 avec claim `act`), `importUsers` (import en masse avec conversion automatique bcrypt vers Argon2id), `listSigningKeys` (clés ES256 du projet), `getAuditLog`, `getSettings`, `updateSettings`, `listEmailTemplates`/`getEmailTemplate`/`updateEmailTemplate`/`deleteEmailTemplate`, `testEmailTemplate`.
|
|
292
292
|
|
|
293
|
-
|
|
293
|
+
### Authentification Multi-Facteurs (MFA v0.7.0)
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
-
|
|
297
|
-
-
|
|
298
|
-
-
|
|
295
|
+
Aurabase supporte l'authentification multifacteur complète avec niveaux d'assurance (AAL1, AAL2) et claim standard `amr` :
|
|
296
|
+
- **TOTP** : `c.auth.mfa.enroll()`, `c.auth.mfa.verifySetup(token)`, `c.auth.mfa.verify({ mfa_token, code })`
|
|
297
|
+
- **Facteurs disponibles** : `c.auth.mfa.factors(mfa_token)` liste les méthodes éligibles (`totp`, `webauthn`, `sms`)
|
|
298
|
+
- **WebAuthn / Passkeys** : `c.auth.mfa.webauthn`
|
|
299
|
+
- **SMS OTP** : `c.auth.mfa.sms`
|
|
300
|
+
|
|
301
|
+
### Vérification locale de jeton (JWKS / ES256)
|
|
302
|
+
|
|
303
|
+
Chaque projet émet des jetons signés avec sa propre clé asymétrique **ES256**. Vous pouvez vérifier les jetons hors-ligne via n'importe quelle bibliothèque standard (`jose`, Go, Python) en récupérant la clé publique sur `https://<projet>.aurabase.cloud/v1/auth/.well-known/jwks.json`.
|
|
299
304
|
|
|
300
305
|
## Documentation
|
|
301
306
|
|
package/dist/index.cjs
CHANGED
|
@@ -56,6 +56,46 @@ var AuthAdminService = class extends core.BaseService {
|
|
|
56
56
|
async generateLink(userId, type) {
|
|
57
57
|
return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Impersonation (AUTH-037) : jeton COURT (10 minutes au plus) pour agir en tant que l'utilisateur,
|
|
61
|
+
* avec le claim `act: { sub }`. Pas de refresh token, journalisé (`admin_impersonation`).
|
|
62
|
+
* Un compte `owner`/`admin` exige `allowPrivileged: true` ET une `reason`.
|
|
63
|
+
*/
|
|
64
|
+
async impersonate(userId, options) {
|
|
65
|
+
return this.request("POST", `${this.prefix}/users/${userId}/impersonate`, {
|
|
66
|
+
reason: options?.reason,
|
|
67
|
+
ttl_seconds: options?.ttlSeconds,
|
|
68
|
+
allow_privileged: options?.allowPrivileged ?? false
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Import en masse (AUTH-037) : jusqu'a 1000 utilisateurs par appel, avec hash bcrypt ou argon2id.
|
|
73
|
+
* Rapport ligne par ligne ; une ligne en erreur n'arrete pas les autres.
|
|
74
|
+
*/
|
|
75
|
+
async importUsers(users) {
|
|
76
|
+
return this.request("POST", `${this.prefix}/users/import`, {
|
|
77
|
+
users: users.map((u) => ({
|
|
78
|
+
email: u.email,
|
|
79
|
+
email_verified: u.emailVerified ?? false,
|
|
80
|
+
password_hash: u.passwordHash,
|
|
81
|
+
name: u.name,
|
|
82
|
+
user_metadata: u.userMetadata,
|
|
83
|
+
app_metadata: u.appMetadata
|
|
84
|
+
}))
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/** Cles de signature du projet (active + previous), URL du JWKS et emetteur (`iss`). */
|
|
88
|
+
async listSigningKeys() {
|
|
89
|
+
return this.request("GET", `${this.prefix}/signing-keys`);
|
|
90
|
+
}
|
|
91
|
+
/** Rotation : une nouvelle cle signe les nouveaux jetons, l'ancienne reste publiee 24 h. */
|
|
92
|
+
async rotateSigningKey() {
|
|
93
|
+
return this.request("POST", `${this.prefix}/signing-keys/rotate`, {});
|
|
94
|
+
}
|
|
95
|
+
/** Revoque une ancienne cle compromise (retiree du JWKS, jetons refuses). */
|
|
96
|
+
async revokeSigningKey(kid) {
|
|
97
|
+
return this.request("DELETE", `${this.prefix}/signing-keys/${encodeURIComponent(kid)}`);
|
|
98
|
+
}
|
|
59
99
|
/**
|
|
60
100
|
* Statistiques des utilisateurs : total, vérifiés, bannis, avec MFA et nouveaux sur 7 jours.
|
|
61
101
|
*/
|
|
@@ -353,6 +393,77 @@ async function codeChallengeS256(verifier) {
|
|
|
353
393
|
return b64url(new Uint8Array(digest));
|
|
354
394
|
}
|
|
355
395
|
|
|
396
|
+
// src/webauthn.ts
|
|
397
|
+
function base64urlToBytes(valeur) {
|
|
398
|
+
const b64 = valeur.replace(/-/g, "+").replace(/_/g, "/");
|
|
399
|
+
const bin = atob(b64 + "=".repeat((4 - b64.length % 4) % 4));
|
|
400
|
+
const octets = new Uint8Array(new ArrayBuffer(bin.length));
|
|
401
|
+
for (let i = 0; i < bin.length; i++) octets[i] = bin.charCodeAt(i);
|
|
402
|
+
return octets;
|
|
403
|
+
}
|
|
404
|
+
function bytesToBase64url(octets) {
|
|
405
|
+
const vue = octets instanceof ArrayBuffer ? new Uint8Array(octets) : new Uint8Array(octets.buffer, octets.byteOffset, octets.byteLength);
|
|
406
|
+
let bin = "";
|
|
407
|
+
for (const o of vue) bin += String.fromCharCode(o);
|
|
408
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
409
|
+
}
|
|
410
|
+
function creationOptionsFromJson(json) {
|
|
411
|
+
const pk = json.publicKey;
|
|
412
|
+
return {
|
|
413
|
+
publicKey: {
|
|
414
|
+
...pk,
|
|
415
|
+
challenge: base64urlToBytes(pk.challenge),
|
|
416
|
+
user: { ...pk.user, id: base64urlToBytes(pk.user.id) },
|
|
417
|
+
excludeCredentials: (pk.excludeCredentials ?? []).map((c) => ({
|
|
418
|
+
...c,
|
|
419
|
+
id: base64urlToBytes(c.id)
|
|
420
|
+
}))
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function requestOptionsFromJson(json) {
|
|
425
|
+
const pk = json.publicKey;
|
|
426
|
+
return {
|
|
427
|
+
publicKey: {
|
|
428
|
+
...pk,
|
|
429
|
+
challenge: base64urlToBytes(pk.challenge),
|
|
430
|
+
allowCredentials: (pk.allowCredentials ?? []).map((c) => ({
|
|
431
|
+
...c,
|
|
432
|
+
id: base64urlToBytes(c.id)
|
|
433
|
+
}))
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function attestationToJson(cred) {
|
|
438
|
+
const r = cred.response;
|
|
439
|
+
return {
|
|
440
|
+
id: cred.id,
|
|
441
|
+
rawId: bytesToBase64url(cred.rawId),
|
|
442
|
+
type: cred.type,
|
|
443
|
+
response: {
|
|
444
|
+
attestationObject: bytesToBase64url(r.attestationObject),
|
|
445
|
+
clientDataJSON: bytesToBase64url(r.clientDataJSON),
|
|
446
|
+
transports: typeof r.getTransports === "function" ? r.getTransports() : void 0
|
|
447
|
+
},
|
|
448
|
+
extensions: cred.getClientExtensionResults?.() ?? {}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function assertionToJson(cred) {
|
|
452
|
+
const r = cred.response;
|
|
453
|
+
return {
|
|
454
|
+
id: cred.id,
|
|
455
|
+
rawId: bytesToBase64url(cred.rawId),
|
|
456
|
+
type: cred.type,
|
|
457
|
+
response: {
|
|
458
|
+
authenticatorData: bytesToBase64url(r.authenticatorData),
|
|
459
|
+
clientDataJSON: bytesToBase64url(r.clientDataJSON),
|
|
460
|
+
signature: bytesToBase64url(r.signature),
|
|
461
|
+
userHandle: r.userHandle ? bytesToBase64url(r.userHandle) : null
|
|
462
|
+
},
|
|
463
|
+
extensions: cred.getClientExtensionResults?.() ?? {}
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
356
467
|
// src/SessionManager.ts
|
|
357
468
|
var SessionManager = class {
|
|
358
469
|
opts;
|
|
@@ -454,10 +565,16 @@ var AuthService = class extends core.BaseService {
|
|
|
454
565
|
_stateListeners = /* @__PURE__ */ new Set();
|
|
455
566
|
_sessionManager;
|
|
456
567
|
_applyingRemote = false;
|
|
568
|
+
/** AUTH-011 — séquence des effacements de session et dernier effacement annoncé par SIGNED_OUT. */
|
|
569
|
+
_clearSeq = 0;
|
|
570
|
+
_signedOutSeq = 0;
|
|
457
571
|
_unsubStore = null;
|
|
458
572
|
_disposed = false;
|
|
573
|
+
/** AUTH-015 — flux OAuth par défaut du client. */
|
|
574
|
+
_defaultFlowType;
|
|
459
575
|
constructor(client, options) {
|
|
460
576
|
super(client);
|
|
577
|
+
this._defaultFlowType = options?.flowType;
|
|
461
578
|
const browser = typeof window !== "undefined";
|
|
462
579
|
const enabled = !!options?.forceEnabled || (options?.autoRefresh ?? true) && browser && (options?.persistSession ?? true);
|
|
463
580
|
this._sessionManager = new SessionManager({
|
|
@@ -473,8 +590,18 @@ var AuthService = class extends core.BaseService {
|
|
|
473
590
|
onRemoteMessage: (data) => this._applyRemoteState(data)
|
|
474
591
|
});
|
|
475
592
|
this._sessionManager.start();
|
|
593
|
+
let hadToken = !!this.client.authStore.token;
|
|
476
594
|
this._unsubStore = this.client.authStore.onChange((token) => {
|
|
477
595
|
this._sessionManager.setActive(!!token);
|
|
596
|
+
if (!token && hadToken && !this._applyingRemote) {
|
|
597
|
+
const seq = ++this._clearSeq;
|
|
598
|
+
queueMicrotask(() => {
|
|
599
|
+
if (this._signedOutSeq < seq && !this.client.authStore.token) {
|
|
600
|
+
this._emitEvent("SIGNED_OUT", null, null);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
hadToken = !!token;
|
|
478
605
|
if (!this._applyingRemote) {
|
|
479
606
|
const state = this._broadcastState();
|
|
480
607
|
if (!token || state !== null) {
|
|
@@ -689,8 +816,6 @@ var AuthService = class extends core.BaseService {
|
|
|
689
816
|
const session = store.token && store.user ? {
|
|
690
817
|
access_token: store.token,
|
|
691
818
|
refresh_token: store.refreshToken ?? "",
|
|
692
|
-
accessToken: store.token,
|
|
693
|
-
refreshToken: store.refreshToken ?? "",
|
|
694
819
|
token_type: "Bearer",
|
|
695
820
|
expires_in: 900,
|
|
696
821
|
user: store.user
|
|
@@ -701,11 +826,10 @@ var AuthService = class extends core.BaseService {
|
|
|
701
826
|
return result;
|
|
702
827
|
}
|
|
703
828
|
_emitEvent(event, user, accessToken, extra) {
|
|
829
|
+
if (event === "SIGNED_OUT") this._signedOutSeq = this._clearSeq;
|
|
704
830
|
const session = accessToken && user ? {
|
|
705
831
|
access_token: accessToken,
|
|
706
832
|
refresh_token: this.client.authStore.refreshToken ?? "",
|
|
707
|
-
accessToken,
|
|
708
|
-
refreshToken: this.client.authStore.refreshToken ?? "",
|
|
709
833
|
token_type: "Bearer",
|
|
710
834
|
expires_in: 900,
|
|
711
835
|
user
|
|
@@ -736,15 +860,15 @@ var AuthService = class extends core.BaseService {
|
|
|
736
860
|
if (req.options?.data) payload.data = req.options.data;
|
|
737
861
|
if (req.options?.emailRedirectTo) payload.email_redirect_to = req.options.emailRedirectTo;
|
|
738
862
|
const res = await this.request("POST", `${this.prefix}/register`, payload);
|
|
863
|
+
if (res.data && !isMfaChallenge(res.data) && !res.data.access_token) {
|
|
864
|
+
res.data.session = null;
|
|
865
|
+
return res;
|
|
866
|
+
}
|
|
739
867
|
if (res.data && !isMfaChallenge(res.data)) {
|
|
740
868
|
const tp = res.data;
|
|
741
|
-
tp.accessToken = tp.access_token;
|
|
742
|
-
tp.refreshToken = tp.refresh_token;
|
|
743
869
|
tp.session = {
|
|
744
870
|
access_token: tp.access_token,
|
|
745
871
|
refresh_token: tp.refresh_token,
|
|
746
|
-
accessToken: tp.access_token,
|
|
747
|
-
refreshToken: tp.refresh_token,
|
|
748
872
|
token_type: tp.token_type,
|
|
749
873
|
expires_in: tp.expires_in,
|
|
750
874
|
user: tp.user,
|
|
@@ -756,11 +880,6 @@ var AuthService = class extends core.BaseService {
|
|
|
756
880
|
}
|
|
757
881
|
if (res.data && isMfaChallenge(res.data)) {
|
|
758
882
|
const mfa = res.data;
|
|
759
|
-
mfa.mfaChallenge = {
|
|
760
|
-
mfaRequired: mfa.mfa_required,
|
|
761
|
-
mfaToken: mfa.mfa_token,
|
|
762
|
-
mfaSetupRequired: mfa.mfa_setup_required
|
|
763
|
-
};
|
|
764
883
|
this._emitEvent("MFA_CHALLENGE", null, null, {
|
|
765
884
|
mfaToken: mfa.mfa_token,
|
|
766
885
|
mfaSetupRequired: mfa.mfa_setup_required
|
|
@@ -782,13 +901,9 @@ var AuthService = class extends core.BaseService {
|
|
|
782
901
|
const res = await this.request("POST", `${this.prefix}/login`, payload);
|
|
783
902
|
if (res.data && !isMfaChallenge(res.data)) {
|
|
784
903
|
const tp = res.data;
|
|
785
|
-
tp.accessToken = tp.access_token;
|
|
786
|
-
tp.refreshToken = tp.refresh_token;
|
|
787
904
|
tp.session = {
|
|
788
905
|
access_token: tp.access_token,
|
|
789
906
|
refresh_token: tp.refresh_token,
|
|
790
|
-
accessToken: tp.access_token,
|
|
791
|
-
refreshToken: tp.refresh_token,
|
|
792
907
|
token_type: tp.token_type,
|
|
793
908
|
expires_in: tp.expires_in,
|
|
794
909
|
user: tp.user,
|
|
@@ -800,11 +915,6 @@ var AuthService = class extends core.BaseService {
|
|
|
800
915
|
}
|
|
801
916
|
if (res.data && isMfaChallenge(res.data)) {
|
|
802
917
|
const mfa = res.data;
|
|
803
|
-
mfa.mfaChallenge = {
|
|
804
|
-
mfaRequired: mfa.mfa_required,
|
|
805
|
-
mfaToken: mfa.mfa_token,
|
|
806
|
-
mfaSetupRequired: mfa.mfa_setup_required
|
|
807
|
-
};
|
|
808
918
|
this._emitEvent("MFA_CHALLENGE", null, null, {
|
|
809
919
|
mfaToken: mfa.mfa_token,
|
|
810
920
|
mfaSetupRequired: mfa.mfa_setup_required
|
|
@@ -833,7 +943,7 @@ var AuthService = class extends core.BaseService {
|
|
|
833
943
|
error: new core.AuraError(0, {
|
|
834
944
|
error: {
|
|
835
945
|
code: "invalid_provider",
|
|
836
|
-
message: "signInWithOAuth:
|
|
946
|
+
message: "signInWithOAuth: the provider must be a non-empty string, e.g. signInWithOAuth('google', { redirectTo }) or signInWithOAuth({ provider: 'google', redirectTo })"
|
|
837
947
|
}
|
|
838
948
|
})
|
|
839
949
|
};
|
|
@@ -847,22 +957,22 @@ var AuthService = class extends core.BaseService {
|
|
|
847
957
|
params.set(k, v);
|
|
848
958
|
continue;
|
|
849
959
|
}
|
|
850
|
-
const raison = OAUTH_SDK_OWNED_QUERY_PARAMS.has(k) ? `
|
|
960
|
+
const raison = OAUTH_SDK_OWNED_QUERY_PARAMS.has(k) ? `it is built by signInWithOAuth (use the dedicated option: ${k === "redirect_uri" || k === "redirect_to" ? "redirectTo" : k === "scopes" ? "scopes" : k === "project_id" ? "the project URL passed to createClient()" : "flowType:'pkce'"})` : `it is not forwarded to the provider. Only these sign-in experience parameters are: ${[...OAUTH_FORWARDABLE_QUERY_PARAMS].join(", ")}`;
|
|
851
961
|
return {
|
|
852
962
|
data: null,
|
|
853
963
|
error: new core.AuraError(400, {
|
|
854
964
|
error: {
|
|
855
965
|
code: "invalid_oauth_query_param",
|
|
856
|
-
message: `signInWithOAuth: queryParams.${k}
|
|
966
|
+
message: `signInWithOAuth: queryParams.${k} rejected \u2014 ${raison}.`
|
|
857
967
|
}
|
|
858
968
|
})
|
|
859
969
|
};
|
|
860
970
|
}
|
|
861
971
|
}
|
|
862
|
-
if (options?.flowType === "pkce") {
|
|
972
|
+
if ((options?.flowType ?? this._defaultFlowType) === "pkce") {
|
|
863
973
|
if (typeof window !== "undefined" && !this.client.authStore.persistent) {
|
|
864
974
|
console.warn(
|
|
865
|
-
|
|
975
|
+
'[aurabase] PKCE (flowType:"pkce") requires a persistent store (LocalAuthStore) so that the code_verifier survives the OAuth redirect; the current store is in-memory, so the exchange will fail on return.'
|
|
866
976
|
);
|
|
867
977
|
}
|
|
868
978
|
try {
|
|
@@ -877,7 +987,7 @@ var AuthService = class extends core.BaseService {
|
|
|
877
987
|
error: new core.AuraError(0, {
|
|
878
988
|
error: {
|
|
879
989
|
code: "pkce_generation_failed",
|
|
880
|
-
message: "PKCE:
|
|
990
|
+
message: "PKCE: cannot generate the code_verifier/challenge (WebCrypto unavailable)"
|
|
881
991
|
}
|
|
882
992
|
})
|
|
883
993
|
};
|
|
@@ -885,7 +995,7 @@ var AuthService = class extends core.BaseService {
|
|
|
885
995
|
} else {
|
|
886
996
|
if (typeof window !== "undefined" && !this.client.authStore.persistent) {
|
|
887
997
|
console.warn(
|
|
888
|
-
'[aurabase]
|
|
998
|
+
'[aurabase] SECURITY: signInWithOAuth in implicit flow with a NON-persistent store (in-memory): the anti session-fixation marker will not survive the OAuth redirect and the session will not be adopted on return. Use a LocalAuthStore, and prefer flowType:"pkce" (recommended, immune to session fixation).'
|
|
889
999
|
);
|
|
890
1000
|
}
|
|
891
1001
|
try {
|
|
@@ -894,7 +1004,7 @@ var AuthService = class extends core.BaseService {
|
|
|
894
1004
|
}
|
|
895
1005
|
}
|
|
896
1006
|
const url = `${this.client.baseUrl}/v1/auth/oauth/${encodeURIComponent(provider)}/start?${params.toString()}`;
|
|
897
|
-
if (typeof window !== "undefined") {
|
|
1007
|
+
if (typeof window !== "undefined" && window.location) {
|
|
898
1008
|
window.location.href = url;
|
|
899
1009
|
}
|
|
900
1010
|
return { data: { url }, error: null };
|
|
@@ -951,11 +1061,9 @@ var AuthService = class extends core.BaseService {
|
|
|
951
1061
|
return res;
|
|
952
1062
|
}
|
|
953
1063
|
/**
|
|
954
|
-
* Session courante lue depuis le store (
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
* - Permet `const { data: { session }, error } = await aura.auth.getSession()` (forme canonique)
|
|
958
|
-
* - Permet `const { accessToken, user } = await aura.auth.getSession()` (forme plate historique)
|
|
1064
|
+
* Session courante lue depuis le store (pas d'appel réseau). Forme unique (AUTH-019) :
|
|
1065
|
+
* `const { data: { session } } = await aura.auth.getSession()`.
|
|
1066
|
+
* Côté serveur, cette session n'est PAS vérifiée : autorisez avec `getUser()` (AUTH-004).
|
|
959
1067
|
*/
|
|
960
1068
|
getSession() {
|
|
961
1069
|
const token = this.client.authStore.token;
|
|
@@ -963,19 +1071,11 @@ var AuthService = class extends core.BaseService {
|
|
|
963
1071
|
const session = token && user ? {
|
|
964
1072
|
access_token: token,
|
|
965
1073
|
refresh_token: this.client.authStore.refreshToken ?? "",
|
|
966
|
-
accessToken: token,
|
|
967
|
-
refreshToken: this.client.authStore.refreshToken ?? "",
|
|
968
1074
|
token_type: "Bearer",
|
|
969
1075
|
expires_in: 900,
|
|
970
1076
|
user
|
|
971
1077
|
} : null;
|
|
972
|
-
return Promise.resolve({
|
|
973
|
-
data: { session },
|
|
974
|
-
error: null,
|
|
975
|
-
accessToken: token,
|
|
976
|
-
access_token: token,
|
|
977
|
-
user
|
|
978
|
-
});
|
|
1078
|
+
return Promise.resolve({ data: { session }, error: null });
|
|
979
1079
|
}
|
|
980
1080
|
/**
|
|
981
1081
|
* Installe une session à partir d'un jeton d'accès et d'un jeton de rafraîchissement existants (par exemple reçus d'un serveur).
|
|
@@ -1125,13 +1225,26 @@ var AuthService = class extends core.BaseService {
|
|
|
1125
1225
|
* applique l'isolation projet native : un token d'un AUTRE projet → 403.
|
|
1126
1226
|
*/
|
|
1127
1227
|
async getUser(jwt) {
|
|
1228
|
+
let res;
|
|
1128
1229
|
if (jwt !== void 0) {
|
|
1129
|
-
|
|
1230
|
+
res = await this.request("GET", `${this.prefix}/user`, void 0, {
|
|
1130
1231
|
authToken: jwt,
|
|
1131
1232
|
skipAuthRefresh: true
|
|
1132
1233
|
});
|
|
1234
|
+
} else {
|
|
1235
|
+
res = await this.request("GET", `${this.prefix}/user`);
|
|
1236
|
+
}
|
|
1237
|
+
if (res.data && typeof res.data === "object") {
|
|
1238
|
+
const userObj = res.data;
|
|
1239
|
+
Object.defineProperty(userObj, "user", {
|
|
1240
|
+
get() {
|
|
1241
|
+
return userObj;
|
|
1242
|
+
},
|
|
1243
|
+
enumerable: false,
|
|
1244
|
+
configurable: true
|
|
1245
|
+
});
|
|
1133
1246
|
}
|
|
1134
|
-
return
|
|
1247
|
+
return res;
|
|
1135
1248
|
}
|
|
1136
1249
|
/**
|
|
1137
1250
|
* Vérifie et décode les claims d'un JWT.
|
|
@@ -1230,7 +1343,7 @@ var AuthService = class extends core.BaseService {
|
|
|
1230
1343
|
error: new core.AuraError(0, {
|
|
1231
1344
|
error: {
|
|
1232
1345
|
code: "allow_expired_unsupported_on_fallback",
|
|
1233
|
-
message: "getClaims: allowExpired
|
|
1346
|
+
message: "getClaims: allowExpired cannot be honored on the server validation path (HS256/JWKS fallback)"
|
|
1234
1347
|
}
|
|
1235
1348
|
})
|
|
1236
1349
|
};
|
|
@@ -1258,7 +1371,7 @@ var AuthService = class extends core.BaseService {
|
|
|
1258
1371
|
error: new core.AuraError(0, {
|
|
1259
1372
|
error: {
|
|
1260
1373
|
code: "invalid_response",
|
|
1261
|
-
message: "getClaims:
|
|
1374
|
+
message: "getClaims: the server token validation (getUser) answered with no content, so the claims cannot be determined"
|
|
1262
1375
|
}
|
|
1263
1376
|
})
|
|
1264
1377
|
};
|
|
@@ -1274,6 +1387,17 @@ var AuthService = class extends core.BaseService {
|
|
|
1274
1387
|
* Supprime le compte de l'utilisateur connecté après confirmation par son mot de passe, puis vide la session locale.
|
|
1275
1388
|
*/
|
|
1276
1389
|
async deleteUser(password) {
|
|
1390
|
+
if (!password || typeof password !== "string" || password.trim() === "") {
|
|
1391
|
+
return {
|
|
1392
|
+
data: null,
|
|
1393
|
+
error: new core.AuraError(400, {
|
|
1394
|
+
error: {
|
|
1395
|
+
code: "PASSWORD_REQUIRED",
|
|
1396
|
+
message: "Password is required to delete account"
|
|
1397
|
+
}
|
|
1398
|
+
})
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1277
1401
|
const res = await this.request("DELETE", `${this.prefix}/user`, { password });
|
|
1278
1402
|
if (!res.error) {
|
|
1279
1403
|
this.client.authStore.clear();
|
|
@@ -1296,9 +1420,10 @@ var AuthService = class extends core.BaseService {
|
|
|
1296
1420
|
return res;
|
|
1297
1421
|
}
|
|
1298
1422
|
// -- Password recovery ------------------------------------------------------
|
|
1299
|
-
/** Envoie un e-mail de réinitialisation du mot de passe. */
|
|
1300
|
-
async resetPasswordForEmail(
|
|
1301
|
-
|
|
1423
|
+
/** Envoie un e-mail de réinitialisation du mot de passe (accepte un email sous forme de chaîne ou un objet PasswordResetRequest). */
|
|
1424
|
+
async resetPasswordForEmail(emailOrReq) {
|
|
1425
|
+
const body = typeof emailOrReq === "string" ? { email: emailOrReq } : emailOrReq;
|
|
1426
|
+
return this.request("POST", `${this.prefix}/forgot-password`, body);
|
|
1302
1427
|
}
|
|
1303
1428
|
/**
|
|
1304
1429
|
* Change le mot de passe de l'utilisateur connecté. Le serveur révoque toutes ses sessions : le SDK vide la session locale et émet `SIGNED_OUT`.
|
|
@@ -1334,9 +1459,107 @@ var AuthService = class extends core.BaseService {
|
|
|
1334
1459
|
regenerateRecoveryCodes: (codeOrParams) => {
|
|
1335
1460
|
const code = typeof codeOrParams === "string" ? codeOrParams : codeOrParams.code;
|
|
1336
1461
|
return this.regenerateRecoveryCodes(code);
|
|
1462
|
+
},
|
|
1463
|
+
factors: (mfaToken) => this.request("POST", `${this.prefix}/mfa/factors`, { mfa_token: mfaToken }),
|
|
1464
|
+
sms: {
|
|
1465
|
+
setup: (phone) => this.request("POST", `${this.prefix}/mfa/sms/setup`, { phone }),
|
|
1466
|
+
verifySetup: (code) => this.request("POST", `${this.prefix}/mfa/sms/setup/verify`, { code }),
|
|
1467
|
+
send: (mfaToken) => this.request("POST", `${this.prefix}/mfa/sms/send`, {
|
|
1468
|
+
mfa_token: mfaToken
|
|
1469
|
+
}),
|
|
1470
|
+
remove: (password) => this.request("DELETE", `${this.prefix}/mfa/sms`, { password })
|
|
1471
|
+
},
|
|
1472
|
+
webauthn: {
|
|
1473
|
+
register: (params) => this.webauthnRegister(params),
|
|
1474
|
+
verify: (params) => this.webauthnVerify(params),
|
|
1475
|
+
list: () => this.request("GET", `${this.prefix}/mfa/webauthn`),
|
|
1476
|
+
remove: (id, password) => this.request(
|
|
1477
|
+
"DELETE",
|
|
1478
|
+
`${this.prefix}/mfa/webauthn/${encodeURIComponent(id)}`,
|
|
1479
|
+
{ password }
|
|
1480
|
+
)
|
|
1337
1481
|
}
|
|
1338
1482
|
};
|
|
1339
1483
|
}
|
|
1484
|
+
webauthnIndisponible() {
|
|
1485
|
+
return {
|
|
1486
|
+
data: null,
|
|
1487
|
+
error: new core.AuraError(0, {
|
|
1488
|
+
error: {
|
|
1489
|
+
code: "webauthn_unavailable",
|
|
1490
|
+
message: "WebAuthn is not available here: navigator.credentials is missing. Use a browser in a secure context (https or localhost), or pass `credentials` explicitly."
|
|
1491
|
+
}
|
|
1492
|
+
})
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Enrôle une passkey WebAuthn pour l'utilisateur connecté (cérémonie complète : options du serveur,
|
|
1497
|
+
* `navigator.credentials.create`, confirmation). Le premier facteur du compte active le MFA et
|
|
1498
|
+
* renvoie les codes de récupération (`recovery_codes`) — à afficher une seule fois.
|
|
1499
|
+
*/
|
|
1500
|
+
async webauthnRegister(params) {
|
|
1501
|
+
const conteneur = params?.credentials ?? (typeof navigator !== "undefined" ? navigator.credentials : void 0);
|
|
1502
|
+
if (!conteneur) return this.webauthnIndisponible();
|
|
1503
|
+
const debut = await this.request(
|
|
1504
|
+
"POST",
|
|
1505
|
+
`${this.prefix}/mfa/webauthn/register/start`,
|
|
1506
|
+
{ friendly_name: params?.friendlyName }
|
|
1507
|
+
);
|
|
1508
|
+
if (!debut.data) return debut;
|
|
1509
|
+
let cred;
|
|
1510
|
+
try {
|
|
1511
|
+
cred = await conteneur.create(creationOptionsFromJson(debut.data.options));
|
|
1512
|
+
} catch (e) {
|
|
1513
|
+
return this.webauthnAnnule(e);
|
|
1514
|
+
}
|
|
1515
|
+
if (!cred) return this.webauthnAnnule(new Error("no credential returned"));
|
|
1516
|
+
return this.request("POST", `${this.prefix}/mfa/webauthn/register/finish`, {
|
|
1517
|
+
credential: attestationToJson(cred),
|
|
1518
|
+
friendly_name: params?.friendlyName
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Relève un défi MFA (`mfaToken`, reçu d'un `signIn`) avec une passkey : options du serveur,
|
|
1523
|
+
* `navigator.credentials.get`, vérification. Enregistre la session et émet `SIGNED_IN`.
|
|
1524
|
+
*/
|
|
1525
|
+
async webauthnVerify(params) {
|
|
1526
|
+
const conteneur = params.credentials ?? (typeof navigator !== "undefined" ? navigator.credentials : void 0);
|
|
1527
|
+
if (!conteneur) return this.webauthnIndisponible();
|
|
1528
|
+
const debut = await this.request(
|
|
1529
|
+
"POST",
|
|
1530
|
+
`${this.prefix}/mfa/webauthn/verify/start`,
|
|
1531
|
+
{ mfa_token: params.mfaToken }
|
|
1532
|
+
);
|
|
1533
|
+
if (!debut.data) return debut;
|
|
1534
|
+
let cred;
|
|
1535
|
+
try {
|
|
1536
|
+
cred = await conteneur.get(requestOptionsFromJson(debut.data.options));
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
return this.webauthnAnnule(e);
|
|
1539
|
+
}
|
|
1540
|
+
if (!cred) return this.webauthnAnnule(new Error("no credential returned"));
|
|
1541
|
+
const res = await this.request("POST", `${this.prefix}/mfa/webauthn/verify/finish`, {
|
|
1542
|
+
mfa_token: params.mfaToken,
|
|
1543
|
+
credential: assertionToJson(cred)
|
|
1544
|
+
});
|
|
1545
|
+
if (res.data) {
|
|
1546
|
+
this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
|
|
1547
|
+
this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
|
|
1548
|
+
}
|
|
1549
|
+
return res;
|
|
1550
|
+
}
|
|
1551
|
+
webauthnAnnule(cause) {
|
|
1552
|
+
const nom = cause instanceof Error ? cause.name : "Error";
|
|
1553
|
+
return {
|
|
1554
|
+
data: null,
|
|
1555
|
+
error: new core.AuraError(0, {
|
|
1556
|
+
error: {
|
|
1557
|
+
code: nom === "NotAllowedError" ? "webauthn_cancelled" : "webauthn_failed",
|
|
1558
|
+
message: nom === "NotAllowedError" ? "The passkey prompt was cancelled or timed out." : `The passkey ceremony failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
1559
|
+
}
|
|
1560
|
+
})
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1340
1563
|
/** MFA : démarre l'enrôlement TOTP et renvoie le `secret` et l'URL `otpauth_url` à afficher en QR code. */
|
|
1341
1564
|
async mfaEnroll() {
|
|
1342
1565
|
return this.request("POST", `${this.prefix}/mfa/setup`);
|
|
@@ -1506,10 +1729,10 @@ var AuthService = class extends core.BaseService {
|
|
|
1506
1729
|
async getSettings() {
|
|
1507
1730
|
return this.request("GET", `${this.prefix}/settings`);
|
|
1508
1731
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1732
|
+
// AUTH-023 — `getAnonKey()` retirée : la route `/anon-key` n'est servie qu'aux projets qui
|
|
1733
|
+
// activent l'auth hébergée (garde C1, aucun BaaS n'expose la clé d'un projet à un anonyme), et
|
|
1734
|
+
// un client qui l'appelle possède déjà la clé anon (passée à createClient). La méthode
|
|
1735
|
+
// répondait donc 404 pour presque tous les projets.
|
|
1513
1736
|
// -- Password recovery (token consumption) ----------------------------------
|
|
1514
1737
|
/**
|
|
1515
1738
|
* Consomme le token de récupération de mot de passe et applique le nouveau mot de passe.
|
|
@@ -1549,4 +1772,10 @@ var AuthService = class extends core.BaseService {
|
|
|
1549
1772
|
exports.AuthAdminService = AuthAdminService;
|
|
1550
1773
|
exports.AuthService = AuthService;
|
|
1551
1774
|
exports.SessionManager = SessionManager;
|
|
1775
|
+
exports.assertionToJson = assertionToJson;
|
|
1776
|
+
exports.attestationToJson = attestationToJson;
|
|
1777
|
+
exports.base64urlToBytes = base64urlToBytes;
|
|
1778
|
+
exports.bytesToBase64url = bytesToBase64url;
|
|
1779
|
+
exports.creationOptionsFromJson = creationOptionsFromJson;
|
|
1552
1780
|
exports.isMfaChallenge = isMfaChallenge;
|
|
1781
|
+
exports.requestOptionsFromJson = requestOptionsFromJson;
|