@iqauth/sdk 2.6.4 → 2.8.1

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 (117) hide show
  1. package/README.md +173 -1
  2. package/dist/browser-session.d.mts +4 -4
  3. package/dist/browser-session.d.ts +4 -4
  4. package/dist/browser-session.js +212 -46
  5. package/dist/browser-session.mjs +3 -3
  6. package/dist/browser.d.mts +5 -5
  7. package/dist/browser.d.ts +5 -5
  8. package/dist/browser.js +293 -34
  9. package/dist/browser.mjs +5 -5
  10. package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
  11. package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
  12. package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
  13. package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
  14. package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
  15. package/dist/chunk-GLXSIGVS.mjs +66 -0
  16. package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
  17. package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
  18. package/dist/chunk-JRDVUWAL.mjs +46 -0
  19. package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
  20. package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
  21. package/dist/chunk-VYQ3ETCK.mjs +244 -0
  22. package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
  23. package/dist/chunk-WHT6WKTY.mjs +3180 -0
  24. package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
  25. package/dist/chunk-WSH4SW7F.mjs +490 -0
  26. package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
  27. package/dist/cli/index.js +2 -2
  28. package/dist/cli/index.mjs +2 -2
  29. package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
  30. package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
  31. package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
  32. package/dist/errors-Jl1Jtm-6.d.mts +107 -0
  33. package/dist/errors-Jl1Jtm-6.d.ts +107 -0
  34. package/dist/{express-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
  35. package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
  36. package/dist/express.d.mts +7 -6
  37. package/dist/express.d.ts +7 -6
  38. package/dist/express.js +563 -85
  39. package/dist/express.mjs +73 -34
  40. package/dist/fastify.d.mts +10 -0
  41. package/dist/fastify.d.ts +10 -0
  42. package/dist/fastify.js +589 -65
  43. package/dist/fastify.mjs +101 -11
  44. package/dist/hono.d.mts +10 -0
  45. package/dist/hono.d.ts +10 -0
  46. package/dist/hono.js +566 -65
  47. package/dist/hono.mjs +78 -11
  48. package/dist/index-Cko-d5po.d.mts +1848 -0
  49. package/dist/index-RNqwEcmY.d.ts +1848 -0
  50. package/dist/index.d.mts +56 -8
  51. package/dist/index.d.ts +56 -8
  52. package/dist/index.js +694 -75
  53. package/dist/index.mjs +30 -10
  54. package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
  55. package/dist/locales.d.mts +1 -1
  56. package/dist/locales.d.ts +1 -1
  57. package/dist/locales.js +36 -0
  58. package/dist/locales.mjs +1 -1
  59. package/dist/mobile.d.mts +77 -7
  60. package/dist/mobile.d.ts +77 -7
  61. package/dist/mobile.js +307 -46
  62. package/dist/mobile.mjs +98 -3
  63. package/dist/next.d.mts +10 -1
  64. package/dist/next.d.ts +10 -1
  65. package/dist/next.js +596 -205
  66. package/dist/next.mjs +83 -10
  67. package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
  68. package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
  69. package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
  70. package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
  71. package/dist/react-permissions.d.mts +52 -0
  72. package/dist/react-permissions.d.ts +52 -0
  73. package/dist/react-permissions.js +239 -0
  74. package/dist/react-permissions.mjs +98 -0
  75. package/dist/react.d.mts +9 -1624
  76. package/dist/react.d.ts +9 -1624
  77. package/dist/react.js +882 -73
  78. package/dist/react.mjs +71 -2631
  79. package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
  80. package/dist/server/handlers.d.mts +200 -4
  81. package/dist/server/handlers.d.ts +200 -4
  82. package/dist/server/handlers.js +530 -16
  83. package/dist/server/handlers.mjs +14 -3
  84. package/dist/server.d.mts +171 -8
  85. package/dist/server.d.ts +171 -8
  86. package/dist/server.js +579 -61
  87. package/dist/server.mjs +99 -12
  88. package/dist/service.d.mts +4 -4
  89. package/dist/service.d.ts +4 -4
  90. package/dist/service.js +212 -46
  91. package/dist/service.mjs +3 -3
  92. package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
  93. package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
  94. package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
  95. package/dist/test.mjs +3 -3
  96. package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
  97. package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
  98. package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
  99. package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
  100. package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
  101. package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
  102. package/dist/webhooks.d.mts +113 -17
  103. package/dist/webhooks.d.ts +113 -17
  104. package/dist/webhooks.js +179 -15
  105. package/dist/webhooks.mjs +7 -1
  106. package/dist/ws.d.mts +2 -2
  107. package/dist/ws.d.ts +2 -2
  108. package/dist/ws.js +80 -30
  109. package/dist/ws.mjs +4 -4
  110. package/docs/error-handling.md +101 -0
  111. package/docs/guides/effective-permissions.md +171 -0
  112. package/docs/guides/invitations.md +65 -0
  113. package/package.json +19 -4
  114. package/dist/chunk-6TDJJER7.mjs +0 -217
  115. package/dist/chunk-UKZLOHZG.mjs +0 -83
  116. package/dist/errors-CDdl24MP.d.mts +0 -52
  117. package/dist/errors-CDdl24MP.d.ts +0 -52
@@ -39,13 +39,30 @@ __export(browser_session_exports, {
39
39
  module.exports = __toCommonJS(browser_session_exports);
40
40
 
41
41
  // src/errors.ts
42
- var IQAuthError = class extends Error {
43
- constructor(code, message, status, raw) {
42
+ var IQAuthError = class _IQAuthError extends Error {
43
+ constructor(code, message, status, cause) {
44
44
  super(message);
45
45
  this.name = "IQAuthError";
46
46
  this.code = code;
47
47
  this.status = status;
48
- this.raw = raw;
48
+ this.cause = cause;
49
+ this.raw = cause;
50
+ }
51
+ /**
52
+ * Type guard: true when `value` is an `IQAuthError`. Useful for adapters
53
+ * that round-trip errors through `unknown` (e.g. fastify's `setErrorHandler`).
54
+ */
55
+ static isIQAuthError(value) {
56
+ return value instanceof _IQAuthError || typeof value === "object" && value !== null && value.name === "IQAuthError" && typeof value.code === "string";
57
+ }
58
+ /**
59
+ * Type-narrowed code check. Lets callers write
60
+ * `if (err.is("token_expired")) …` with full IntelliSense for the typed
61
+ * taxonomy without losing the ability to handle server codes via
62
+ * `err.code === "TOKEN_REVOKED"`.
63
+ */
64
+ is(code) {
65
+ return this.code === code;
49
66
  }
50
67
  };
51
68
  var ErrorCodes = {
@@ -196,7 +213,7 @@ var HttpClient = class {
196
213
  headers: this.buildHeaders(),
197
214
  ...this.isBrowserSession() ? { credentials: "include" } : (() => {
198
215
  const refreshToken = this.config.getRefreshToken();
199
- if (!refreshToken) throw new IQAuthError("TOKEN_INVALID", "No refresh token available");
216
+ if (!refreshToken) throw new IQAuthError("config_invalid", "No refresh token available");
200
217
  return { body: JSON.stringify({ refreshToken }) };
201
218
  })()
202
219
  });
@@ -213,7 +230,7 @@ var HttpClient = class {
213
230
  return;
214
231
  }
215
232
  if (!body.data.accessToken || !body.data.refreshToken) {
216
- throw new IQAuthError("TOKEN_INVALID", "Refresh response did not include a token pair");
233
+ throw new IQAuthError("token_invalid", "Refresh response did not include a token pair");
217
234
  }
218
235
  const tokens = {
219
236
  accessToken: body.data.accessToken,
@@ -231,7 +248,7 @@ var HttpClient = class {
231
248
  return this.requestWithRetry(method, path, body, options, false);
232
249
  }
233
250
  async requestWithRetry(method, path, body, options, hasRetried) {
234
- if (this.config.autoRefresh && !options?.skipAutoRefresh && !this.isBrowserSession() && this.config.getRefreshToken() && this.isTokenExpiringSoon()) {
251
+ if (this.config.autoRefresh && this.config.proactiveRefresh !== false && !options?.skipAutoRefresh && !this.isBrowserSession() && this.config.getRefreshToken() && this.isTokenExpiringSoon()) {
235
252
  await this.attemptRefresh();
236
253
  }
237
254
  const url = `${this.config.baseUrl}${path}`;
@@ -318,17 +335,27 @@ function parseLoginResponse(data, browserSessionMode) {
318
335
  tenants: data.tenants
319
336
  };
320
337
  }
338
+ if (data.type === "scope_selection" && data.scopeSelectionToken && data.scopes && data.tenantId) {
339
+ return {
340
+ status: "scope_selection",
341
+ scopeSelectionToken: data.scopeSelectionToken,
342
+ tenantId: data.tenantId,
343
+ scopes: data.scopes
344
+ };
345
+ }
321
346
  throw new Error("Unexpected login response shape");
322
347
  }
323
348
  var AuthModule = class {
324
349
  constructor(http) {
325
350
  this.http = http;
326
351
  }
327
- async login(email, password) {
352
+ async login(email, password, opts) {
353
+ const body = { email, password };
354
+ if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
328
355
  const data = await this.http.request(
329
356
  "POST",
330
357
  "/api/v1/auth/login",
331
- { email, password },
358
+ body,
332
359
  { skipAutoRefresh: true }
333
360
  );
334
361
  return parseLoginResponse(data, this.http.isBrowserSession());
@@ -366,13 +393,29 @@ var AuthModule = class {
366
393
  method
367
394
  }, { skipAutoRefresh: true });
368
395
  }
369
- async selectTenant(tenantSelectionToken, tenantId) {
396
+ async selectTenant(tenantSelectionToken, tenantId, opts) {
397
+ const body = { tenantSelectionToken, tenantId };
398
+ if (opts?.scopeHint) body.scopeHint = opts.scopeHint;
370
399
  const data = await this.http.request(
371
400
  "POST",
372
401
  "/api/v1/auth/select-tenant",
402
+ body,
403
+ { skipAutoRefresh: true }
404
+ );
405
+ return parseLoginResponse(data, this.http.isBrowserSession());
406
+ }
407
+ /**
408
+ * Task #171 — redeem a scope-selection token + chosen membership for a
409
+ * real authenticated session. `membershipId` must be one of the scopes
410
+ * returned in the prior `scope_selection` envelope.
411
+ */
412
+ async selectScope(scopeSelectionToken, membershipId) {
413
+ const data = await this.http.request(
414
+ "POST",
415
+ "/api/v1/auth/select-scope",
373
416
  {
374
- tenantSelectionToken,
375
- tenantId
417
+ scopeSelectionToken,
418
+ membershipId
376
419
  },
377
420
  { skipAutoRefresh: true }
378
421
  );
@@ -459,6 +502,18 @@ var DEFAULT_TOKEN_AUDIENCE = [
459
502
  "iqvalidate"
460
503
  ];
461
504
  var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
505
+ function classifyJoseError(err) {
506
+ if (err instanceof import_jose.errors.JWTExpired) {
507
+ return { code: "token_expired", message: "Token has expired" };
508
+ }
509
+ if (err instanceof import_jose.errors.JOSEError) {
510
+ return { code: "token_invalid", message: err.message };
511
+ }
512
+ if (err instanceof Error) {
513
+ return { code: "token_invalid", message: err.message };
514
+ }
515
+ return { code: "token_invalid", message: "Token verification failed" };
516
+ }
462
517
  function decodeProtectedHeader(token) {
463
518
  const parts = token.split(".");
464
519
  if (parts.length < 2) return null;
@@ -495,11 +550,11 @@ var TokensModule = class {
495
550
  async verify(token, options = {}) {
496
551
  const header = decodeProtectedHeader(token);
497
552
  if (!header) {
498
- throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
553
+ throw new IQAuthError("token_invalid", "Unable to decode token");
499
554
  }
500
555
  const kid = header.kid;
501
556
  if (!kid) {
502
- throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
557
+ throw new IQAuthError("token_invalid", "Token missing kid header");
503
558
  }
504
559
  let cache = await this.ensureCache();
505
560
  if (!cache.byKid.has(kid)) {
@@ -507,7 +562,7 @@ var TokensModule = class {
507
562
  cache = await this.ensureCache();
508
563
  }
509
564
  if (!cache.byKid.has(kid)) {
510
- throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
565
+ throw new IQAuthError("token_invalid", `Unknown key ID: ${kid}`);
511
566
  }
512
567
  const issuer = options.issuer ?? this.defaultIssuer;
513
568
  const audience = options.audience ?? this.defaultAudience;
@@ -523,16 +578,8 @@ var TokensModule = class {
523
578
  const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
524
579
  return payload;
525
580
  } catch (err) {
526
- if (err instanceof import_jose.errors.JWTExpired) {
527
- throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
528
- }
529
- if (err instanceof import_jose.errors.JOSEError) {
530
- throw new IQAuthError("TOKEN_INVALID", err.message);
531
- }
532
- if (err instanceof Error) {
533
- throw new IQAuthError("TOKEN_INVALID", err.message);
534
- }
535
- throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
581
+ const classified = classifyJoseError(err);
582
+ throw new IQAuthError(classified.code, classified.message, void 0, err);
536
583
  }
537
584
  }
538
585
  /**
@@ -574,7 +621,7 @@ var TokensModule = class {
574
621
  getClaims(token) {
575
622
  const claims = this.decode(token);
576
623
  if (!claims) {
577
- throw new IQAuthError("TOKEN_INVALID", "Unable to decode token claims");
624
+ throw new IQAuthError("token_invalid", "Unable to decode token claims");
578
625
  }
579
626
  return claims;
580
627
  }
@@ -584,7 +631,7 @@ var TokensModule = class {
584
631
  }
585
632
  await this.refreshJwks();
586
633
  if (!this.jwksCache) {
587
- throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
634
+ throw new IQAuthError("jwks_unavailable", "JWKS cache unavailable after refresh");
588
635
  }
589
636
  return this.jwksCache;
590
637
  }
@@ -594,22 +641,38 @@ var TokensModule = class {
594
641
  }
595
642
  this.inFlightRefresh = (async () => {
596
643
  try {
597
- const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
644
+ let res;
645
+ try {
646
+ res = await fetch(`${this.baseUrl}/.well-known/jwks.json`);
647
+ } catch (err) {
648
+ throw new IQAuthError(
649
+ "network",
650
+ err instanceof Error ? err.message : "JWKS fetch network error",
651
+ void 0,
652
+ err
653
+ );
654
+ }
598
655
  if (!res.ok) {
599
656
  throw new IQAuthError(
600
- "INTERNAL_ERROR",
601
- `Failed to fetch JWKS: ${res.status}`
657
+ "jwks_fetch_failed",
658
+ `Failed to fetch JWKS: ${res.status}`,
659
+ res.status
602
660
  );
603
661
  }
604
662
  let jwks;
605
663
  try {
606
664
  jwks = await res.json();
607
- } catch {
608
- throw new IQAuthError("INTERNAL_ERROR", "Malformed JWKS response: invalid JSON");
665
+ } catch (err) {
666
+ throw new IQAuthError(
667
+ "jwks_fetch_failed",
668
+ "Malformed JWKS response: invalid JSON",
669
+ res.status,
670
+ err
671
+ );
609
672
  }
610
673
  if (!jwks || !Array.isArray(jwks.keys)) {
611
674
  throw new IQAuthError(
612
- "INTERNAL_ERROR",
675
+ "jwks_fetch_failed",
613
676
  "Malformed JWKS response: expected { keys: [...] }"
614
677
  );
615
678
  }
@@ -617,7 +680,7 @@ var TokensModule = class {
617
680
  for (const key of jwks.keys) {
618
681
  if (!key || typeof key.kid !== "string" || typeof key.n !== "string" && typeof key.x !== "string" || key.kty === "RSA" && (typeof key.n !== "string" || typeof key.e !== "string")) {
619
682
  throw new IQAuthError(
620
- "INTERNAL_ERROR",
683
+ "jwks_fetch_failed",
621
684
  "Malformed JWKS response: key missing required fields"
622
685
  );
623
686
  }
@@ -635,6 +698,19 @@ var TokensModule = class {
635
698
  clearCache() {
636
699
  this.jwksCache = null;
637
700
  }
701
+ /**
702
+ * Task #126: Eagerly populate the JWKS cache so the first verify() call
703
+ * doesn't pay a network round-trip. Safe to call repeatedly — single-flight
704
+ * behavior is shared with the lazy refresh path. Errors are swallowed so
705
+ * callers (e.g. `attachHelpers` auto-prewarm) can fire-and-forget.
706
+ */
707
+ async prewarm() {
708
+ if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) return;
709
+ try {
710
+ await this.refreshJwks();
711
+ } catch {
712
+ }
713
+ }
638
714
  };
639
715
 
640
716
  // src/modules/sessions.ts
@@ -958,14 +1034,14 @@ var OidcModule = class {
958
1034
  */
959
1035
  async handleCallback(params) {
960
1036
  if (!params.state) {
961
- throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing state parameter");
1037
+ throw new IQAuthError("config_invalid", "OIDC callback missing state parameter");
962
1038
  }
963
1039
  if (!params.code) {
964
- throw new IQAuthError("VALIDATION_ERROR", "OIDC callback missing code parameter");
1040
+ throw new IQAuthError("config_invalid", "OIDC callback missing code parameter");
965
1041
  }
966
1042
  const stored = await this.stateStore.get(params.state);
967
1043
  if (!stored) {
968
- throw new IQAuthError("VALIDATION_ERROR", "Unknown or expired OIDC state");
1044
+ throw new IQAuthError("config_invalid", "Unknown or expired OIDC state");
969
1045
  }
970
1046
  let tokens;
971
1047
  try {
@@ -983,7 +1059,7 @@ var OidcModule = class {
983
1059
  if (tokens.id_token) {
984
1060
  if (!this.tokensModule) {
985
1061
  throw new IQAuthError(
986
- "INTERNAL_ERROR",
1062
+ "config_invalid",
987
1063
  "OIDC handleCallback received an id_token but no TokensModule is configured for verification"
988
1064
  );
989
1065
  }
@@ -994,7 +1070,7 @@ var OidcModule = class {
994
1070
  const tokenNonce = typeof claimsBag.nonce === "string" ? claimsBag.nonce : void 0;
995
1071
  if (!tokenNonce || tokenNonce !== stored.nonce) {
996
1072
  throw new IQAuthError(
997
- "TOKEN_INVALID",
1073
+ "token_invalid",
998
1074
  "OIDC id_token nonce did not match the stored value"
999
1075
  );
1000
1076
  }
@@ -1195,6 +1271,9 @@ var AppsModule = class {
1195
1271
  * @remarks Wraps GET /api/v1/apps/:appKey
1196
1272
  */
1197
1273
  async get(appKey) {
1274
+ if (typeof appKey !== "string" || appKey.trim() === "") {
1275
+ throw new IQAuthError("VALIDATION_ERROR", "appKey is required (no env-var fallback).", 400);
1276
+ }
1198
1277
  return this.http.request("GET", `/api/v1/apps/${encodeURIComponent(appKey)}`);
1199
1278
  }
1200
1279
  /**
@@ -1214,6 +1293,16 @@ var AppsModule = class {
1214
1293
  401
1215
1294
  );
1216
1295
  }
1296
+ if (!manifest || typeof manifest.key !== "string" || manifest.key.trim() === "") {
1297
+ throw new IQAuthError("VALIDATION_ERROR", "manifest.key (appKey) is required for register().", 400);
1298
+ }
1299
+ if (!manifest.environment || !["production", "staging", "development"].includes(manifest.environment)) {
1300
+ throw new IQAuthError(
1301
+ "ENVIRONMENT_REQUIRED",
1302
+ "manifest.environment is required and must be 'production', 'staging', or 'development'. This guards against a dev workstation silently overwriting a production app's permission tree.",
1303
+ 400
1304
+ );
1305
+ }
1217
1306
  return this.http.request("POST", "/api/v1/apps/sync", manifest);
1218
1307
  }
1219
1308
  /**
@@ -1223,11 +1312,14 @@ var AppsModule = class {
1223
1312
  * @remarks Uses GET /api/v1/apps/:appKey — catches 404 errors
1224
1313
  */
1225
1314
  async isRegistered(appKey) {
1315
+ if (typeof appKey !== "string" || appKey.trim() === "") {
1316
+ throw new IQAuthError("VALIDATION_ERROR", "appKey is required (no env-var fallback).", 400);
1317
+ }
1226
1318
  try {
1227
1319
  await this.get(appKey);
1228
1320
  return true;
1229
1321
  } catch (err) {
1230
- if (err.code === "NOT_FOUND" || err.status === 404) {
1322
+ if (err.code === "app_not_found" || err.code === "NOT_FOUND" || err.status === 404) {
1231
1323
  return false;
1232
1324
  }
1233
1325
  throw err;
@@ -1264,6 +1356,20 @@ var RolesModule = class {
1264
1356
  };
1265
1357
 
1266
1358
  // src/modules/permissionGroups.ts
1359
+ function assertAppKey(appKey, callsite) {
1360
+ if (typeof appKey !== "string" || appKey.trim() === "") {
1361
+ throw new IQAuthError(
1362
+ "VALIDATION_ERROR",
1363
+ `appKey is required for ${callsite} (no env-var fallback, no 'product' alias).`,
1364
+ 400
1365
+ );
1366
+ }
1367
+ }
1368
+ function assertNodeKey(nodeKey, callsite) {
1369
+ if (typeof nodeKey !== "string" || nodeKey.trim() === "") {
1370
+ throw new IQAuthError("VALIDATION_ERROR", `nodeKey is required for ${callsite}.`, 400);
1371
+ }
1372
+ }
1267
1373
  var PermissionGroupsModule = class {
1268
1374
  constructor(http) {
1269
1375
  this.http = http;
@@ -1284,7 +1390,14 @@ var PermissionGroupsModule = class {
1284
1390
  return this.http.request("GET", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`);
1285
1391
  }
1286
1392
  async addPermission(tenantId, groupId, data) {
1287
- return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`, data);
1393
+ assertAppKey(data?.appKey, "permissionGroups.addPermission");
1394
+ assertNodeKey(data?.nodeKey, "permissionGroups.addPermission");
1395
+ return this.http.request("POST", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions`, {
1396
+ appKey: data.appKey,
1397
+ nodeKey: data.nodeKey,
1398
+ effect: data.effect,
1399
+ weight: data.weight
1400
+ });
1288
1401
  }
1289
1402
  async removePermission(tenantId, groupId, permissionId) {
1290
1403
  return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/permission-groups/${groupId}/permissions/${permissionId}`);
@@ -1308,21 +1421,51 @@ var PermissionGroupsModule = class {
1308
1421
  return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`);
1309
1422
  }
1310
1423
  async addUserOverride(tenantId, userId, data) {
1311
- return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`, data);
1424
+ assertAppKey(data?.appKey, "permissionGroups.addUserOverride");
1425
+ assertNodeKey(data?.nodeKey, "permissionGroups.addUserOverride");
1426
+ return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides`, {
1427
+ appKey: data.appKey,
1428
+ nodeKey: data.nodeKey,
1429
+ effect: data.effect,
1430
+ weight: data.weight,
1431
+ expiresAt: data.expiresAt
1432
+ });
1312
1433
  }
1313
1434
  async removeUserOverride(tenantId, userId, overrideId) {
1314
1435
  return this.http.request("DELETE", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/overrides/${overrideId}`);
1315
1436
  }
1437
+ /**
1438
+ * Task #130 — `appKey` is REQUIRED. The legacy `product` query alias is no
1439
+ * longer accepted at the SDK boundary; pass it as `appKey` instead. The
1440
+ * server still accepts `product=` from raw HTTP callers during the
1441
+ * deprecation window, but the SDK will not silently translate it.
1442
+ */
1316
1443
  async getEffectivePermissions(tenantId, userId, params) {
1317
- const query = new URLSearchParams();
1318
- if (params.product) query.set("product", params.product);
1319
- if (params.appKey) query.set("appKey", params.appKey);
1320
- const qs = query.toString();
1321
- return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/effective${qs ? `?${qs}` : ""}`);
1444
+ assertAppKey(params?.appKey, "permissionGroups.getEffectivePermissions");
1445
+ const qs = new URLSearchParams({ appKey: params.appKey }).toString();
1446
+ return this.http.request("GET", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/effective?${qs}`);
1322
1447
  }
1323
1448
  async checkPermission(tenantId, userId, appKey, nodeKey) {
1449
+ assertAppKey(appKey, "permissionGroups.checkPermission");
1450
+ assertNodeKey(nodeKey, "permissionGroups.checkPermission");
1324
1451
  return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/check`, { appKey, nodeKey });
1325
1452
  }
1453
+ /**
1454
+ * Task #130 — every entry in `checks` must include a non-empty `appKey`
1455
+ * AND `nodeKey`. The SDK validates the whole batch before sending so a
1456
+ * single misconfigured entry can't slip through and silently report
1457
+ * `allowed: false` from the server's per-entry validation branch.
1458
+ */
1459
+ async batchCheckPermissions(tenantId, userId, checks) {
1460
+ if (!Array.isArray(checks) || checks.length === 0) {
1461
+ throw new IQAuthError("VALIDATION_ERROR", "checks must be a non-empty array of { appKey, nodeKey }.", 400);
1462
+ }
1463
+ checks.forEach((c, i) => {
1464
+ assertAppKey(c?.appKey, `permissionGroups.batchCheckPermissions[${i}]`);
1465
+ assertNodeKey(c?.nodeKey, `permissionGroups.batchCheckPermissions[${i}]`);
1466
+ });
1467
+ return this.http.request("POST", `/api/v1/tenants/${tenantId}/users/${userId}/permissions/batch-check`, { checks });
1468
+ }
1326
1469
  };
1327
1470
 
1328
1471
  // src/modules/apiKeys.ts
@@ -1747,6 +1890,10 @@ var IQAuthClient = class _IQAuthClient {
1747
1890
  this._refreshToken = tokens.refreshToken;
1748
1891
  },
1749
1892
  autoRefresh: "autoRefresh" in config ? config.autoRefresh !== false : true,
1893
+ // `'app-state'` is mobile-only — on any other environment we treat it
1894
+ // as the default `true` (proactive refresh ON). Only the mobile client
1895
+ // disables proactive refresh and replaces it with an AppState listener.
1896
+ proactiveRefresh: "autoRefresh" in config && config.autoRefresh === "app-state" && _IQAuthClient.resolveEnvironment(config) === "mobile" ? false : true,
1750
1897
  onTokenRefresh: "onTokenRefresh" in config ? config.onTokenRefresh : void 0,
1751
1898
  sessionHeaderName: config.sessionHeaderName,
1752
1899
  sessionHeaderValue: config.sessionHeaderValue,
@@ -1787,6 +1934,13 @@ var IQAuthClient = class _IQAuthClient {
1787
1934
  static forServer(config) {
1788
1935
  return new _IQAuthClient({ ...config, environment: "server" });
1789
1936
  }
1937
+ /**
1938
+ * Construct a mobile-environment client. NOTE: this constructor does NOT
1939
+ * subscribe to React Native's `AppState` even when `autoRefresh: 'app-state'`
1940
+ * is passed — it only disables the per-request proactive refresh. Use
1941
+ * `createMobileClient` from `@iqauth/sdk/mobile` if you want the full
1942
+ * AppState-driven refresh behavior (recommended for Expo / React Native).
1943
+ */
1790
1944
  static forMobile(config) {
1791
1945
  return new _IQAuthClient({ ...config, environment: "mobile" });
1792
1946
  }
@@ -1803,6 +1957,18 @@ var IQAuthClient = class _IQAuthClient {
1803
1957
  getRefreshToken() {
1804
1958
  return this._refreshToken;
1805
1959
  }
1960
+ /**
1961
+ * Task #126: Eagerly fetch JWKS + OIDC discovery so the first verify() /
1962
+ * refresh round-trip on the request hot path doesn't pay the discovery
1963
+ * fetch latency. Safe to call repeatedly. Errors are swallowed; callers
1964
+ * may fire-and-forget. Called automatically by `iqAuth({...}).attachHelpers()`.
1965
+ */
1966
+ async prewarm() {
1967
+ await Promise.all([
1968
+ this.tokens.prewarm(),
1969
+ this.oidc.getDiscovery().catch(() => void 0)
1970
+ ]);
1971
+ }
1806
1972
  getCurrentClaims() {
1807
1973
  if (!this._accessToken) return null;
1808
1974
  return this.tokens.decode(this._accessToken);
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  IQAuthClient
3
- } from "./chunk-W3F4JYGP.mjs";
4
- import "./chunk-UNYDG2L4.mjs";
3
+ } from "./chunk-ZLJPABB7.mjs";
4
+ import "./chunk-NUO2I65G.mjs";
5
5
  import {
6
6
  ErrorCodes,
7
7
  IQAuthError
8
- } from "./chunk-6I6RM4MN.mjs";
8
+ } from "./chunk-6PJRLRB4.mjs";
9
9
  import "./chunk-Y6FXYEAI.mjs";
10
10
 
11
11
  // src/browser-session.ts
@@ -1,8 +1,8 @@
1
- import { S as SessionManager } from './signIn-CiIBTJIh.mjs';
2
- export { n as AccountRecord, A as AccountRegistry, C as CallbackResult, k as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, m as MultiAccountTokenStore, j as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, a as SessionManagerOptions, b as SessionSnapshot, c as SessionStatus, x as SignInOptions, y as SignOutOptions, U as UnlinkProviderInput, d as beginPasskeyAuthentication, e as beginPasskeyRegistration, o as buildSignInUrl, B as clearCookie, h as enrollPasskey, f as finishPasskeyAuthentication, g as finishPasskeyRegistration, D as getCookie, p as handleAuthCallback, i as linkProvider, l as listLinkedIdentities, q as redirectToSignIn, r as requestMagicLink, E as setCookie, t as signIn, s as signInWithPasskey, w as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-CiIBTJIh.mjs';
3
- export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-BaR0HoAH.mjs';
4
- export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.mjs';
5
- import './types-DZAflmmq.mjs';
1
+ import { S as SessionManager } from './signIn-CReqfXsh.mjs';
2
+ export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-CReqfXsh.mjs';
3
+ export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-f2kq-rKw.mjs';
4
+ export { b as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.mjs';
5
+ import './types-Bn8O-OEd.mjs';
6
6
 
7
7
  /**
8
8
  * Browser-safe PKCE + state/nonce generation using WebCrypto.
package/dist/browser.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { S as SessionManager } from './signIn-OCr88Zf8.js';
2
- export { n as AccountRecord, A as AccountRegistry, C as CallbackResult, k as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, m as MultiAccountTokenStore, j as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, a as SessionManagerOptions, b as SessionSnapshot, c as SessionStatus, x as SignInOptions, y as SignOutOptions, U as UnlinkProviderInput, d as beginPasskeyAuthentication, e as beginPasskeyRegistration, o as buildSignInUrl, B as clearCookie, h as enrollPasskey, f as finishPasskeyAuthentication, g as finishPasskeyRegistration, D as getCookie, p as handleAuthCallback, i as linkProvider, l as listLinkedIdentities, q as redirectToSignIn, r as requestMagicLink, E as setCookie, t as signIn, s as signInWithPasskey, w as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-OCr88Zf8.js';
3
- export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-BaR0HoAH.js';
4
- export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.js';
5
- import './types-DZAflmmq.js';
1
+ import { S as SessionManager } from './signIn-Cfa1GTpO.js';
2
+ export { p as AccountRecord, A as AccountRegistry, C as CallbackResult, d as LinkProviderInput, L as LinkedIdentity, M as MagicLinkRequestInput, o as MultiAccountTokenStore, n as PasskeyAuthInput, P as PasswordlessOptions, z as REFRESH_COOKIE, R as RefreshTokenStore, e as SessionManagerOptions, a as SessionSnapshot, f as SessionStatus, b as SignInOptions, c as SignOutOptions, U as UnlinkProviderInput, g as beginPasskeyAuthentication, i as beginPasskeyRegistration, q as buildSignInUrl, B as clearCookie, k as enrollPasskey, h as finishPasskeyAuthentication, j as finishPasskeyRegistration, D as getCookie, t as handleAuthCallback, m as linkProvider, l as listLinkedIdentities, w as redirectToSignIn, r as requestMagicLink, E as setCookie, x as signIn, s as signInWithPasskey, y as signOut, u as unlinkProvider, v as verifyMagicLink } from './signIn-Cfa1GTpO.js';
3
+ export { K as KeyMode, c as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, b as isSecretKey, p as parsePublishableKey } from './publishableKey-f2kq-rKw.js';
4
+ export { b as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-Jl1Jtm-6.js';
5
+ import './types-Bn8O-OEd.js';
6
6
 
7
7
  /**
8
8
  * Browser-safe PKCE + state/nonce generation using WebCrypto.