@iqauth/sdk 2.2.0 → 2.5.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 (96) hide show
  1. package/README.md +134 -0
  2. package/dist/browser-session.d.mts +3 -3
  3. package/dist/browser-session.d.ts +3 -3
  4. package/dist/browser-session.js +89 -68
  5. package/dist/browser-session.mjs +2 -1
  6. package/dist/browser.d.mts +64 -29
  7. package/dist/browser.d.ts +64 -29
  8. package/dist/browser.js +794 -39
  9. package/dist/browser.mjs +44 -4
  10. package/dist/bundle-LUKDQYVQ.mjs +374 -0
  11. package/dist/chunk-3JULWS6F.mjs +106 -0
  12. package/dist/chunk-5T7GHBX6.mjs +1165 -0
  13. package/dist/{chunk-M4J6BPK7.mjs → chunk-6TDJJER7.mjs} +12 -3
  14. package/dist/{chunk-QZB745C2.mjs → chunk-76W5TLQQ.mjs} +264 -211
  15. package/dist/{chunk-D72UL5HL.mjs → chunk-BVV54LPI.mjs} +36 -4
  16. package/dist/chunk-LIZYFXH7.mjs +90 -0
  17. package/dist/chunk-MKKZULZR.mjs +241 -0
  18. package/dist/chunk-SL3KRS4W.mjs +54 -0
  19. package/dist/chunk-TKZTCPEK.mjs +232 -0
  20. package/dist/chunk-UKZLOHZG.mjs +83 -0
  21. package/dist/chunk-UNYDG2L4.mjs +209 -0
  22. package/dist/{chunk-MDUHPQMM.mjs → chunk-W3F4JYGP.mjs} +8 -180
  23. package/dist/{chunk-QEJB7WEQ.mjs → chunk-WQWBJSSS.mjs} +1 -1
  24. package/dist/cli/index.js +144 -36
  25. package/dist/cli/index.mjs +1 -1
  26. package/dist/{client-DXbHb2ul.d.ts → client-BNQe3AgF.d.ts} +3 -67
  27. package/dist/{client-Dv4v92Mj.d.mts → client-kYlJFgPv.d.mts} +3 -67
  28. package/dist/doctor-YYNHNMLD.mjs +198 -0
  29. package/dist/{express-BZmF1llh.d.mts → express-B6_1vBYZ.d.mts} +23 -2
  30. package/dist/{express-B4o3P8vK.d.ts → express-CHpfa7D_.d.ts} +23 -2
  31. package/dist/express.d.mts +77 -6
  32. package/dist/express.d.ts +77 -6
  33. package/dist/express.js +336 -74
  34. package/dist/express.mjs +209 -8
  35. package/dist/fastify.js +103 -72
  36. package/dist/fastify.mjs +6 -4
  37. package/dist/hono.js +102 -72
  38. package/dist/hono.mjs +5 -4
  39. package/dist/index.d.mts +8 -4
  40. package/dist/index.d.ts +8 -4
  41. package/dist/index.js +590 -73
  42. package/dist/index.mjs +30 -8
  43. package/dist/locales.d.mts +53 -0
  44. package/dist/locales.d.ts +53 -0
  45. package/dist/locales.js +1202 -0
  46. package/dist/locales.mjs +29 -0
  47. package/dist/mobile.d.mts +3 -3
  48. package/dist/mobile.d.ts +3 -3
  49. package/dist/mobile.js +89 -68
  50. package/dist/mobile.mjs +2 -1
  51. package/dist/next.d.mts +10 -1
  52. package/dist/next.d.ts +10 -1
  53. package/dist/next.js +101 -1618
  54. package/dist/next.mjs +9 -9
  55. package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
  56. package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
  57. package/dist/react.d.mts +1349 -10
  58. package/dist/react.d.ts +1349 -10
  59. package/dist/react.js +2998 -569
  60. package/dist/react.mjs +1518 -95
  61. package/dist/reverify-4UEJXUS6.mjs +16 -0
  62. package/dist/server/handlers.d.mts +12 -1
  63. package/dist/server/handlers.d.ts +12 -1
  64. package/dist/server/handlers.js +12 -3
  65. package/dist/server/handlers.mjs +2 -2
  66. package/dist/server.d.mts +5 -4
  67. package/dist/server.d.ts +5 -4
  68. package/dist/server.js +188 -73
  69. package/dist/server.mjs +13 -8
  70. package/dist/service.d.mts +3 -3
  71. package/dist/service.d.ts +3 -3
  72. package/dist/service.js +89 -68
  73. package/dist/service.mjs +2 -1
  74. package/dist/signIn-CCY4JE5G.mjs +15 -0
  75. package/dist/{signIn-D_kP3v-c.d.mts → signIn-CiIBTJIh.d.mts} +232 -4
  76. package/dist/{signIn-BVDTIA_t.d.ts → signIn-OCr88Zf8.d.ts} +232 -4
  77. package/dist/test.d.mts +86 -0
  78. package/dist/test.d.ts +86 -0
  79. package/dist/test.js +289 -0
  80. package/dist/test.mjs +9 -0
  81. package/dist/tokens-DCyzzn8L.d.mts +63 -0
  82. package/dist/tokens-aHiGFr_E.d.ts +63 -0
  83. package/dist/types-6bNdxesb.d.mts +196 -0
  84. package/dist/types-6bNdxesb.d.ts +196 -0
  85. package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.mts} +6 -0
  86. package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.ts} +6 -0
  87. package/dist/webhooks.d.mts +61 -0
  88. package/dist/webhooks.d.ts +61 -0
  89. package/dist/webhooks.js +119 -0
  90. package/dist/webhooks.mjs +11 -0
  91. package/dist/ws.d.mts +73 -0
  92. package/dist/ws.d.ts +73 -0
  93. package/dist/ws.js +397 -0
  94. package/dist/ws.mjs +12 -0
  95. package/package.json +24 -3
  96. package/dist/doctor-XCI77BQS.mjs +0 -90
package/dist/index.js CHANGED
@@ -60,13 +60,19 @@ __export(index_exports, {
60
60
  TokensModule: () => TokensModule,
61
61
  UsersModule: () => UsersModule,
62
62
  VendorsModule: () => VendorsModule,
63
+ WebhookSignatureError: () => WebhookSignatureError,
63
64
  WebhooksModule: () => WebhooksModule,
64
65
  assertPublishableKey: () => assertPublishableKey,
66
+ createProvisioningBridge: () => createProvisioningBridge,
67
+ createTestIssuer: () => createTestIssuer,
65
68
  encodePublishableKey: () => encodePublishableKey,
66
69
  iqAuthMiddleware: () => iqAuthMiddleware,
67
70
  isPublishableKey: () => isPublishableKey,
68
71
  isSecretKey: () => isSecretKey,
69
- parsePublishableKey: () => parsePublishableKey
72
+ isValidWebhookSignature: () => isValidWebhookSignature,
73
+ parsePublishableKey: () => parsePublishableKey,
74
+ verifyWebhookSignature: () => verifyWebhookSignature,
75
+ verifyWsUpgrade: () => verifyWsUpgrade
70
76
  });
71
77
  module.exports = __toCommonJS(index_exports);
72
78
 
@@ -478,8 +484,7 @@ function parseMfaResponse(data, browserSessionMode) {
478
484
  }
479
485
 
480
486
  // src/modules/tokens.ts
481
- var import_crypto = __toESM(require("crypto"));
482
- var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
487
+ var import_jose = require("jose");
483
488
  var JWKS_CACHE_TTL_MS = 60 * 60 * 1e3;
484
489
  var DEFAULT_TOKEN_ISSUER = [
485
490
  "https://auth.dispositioniq.com",
@@ -492,6 +497,24 @@ var DEFAULT_TOKEN_AUDIENCE = [
492
497
  "iqvalidate"
493
498
  ];
494
499
  var DEFAULT_CLOCK_TOLERANCE_SECONDS = 30;
500
+ function decodeProtectedHeader(token) {
501
+ const parts = token.split(".");
502
+ if (parts.length < 2) return null;
503
+ try {
504
+ const padded = parts[0] + "=".repeat((4 - parts[0].length % 4) % 4);
505
+ const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
506
+ let json;
507
+ if (typeof atob === "function") {
508
+ json = atob(b64);
509
+ } else {
510
+ const { Buffer: Buffer2 } = require("buffer");
511
+ json = Buffer2.from(b64, "base64").toString("utf8");
512
+ }
513
+ return JSON.parse(json);
514
+ } catch {
515
+ return null;
516
+ }
517
+ }
495
518
  var TokensModule = class {
496
519
  constructor(baseUrl, options = {}) {
497
520
  this.jwksCache = null;
@@ -502,49 +525,49 @@ var TokensModule = class {
502
525
  this.defaultClockTolerance = options.clockTolerance ?? DEFAULT_CLOCK_TOLERANCE_SECONDS;
503
526
  }
504
527
  /**
505
- * Verify a JWT access token using RS256 via JWKS from /.well-known/jwks.json.
506
- * Caches JWKS keys for 1 hour. Retries once on unknown `kid`.
507
- *
508
- * @remarks Validates against /.well-known/jwks.json. Issuer, audience, and
509
- * clock tolerance default to client config but can be overridden per call.
528
+ * Verify a JWT access token using RS256/ES256 via JWKS from
529
+ * `/.well-known/jwks.json`. Backed by `jose` (Web Crypto) so it runs on
530
+ * Node, browser, and edge runtimes alike — no `node:crypto` dependency.
531
+ * Caches JWKS for 1 hour and refetches once on unknown `kid`.
510
532
  */
511
533
  async verify(token, options = {}) {
512
- const decoded = import_jsonwebtoken.default.decode(token, { complete: true });
513
- if (!decoded || typeof decoded === "string") {
534
+ const header = decodeProtectedHeader(token);
535
+ if (!header) {
514
536
  throw new IQAuthError("TOKEN_INVALID", "Unable to decode token");
515
537
  }
516
- const kid = decoded.header.kid;
538
+ const kid = header.kid;
517
539
  if (!kid) {
518
540
  throw new IQAuthError("TOKEN_INVALID", "Token missing kid header");
519
541
  }
520
- let publicKey = await this.getPublicKey(kid);
521
- if (!publicKey) {
522
- await this.refreshJwks();
523
- publicKey = await this.getPublicKey(kid);
542
+ let cache = await this.ensureCache();
543
+ if (!cache.byKid.has(kid)) {
544
+ this.jwksCache = null;
545
+ cache = await this.ensureCache();
524
546
  }
525
- if (!publicKey) {
547
+ if (!cache.byKid.has(kid)) {
526
548
  throw new IQAuthError("TOKEN_INVALID", `Unknown key ID: ${kid}`);
527
549
  }
528
550
  const issuer = options.issuer ?? this.defaultIssuer;
529
551
  const audience = options.audience ?? this.defaultAudience;
530
552
  const clockTolerance = options.clockTolerance ?? this.defaultClockTolerance;
531
- const algorithms = options.algorithms ?? ["RS256"];
553
+ const algorithms = options.algorithms ?? ["RS256", "ES256"];
554
+ const verifyOptions = {
555
+ algorithms,
556
+ clockTolerance,
557
+ issuer,
558
+ audience
559
+ };
532
560
  try {
533
- const verifyOptions = {
534
- algorithms,
535
- clockTolerance,
536
- // The jsonwebtoken types insist on tuple types for arrays; runtime
537
- // accepts plain string[] so we cast to satisfy the compiler.
538
- issuer,
539
- audience
540
- };
541
- const verified = import_jsonwebtoken.default.verify(token, publicKey, verifyOptions);
542
- return verified;
561
+ const { payload } = await (0, import_jose.jwtVerify)(token, cache.verifier, verifyOptions);
562
+ return payload;
543
563
  } catch (err) {
564
+ if (err instanceof import_jose.errors.JWTExpired) {
565
+ throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
566
+ }
567
+ if (err instanceof import_jose.errors.JOSEError) {
568
+ throw new IQAuthError("TOKEN_INVALID", err.message);
569
+ }
544
570
  if (err instanceof Error) {
545
- if (err.name === "TokenExpiredError") {
546
- throw new IQAuthError("TOKEN_EXPIRED", "Token has expired");
547
- }
548
571
  throw new IQAuthError("TOKEN_INVALID", err.message);
549
572
  }
550
573
  throw new IQAuthError("TOKEN_INVALID", "Token verification failed");
@@ -552,29 +575,40 @@ var TokensModule = class {
552
575
  }
553
576
  /**
554
577
  * Decode a JWT without verification. Returns null if malformed.
555
- *
556
- * @remarks Local decode only — no network call
557
578
  */
558
579
  decode(token) {
559
- const decoded = import_jsonwebtoken.default.decode(token);
560
- return decoded;
580
+ try {
581
+ const parts = token.split(".");
582
+ if (parts.length < 2) return null;
583
+ const payload = parts[1];
584
+ const padded = payload + "=".repeat((4 - payload.length % 4) % 4);
585
+ const b64 = padded.replace(/-/g, "+").replace(/_/g, "/");
586
+ let json;
587
+ if (typeof atob === "function") {
588
+ json = atob(b64);
589
+ } else {
590
+ const { Buffer: Buffer2 } = require("buffer");
591
+ json = Buffer2.from(b64, "base64").toString("utf8");
592
+ }
593
+ try {
594
+ json = decodeURIComponent(escape(json));
595
+ } catch {
596
+ }
597
+ const claims = JSON.parse(json);
598
+ if (!claims || typeof claims !== "object") return null;
599
+ return claims;
600
+ } catch {
601
+ return null;
602
+ }
561
603
  }
562
- /**
563
- * Check if a token is expired based on the `exp` claim.
564
- *
565
- * @remarks Local check only — no network call
566
- */
604
+ /** Check if a token is expired based on the `exp` claim. */
567
605
  isExpired(token) {
568
606
  const claims = this.decode(token);
569
607
  if (!claims?.exp) return true;
570
608
  const now = Math.floor(Date.now() / 1e3);
571
609
  return claims.exp <= now;
572
610
  }
573
- /**
574
- * Get the claims from a token without verification.
575
- *
576
- * @remarks Local decode only — no network call
577
- */
611
+ /** Get the claims from a token without verification. */
578
612
  getClaims(token) {
579
613
  const claims = this.decode(token);
580
614
  if (!claims) {
@@ -582,11 +616,15 @@ var TokensModule = class {
582
616
  }
583
617
  return claims;
584
618
  }
585
- async getPublicKey(kid) {
586
- if (!this.jwksCache || Date.now() - this.jwksCache.fetchedAt > JWKS_CACHE_TTL_MS) {
587
- await this.refreshJwks();
619
+ async ensureCache() {
620
+ if (this.jwksCache && Date.now() - this.jwksCache.fetchedAt <= JWKS_CACHE_TTL_MS) {
621
+ return this.jwksCache;
622
+ }
623
+ await this.refreshJwks();
624
+ if (!this.jwksCache) {
625
+ throw new IQAuthError("INTERNAL_ERROR", "JWKS cache unavailable after refresh");
588
626
  }
589
- return this.jwksCache?.keys.get(kid) ?? null;
627
+ return this.jwksCache;
590
628
  }
591
629
  async refreshJwks() {
592
630
  if (this.inFlightRefresh) {
@@ -613,35 +651,24 @@ var TokensModule = class {
613
651
  "Malformed JWKS response: expected { keys: [...] }"
614
652
  );
615
653
  }
616
- const keys = /* @__PURE__ */ new Map();
654
+ const byKid = /* @__PURE__ */ new Set();
617
655
  for (const key of jwks.keys) {
618
- if (!key || typeof key.kid !== "string" || typeof key.n !== "string" || typeof key.e !== "string") {
656
+ 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
657
  throw new IQAuthError(
620
658
  "INTERNAL_ERROR",
621
659
  "Malformed JWKS response: key missing required fields"
622
660
  );
623
661
  }
624
- const pem = this.jwkToPem(key);
625
- keys.set(key.kid, pem);
662
+ byKid.add(key.kid);
626
663
  }
627
- this.jwksCache = { keys, fetchedAt: Date.now() };
664
+ const verifier = (0, import_jose.createLocalJWKSet)({ keys: jwks.keys });
665
+ this.jwksCache = { raw: jwks.keys, byKid, verifier, fetchedAt: Date.now() };
628
666
  } finally {
629
667
  this.inFlightRefresh = null;
630
668
  }
631
669
  })();
632
670
  return this.inFlightRefresh;
633
671
  }
634
- jwkToPem(jwk) {
635
- const keyObject = import_crypto.default.createPublicKey({
636
- key: {
637
- kty: jwk.kty,
638
- n: jwk.n,
639
- e: jwk.e
640
- },
641
- format: "jwk"
642
- });
643
- return keyObject.export({ type: "spki", format: "pem" });
644
- }
645
672
  /** @internal Exposed for testing — clears JWKS cache */
646
673
  clearCache() {
647
674
  this.jwksCache = null;
@@ -849,7 +876,7 @@ var PermissionsModule = class {
849
876
  };
850
877
 
851
878
  // src/modules/oidc.ts
852
- var import_crypto2 = __toESM(require("crypto"));
879
+ var import_crypto = __toESM(require("crypto"));
853
880
  var InMemoryOidcStateStore = class {
854
881
  constructor() {
855
882
  this.map = /* @__PURE__ */ new Map();
@@ -930,12 +957,12 @@ var OidcModule = class {
930
957
  * ready to redirect the user to.
931
958
  */
932
959
  async createAuthRequest(params) {
933
- const codeVerifier = base64UrlEncode(import_crypto2.default.randomBytes(32));
960
+ const codeVerifier = base64UrlEncode(import_crypto.default.randomBytes(32));
934
961
  const codeChallenge = base64UrlEncode(
935
- import_crypto2.default.createHash("sha256").update(codeVerifier).digest()
962
+ import_crypto.default.createHash("sha256").update(codeVerifier).digest()
936
963
  );
937
- const state = base64UrlEncode(import_crypto2.default.randomBytes(16));
938
- const nonce = base64UrlEncode(import_crypto2.default.randomBytes(16));
964
+ const state = base64UrlEncode(import_crypto.default.randomBytes(16));
965
+ const nonce = base64UrlEncode(import_crypto.default.randomBytes(16));
939
966
  await this.stateStore.set(state, {
940
967
  codeVerifier,
941
968
  state,
@@ -1913,7 +1940,7 @@ function assertPublishableKey(raw, opts) {
1913
1940
  if (!isValidIssuerUrl(decoded.iss)) {
1914
1941
  throw new IQAuthError(
1915
1942
  "CONFIG_INVALID",
1916
- `${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console, or set IQAUTH_ISSUER to the correct issuer URL as a temporary workaround.`
1943
+ `${ctx}IQAuth publishable key encodes an invalid issuer (iss=${JSON.stringify(decoded.iss)}). Expected a fully-qualified URL like "https://auth.example.com" (scheme required). Regenerate the key from the IQAuth admin console \u2014 the new key will encode a valid issuer URL.`
1917
1944
  );
1918
1945
  }
1919
1946
  return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
@@ -1964,6 +1991,22 @@ function readCookie(req, name) {
1964
1991
  }
1965
1992
  return void 0;
1966
1993
  }
1994
+ function compileMatcher(pat) {
1995
+ if (pat instanceof RegExp) return (p) => pat.test(p);
1996
+ const re = new RegExp(
1997
+ "^" + pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLE::").replace(/\*/g, "[^/]*").replace(/::DOUBLE::/g, ".*") + "$"
1998
+ );
1999
+ return (p) => re.test(p);
2000
+ }
2001
+ function compileMatchers(pats) {
2002
+ return (pats ?? []).map(compileMatcher);
2003
+ }
2004
+ function pathOf(req) {
2005
+ const r = req;
2006
+ const raw = r.path || r.originalUrl || r.url || "/";
2007
+ const q = raw.indexOf("?");
2008
+ return q >= 0 ? raw.slice(0, q) : raw;
2009
+ }
1967
2010
  function clientFromPublishableKey(opts) {
1968
2011
  const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
1969
2012
  const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
@@ -1991,10 +2034,26 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
1991
2034
  onUnauthorized,
1992
2035
  onForbidden,
1993
2036
  onError,
1994
- accessCookieName = DEFAULT_ACCESS_COOKIE,
1995
- cookieAware = true
2037
+ cookieAware = true,
2038
+ cookieNames,
2039
+ protect,
2040
+ publicRoutes
1996
2041
  } = resolvedOptions;
2042
+ const accessCookieName = resolvedOptions.accessCookieName ?? cookieNames?.access ?? DEFAULT_ACCESS_COOKIE;
2043
+ const protectMatchers = compileMatchers(protect);
2044
+ const publicMatchers = compileMatchers(publicRoutes);
2045
+ const hasProtect = protectMatchers.length > 0;
2046
+ const hasPublic = publicMatchers.length > 0;
1997
2047
  return async (req, res, next) => {
2048
+ if (hasProtect || hasPublic) {
2049
+ const path = pathOf(req);
2050
+ if (hasPublic && publicMatchers.some((m) => m(path))) {
2051
+ return next();
2052
+ }
2053
+ if (hasProtect && !protectMatchers.some((m) => m(path))) {
2054
+ return next();
2055
+ }
2056
+ }
1998
2057
  let token;
1999
2058
  const authHeader = getAuthorizationHeader(req);
2000
2059
  if (authHeader && authHeader.startsWith("Bearer ")) {
@@ -2086,6 +2145,458 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
2086
2145
  next();
2087
2146
  };
2088
2147
  }
2148
+
2149
+ // src/ws.ts
2150
+ var DEFAULT_COOKIE = "iqauth_at";
2151
+ var DEFAULT_SUBPROTOCOL_PREFIX = "iqauth.bearer.";
2152
+ var JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
2153
+ var tokensByIssuer = /* @__PURE__ */ new Map();
2154
+ function getTokens(issuer) {
2155
+ let mod = tokensByIssuer.get(issuer);
2156
+ if (!mod) {
2157
+ mod = new TokensModule(issuer);
2158
+ tokensByIssuer.set(issuer, mod);
2159
+ }
2160
+ return mod;
2161
+ }
2162
+ function firstHeader(value) {
2163
+ if (Array.isArray(value)) return value[0];
2164
+ return value;
2165
+ }
2166
+ function readCookie2(cookieHeader, name) {
2167
+ if (!cookieHeader) return void 0;
2168
+ const target = `${name}=`;
2169
+ for (const seg of cookieHeader.split(";")) {
2170
+ const t = seg.trim();
2171
+ if (t.startsWith(target)) {
2172
+ try {
2173
+ return decodeURIComponent(t.slice(target.length));
2174
+ } catch {
2175
+ return t.slice(target.length);
2176
+ }
2177
+ }
2178
+ }
2179
+ return void 0;
2180
+ }
2181
+ function extractToken(req, cookieName, subprotocolPrefix) {
2182
+ let authHeader;
2183
+ let cookieHeader;
2184
+ let subprotoHeader;
2185
+ if ("headers" in req && req.headers && typeof req.headers === "object") {
2186
+ authHeader = firstHeader(req.headers.authorization);
2187
+ cookieHeader = firstHeader(req.headers.cookie);
2188
+ subprotoHeader = firstHeader(req.headers["sec-websocket-protocol"]);
2189
+ } else {
2190
+ const r = req;
2191
+ authHeader = r.authorization;
2192
+ cookieHeader = r.cookie;
2193
+ subprotoHeader = r.secWebSocketProtocol;
2194
+ }
2195
+ if (authHeader && /^Bearer /i.test(authHeader)) {
2196
+ return authHeader.slice(7).trim();
2197
+ }
2198
+ if (cookieName && cookieHeader) {
2199
+ const fromCookie = readCookie2(cookieHeader, cookieName);
2200
+ if (fromCookie) return fromCookie;
2201
+ }
2202
+ if (subprotocolPrefix !== null && subprotoHeader) {
2203
+ const protos = subprotoHeader.split(",").map((s) => s.trim()).filter(Boolean);
2204
+ for (const proto of protos) {
2205
+ if (subprotocolPrefix && proto.startsWith(subprotocolPrefix)) {
2206
+ return proto.slice(subprotocolPrefix.length);
2207
+ }
2208
+ if (JWT_SHAPE.test(proto)) return proto;
2209
+ }
2210
+ }
2211
+ return void 0;
2212
+ }
2213
+ async function verifyWsUpgrade(req, options) {
2214
+ const parsed = assertPublishableKey(options.publishableKey, {
2215
+ context: "@iqauth/sdk/ws"
2216
+ });
2217
+ const issuer = (options.issuer && typeof options.issuer === "string" ? options.issuer : parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`).replace(/\/+$/, "");
2218
+ const cookieName = options.cookieName === void 0 ? DEFAULT_COOKIE : options.cookieName;
2219
+ const subprotocolPrefix = options.subprotocolPrefix === void 0 ? DEFAULT_SUBPROTOCOL_PREFIX : options.subprotocolPrefix;
2220
+ const token = extractToken(req, cookieName, subprotocolPrefix);
2221
+ if (!token) return null;
2222
+ const tokens = getTokens(issuer);
2223
+ try {
2224
+ const verifyOpts = {};
2225
+ if (options.audience !== void 0) verifyOpts.audience = options.audience;
2226
+ verifyOpts.issuer = options.issuer ?? issuer;
2227
+ if (options.clockTolerance !== void 0)
2228
+ verifyOpts.clockTolerance = options.clockTolerance;
2229
+ if (options.algorithms !== void 0) verifyOpts.algorithms = options.algorithms;
2230
+ const claims = await tokens.verify(token, verifyOpts);
2231
+ return { claims };
2232
+ } catch (err) {
2233
+ if (err instanceof IQAuthError) return null;
2234
+ return null;
2235
+ }
2236
+ }
2237
+
2238
+ // src/test.ts
2239
+ var import_http2 = require("http");
2240
+ var import_crypto2 = require("crypto");
2241
+ var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
2242
+ function jwkFromPublicKey(publicKey, kid) {
2243
+ const jwk = publicKey.export({ format: "jwk" });
2244
+ return { kty: "RSA", use: "sig", alg: "RS256", kid, n: jwk.n, e: jwk.e };
2245
+ }
2246
+ function readBody(req) {
2247
+ return new Promise((resolve, reject) => {
2248
+ const chunks = [];
2249
+ req.on("data", (c) => chunks.push(c));
2250
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
2251
+ req.on("error", reject);
2252
+ });
2253
+ }
2254
+ function parseFormOrJson(raw, contentType) {
2255
+ if (!raw) return {};
2256
+ if (contentType && contentType.includes("application/json")) {
2257
+ try {
2258
+ const obj = JSON.parse(raw);
2259
+ const out2 = {};
2260
+ for (const [k, v] of Object.entries(obj || {})) {
2261
+ if (typeof v === "string") out2[k] = v;
2262
+ else if (v != null) out2[k] = String(v);
2263
+ }
2264
+ return out2;
2265
+ } catch {
2266
+ return {};
2267
+ }
2268
+ }
2269
+ const out = {};
2270
+ for (const part of raw.split("&")) {
2271
+ if (!part) continue;
2272
+ const eq = part.indexOf("=");
2273
+ const k = decodeURIComponent(eq === -1 ? part : part.slice(0, eq)).replace(/\+/g, " ");
2274
+ const v = eq === -1 ? "" : decodeURIComponent(part.slice(eq + 1)).replace(/\+/g, " ");
2275
+ out[k] = v;
2276
+ }
2277
+ return out;
2278
+ }
2279
+ function send(res, status, body, headers = {}) {
2280
+ const payload = typeof body === "string" ? body : JSON.stringify(body);
2281
+ res.writeHead(status, {
2282
+ "Content-Type": typeof body === "string" ? "text/plain; charset=utf-8" : "application/json; charset=utf-8",
2283
+ "Content-Length": Buffer.byteLength(payload),
2284
+ ...headers
2285
+ });
2286
+ res.end(payload);
2287
+ }
2288
+ async function createTestIssuer(options = {}) {
2289
+ const host = options.host ?? "127.0.0.1";
2290
+ const port = options.port ?? 0;
2291
+ const tenantId = options.tenantId ?? "tenant-test";
2292
+ const appId = options.appId ?? "app-test";
2293
+ const kid = options.kid ?? `test-${(0, import_crypto2.randomBytes)(6).toString("hex")}`;
2294
+ const defaultAudience = options.defaultAudience ?? ["dispositioniq"];
2295
+ const { privateKey, publicKey } = (0, import_crypto2.generateKeyPairSync)("rsa", {
2296
+ modulusLength: 2048,
2297
+ publicKeyEncoding: { type: "spki", format: "pem" },
2298
+ privateKeyEncoding: { type: "pkcs8", format: "pem" }
2299
+ });
2300
+ const publicKeyObj = (0, import_crypto2.createPublicKey)(publicKey);
2301
+ const jwk = jwkFromPublicKey(publicKeyObj, kid);
2302
+ const pendingCodes = /* @__PURE__ */ new Map();
2303
+ let baseUrl = "";
2304
+ const buildToken = (opts) => {
2305
+ const payload = {
2306
+ sub: opts.sub ?? "test-user",
2307
+ email: opts.email ?? "test@example.com",
2308
+ name: opts.name ?? "Test User",
2309
+ tenantId: opts.tenantId ?? tenantId,
2310
+ vendorId: opts.vendorId ?? null,
2311
+ roles: opts.roles ?? [],
2312
+ entitlements: opts.entitlements ?? [],
2313
+ sessionId: opts.sessionId ?? `sess-${(0, import_crypto2.randomBytes)(4).toString("hex")}`,
2314
+ jti: opts.jti ?? `jti-${(0, import_crypto2.randomBytes)(4).toString("hex")}`
2315
+ };
2316
+ if (opts.scopeContext !== void 0) payload.scopeContext = opts.scopeContext;
2317
+ if (opts.loginMethod !== void 0) payload.loginMethod = opts.loginMethod;
2318
+ for (const [k, v] of Object.entries(opts)) {
2319
+ if (["sub", "email", "name", "tenantId", "vendorId", "roles", "entitlements", "sessionId", "jti", "scopeContext", "loginMethod", "audience", "issuer", "expiresInSeconds", "iat"].includes(k))
2320
+ continue;
2321
+ payload[k] = v;
2322
+ }
2323
+ const audience = opts.audience ?? defaultAudience;
2324
+ const issuer = opts.issuer ?? baseUrl;
2325
+ const expiresIn = opts.expiresInSeconds ?? 900;
2326
+ const signOpts = {
2327
+ algorithm: "RS256",
2328
+ keyid: kid,
2329
+ issuer,
2330
+ audience
2331
+ };
2332
+ if (opts.iat !== void 0) {
2333
+ payload.iat = opts.iat;
2334
+ payload.exp = opts.iat + expiresIn;
2335
+ } else {
2336
+ signOpts.expiresIn = expiresIn;
2337
+ }
2338
+ return import_jsonwebtoken.default.sign(payload, privateKey, signOpts);
2339
+ };
2340
+ const handler = async (req, res) => {
2341
+ try {
2342
+ const url = new URL(req.url || "/", baseUrl || `http://${host}`);
2343
+ const path = url.pathname;
2344
+ if (req.method === "OPTIONS") {
2345
+ res.writeHead(204, {
2346
+ "Access-Control-Allow-Origin": "*",
2347
+ "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
2348
+ "Access-Control-Allow-Headers": "Authorization,Content-Type"
2349
+ });
2350
+ return res.end();
2351
+ }
2352
+ const cors = { "Access-Control-Allow-Origin": "*" };
2353
+ if (req.method === "GET" && path === "/.well-known/openid-configuration") {
2354
+ return send(res, 200, {
2355
+ issuer: baseUrl,
2356
+ jwks_uri: `${baseUrl}/.well-known/jwks.json`,
2357
+ authorization_endpoint: `${baseUrl}/oidc/authorize`,
2358
+ token_endpoint: `${baseUrl}/oidc/token`,
2359
+ userinfo_endpoint: `${baseUrl}/api/v1/auth/me`,
2360
+ response_types_supported: ["code"],
2361
+ grant_types_supported: ["authorization_code", "refresh_token"],
2362
+ subject_types_supported: ["public"],
2363
+ id_token_signing_alg_values_supported: ["RS256"],
2364
+ code_challenge_methods_supported: ["S256"]
2365
+ }, cors);
2366
+ }
2367
+ if (req.method === "GET" && path === "/.well-known/jwks.json") {
2368
+ return send(res, 200, { keys: [jwk] }, { ...cors, "Cache-Control": "public, max-age=3600" });
2369
+ }
2370
+ if (req.method === "POST" && path === "/oidc/token") {
2371
+ const raw = await readBody(req);
2372
+ const params = parseFormOrJson(raw, req.headers["content-type"]);
2373
+ const grant = params.grant_type;
2374
+ if (grant === "authorization_code") {
2375
+ const code = params.code;
2376
+ const pending = code ? pendingCodes.get(code) : void 0;
2377
+ if (!pending) {
2378
+ return send(res, 400, { error: "invalid_grant", error_description: "Unknown or expired code" }, cors);
2379
+ }
2380
+ pendingCodes.delete(code);
2381
+ const accessToken = buildToken(pending.claims);
2382
+ return send(res, 200, {
2383
+ access_token: accessToken,
2384
+ refresh_token: pending.refreshToken,
2385
+ id_token: accessToken,
2386
+ token_type: "Bearer",
2387
+ expires_in: pending.claims.expiresInSeconds ?? 900
2388
+ }, cors);
2389
+ }
2390
+ if (grant === "refresh_token") {
2391
+ const accessToken = buildToken({ sub: "test-user" });
2392
+ return send(res, 200, {
2393
+ access_token: accessToken,
2394
+ refresh_token: params.refresh_token || `rt-${(0, import_crypto2.randomBytes)(8).toString("hex")}`,
2395
+ token_type: "Bearer",
2396
+ expires_in: 900
2397
+ }, cors);
2398
+ }
2399
+ return send(res, 400, { error: "unsupported_grant_type" }, cors);
2400
+ }
2401
+ if (req.method === "GET" && path === "/api/v1/auth/me") {
2402
+ const auth = req.headers.authorization || "";
2403
+ if (!/^Bearer /i.test(auth)) {
2404
+ return send(res, 401, { success: false, error: { code: "TOKEN_INVALID", message: "Missing bearer" } }, cors);
2405
+ }
2406
+ const token = auth.slice(7).trim();
2407
+ try {
2408
+ const decoded = import_jsonwebtoken.default.verify(token, publicKey, {
2409
+ algorithms: ["RS256"],
2410
+ issuer: baseUrl,
2411
+ audience: defaultAudience
2412
+ });
2413
+ return send(res, 200, {
2414
+ success: true,
2415
+ data: {
2416
+ id: decoded.sub,
2417
+ email: decoded.email,
2418
+ name: decoded.name,
2419
+ tenantId: decoded.tenantId,
2420
+ roles: decoded.roles,
2421
+ entitlements: decoded.entitlements
2422
+ }
2423
+ }, cors);
2424
+ } catch (err) {
2425
+ const msg = err instanceof Error ? err.message : "verify failed";
2426
+ return send(res, 401, { success: false, error: { code: "TOKEN_INVALID", message: msg } }, cors);
2427
+ }
2428
+ }
2429
+ send(res, 404, { error: "not_found", path }, cors);
2430
+ } catch (err) {
2431
+ const msg = err instanceof Error ? err.message : "internal error";
2432
+ send(res, 500, { error: "internal", message: msg });
2433
+ }
2434
+ };
2435
+ const server = (0, import_http2.createServer)((req, res) => {
2436
+ void handler(req, res);
2437
+ });
2438
+ await new Promise((resolve, reject) => {
2439
+ server.once("error", reject);
2440
+ server.listen(port, host, () => {
2441
+ server.off("error", reject);
2442
+ resolve();
2443
+ });
2444
+ });
2445
+ const addr = server.address();
2446
+ const boundPort = typeof addr === "object" && addr ? addr.port : port;
2447
+ baseUrl = `http://${host}:${boundPort}`;
2448
+ const publishableKey = encodePublishableKey("test", {
2449
+ iss: baseUrl,
2450
+ appId,
2451
+ tenantId,
2452
+ kid
2453
+ });
2454
+ return {
2455
+ baseUrl,
2456
+ publishableKey,
2457
+ kid,
2458
+ publicKey,
2459
+ mintToken: (opts = {}) => buildToken(opts),
2460
+ mintAuthCode: (opts = {}) => {
2461
+ const code = `code-${(0, import_crypto2.randomBytes)(12).toString("hex")}`;
2462
+ const refreshToken = opts.refreshToken ?? `rt-${(0, import_crypto2.randomBytes)(12).toString("hex")}`;
2463
+ pendingCodes.set(code, { claims: opts, refreshToken });
2464
+ return code;
2465
+ },
2466
+ close: () => new Promise((resolve, reject) => {
2467
+ server.close((err) => err ? reject(err) : resolve());
2468
+ })
2469
+ };
2470
+ }
2471
+
2472
+ // src/webhooks.ts
2473
+ var import_crypto3 = __toESM(require("crypto"));
2474
+ var WebhookSignatureError = class extends Error {
2475
+ constructor(code, message) {
2476
+ super(message);
2477
+ this.name = "WebhookSignatureError";
2478
+ this.code = code;
2479
+ }
2480
+ };
2481
+ function toBuffer(p) {
2482
+ if (typeof p === "string") return Buffer.from(p, "utf8");
2483
+ if (Buffer.isBuffer(p)) return p;
2484
+ return Buffer.from(p);
2485
+ }
2486
+ function parseHeader(header) {
2487
+ let t = NaN;
2488
+ const v1 = [];
2489
+ for (const part of header.split(",")) {
2490
+ const [k, v] = part.split("=", 2);
2491
+ if (!k || v === void 0) continue;
2492
+ const key = k.trim();
2493
+ const value = v.trim();
2494
+ if (key === "t") t = Number(value);
2495
+ else if (key === "v1") v1.push(value);
2496
+ }
2497
+ return { t, v1 };
2498
+ }
2499
+ function timingSafeEqualHex(a, b) {
2500
+ if (a.length !== b.length) return false;
2501
+ try {
2502
+ return import_crypto3.default.timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
2503
+ } catch {
2504
+ return false;
2505
+ }
2506
+ }
2507
+ function verifyWebhookSignature(opts) {
2508
+ const headerRaw = Array.isArray(opts.header) ? opts.header[0] : opts.header;
2509
+ if (!headerRaw || typeof headerRaw !== "string") {
2510
+ throw new WebhookSignatureError("MISSING_HEADER", "Missing X-IQAuth-Signature header");
2511
+ }
2512
+ if (!opts.secret) {
2513
+ throw new WebhookSignatureError("MISSING_SECRET", "secret is required");
2514
+ }
2515
+ const { t, v1 } = parseHeader(headerRaw);
2516
+ if (!Number.isFinite(t) || v1.length === 0) {
2517
+ throw new WebhookSignatureError("MALFORMED_HEADER", `Could not parse signature header: ${headerRaw}`);
2518
+ }
2519
+ const tolerance = opts.toleranceSeconds ?? 300;
2520
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
2521
+ if (Math.abs(now - t) > tolerance) {
2522
+ throw new WebhookSignatureError(
2523
+ "TIMESTAMP_OUT_OF_TOLERANCE",
2524
+ `Signature timestamp ${t} is outside the ${tolerance}s tolerance window (now=${now})`
2525
+ );
2526
+ }
2527
+ const body = toBuffer(opts.payload);
2528
+ const expected = import_crypto3.default.createHmac("sha256", opts.secret).update(`${t}.`).update(body).digest("hex");
2529
+ const matched = v1.some((sig) => timingSafeEqualHex(sig, expected));
2530
+ if (!matched) {
2531
+ throw new WebhookSignatureError("SIGNATURE_MISMATCH", "Webhook signature does not match expected value");
2532
+ }
2533
+ let parsed;
2534
+ try {
2535
+ parsed = JSON.parse(body.toString("utf8"));
2536
+ } catch {
2537
+ throw new WebhookSignatureError("MALFORMED_BODY", "Webhook body is not valid JSON");
2538
+ }
2539
+ return parsed;
2540
+ }
2541
+ function isValidWebhookSignature(opts) {
2542
+ try {
2543
+ verifyWebhookSignature(opts);
2544
+ return true;
2545
+ } catch {
2546
+ return false;
2547
+ }
2548
+ }
2549
+
2550
+ // src/server/provisioningBridge.ts
2551
+ function defaultIsUniqueViolation(err) {
2552
+ if (!err || typeof err !== "object") return false;
2553
+ const e = err;
2554
+ if (e.code === "23505") return true;
2555
+ if (typeof e.code === "string" && e.code.startsWith("SQLITE_CONSTRAINT")) return true;
2556
+ if (typeof e.message === "string" && /unique constraint|duplicate key/i.test(e.message)) return true;
2557
+ return false;
2558
+ }
2559
+ function createProvisioningBridge(options) {
2560
+ const { storage } = options;
2561
+ const isUniqueViolation = options.isUniqueViolation ?? defaultIsUniqueViolation;
2562
+ const roleOf = (claims) => {
2563
+ try {
2564
+ return options.roleMapper?.(claims) ?? null;
2565
+ } catch {
2566
+ return null;
2567
+ }
2568
+ };
2569
+ const ensureUser = async (claims) => {
2570
+ if (!claims?.sub) {
2571
+ throw new Error("createProvisioningBridge: claims.sub is required");
2572
+ }
2573
+ const byId = await storage.findByIqAuthUserId(claims.sub);
2574
+ if (byId) return { user: byId, claims, created: false, adopted: false };
2575
+ if (claims.email) {
2576
+ const byEmail = await storage.findByEmail(claims.email);
2577
+ if (byEmail) {
2578
+ if (storage.adoptByEmail) {
2579
+ const adopted = await storage.adoptByEmail(byEmail, claims, roleOf(claims));
2580
+ return { user: adopted, claims, created: false, adopted: true };
2581
+ }
2582
+ }
2583
+ }
2584
+ try {
2585
+ const created = await storage.insertFromClaims(claims, roleOf(claims));
2586
+ return { user: created, claims, created: true, adopted: false };
2587
+ } catch (err) {
2588
+ if (!isUniqueViolation(err)) throw err;
2589
+ const after = await storage.findByIqAuthUserId(claims.sub);
2590
+ if (after) return { user: after, claims, created: false, adopted: false };
2591
+ if (claims.email) {
2592
+ const byEmail = await storage.findByEmail(claims.email);
2593
+ if (byEmail) return { user: byEmail, claims, created: false, adopted: true };
2594
+ }
2595
+ throw err;
2596
+ }
2597
+ };
2598
+ return { ensureUser };
2599
+ }
2089
2600
  // Annotate the CommonJS export names for ESM import in node:
2090
2601
  0 && (module.exports = {
2091
2602
  ApiKeysModule,
@@ -2118,11 +2629,17 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
2118
2629
  TokensModule,
2119
2630
  UsersModule,
2120
2631
  VendorsModule,
2632
+ WebhookSignatureError,
2121
2633
  WebhooksModule,
2122
2634
  assertPublishableKey,
2635
+ createProvisioningBridge,
2636
+ createTestIssuer,
2123
2637
  encodePublishableKey,
2124
2638
  iqAuthMiddleware,
2125
2639
  isPublishableKey,
2126
2640
  isSecretKey,
2127
- parsePublishableKey
2641
+ isValidWebhookSignature,
2642
+ parsePublishableKey,
2643
+ verifyWebhookSignature,
2644
+ verifyWsUpgrade
2128
2645
  });