@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/dist/index.js
CHANGED
|
@@ -54,6 +54,46 @@ var AuthAdminService = class extends BaseService {
|
|
|
54
54
|
async generateLink(userId, type) {
|
|
55
55
|
return this.request("POST", `${this.prefix}/users/${userId}/generate-link`, { type });
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Impersonation (AUTH-037) : jeton COURT (10 minutes au plus) pour agir en tant que l'utilisateur,
|
|
59
|
+
* avec le claim `act: { sub }`. Pas de refresh token, journalisé (`admin_impersonation`).
|
|
60
|
+
* Un compte `owner`/`admin` exige `allowPrivileged: true` ET une `reason`.
|
|
61
|
+
*/
|
|
62
|
+
async impersonate(userId, options) {
|
|
63
|
+
return this.request("POST", `${this.prefix}/users/${userId}/impersonate`, {
|
|
64
|
+
reason: options?.reason,
|
|
65
|
+
ttl_seconds: options?.ttlSeconds,
|
|
66
|
+
allow_privileged: options?.allowPrivileged ?? false
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Import en masse (AUTH-037) : jusqu'a 1000 utilisateurs par appel, avec hash bcrypt ou argon2id.
|
|
71
|
+
* Rapport ligne par ligne ; une ligne en erreur n'arrete pas les autres.
|
|
72
|
+
*/
|
|
73
|
+
async importUsers(users) {
|
|
74
|
+
return this.request("POST", `${this.prefix}/users/import`, {
|
|
75
|
+
users: users.map((u) => ({
|
|
76
|
+
email: u.email,
|
|
77
|
+
email_verified: u.emailVerified ?? false,
|
|
78
|
+
password_hash: u.passwordHash,
|
|
79
|
+
name: u.name,
|
|
80
|
+
user_metadata: u.userMetadata,
|
|
81
|
+
app_metadata: u.appMetadata
|
|
82
|
+
}))
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Cles de signature du projet (active + previous), URL du JWKS et emetteur (`iss`). */
|
|
86
|
+
async listSigningKeys() {
|
|
87
|
+
return this.request("GET", `${this.prefix}/signing-keys`);
|
|
88
|
+
}
|
|
89
|
+
/** Rotation : une nouvelle cle signe les nouveaux jetons, l'ancienne reste publiee 24 h. */
|
|
90
|
+
async rotateSigningKey() {
|
|
91
|
+
return this.request("POST", `${this.prefix}/signing-keys/rotate`, {});
|
|
92
|
+
}
|
|
93
|
+
/** Revoque une ancienne cle compromise (retiree du JWKS, jetons refuses). */
|
|
94
|
+
async revokeSigningKey(kid) {
|
|
95
|
+
return this.request("DELETE", `${this.prefix}/signing-keys/${encodeURIComponent(kid)}`);
|
|
96
|
+
}
|
|
57
97
|
/**
|
|
58
98
|
* Statistiques des utilisateurs : total, vérifiés, bannis, avec MFA et nouveaux sur 7 jours.
|
|
59
99
|
*/
|
|
@@ -351,6 +391,77 @@ async function codeChallengeS256(verifier) {
|
|
|
351
391
|
return b64url(new Uint8Array(digest));
|
|
352
392
|
}
|
|
353
393
|
|
|
394
|
+
// src/webauthn.ts
|
|
395
|
+
function base64urlToBytes(valeur) {
|
|
396
|
+
const b64 = valeur.replace(/-/g, "+").replace(/_/g, "/");
|
|
397
|
+
const bin = atob(b64 + "=".repeat((4 - b64.length % 4) % 4));
|
|
398
|
+
const octets = new Uint8Array(new ArrayBuffer(bin.length));
|
|
399
|
+
for (let i = 0; i < bin.length; i++) octets[i] = bin.charCodeAt(i);
|
|
400
|
+
return octets;
|
|
401
|
+
}
|
|
402
|
+
function bytesToBase64url(octets) {
|
|
403
|
+
const vue = octets instanceof ArrayBuffer ? new Uint8Array(octets) : new Uint8Array(octets.buffer, octets.byteOffset, octets.byteLength);
|
|
404
|
+
let bin = "";
|
|
405
|
+
for (const o of vue) bin += String.fromCharCode(o);
|
|
406
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
407
|
+
}
|
|
408
|
+
function creationOptionsFromJson(json) {
|
|
409
|
+
const pk = json.publicKey;
|
|
410
|
+
return {
|
|
411
|
+
publicKey: {
|
|
412
|
+
...pk,
|
|
413
|
+
challenge: base64urlToBytes(pk.challenge),
|
|
414
|
+
user: { ...pk.user, id: base64urlToBytes(pk.user.id) },
|
|
415
|
+
excludeCredentials: (pk.excludeCredentials ?? []).map((c) => ({
|
|
416
|
+
...c,
|
|
417
|
+
id: base64urlToBytes(c.id)
|
|
418
|
+
}))
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function requestOptionsFromJson(json) {
|
|
423
|
+
const pk = json.publicKey;
|
|
424
|
+
return {
|
|
425
|
+
publicKey: {
|
|
426
|
+
...pk,
|
|
427
|
+
challenge: base64urlToBytes(pk.challenge),
|
|
428
|
+
allowCredentials: (pk.allowCredentials ?? []).map((c) => ({
|
|
429
|
+
...c,
|
|
430
|
+
id: base64urlToBytes(c.id)
|
|
431
|
+
}))
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
function attestationToJson(cred) {
|
|
436
|
+
const r = cred.response;
|
|
437
|
+
return {
|
|
438
|
+
id: cred.id,
|
|
439
|
+
rawId: bytesToBase64url(cred.rawId),
|
|
440
|
+
type: cred.type,
|
|
441
|
+
response: {
|
|
442
|
+
attestationObject: bytesToBase64url(r.attestationObject),
|
|
443
|
+
clientDataJSON: bytesToBase64url(r.clientDataJSON),
|
|
444
|
+
transports: typeof r.getTransports === "function" ? r.getTransports() : void 0
|
|
445
|
+
},
|
|
446
|
+
extensions: cred.getClientExtensionResults?.() ?? {}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function assertionToJson(cred) {
|
|
450
|
+
const r = cred.response;
|
|
451
|
+
return {
|
|
452
|
+
id: cred.id,
|
|
453
|
+
rawId: bytesToBase64url(cred.rawId),
|
|
454
|
+
type: cred.type,
|
|
455
|
+
response: {
|
|
456
|
+
authenticatorData: bytesToBase64url(r.authenticatorData),
|
|
457
|
+
clientDataJSON: bytesToBase64url(r.clientDataJSON),
|
|
458
|
+
signature: bytesToBase64url(r.signature),
|
|
459
|
+
userHandle: r.userHandle ? bytesToBase64url(r.userHandle) : null
|
|
460
|
+
},
|
|
461
|
+
extensions: cred.getClientExtensionResults?.() ?? {}
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
354
465
|
// src/SessionManager.ts
|
|
355
466
|
var SessionManager = class {
|
|
356
467
|
opts;
|
|
@@ -452,10 +563,16 @@ var AuthService = class extends BaseService {
|
|
|
452
563
|
_stateListeners = /* @__PURE__ */ new Set();
|
|
453
564
|
_sessionManager;
|
|
454
565
|
_applyingRemote = false;
|
|
566
|
+
/** AUTH-011 — séquence des effacements de session et dernier effacement annoncé par SIGNED_OUT. */
|
|
567
|
+
_clearSeq = 0;
|
|
568
|
+
_signedOutSeq = 0;
|
|
455
569
|
_unsubStore = null;
|
|
456
570
|
_disposed = false;
|
|
571
|
+
/** AUTH-015 — flux OAuth par défaut du client. */
|
|
572
|
+
_defaultFlowType;
|
|
457
573
|
constructor(client, options) {
|
|
458
574
|
super(client);
|
|
575
|
+
this._defaultFlowType = options?.flowType;
|
|
459
576
|
const browser = typeof window !== "undefined";
|
|
460
577
|
const enabled = !!options?.forceEnabled || (options?.autoRefresh ?? true) && browser && (options?.persistSession ?? true);
|
|
461
578
|
this._sessionManager = new SessionManager({
|
|
@@ -471,8 +588,18 @@ var AuthService = class extends BaseService {
|
|
|
471
588
|
onRemoteMessage: (data) => this._applyRemoteState(data)
|
|
472
589
|
});
|
|
473
590
|
this._sessionManager.start();
|
|
591
|
+
let hadToken = !!this.client.authStore.token;
|
|
474
592
|
this._unsubStore = this.client.authStore.onChange((token) => {
|
|
475
593
|
this._sessionManager.setActive(!!token);
|
|
594
|
+
if (!token && hadToken && !this._applyingRemote) {
|
|
595
|
+
const seq = ++this._clearSeq;
|
|
596
|
+
queueMicrotask(() => {
|
|
597
|
+
if (this._signedOutSeq < seq && !this.client.authStore.token) {
|
|
598
|
+
this._emitEvent("SIGNED_OUT", null, null);
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
hadToken = !!token;
|
|
476
603
|
if (!this._applyingRemote) {
|
|
477
604
|
const state = this._broadcastState();
|
|
478
605
|
if (!token || state !== null) {
|
|
@@ -687,8 +814,6 @@ var AuthService = class extends BaseService {
|
|
|
687
814
|
const session = store.token && store.user ? {
|
|
688
815
|
access_token: store.token,
|
|
689
816
|
refresh_token: store.refreshToken ?? "",
|
|
690
|
-
accessToken: store.token,
|
|
691
|
-
refreshToken: store.refreshToken ?? "",
|
|
692
817
|
token_type: "Bearer",
|
|
693
818
|
expires_in: 900,
|
|
694
819
|
user: store.user
|
|
@@ -699,11 +824,10 @@ var AuthService = class extends BaseService {
|
|
|
699
824
|
return result;
|
|
700
825
|
}
|
|
701
826
|
_emitEvent(event, user, accessToken, extra) {
|
|
827
|
+
if (event === "SIGNED_OUT") this._signedOutSeq = this._clearSeq;
|
|
702
828
|
const session = accessToken && user ? {
|
|
703
829
|
access_token: accessToken,
|
|
704
830
|
refresh_token: this.client.authStore.refreshToken ?? "",
|
|
705
|
-
accessToken,
|
|
706
|
-
refreshToken: this.client.authStore.refreshToken ?? "",
|
|
707
831
|
token_type: "Bearer",
|
|
708
832
|
expires_in: 900,
|
|
709
833
|
user
|
|
@@ -734,15 +858,15 @@ var AuthService = class extends BaseService {
|
|
|
734
858
|
if (req.options?.data) payload.data = req.options.data;
|
|
735
859
|
if (req.options?.emailRedirectTo) payload.email_redirect_to = req.options.emailRedirectTo;
|
|
736
860
|
const res = await this.request("POST", `${this.prefix}/register`, payload);
|
|
861
|
+
if (res.data && !isMfaChallenge(res.data) && !res.data.access_token) {
|
|
862
|
+
res.data.session = null;
|
|
863
|
+
return res;
|
|
864
|
+
}
|
|
737
865
|
if (res.data && !isMfaChallenge(res.data)) {
|
|
738
866
|
const tp = res.data;
|
|
739
|
-
tp.accessToken = tp.access_token;
|
|
740
|
-
tp.refreshToken = tp.refresh_token;
|
|
741
867
|
tp.session = {
|
|
742
868
|
access_token: tp.access_token,
|
|
743
869
|
refresh_token: tp.refresh_token,
|
|
744
|
-
accessToken: tp.access_token,
|
|
745
|
-
refreshToken: tp.refresh_token,
|
|
746
870
|
token_type: tp.token_type,
|
|
747
871
|
expires_in: tp.expires_in,
|
|
748
872
|
user: tp.user,
|
|
@@ -754,11 +878,6 @@ var AuthService = class extends BaseService {
|
|
|
754
878
|
}
|
|
755
879
|
if (res.data && isMfaChallenge(res.data)) {
|
|
756
880
|
const mfa = res.data;
|
|
757
|
-
mfa.mfaChallenge = {
|
|
758
|
-
mfaRequired: mfa.mfa_required,
|
|
759
|
-
mfaToken: mfa.mfa_token,
|
|
760
|
-
mfaSetupRequired: mfa.mfa_setup_required
|
|
761
|
-
};
|
|
762
881
|
this._emitEvent("MFA_CHALLENGE", null, null, {
|
|
763
882
|
mfaToken: mfa.mfa_token,
|
|
764
883
|
mfaSetupRequired: mfa.mfa_setup_required
|
|
@@ -780,13 +899,9 @@ var AuthService = class extends BaseService {
|
|
|
780
899
|
const res = await this.request("POST", `${this.prefix}/login`, payload);
|
|
781
900
|
if (res.data && !isMfaChallenge(res.data)) {
|
|
782
901
|
const tp = res.data;
|
|
783
|
-
tp.accessToken = tp.access_token;
|
|
784
|
-
tp.refreshToken = tp.refresh_token;
|
|
785
902
|
tp.session = {
|
|
786
903
|
access_token: tp.access_token,
|
|
787
904
|
refresh_token: tp.refresh_token,
|
|
788
|
-
accessToken: tp.access_token,
|
|
789
|
-
refreshToken: tp.refresh_token,
|
|
790
905
|
token_type: tp.token_type,
|
|
791
906
|
expires_in: tp.expires_in,
|
|
792
907
|
user: tp.user,
|
|
@@ -798,11 +913,6 @@ var AuthService = class extends BaseService {
|
|
|
798
913
|
}
|
|
799
914
|
if (res.data && isMfaChallenge(res.data)) {
|
|
800
915
|
const mfa = res.data;
|
|
801
|
-
mfa.mfaChallenge = {
|
|
802
|
-
mfaRequired: mfa.mfa_required,
|
|
803
|
-
mfaToken: mfa.mfa_token,
|
|
804
|
-
mfaSetupRequired: mfa.mfa_setup_required
|
|
805
|
-
};
|
|
806
916
|
this._emitEvent("MFA_CHALLENGE", null, null, {
|
|
807
917
|
mfaToken: mfa.mfa_token,
|
|
808
918
|
mfaSetupRequired: mfa.mfa_setup_required
|
|
@@ -831,7 +941,7 @@ var AuthService = class extends BaseService {
|
|
|
831
941
|
error: new AuraError(0, {
|
|
832
942
|
error: {
|
|
833
943
|
code: "invalid_provider",
|
|
834
|
-
message: "signInWithOAuth:
|
|
944
|
+
message: "signInWithOAuth: the provider must be a non-empty string, e.g. signInWithOAuth('google', { redirectTo }) or signInWithOAuth({ provider: 'google', redirectTo })"
|
|
835
945
|
}
|
|
836
946
|
})
|
|
837
947
|
};
|
|
@@ -845,22 +955,22 @@ var AuthService = class extends BaseService {
|
|
|
845
955
|
params.set(k, v);
|
|
846
956
|
continue;
|
|
847
957
|
}
|
|
848
|
-
const raison = OAUTH_SDK_OWNED_QUERY_PARAMS.has(k) ? `
|
|
958
|
+
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(", ")}`;
|
|
849
959
|
return {
|
|
850
960
|
data: null,
|
|
851
961
|
error: new AuraError(400, {
|
|
852
962
|
error: {
|
|
853
963
|
code: "invalid_oauth_query_param",
|
|
854
|
-
message: `signInWithOAuth: queryParams.${k}
|
|
964
|
+
message: `signInWithOAuth: queryParams.${k} rejected \u2014 ${raison}.`
|
|
855
965
|
}
|
|
856
966
|
})
|
|
857
967
|
};
|
|
858
968
|
}
|
|
859
969
|
}
|
|
860
|
-
if (options?.flowType === "pkce") {
|
|
970
|
+
if ((options?.flowType ?? this._defaultFlowType) === "pkce") {
|
|
861
971
|
if (typeof window !== "undefined" && !this.client.authStore.persistent) {
|
|
862
972
|
console.warn(
|
|
863
|
-
|
|
973
|
+
'[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.'
|
|
864
974
|
);
|
|
865
975
|
}
|
|
866
976
|
try {
|
|
@@ -875,7 +985,7 @@ var AuthService = class extends BaseService {
|
|
|
875
985
|
error: new AuraError(0, {
|
|
876
986
|
error: {
|
|
877
987
|
code: "pkce_generation_failed",
|
|
878
|
-
message: "PKCE:
|
|
988
|
+
message: "PKCE: cannot generate the code_verifier/challenge (WebCrypto unavailable)"
|
|
879
989
|
}
|
|
880
990
|
})
|
|
881
991
|
};
|
|
@@ -883,7 +993,7 @@ var AuthService = class extends BaseService {
|
|
|
883
993
|
} else {
|
|
884
994
|
if (typeof window !== "undefined" && !this.client.authStore.persistent) {
|
|
885
995
|
console.warn(
|
|
886
|
-
'[aurabase]
|
|
996
|
+
'[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).'
|
|
887
997
|
);
|
|
888
998
|
}
|
|
889
999
|
try {
|
|
@@ -892,7 +1002,7 @@ var AuthService = class extends BaseService {
|
|
|
892
1002
|
}
|
|
893
1003
|
}
|
|
894
1004
|
const url = `${this.client.baseUrl}/v1/auth/oauth/${encodeURIComponent(provider)}/start?${params.toString()}`;
|
|
895
|
-
if (typeof window !== "undefined") {
|
|
1005
|
+
if (typeof window !== "undefined" && window.location) {
|
|
896
1006
|
window.location.href = url;
|
|
897
1007
|
}
|
|
898
1008
|
return { data: { url }, error: null };
|
|
@@ -949,11 +1059,9 @@ var AuthService = class extends BaseService {
|
|
|
949
1059
|
return res;
|
|
950
1060
|
}
|
|
951
1061
|
/**
|
|
952
|
-
* Session courante lue depuis le store (
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
* - Permet `const { data: { session }, error } = await aura.auth.getSession()` (forme canonique)
|
|
956
|
-
* - Permet `const { accessToken, user } = await aura.auth.getSession()` (forme plate historique)
|
|
1062
|
+
* Session courante lue depuis le store (pas d'appel réseau). Forme unique (AUTH-019) :
|
|
1063
|
+
* `const { data: { session } } = await aura.auth.getSession()`.
|
|
1064
|
+
* Côté serveur, cette session n'est PAS vérifiée : autorisez avec `getUser()` (AUTH-004).
|
|
957
1065
|
*/
|
|
958
1066
|
getSession() {
|
|
959
1067
|
const token = this.client.authStore.token;
|
|
@@ -961,19 +1069,11 @@ var AuthService = class extends BaseService {
|
|
|
961
1069
|
const session = token && user ? {
|
|
962
1070
|
access_token: token,
|
|
963
1071
|
refresh_token: this.client.authStore.refreshToken ?? "",
|
|
964
|
-
accessToken: token,
|
|
965
|
-
refreshToken: this.client.authStore.refreshToken ?? "",
|
|
966
1072
|
token_type: "Bearer",
|
|
967
1073
|
expires_in: 900,
|
|
968
1074
|
user
|
|
969
1075
|
} : null;
|
|
970
|
-
return Promise.resolve({
|
|
971
|
-
data: { session },
|
|
972
|
-
error: null,
|
|
973
|
-
accessToken: token,
|
|
974
|
-
access_token: token,
|
|
975
|
-
user
|
|
976
|
-
});
|
|
1076
|
+
return Promise.resolve({ data: { session }, error: null });
|
|
977
1077
|
}
|
|
978
1078
|
/**
|
|
979
1079
|
* 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).
|
|
@@ -1123,13 +1223,26 @@ var AuthService = class extends BaseService {
|
|
|
1123
1223
|
* applique l'isolation projet native : un token d'un AUTRE projet → 403.
|
|
1124
1224
|
*/
|
|
1125
1225
|
async getUser(jwt) {
|
|
1226
|
+
let res;
|
|
1126
1227
|
if (jwt !== void 0) {
|
|
1127
|
-
|
|
1228
|
+
res = await this.request("GET", `${this.prefix}/user`, void 0, {
|
|
1128
1229
|
authToken: jwt,
|
|
1129
1230
|
skipAuthRefresh: true
|
|
1130
1231
|
});
|
|
1232
|
+
} else {
|
|
1233
|
+
res = await this.request("GET", `${this.prefix}/user`);
|
|
1234
|
+
}
|
|
1235
|
+
if (res.data && typeof res.data === "object") {
|
|
1236
|
+
const userObj = res.data;
|
|
1237
|
+
Object.defineProperty(userObj, "user", {
|
|
1238
|
+
get() {
|
|
1239
|
+
return userObj;
|
|
1240
|
+
},
|
|
1241
|
+
enumerable: false,
|
|
1242
|
+
configurable: true
|
|
1243
|
+
});
|
|
1131
1244
|
}
|
|
1132
|
-
return
|
|
1245
|
+
return res;
|
|
1133
1246
|
}
|
|
1134
1247
|
/**
|
|
1135
1248
|
* Vérifie et décode les claims d'un JWT.
|
|
@@ -1228,7 +1341,7 @@ var AuthService = class extends BaseService {
|
|
|
1228
1341
|
error: new AuraError(0, {
|
|
1229
1342
|
error: {
|
|
1230
1343
|
code: "allow_expired_unsupported_on_fallback",
|
|
1231
|
-
message: "getClaims: allowExpired
|
|
1344
|
+
message: "getClaims: allowExpired cannot be honored on the server validation path (HS256/JWKS fallback)"
|
|
1232
1345
|
}
|
|
1233
1346
|
})
|
|
1234
1347
|
};
|
|
@@ -1256,7 +1369,7 @@ var AuthService = class extends BaseService {
|
|
|
1256
1369
|
error: new AuraError(0, {
|
|
1257
1370
|
error: {
|
|
1258
1371
|
code: "invalid_response",
|
|
1259
|
-
message: "getClaims:
|
|
1372
|
+
message: "getClaims: the server token validation (getUser) answered with no content, so the claims cannot be determined"
|
|
1260
1373
|
}
|
|
1261
1374
|
})
|
|
1262
1375
|
};
|
|
@@ -1272,6 +1385,17 @@ var AuthService = class extends BaseService {
|
|
|
1272
1385
|
* Supprime le compte de l'utilisateur connecté après confirmation par son mot de passe, puis vide la session locale.
|
|
1273
1386
|
*/
|
|
1274
1387
|
async deleteUser(password) {
|
|
1388
|
+
if (!password || typeof password !== "string" || password.trim() === "") {
|
|
1389
|
+
return {
|
|
1390
|
+
data: null,
|
|
1391
|
+
error: new AuraError(400, {
|
|
1392
|
+
error: {
|
|
1393
|
+
code: "PASSWORD_REQUIRED",
|
|
1394
|
+
message: "Password is required to delete account"
|
|
1395
|
+
}
|
|
1396
|
+
})
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1275
1399
|
const res = await this.request("DELETE", `${this.prefix}/user`, { password });
|
|
1276
1400
|
if (!res.error) {
|
|
1277
1401
|
this.client.authStore.clear();
|
|
@@ -1294,9 +1418,10 @@ var AuthService = class extends BaseService {
|
|
|
1294
1418
|
return res;
|
|
1295
1419
|
}
|
|
1296
1420
|
// -- Password recovery ------------------------------------------------------
|
|
1297
|
-
/** Envoie un e-mail de réinitialisation du mot de passe. */
|
|
1298
|
-
async resetPasswordForEmail(
|
|
1299
|
-
|
|
1421
|
+
/** Envoie un e-mail de réinitialisation du mot de passe (accepte un email sous forme de chaîne ou un objet PasswordResetRequest). */
|
|
1422
|
+
async resetPasswordForEmail(emailOrReq) {
|
|
1423
|
+
const body = typeof emailOrReq === "string" ? { email: emailOrReq } : emailOrReq;
|
|
1424
|
+
return this.request("POST", `${this.prefix}/forgot-password`, body);
|
|
1300
1425
|
}
|
|
1301
1426
|
/**
|
|
1302
1427
|
* 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`.
|
|
@@ -1332,9 +1457,107 @@ var AuthService = class extends BaseService {
|
|
|
1332
1457
|
regenerateRecoveryCodes: (codeOrParams) => {
|
|
1333
1458
|
const code = typeof codeOrParams === "string" ? codeOrParams : codeOrParams.code;
|
|
1334
1459
|
return this.regenerateRecoveryCodes(code);
|
|
1460
|
+
},
|
|
1461
|
+
factors: (mfaToken) => this.request("POST", `${this.prefix}/mfa/factors`, { mfa_token: mfaToken }),
|
|
1462
|
+
sms: {
|
|
1463
|
+
setup: (phone) => this.request("POST", `${this.prefix}/mfa/sms/setup`, { phone }),
|
|
1464
|
+
verifySetup: (code) => this.request("POST", `${this.prefix}/mfa/sms/setup/verify`, { code }),
|
|
1465
|
+
send: (mfaToken) => this.request("POST", `${this.prefix}/mfa/sms/send`, {
|
|
1466
|
+
mfa_token: mfaToken
|
|
1467
|
+
}),
|
|
1468
|
+
remove: (password) => this.request("DELETE", `${this.prefix}/mfa/sms`, { password })
|
|
1469
|
+
},
|
|
1470
|
+
webauthn: {
|
|
1471
|
+
register: (params) => this.webauthnRegister(params),
|
|
1472
|
+
verify: (params) => this.webauthnVerify(params),
|
|
1473
|
+
list: () => this.request("GET", `${this.prefix}/mfa/webauthn`),
|
|
1474
|
+
remove: (id, password) => this.request(
|
|
1475
|
+
"DELETE",
|
|
1476
|
+
`${this.prefix}/mfa/webauthn/${encodeURIComponent(id)}`,
|
|
1477
|
+
{ password }
|
|
1478
|
+
)
|
|
1335
1479
|
}
|
|
1336
1480
|
};
|
|
1337
1481
|
}
|
|
1482
|
+
webauthnIndisponible() {
|
|
1483
|
+
return {
|
|
1484
|
+
data: null,
|
|
1485
|
+
error: new AuraError(0, {
|
|
1486
|
+
error: {
|
|
1487
|
+
code: "webauthn_unavailable",
|
|
1488
|
+
message: "WebAuthn is not available here: navigator.credentials is missing. Use a browser in a secure context (https or localhost), or pass `credentials` explicitly."
|
|
1489
|
+
}
|
|
1490
|
+
})
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* Enrôle une passkey WebAuthn pour l'utilisateur connecté (cérémonie complète : options du serveur,
|
|
1495
|
+
* `navigator.credentials.create`, confirmation). Le premier facteur du compte active le MFA et
|
|
1496
|
+
* renvoie les codes de récupération (`recovery_codes`) — à afficher une seule fois.
|
|
1497
|
+
*/
|
|
1498
|
+
async webauthnRegister(params) {
|
|
1499
|
+
const conteneur = params?.credentials ?? (typeof navigator !== "undefined" ? navigator.credentials : void 0);
|
|
1500
|
+
if (!conteneur) return this.webauthnIndisponible();
|
|
1501
|
+
const debut = await this.request(
|
|
1502
|
+
"POST",
|
|
1503
|
+
`${this.prefix}/mfa/webauthn/register/start`,
|
|
1504
|
+
{ friendly_name: params?.friendlyName }
|
|
1505
|
+
);
|
|
1506
|
+
if (!debut.data) return debut;
|
|
1507
|
+
let cred;
|
|
1508
|
+
try {
|
|
1509
|
+
cred = await conteneur.create(creationOptionsFromJson(debut.data.options));
|
|
1510
|
+
} catch (e) {
|
|
1511
|
+
return this.webauthnAnnule(e);
|
|
1512
|
+
}
|
|
1513
|
+
if (!cred) return this.webauthnAnnule(new Error("no credential returned"));
|
|
1514
|
+
return this.request("POST", `${this.prefix}/mfa/webauthn/register/finish`, {
|
|
1515
|
+
credential: attestationToJson(cred),
|
|
1516
|
+
friendly_name: params?.friendlyName
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Relève un défi MFA (`mfaToken`, reçu d'un `signIn`) avec une passkey : options du serveur,
|
|
1521
|
+
* `navigator.credentials.get`, vérification. Enregistre la session et émet `SIGNED_IN`.
|
|
1522
|
+
*/
|
|
1523
|
+
async webauthnVerify(params) {
|
|
1524
|
+
const conteneur = params.credentials ?? (typeof navigator !== "undefined" ? navigator.credentials : void 0);
|
|
1525
|
+
if (!conteneur) return this.webauthnIndisponible();
|
|
1526
|
+
const debut = await this.request(
|
|
1527
|
+
"POST",
|
|
1528
|
+
`${this.prefix}/mfa/webauthn/verify/start`,
|
|
1529
|
+
{ mfa_token: params.mfaToken }
|
|
1530
|
+
);
|
|
1531
|
+
if (!debut.data) return debut;
|
|
1532
|
+
let cred;
|
|
1533
|
+
try {
|
|
1534
|
+
cred = await conteneur.get(requestOptionsFromJson(debut.data.options));
|
|
1535
|
+
} catch (e) {
|
|
1536
|
+
return this.webauthnAnnule(e);
|
|
1537
|
+
}
|
|
1538
|
+
if (!cred) return this.webauthnAnnule(new Error("no credential returned"));
|
|
1539
|
+
const res = await this.request("POST", `${this.prefix}/mfa/webauthn/verify/finish`, {
|
|
1540
|
+
mfa_token: params.mfaToken,
|
|
1541
|
+
credential: assertionToJson(cred)
|
|
1542
|
+
});
|
|
1543
|
+
if (res.data) {
|
|
1544
|
+
this.client.authStore.save(res.data.access_token, res.data.refresh_token, res.data.user);
|
|
1545
|
+
this._emitEvent("SIGNED_IN", res.data.user, res.data.access_token);
|
|
1546
|
+
}
|
|
1547
|
+
return res;
|
|
1548
|
+
}
|
|
1549
|
+
webauthnAnnule(cause) {
|
|
1550
|
+
const nom = cause instanceof Error ? cause.name : "Error";
|
|
1551
|
+
return {
|
|
1552
|
+
data: null,
|
|
1553
|
+
error: new AuraError(0, {
|
|
1554
|
+
error: {
|
|
1555
|
+
code: nom === "NotAllowedError" ? "webauthn_cancelled" : "webauthn_failed",
|
|
1556
|
+
message: nom === "NotAllowedError" ? "The passkey prompt was cancelled or timed out." : `The passkey ceremony failed: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
1557
|
+
}
|
|
1558
|
+
})
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1338
1561
|
/** MFA : démarre l'enrôlement TOTP et renvoie le `secret` et l'URL `otpauth_url` à afficher en QR code. */
|
|
1339
1562
|
async mfaEnroll() {
|
|
1340
1563
|
return this.request("POST", `${this.prefix}/mfa/setup`);
|
|
@@ -1504,10 +1727,10 @@ var AuthService = class extends BaseService {
|
|
|
1504
1727
|
async getSettings() {
|
|
1505
1728
|
return this.request("GET", `${this.prefix}/settings`);
|
|
1506
1729
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1730
|
+
// AUTH-023 — `getAnonKey()` retirée : la route `/anon-key` n'est servie qu'aux projets qui
|
|
1731
|
+
// activent l'auth hébergée (garde C1, aucun BaaS n'expose la clé d'un projet à un anonyme), et
|
|
1732
|
+
// un client qui l'appelle possède déjà la clé anon (passée à createClient). La méthode
|
|
1733
|
+
// répondait donc 404 pour presque tous les projets.
|
|
1511
1734
|
// -- Password recovery (token consumption) ----------------------------------
|
|
1512
1735
|
/**
|
|
1513
1736
|
* Consomme le token de récupération de mot de passe et applique le nouveau mot de passe.
|
|
@@ -1544,4 +1767,4 @@ var AuthService = class extends BaseService {
|
|
|
1544
1767
|
}
|
|
1545
1768
|
};
|
|
1546
1769
|
|
|
1547
|
-
export { AuthAdminService, AuthService, SessionManager, isMfaChallenge };
|
|
1770
|
+
export { AuthAdminService, AuthService, SessionManager, assertionToJson, attestationToJson, base64urlToBytes, bytesToBase64url, creationOptionsFromJson, isMfaChallenge, requestOptionsFromJson };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aurabase/auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Service d'authentification du SDK Aurabase (sessions, OAuth/PKCE, MFA, magic link, OTP, admin)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"author": "Aurabase",
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/
|
|
34
|
+
"url": "git+https://github.com/Aurabase-cloud/Aurabase.git",
|
|
35
35
|
"directory": "aurabase-js/packages/auth"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://aurabase.cloud",
|
|
38
38
|
"bugs": {
|
|
39
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/Aurabase-cloud/Aurabase/issues"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"aurabase",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"authentication"
|
|
47
47
|
],
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@aurabase/core": "0.
|
|
49
|
+
"@aurabase/core": "0.7.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "20.14.15",
|