@aurabase/auth 0.6.2 → 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 +11 -6
- package/dist/index.cjs +265 -50
- package/dist/index.d.cts +180 -14
- package/dist/index.d.ts +180 -14
- package/dist/index.js +260 -51
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -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).
|
|
@@ -1243,7 +1343,7 @@ var AuthService = class extends core.BaseService {
|
|
|
1243
1343
|
error: new core.AuraError(0, {
|
|
1244
1344
|
error: {
|
|
1245
1345
|
code: "allow_expired_unsupported_on_fallback",
|
|
1246
|
-
message: "getClaims: allowExpired
|
|
1346
|
+
message: "getClaims: allowExpired cannot be honored on the server validation path (HS256/JWKS fallback)"
|
|
1247
1347
|
}
|
|
1248
1348
|
})
|
|
1249
1349
|
};
|
|
@@ -1271,7 +1371,7 @@ var AuthService = class extends core.BaseService {
|
|
|
1271
1371
|
error: new core.AuraError(0, {
|
|
1272
1372
|
error: {
|
|
1273
1373
|
code: "invalid_response",
|
|
1274
|
-
message: "getClaims:
|
|
1374
|
+
message: "getClaims: the server token validation (getUser) answered with no content, so the claims cannot be determined"
|
|
1275
1375
|
}
|
|
1276
1376
|
})
|
|
1277
1377
|
};
|
|
@@ -1287,6 +1387,17 @@ var AuthService = class extends core.BaseService {
|
|
|
1287
1387
|
* Supprime le compte de l'utilisateur connecté après confirmation par son mot de passe, puis vide la session locale.
|
|
1288
1388
|
*/
|
|
1289
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
|
+
}
|
|
1290
1401
|
const res = await this.request("DELETE", `${this.prefix}/user`, { password });
|
|
1291
1402
|
if (!res.error) {
|
|
1292
1403
|
this.client.authStore.clear();
|
|
@@ -1348,9 +1459,107 @@ var AuthService = class extends core.BaseService {
|
|
|
1348
1459
|
regenerateRecoveryCodes: (codeOrParams) => {
|
|
1349
1460
|
const code = typeof codeOrParams === "string" ? codeOrParams : codeOrParams.code;
|
|
1350
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
|
+
)
|
|
1351
1481
|
}
|
|
1352
1482
|
};
|
|
1353
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
|
+
}
|
|
1354
1563
|
/** MFA : démarre l'enrôlement TOTP et renvoie le `secret` et l'URL `otpauth_url` à afficher en QR code. */
|
|
1355
1564
|
async mfaEnroll() {
|
|
1356
1565
|
return this.request("POST", `${this.prefix}/mfa/setup`);
|
|
@@ -1520,10 +1729,10 @@ var AuthService = class extends core.BaseService {
|
|
|
1520
1729
|
async getSettings() {
|
|
1521
1730
|
return this.request("GET", `${this.prefix}/settings`);
|
|
1522
1731
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
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.
|
|
1527
1736
|
// -- Password recovery (token consumption) ----------------------------------
|
|
1528
1737
|
/**
|
|
1529
1738
|
* Consomme le token de récupération de mot de passe et applique le nouveau mot de passe.
|
|
@@ -1563,4 +1772,10 @@ var AuthService = class extends core.BaseService {
|
|
|
1563
1772
|
exports.AuthAdminService = AuthAdminService;
|
|
1564
1773
|
exports.AuthService = AuthService;
|
|
1565
1774
|
exports.SessionManager = SessionManager;
|
|
1775
|
+
exports.assertionToJson = assertionToJson;
|
|
1776
|
+
exports.attestationToJson = attestationToJson;
|
|
1777
|
+
exports.base64urlToBytes = base64urlToBytes;
|
|
1778
|
+
exports.bytesToBase64url = bytesToBase64url;
|
|
1779
|
+
exports.creationOptionsFromJson = creationOptionsFromJson;
|
|
1566
1780
|
exports.isMfaChallenge = isMfaChallenge;
|
|
1781
|
+
exports.requestOptionsFromJson = requestOptionsFromJson;
|