@massimo.mazzoleni/cognito-max 1.0.0 → 1.1.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/dist/{chunk-DKPFVGTY.js → chunk-GNMHM6IU.js} +314 -20
- package/dist/chunk-GNMHM6IU.js.map +1 -0
- package/dist/{chunk-N4OQLBV6.js → chunk-ZHOUWOWA.js} +16 -10
- package/dist/chunk-ZHOUWOWA.js.map +1 -0
- package/dist/client-D-Y0e9K1.d.cts +127 -0
- package/dist/client-DXA7_YD0.d.ts +127 -0
- package/dist/core/index.cjs +311 -17
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +3 -2
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +1 -1
- package/dist/index.cjs +311 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +349 -24
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +35 -9
- package/dist/react/index.d.ts +35 -9
- package/dist/react/index.js +27 -4
- package/dist/react/index.js.map +1 -1
- package/dist/types-CkkFalax.d.cts +181 -0
- package/dist/types-CkkFalax.d.ts +181 -0
- package/dist/ui/index.cjs +14 -7
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +3 -2
- package/package.json +2 -2
- package/src/core/client.ts +353 -15
- package/src/core/internal/pkce.ts +26 -0
- package/src/core/types.ts +83 -3
- package/src/react/components/ProtectedRoute.tsx +2 -0
- package/src/react/context.tsx +2 -0
- package/src/react/hooks/useAuth.ts +34 -11
- package/src/react/hooks/useDevice.ts +17 -0
- package/src/react/hooks/useMfa.ts +2 -0
- package/src/react/hooks/usePasswordPolicy.ts +13 -0
- package/src/react/hooks/useSession.ts +2 -0
- package/src/react/hooks/useUser.ts +2 -0
- package/src/react/index.ts +2 -0
- package/dist/chunk-DKPFVGTY.js.map +0 -1
- package/dist/chunk-N4OQLBV6.js.map +0 -1
- package/dist/client-63FraVdm.d.ts +0 -69
- package/dist/client-BAoL8h4E.d.cts +0 -69
- package/dist/types-bxA1vonL.d.cts +0 -113
- package/dist/types-bxA1vonL.d.ts +0 -113
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CognitoUserPool, AuthenticationDetails,
|
|
1
|
+
import { CognitoUserPool, AuthenticationDetails, CognitoIdToken, CognitoAccessToken, CognitoRefreshToken, CognitoUserSession, CognitoUserAttribute, CognitoUser } from 'amazon-cognito-identity-js';
|
|
2
2
|
import { GetUserCommand, CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider';
|
|
3
3
|
|
|
4
4
|
// src/core/errors.ts
|
|
@@ -186,7 +186,27 @@ function buildAuthUser(session, fallbackUsername) {
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
// src/core/internal/pkce.ts
|
|
190
|
+
function base64UrlEncode(bytes) {
|
|
191
|
+
let binary = "";
|
|
192
|
+
for (const b of bytes) binary += String.fromCharCode(b);
|
|
193
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
194
|
+
}
|
|
195
|
+
function generateRandomString(length = 64) {
|
|
196
|
+
const bytes = new Uint8Array(length);
|
|
197
|
+
crypto.getRandomValues(bytes);
|
|
198
|
+
return base64UrlEncode(bytes).slice(0, length);
|
|
199
|
+
}
|
|
200
|
+
async function createPkcePair() {
|
|
201
|
+
const verifier = generateRandomString(64);
|
|
202
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
203
|
+
const challenge = base64UrlEncode(new Uint8Array(digest));
|
|
204
|
+
return { verifier, challenge };
|
|
205
|
+
}
|
|
206
|
+
|
|
189
207
|
// src/core/client.ts
|
|
208
|
+
var FEDERATED_PKCE_STORAGE_KEY = "cognito-max.federated.pkce_verifier";
|
|
209
|
+
var FEDERATED_STATE_STORAGE_KEY = "cognito-max.federated.state";
|
|
190
210
|
function validateConfig(config) {
|
|
191
211
|
const missing = ["userPoolId", "clientId", "region"].filter((k) => !config[k]);
|
|
192
212
|
if (missing.length) {
|
|
@@ -211,12 +231,22 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
211
231
|
this._pendingChallenges = /* @__PURE__ */ new Map();
|
|
212
232
|
this._refreshTimer = null;
|
|
213
233
|
validateConfig(config);
|
|
234
|
+
const { passwordPolicy, ...rest } = config;
|
|
214
235
|
this.config = {
|
|
215
236
|
autoRefresh: true,
|
|
216
237
|
refreshMarginSeconds: 300,
|
|
217
238
|
totpIssuer: config.clientId,
|
|
218
239
|
storage: new AutoStorageAdapter(),
|
|
219
|
-
|
|
240
|
+
deviceTracking: false,
|
|
241
|
+
...rest,
|
|
242
|
+
passwordPolicy: {
|
|
243
|
+
minLength: 8,
|
|
244
|
+
requireUppercase: false,
|
|
245
|
+
requireLowercase: false,
|
|
246
|
+
requireNumbers: false,
|
|
247
|
+
requireSymbols: false,
|
|
248
|
+
...passwordPolicy
|
|
249
|
+
}
|
|
220
250
|
};
|
|
221
251
|
this._pool = new CognitoUserPool({
|
|
222
252
|
UserPoolId: this.config.userPoolId,
|
|
@@ -230,6 +260,14 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
230
260
|
get state() {
|
|
231
261
|
return this._state;
|
|
232
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Lo StorageAdapter configurato (esplicito o AutoStorageAdapter di default). Utile per
|
|
265
|
+
* consumer che vogliono ispezionarlo/estenderlo (es. un adapter "remember me" che decide a
|
|
266
|
+
* runtime se persistere su localStorage o solo su sessionStorage).
|
|
267
|
+
*/
|
|
268
|
+
get storage() {
|
|
269
|
+
return this.config.storage;
|
|
270
|
+
}
|
|
233
271
|
setState(next) {
|
|
234
272
|
if (this._state === next) return;
|
|
235
273
|
this._state = next;
|
|
@@ -279,6 +317,153 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
279
317
|
});
|
|
280
318
|
});
|
|
281
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* Avvia il flusso CUSTOM_AUTH (passwordless / OTP custom / magic link, ecc.). Richiede che il
|
|
322
|
+
* User Pool abbia configurato i Lambda trigger DefineAuthChallenge/CreateAuthChallenge/
|
|
323
|
+
* VerifyAuthChallengeResponse — questa libreria si limita a parlare il protocollo lato client,
|
|
324
|
+
* la logica della sfida (es. invio di un OTP via email) è responsabilità di quei trigger.
|
|
325
|
+
* clientMetadata è passato al trigger CreateAuthChallenge (es. per includere un redirect URL).
|
|
326
|
+
*/
|
|
327
|
+
async signInPasswordless(username, clientMetadata) {
|
|
328
|
+
this.setState("loading");
|
|
329
|
+
const cognitoUser = this._makeCognitoUser(username);
|
|
330
|
+
cognitoUser.setAuthenticationFlowType("CUSTOM_AUTH");
|
|
331
|
+
const authDetails = new AuthenticationDetails({ Username: username, ClientMetadata: clientMetadata });
|
|
332
|
+
return new Promise((resolve, reject) => {
|
|
333
|
+
cognitoUser.initiateAuth(authDetails, {
|
|
334
|
+
onSuccess: (session) => resolve(this._onAuthSuccess(cognitoUser, session)),
|
|
335
|
+
onFailure: (err) => {
|
|
336
|
+
this.setState("unauthenticated");
|
|
337
|
+
reject(mapCognitoError(err));
|
|
338
|
+
},
|
|
339
|
+
customChallenge: (challengeParameters) => {
|
|
340
|
+
this.setState("custom_challenge_required");
|
|
341
|
+
const challengeSession = this._storeChallengeUser(cognitoUser);
|
|
342
|
+
const params = challengeParameters ?? {};
|
|
343
|
+
this.emit("customChallengeRequired", { challengeSession, challengeParameters: params });
|
|
344
|
+
resolve({ status: "CUSTOM_CHALLENGE_REQUIRED", challengeSession, challengeParameters: params });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Risponde a una sfida CUSTOM_AUTH (es. il codice OTP ricevuto). Il Lambda
|
|
351
|
+
* DefineAuthChallenge del pool decide se la sequenza è completa (SUCCESS) o se propone
|
|
352
|
+
* un'altra sfida (CUSTOM_CHALLENGE_REQUIRED di nuovo, con un nuovo challengeSession).
|
|
353
|
+
*/
|
|
354
|
+
async respondToCustomChallenge(challengeSession, answer, clientMetadata) {
|
|
355
|
+
const cognitoUser = this._takeChallengeUser(challengeSession);
|
|
356
|
+
return new Promise((resolve, reject) => {
|
|
357
|
+
cognitoUser.sendCustomChallengeAnswer(
|
|
358
|
+
answer,
|
|
359
|
+
{
|
|
360
|
+
onSuccess: (session) => resolve(this._onAuthSuccess(cognitoUser, session)),
|
|
361
|
+
onFailure: (err) => {
|
|
362
|
+
this.setState("unauthenticated");
|
|
363
|
+
reject(mapCognitoError(err));
|
|
364
|
+
},
|
|
365
|
+
customChallenge: (challengeParameters) => {
|
|
366
|
+
this.setState("custom_challenge_required");
|
|
367
|
+
const newSession = this._storeChallengeUser(cognitoUser);
|
|
368
|
+
const params = challengeParameters ?? {};
|
|
369
|
+
this.emit("customChallengeRequired", { challengeSession: newSession, challengeParameters: params });
|
|
370
|
+
resolve({ status: "CUSTOM_CHALLENGE_REQUIRED", challengeSession: newSession, challengeParameters: params });
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
clientMetadata
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// ─── Federated login (Hosted UI / OAuth2 + PKCE) ────────────────────────────
|
|
378
|
+
//
|
|
379
|
+
// Richiede AuthConfig.federated configurato e il dominio Hosted UI abilitato sul User Pool.
|
|
380
|
+
// Il round-trip passa da una navigazione a pagina intera verso Cognito: usa storage
|
|
381
|
+
// persistente (localStorage/sessionStorage, non InMemoryStorageAdapter) altrimenti il
|
|
382
|
+
// code_verifier PKCE generato prima del redirect va perso al ritorno.
|
|
383
|
+
/**
|
|
384
|
+
* Costruisce l'URL di autorizzazione Hosted UI (Authorization Code + PKCE). Naviga il browser
|
|
385
|
+
* su questo URL (window.location.href = url); l'utente torna sul redirectUri configurato con
|
|
386
|
+
* ?code=...&state=... da passare a handleFederatedCallback().
|
|
387
|
+
*/
|
|
388
|
+
async getFederatedSignInUrl(options) {
|
|
389
|
+
const federated = this._requireFederatedConfig();
|
|
390
|
+
const { verifier, challenge } = await createPkcePair();
|
|
391
|
+
const state = options?.state ?? crypto.randomUUID();
|
|
392
|
+
this.config.storage.setItem(FEDERATED_PKCE_STORAGE_KEY, verifier);
|
|
393
|
+
this.config.storage.setItem(FEDERATED_STATE_STORAGE_KEY, state);
|
|
394
|
+
const params = new URLSearchParams({
|
|
395
|
+
client_id: this.config.clientId,
|
|
396
|
+
response_type: "code",
|
|
397
|
+
scope: (federated.scopes ?? ["openid", "email", "profile"]).join(" "),
|
|
398
|
+
redirect_uri: federated.redirectUri,
|
|
399
|
+
code_challenge: challenge,
|
|
400
|
+
code_challenge_method: "S256",
|
|
401
|
+
state
|
|
402
|
+
});
|
|
403
|
+
if (options?.provider) params.set("identity_provider", options.provider);
|
|
404
|
+
return `https://${federated.domain}/oauth2/authorize?${params.toString()}`;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Da chiamare nella pagina di callback (redirectUri) con { code, state } letti dalla query
|
|
408
|
+
* string. Scambia il code per i token via /oauth2/token (PKCE, nessun client secret), poi
|
|
409
|
+
* costruisce una sessione Cognito standard esattamente come un login SRP riuscito.
|
|
410
|
+
*/
|
|
411
|
+
async handleFederatedCallback(params) {
|
|
412
|
+
const federated = this._requireFederatedConfig();
|
|
413
|
+
const verifier = this.config.storage.getItem(FEDERATED_PKCE_STORAGE_KEY);
|
|
414
|
+
const expectedState = this.config.storage.getItem(FEDERATED_STATE_STORAGE_KEY);
|
|
415
|
+
this.config.storage.removeItem(FEDERATED_PKCE_STORAGE_KEY);
|
|
416
|
+
this.config.storage.removeItem(FEDERATED_STATE_STORAGE_KEY);
|
|
417
|
+
if (!verifier) {
|
|
418
|
+
throw new CognitoAuthError(
|
|
419
|
+
"Nessun code_verifier PKCE trovato: il flusso federato non risulta avviato da questo browser/storage",
|
|
420
|
+
"INVALID_PARAMETER"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
if (params.state && expectedState && params.state !== expectedState) {
|
|
424
|
+
throw new CognitoAuthError(
|
|
425
|
+
"Parametro state non corrispondente \u2014 possibile CSRF, login federato rifiutato",
|
|
426
|
+
"INVALID_PARAMETER"
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
const body = new URLSearchParams({
|
|
430
|
+
grant_type: "authorization_code",
|
|
431
|
+
client_id: this.config.clientId,
|
|
432
|
+
code: params.code,
|
|
433
|
+
redirect_uri: federated.redirectUri,
|
|
434
|
+
code_verifier: verifier
|
|
435
|
+
});
|
|
436
|
+
let response;
|
|
437
|
+
try {
|
|
438
|
+
response = await fetch(`https://${federated.domain}/oauth2/token`, {
|
|
439
|
+
method: "POST",
|
|
440
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
441
|
+
body: body.toString()
|
|
442
|
+
});
|
|
443
|
+
} catch (err) {
|
|
444
|
+
throw new CognitoAuthError("Rete non raggiungibile durante lo scambio token OAuth2", "NETWORK_ERROR", err);
|
|
445
|
+
}
|
|
446
|
+
if (!response.ok) {
|
|
447
|
+
const text = await response.text().catch(() => "");
|
|
448
|
+
throw new CognitoAuthError(`Scambio token OAuth2 fallito (${response.status}): ${text}`, "UNKNOWN");
|
|
449
|
+
}
|
|
450
|
+
const tokens = await response.json();
|
|
451
|
+
if (!tokens.id_token || !tokens.access_token || !tokens.refresh_token) {
|
|
452
|
+
throw new CognitoAuthError(
|
|
453
|
+
"Risposta /oauth2/token incompleta (manca id_token/access_token/refresh_token)",
|
|
454
|
+
"UNKNOWN"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
const idToken = new CognitoIdToken({ IdToken: tokens.id_token });
|
|
458
|
+
const payload = idToken.decodePayload();
|
|
459
|
+
const username = String(payload["cognito:username"] ?? payload["sub"]);
|
|
460
|
+
const cognitoUser = this._makeCognitoUser(username);
|
|
461
|
+
const accessToken = new CognitoAccessToken({ AccessToken: tokens.access_token });
|
|
462
|
+
const refreshToken = new CognitoRefreshToken({ RefreshToken: tokens.refresh_token });
|
|
463
|
+
const session = new CognitoUserSession({ IdToken: idToken, AccessToken: accessToken, RefreshToken: refreshToken });
|
|
464
|
+
cognitoUser.setSignInUserSession(session);
|
|
465
|
+
return this._onAuthSuccess(cognitoUser, session);
|
|
466
|
+
}
|
|
282
467
|
async respondToMfaChallenge(challengeSession, code, mfaType) {
|
|
283
468
|
const cognitoUser = this._takeChallengeUser(challengeSession);
|
|
284
469
|
const sdkType = mfaType === "TOTP" ? "SOFTWARE_TOKEN_MFA" : "SMS_MFA";
|
|
@@ -353,7 +538,12 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
353
538
|
}
|
|
354
539
|
}
|
|
355
540
|
// ─── Registration ──────────────────────────────────────────────────────────
|
|
356
|
-
|
|
541
|
+
/**
|
|
542
|
+
* clientMetadata è passato as-is a Cognito (ValidationData/ClientMetadata) ed è disponibile
|
|
543
|
+
* nell'evento per un Lambda trigger (es. PreSignUp) — usalo per far verificare lato server un
|
|
544
|
+
* token reCAPTCHA/hCaptcha raccolto in UI, la libreria non fa alcuna verifica CAPTCHA da sé.
|
|
545
|
+
*/
|
|
546
|
+
async signUp(email, password, attributes = {}, clientMetadata) {
|
|
357
547
|
const userAttributes = Object.entries({ email, ...attributes }).map(
|
|
358
548
|
([Name, Value]) => new CognitoUserAttribute({ Name, Value })
|
|
359
549
|
);
|
|
@@ -361,29 +551,29 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
361
551
|
this._pool.signUp(email, password, userAttributes, [], (err) => {
|
|
362
552
|
if (err) return reject(mapCognitoError(err));
|
|
363
553
|
resolve();
|
|
364
|
-
});
|
|
554
|
+
}, clientMetadata);
|
|
365
555
|
});
|
|
366
556
|
}
|
|
367
|
-
async confirmSignUp(email, code) {
|
|
557
|
+
async confirmSignUp(email, code, clientMetadata) {
|
|
368
558
|
const cognitoUser = this._makeCognitoUser(email);
|
|
369
559
|
await new Promise((resolve, reject) => {
|
|
370
560
|
cognitoUser.confirmRegistration(code, true, (err) => {
|
|
371
561
|
if (err) return reject(mapCognitoError(err));
|
|
372
562
|
resolve();
|
|
373
|
-
});
|
|
563
|
+
}, clientMetadata);
|
|
374
564
|
});
|
|
375
565
|
}
|
|
376
|
-
async resendConfirmationCode(email) {
|
|
566
|
+
async resendConfirmationCode(email, clientMetadata) {
|
|
377
567
|
const cognitoUser = this._makeCognitoUser(email);
|
|
378
568
|
await new Promise((resolve, reject) => {
|
|
379
569
|
cognitoUser.resendConfirmationCode((err) => {
|
|
380
570
|
if (err) return reject(mapCognitoError(err));
|
|
381
571
|
resolve();
|
|
382
|
-
});
|
|
572
|
+
}, clientMetadata);
|
|
383
573
|
});
|
|
384
574
|
}
|
|
385
575
|
// ─── Password ──────────────────────────────────────────────────────────────
|
|
386
|
-
async forgotPassword(email) {
|
|
576
|
+
async forgotPassword(email, clientMetadata) {
|
|
387
577
|
const cognitoUser = this._makeCognitoUser(email);
|
|
388
578
|
await new Promise((resolve, reject) => {
|
|
389
579
|
cognitoUser.forgotPassword({
|
|
@@ -391,25 +581,25 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
391
581
|
inputVerificationCode: () => resolve(),
|
|
392
582
|
onSuccess: () => resolve(),
|
|
393
583
|
onFailure: (err) => reject(mapCognitoError(err))
|
|
394
|
-
});
|
|
584
|
+
}, clientMetadata);
|
|
395
585
|
});
|
|
396
586
|
}
|
|
397
|
-
async confirmForgotPassword(email, code, newPassword) {
|
|
587
|
+
async confirmForgotPassword(email, code, newPassword, clientMetadata) {
|
|
398
588
|
const cognitoUser = this._makeCognitoUser(email);
|
|
399
589
|
await new Promise((resolve, reject) => {
|
|
400
590
|
cognitoUser.confirmPassword(code, newPassword, {
|
|
401
591
|
onSuccess: () => resolve(),
|
|
402
592
|
onFailure: (err) => reject(mapCognitoError(err))
|
|
403
|
-
});
|
|
593
|
+
}, clientMetadata);
|
|
404
594
|
});
|
|
405
595
|
}
|
|
406
|
-
async changePassword(currentPassword, newPassword) {
|
|
596
|
+
async changePassword(currentPassword, newPassword, clientMetadata) {
|
|
407
597
|
const cognitoUser = await this._getAuthenticatedUser();
|
|
408
598
|
await new Promise((resolve, reject) => {
|
|
409
599
|
cognitoUser.changePassword(currentPassword, newPassword, (err) => {
|
|
410
600
|
if (err) return reject(mapCognitoError(err));
|
|
411
601
|
resolve();
|
|
412
|
-
});
|
|
602
|
+
}, clientMetadata);
|
|
413
603
|
});
|
|
414
604
|
}
|
|
415
605
|
// ─── Session & User ────────────────────────────────────────────────────────
|
|
@@ -489,7 +679,7 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
489
679
|
return new Promise((resolve, reject) => {
|
|
490
680
|
cognitoUser.associateSoftwareToken({
|
|
491
681
|
associateSecretCode: (secretCode) => {
|
|
492
|
-
const issuer = encodeURIComponent(this.
|
|
682
|
+
const issuer = encodeURIComponent(this._resolveTotpIssuer());
|
|
493
683
|
const account = encodeURIComponent(cognitoUser.getUsername());
|
|
494
684
|
const qrCodeUri = `otpauth://totp/${issuer}:${account}?secret=${secretCode}&issuer=${issuer}`;
|
|
495
685
|
resolve({ secretCode, qrCodeUri });
|
|
@@ -501,7 +691,7 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
501
691
|
async verifyTotpSetup(code) {
|
|
502
692
|
const cognitoUser = await this._getAuthenticatedUser();
|
|
503
693
|
return new Promise((resolve, reject) => {
|
|
504
|
-
cognitoUser.verifySoftwareToken(code, this.
|
|
694
|
+
cognitoUser.verifySoftwareToken(code, this._resolveTotpIssuer(), {
|
|
505
695
|
onSuccess: () => resolve(),
|
|
506
696
|
onFailure: (err) => reject(mapCognitoError(err))
|
|
507
697
|
});
|
|
@@ -547,6 +737,90 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
547
737
|
});
|
|
548
738
|
});
|
|
549
739
|
}
|
|
740
|
+
// ─── Device tracking ────────────────────────────────────────────────────────
|
|
741
|
+
//
|
|
742
|
+
// Richiede che il User Pool abbia "Remember user devices" attivo: in quel caso Cognito
|
|
743
|
+
// conferma automaticamente il dispositivo durante l'autenticazione (nessuna chiamata
|
|
744
|
+
// esplicita richiesta qui, lo fa amazon-cognito-identity-js internamente). Queste API
|
|
745
|
+
// servono solo a "ricordare" (skip MFA) o dimenticare il dispositivo già confermato.
|
|
746
|
+
/** true/false per ricordare o smettere di ricordare il dispositivo corrente (skip MFA). */
|
|
747
|
+
async rememberCurrentDevice(remember) {
|
|
748
|
+
this._requireDeviceTracking();
|
|
749
|
+
const cognitoUser = await this._getAuthenticatedUser();
|
|
750
|
+
cognitoUser.getCachedDeviceKeyAndPassword();
|
|
751
|
+
return new Promise((resolve, reject) => {
|
|
752
|
+
const callbacks = {
|
|
753
|
+
onSuccess: () => resolve(),
|
|
754
|
+
onFailure: (err) => reject(mapCognitoError(err))
|
|
755
|
+
};
|
|
756
|
+
if (remember) {
|
|
757
|
+
cognitoUser.setDeviceStatusRemembered(callbacks);
|
|
758
|
+
} else {
|
|
759
|
+
cognitoUser.setDeviceStatusNotRemembered(callbacks);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
/** Dimentica del tutto il dispositivo corrente (richiederà di nuovo MFA al prossimo login). */
|
|
764
|
+
async forgetCurrentDevice() {
|
|
765
|
+
this._requireDeviceTracking();
|
|
766
|
+
const cognitoUser = await this._getAuthenticatedUser();
|
|
767
|
+
cognitoUser.getCachedDeviceKeyAndPassword();
|
|
768
|
+
return new Promise((resolve, reject) => {
|
|
769
|
+
cognitoUser.forgetDevice({
|
|
770
|
+
onSuccess: () => resolve(),
|
|
771
|
+
onFailure: (err) => reject(mapCognitoError(err))
|
|
772
|
+
});
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
/** Elenca i dispositivi noti a Cognito per l'utente corrente. */
|
|
776
|
+
async listDevices(limit = 20) {
|
|
777
|
+
this._requireDeviceTracking();
|
|
778
|
+
const cognitoUser = await this._getAuthenticatedUser();
|
|
779
|
+
cognitoUser.getCachedDeviceKeyAndPassword();
|
|
780
|
+
return new Promise((resolve, reject) => {
|
|
781
|
+
cognitoUser.listDevices(limit, null, {
|
|
782
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
783
|
+
onSuccess: (data) => {
|
|
784
|
+
const devices = (data?.Devices ?? []).map((d) => {
|
|
785
|
+
const attrs = Object.fromEntries(
|
|
786
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
787
|
+
(d.DeviceAttributes ?? []).map((a) => [a.Name, a.Value])
|
|
788
|
+
);
|
|
789
|
+
return {
|
|
790
|
+
deviceKey: d.DeviceKey,
|
|
791
|
+
deviceName: attrs["device_name"] ?? null,
|
|
792
|
+
createDate: d.DeviceCreateDate ? new Date(d.DeviceCreateDate * 1e3) : null,
|
|
793
|
+
lastAuthenticatedDate: d.DeviceLastAuthenticatedDate ? new Date(d.DeviceLastAuthenticatedDate * 1e3) : null,
|
|
794
|
+
lastModifiedDate: d.DeviceLastModifiedDate ? new Date(d.DeviceLastModifiedDate * 1e3) : null,
|
|
795
|
+
attributes: attrs
|
|
796
|
+
};
|
|
797
|
+
});
|
|
798
|
+
resolve(devices);
|
|
799
|
+
},
|
|
800
|
+
onFailure: (err) => reject(mapCognitoError(err))
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
// ─── Password policy ────────────────────────────────────────────────────────
|
|
805
|
+
/**
|
|
806
|
+
* Restituisce la policy password dichiarata in AuthConfig.passwordPolicy (con i default
|
|
807
|
+
* applicati). Non è letta da Cognito: va tenuta allineata a mano alla policy reale del User
|
|
808
|
+
* Pool (DescribeUserPoolCommand richiede credenziali IAM non disponibili da una SPA anonima).
|
|
809
|
+
*/
|
|
810
|
+
getPasswordPolicy() {
|
|
811
|
+
return this.config.passwordPolicy;
|
|
812
|
+
}
|
|
813
|
+
/** Valida una password contro la policy configurata. Ritorna le violazioni, vuoto se ok. */
|
|
814
|
+
validatePassword(password) {
|
|
815
|
+
const policy = this.config.passwordPolicy;
|
|
816
|
+
const violations = [];
|
|
817
|
+
if (password.length < policy.minLength) violations.push("TOO_SHORT");
|
|
818
|
+
if (policy.requireUppercase && !/[A-Z]/.test(password)) violations.push("MISSING_UPPERCASE");
|
|
819
|
+
if (policy.requireLowercase && !/[a-z]/.test(password)) violations.push("MISSING_LOWERCASE");
|
|
820
|
+
if (policy.requireNumbers && !/[0-9]/.test(password)) violations.push("MISSING_NUMBER");
|
|
821
|
+
if (policy.requireSymbols && !/[^A-Za-z0-9]/.test(password)) violations.push("MISSING_SYMBOL");
|
|
822
|
+
return violations;
|
|
823
|
+
}
|
|
550
824
|
// ─── TOTP setup durante il challenge login ─────────────────────────────────
|
|
551
825
|
/**
|
|
552
826
|
* Ottiene il secretCode/QR URI per il setup TOTP durante il flusso di login
|
|
@@ -558,7 +832,7 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
558
832
|
return new Promise((resolve, reject) => {
|
|
559
833
|
cognitoUser.associateSoftwareToken({
|
|
560
834
|
associateSecretCode: (secretCode) => {
|
|
561
|
-
const issuer = encodeURIComponent(this.
|
|
835
|
+
const issuer = encodeURIComponent(this._resolveTotpIssuer());
|
|
562
836
|
const account = encodeURIComponent(cognitoUser.getUsername());
|
|
563
837
|
const qrCodeUri = `otpauth://totp/${issuer}:${account}?secret=${secretCode}&issuer=${issuer}`;
|
|
564
838
|
resolve({ secretCode, qrCodeUri });
|
|
@@ -574,13 +848,33 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
574
848
|
async verifyTotpChallenge(challengeSession, code) {
|
|
575
849
|
const cognitoUser = this._takeChallengeUser(challengeSession);
|
|
576
850
|
return new Promise((resolve, reject) => {
|
|
577
|
-
cognitoUser.verifySoftwareToken(code, this.
|
|
851
|
+
cognitoUser.verifySoftwareToken(code, this._resolveTotpIssuer(), {
|
|
578
852
|
onSuccess: (session) => resolve(this._onAuthSuccess(cognitoUser, session)),
|
|
579
853
|
onFailure: (err) => reject(mapCognitoError(err))
|
|
580
854
|
});
|
|
581
855
|
});
|
|
582
856
|
}
|
|
583
857
|
// ─── Private helpers ───────────────────────────────────────────────────────
|
|
858
|
+
_requireFederatedConfig() {
|
|
859
|
+
if (!this.config.federated) {
|
|
860
|
+
throw new CognitoAuthError(
|
|
861
|
+
"Login federato non configurato \u2014 imposta AuthConfig.federated (domain, redirectUri)",
|
|
862
|
+
"INVALID_PARAMETER"
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
return this.config.federated;
|
|
866
|
+
}
|
|
867
|
+
_requireDeviceTracking() {
|
|
868
|
+
if (!this.config.deviceTracking) {
|
|
869
|
+
throw new CognitoAuthError(
|
|
870
|
+
"Device tracking non abilitato \u2014 imposta AuthConfig.deviceTracking: true",
|
|
871
|
+
"INVALID_PARAMETER"
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
_resolveTotpIssuer() {
|
|
876
|
+
return typeof this.config.totpIssuer === "function" ? this.config.totpIssuer() : this.config.totpIssuer;
|
|
877
|
+
}
|
|
584
878
|
_getIdpClient() {
|
|
585
879
|
if (!this._idpClient) {
|
|
586
880
|
this._idpClient = new CognitoIdentityProviderClient({ region: this.config.region });
|
|
@@ -679,5 +973,5 @@ var CognitoAuthClient = class extends TypedEventEmitter {
|
|
|
679
973
|
};
|
|
680
974
|
|
|
681
975
|
export { AutoStorageAdapter, CognitoAuthClient, CognitoAuthError, InMemoryStorageAdapter, InvalidCodeError, LocalStorageAdapter, NotAuthorizedError, SessionExpiredError, SessionStorageAdapter, TypedEventEmitter, UserNotConfirmedError, mapCognitoError };
|
|
682
|
-
//# sourceMappingURL=chunk-
|
|
683
|
-
//# sourceMappingURL=chunk-
|
|
976
|
+
//# sourceMappingURL=chunk-GNMHM6IU.js.map
|
|
977
|
+
//# sourceMappingURL=chunk-GNMHM6IU.js.map
|