@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.
Files changed (48) hide show
  1. package/dist/{chunk-DKPFVGTY.js → chunk-GNMHM6IU.js} +314 -20
  2. package/dist/chunk-GNMHM6IU.js.map +1 -0
  3. package/dist/{chunk-N4OQLBV6.js → chunk-ZHOUWOWA.js} +16 -10
  4. package/dist/chunk-ZHOUWOWA.js.map +1 -0
  5. package/dist/client-D-Y0e9K1.d.cts +127 -0
  6. package/dist/client-DXA7_YD0.d.ts +127 -0
  7. package/dist/core/index.cjs +311 -17
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +3 -2
  10. package/dist/core/index.d.ts +3 -2
  11. package/dist/core/index.js +1 -1
  12. package/dist/index.cjs +311 -17
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +3 -2
  15. package/dist/index.d.ts +3 -2
  16. package/dist/index.js +1 -1
  17. package/dist/react/index.cjs +349 -24
  18. package/dist/react/index.cjs.map +1 -1
  19. package/dist/react/index.d.cts +35 -9
  20. package/dist/react/index.d.ts +35 -9
  21. package/dist/react/index.js +27 -4
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/types-CkkFalax.d.cts +181 -0
  24. package/dist/types-CkkFalax.d.ts +181 -0
  25. package/dist/ui/index.cjs +14 -7
  26. package/dist/ui/index.cjs.map +1 -1
  27. package/dist/ui/index.d.cts +1 -1
  28. package/dist/ui/index.d.ts +1 -1
  29. package/dist/ui/index.js +3 -2
  30. package/package.json +2 -2
  31. package/src/core/client.ts +353 -15
  32. package/src/core/internal/pkce.ts +26 -0
  33. package/src/core/types.ts +83 -3
  34. package/src/react/components/ProtectedRoute.tsx +2 -0
  35. package/src/react/context.tsx +2 -0
  36. package/src/react/hooks/useAuth.ts +34 -11
  37. package/src/react/hooks/useDevice.ts +17 -0
  38. package/src/react/hooks/useMfa.ts +2 -0
  39. package/src/react/hooks/usePasswordPolicy.ts +13 -0
  40. package/src/react/hooks/useSession.ts +2 -0
  41. package/src/react/hooks/useUser.ts +2 -0
  42. package/src/react/index.ts +2 -0
  43. package/dist/chunk-DKPFVGTY.js.map +0 -1
  44. package/dist/chunk-N4OQLBV6.js.map +0 -1
  45. package/dist/client-63FraVdm.d.ts +0 -69
  46. package/dist/client-BAoL8h4E.d.cts +0 -69
  47. package/dist/types-bxA1vonL.d.cts +0 -113
  48. package/dist/types-bxA1vonL.d.ts +0 -113
@@ -1,4 +1,4 @@
1
- import { CognitoUserPool, AuthenticationDetails, CognitoUserAttribute, CognitoUser, CognitoRefreshToken } from 'amazon-cognito-identity-js';
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
- ...config
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
- async signUp(email, password, attributes = {}) {
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.config.totpIssuer);
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.config.totpIssuer, {
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.config.totpIssuer);
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.config.totpIssuer, {
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-DKPFVGTY.js.map
683
- //# sourceMappingURL=chunk-DKPFVGTY.js.map
976
+ //# sourceMappingURL=chunk-GNMHM6IU.js.map
977
+ //# sourceMappingURL=chunk-GNMHM6IU.js.map