@iqauth/sdk 2.3.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 (87) hide show
  1. package/README.md +110 -0
  2. package/dist/browser-session.d.mts +3 -2
  3. package/dist/browser-session.d.ts +3 -2
  4. package/dist/browser.d.mts +64 -29
  5. package/dist/browser.d.ts +64 -29
  6. package/dist/browser.js +782 -38
  7. package/dist/browser.mjs +43 -3
  8. package/dist/bundle-LUKDQYVQ.mjs +374 -0
  9. package/dist/chunk-3JULWS6F.mjs +106 -0
  10. package/dist/chunk-5T7GHBX6.mjs +1165 -0
  11. package/dist/{chunk-KGEPDXHU.mjs → chunk-6TDJJER7.mjs} +2 -2
  12. package/dist/{chunk-RACIPVLD.mjs → chunk-76W5TLQQ.mjs} +262 -220
  13. package/dist/{chunk-EKTNEZIH.mjs → chunk-BVV54LPI.mjs} +37 -5
  14. package/dist/chunk-LIZYFXH7.mjs +90 -0
  15. package/dist/chunk-MKKZULZR.mjs +241 -0
  16. package/dist/chunk-SL3KRS4W.mjs +54 -0
  17. package/dist/chunk-TKZTCPEK.mjs +232 -0
  18. package/dist/chunk-UKZLOHZG.mjs +83 -0
  19. package/dist/cli/index.js +144 -36
  20. package/dist/cli/index.mjs +1 -1
  21. package/dist/{client-DTX4hNdS.d.ts → client-BNQe3AgF.d.ts} +3 -62
  22. package/dist/{client-vdh2a9fJ.d.mts → client-kYlJFgPv.d.mts} +3 -62
  23. package/dist/doctor-YYNHNMLD.mjs +198 -0
  24. package/dist/{express-A0-dWEMy.d.mts → express-B6_1vBYZ.d.mts} +23 -2
  25. package/dist/{express-Bo_pJKHN.d.ts → express-CHpfa7D_.d.ts} +23 -2
  26. package/dist/express.d.mts +5 -4
  27. package/dist/express.d.ts +5 -4
  28. package/dist/express.js +36 -4
  29. package/dist/express.mjs +8 -8
  30. package/dist/fastify.js +2 -2
  31. package/dist/fastify.mjs +4 -4
  32. package/dist/hono.js +2 -2
  33. package/dist/hono.mjs +4 -4
  34. package/dist/index.d.mts +8 -3
  35. package/dist/index.d.ts +8 -3
  36. package/dist/index.js +500 -4
  37. package/dist/index.mjs +29 -9
  38. package/dist/locales.d.mts +53 -0
  39. package/dist/locales.d.ts +53 -0
  40. package/dist/locales.js +1202 -0
  41. package/dist/locales.mjs +29 -0
  42. package/dist/mobile.d.mts +3 -2
  43. package/dist/mobile.d.ts +3 -2
  44. package/dist/next.d.mts +1 -1
  45. package/dist/next.d.ts +1 -1
  46. package/dist/next.js +2 -2
  47. package/dist/next.mjs +1 -1
  48. package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
  49. package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
  50. package/dist/react.d.mts +1349 -10
  51. package/dist/react.d.ts +1349 -10
  52. package/dist/react.js +2985 -567
  53. package/dist/react.mjs +1517 -94
  54. package/dist/reverify-4UEJXUS6.mjs +16 -0
  55. package/dist/server/handlers.d.mts +10 -1
  56. package/dist/server/handlers.d.ts +10 -1
  57. package/dist/server/handlers.js +2 -2
  58. package/dist/server/handlers.mjs +1 -1
  59. package/dist/server.d.mts +5 -3
  60. package/dist/server.d.ts +5 -3
  61. package/dist/server.js +89 -4
  62. package/dist/server.mjs +12 -8
  63. package/dist/service.d.mts +3 -2
  64. package/dist/service.d.ts +3 -2
  65. package/dist/signIn-CCY4JE5G.mjs +15 -0
  66. package/dist/{signIn-Cd0P4y9d.d.mts → signIn-CiIBTJIh.d.mts} +224 -4
  67. package/dist/{signIn-DKakyzeu.d.ts → signIn-OCr88Zf8.d.ts} +224 -4
  68. package/dist/test.d.mts +86 -0
  69. package/dist/test.d.ts +86 -0
  70. package/dist/test.js +289 -0
  71. package/dist/test.mjs +9 -0
  72. package/dist/tokens-DCyzzn8L.d.mts +63 -0
  73. package/dist/tokens-aHiGFr_E.d.ts +63 -0
  74. package/dist/types-6bNdxesb.d.mts +196 -0
  75. package/dist/types-6bNdxesb.d.ts +196 -0
  76. package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.mts} +6 -0
  77. package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.ts} +6 -0
  78. package/dist/webhooks.d.mts +61 -0
  79. package/dist/webhooks.d.ts +61 -0
  80. package/dist/webhooks.js +119 -0
  81. package/dist/webhooks.mjs +11 -0
  82. package/dist/ws.d.mts +73 -0
  83. package/dist/ws.d.ts +73 -0
  84. package/dist/ws.js +397 -0
  85. package/dist/ws.mjs +12 -0
  86. package/package.json +22 -2
  87. package/dist/doctor-A5E7LSFW.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
 
@@ -1985,6 +1991,22 @@ function readCookie(req, name) {
1985
1991
  }
1986
1992
  return void 0;
1987
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
+ }
1988
2010
  function clientFromPublishableKey(opts) {
1989
2011
  const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
1990
2012
  const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
@@ -2012,10 +2034,26 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
2012
2034
  onUnauthorized,
2013
2035
  onForbidden,
2014
2036
  onError,
2015
- accessCookieName = DEFAULT_ACCESS_COOKIE,
2016
- cookieAware = true
2037
+ cookieAware = true,
2038
+ cookieNames,
2039
+ protect,
2040
+ publicRoutes
2017
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;
2018
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
+ }
2019
2057
  let token;
2020
2058
  const authHeader = getAuthorizationHeader(req);
2021
2059
  if (authHeader && authHeader.startsWith("Bearer ")) {
@@ -2107,6 +2145,458 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
2107
2145
  next();
2108
2146
  };
2109
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
+ }
2110
2600
  // Annotate the CommonJS export names for ESM import in node:
2111
2601
  0 && (module.exports = {
2112
2602
  ApiKeysModule,
@@ -2139,11 +2629,17 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
2139
2629
  TokensModule,
2140
2630
  UsersModule,
2141
2631
  VendorsModule,
2632
+ WebhookSignatureError,
2142
2633
  WebhooksModule,
2143
2634
  assertPublishableKey,
2635
+ createProvisioningBridge,
2636
+ createTestIssuer,
2144
2637
  encodePublishableKey,
2145
2638
  iqAuthMiddleware,
2146
2639
  isPublishableKey,
2147
2640
  isSecretKey,
2148
- parsePublishableKey
2641
+ isValidWebhookSignature,
2642
+ parsePublishableKey,
2643
+ verifyWebhookSignature,
2644
+ verifyWsUpgrade
2149
2645
  });
package/dist/index.mjs CHANGED
@@ -1,6 +1,27 @@
1
+ import {
2
+ createProvisioningBridge
3
+ } from "./chunk-SL3KRS4W.mjs";
4
+ import {
5
+ createTestIssuer
6
+ } from "./chunk-MKKZULZR.mjs";
7
+ import {
8
+ WebhookSignatureError,
9
+ isValidWebhookSignature,
10
+ verifyWebhookSignature
11
+ } from "./chunk-UKZLOHZG.mjs";
12
+ import {
13
+ verifyWsUpgrade
14
+ } from "./chunk-3JULWS6F.mjs";
1
15
  import {
2
16
  iqAuthMiddleware
3
- } from "./chunk-EKTNEZIH.mjs";
17
+ } from "./chunk-BVV54LPI.mjs";
18
+ import {
19
+ assertPublishableKey,
20
+ encodePublishableKey,
21
+ isPublishableKey,
22
+ isSecretKey,
23
+ parsePublishableKey
24
+ } from "./chunk-WQWBJSSS.mjs";
4
25
  import {
5
26
  ApiKeysModule,
6
27
  AppsModule,
@@ -28,13 +49,6 @@ import {
28
49
  VendorsModule,
29
50
  WebhooksModule
30
51
  } from "./chunk-W3F4JYGP.mjs";
31
- import {
32
- assertPublishableKey,
33
- encodePublishableKey,
34
- isPublishableKey,
35
- isSecretKey,
36
- parsePublishableKey
37
- } from "./chunk-WQWBJSSS.mjs";
38
52
  import {
39
53
  DEFAULT_CLOCK_TOLERANCE_SECONDS,
40
54
  DEFAULT_TOKEN_AUDIENCE,
@@ -77,11 +91,17 @@ export {
77
91
  TokensModule,
78
92
  UsersModule,
79
93
  VendorsModule,
94
+ WebhookSignatureError,
80
95
  WebhooksModule,
81
96
  assertPublishableKey,
97
+ createProvisioningBridge,
98
+ createTestIssuer,
82
99
  encodePublishableKey,
83
100
  iqAuthMiddleware,
84
101
  isPublishableKey,
85
102
  isSecretKey,
86
- parsePublishableKey
103
+ isValidWebhookSignature,
104
+ parsePublishableKey,
105
+ verifyWebhookSignature,
106
+ verifyWsUpgrade
87
107
  };
@@ -0,0 +1,53 @@
1
+ import { I as IQAuthLocaleBundle, a as IQAuthLocaleOverride, b as IQAuthLocaleKey } from './types-6bNdxesb.mjs';
2
+
3
+ declare const enUS: IQAuthLocaleBundle;
4
+
5
+ declare const frFR: IQAuthLocaleBundle;
6
+
7
+ declare const esES: IQAuthLocaleBundle;
8
+
9
+ declare const deDE: IQAuthLocaleBundle;
10
+
11
+ declare const jaJP: IQAuthLocaleBundle;
12
+
13
+ declare const ptBR: IQAuthLocaleBundle;
14
+
15
+ /**
16
+ * Public entry point for `@iqauth/sdk/locales`.
17
+ *
18
+ * Exports the strict bundle type, the default `enUS` bundle, the five other
19
+ * shipped locales, the `t()` resolver, and helpers for error-code mapping
20
+ * and Accept-Language negotiation.
21
+ */
22
+
23
+ /** Bundles indexed by their canonical BCP-47 tag. */
24
+ declare const builtInLocales: Record<string, IQAuthLocaleBundle>;
25
+ /**
26
+ * Default bundle used when a key is missing from a contributor-supplied
27
+ * partial override, or when no `localization` prop is passed at all.
28
+ */
29
+ declare const defaultBundle: IQAuthLocaleBundle;
30
+ /**
31
+ * Resolve a localized string from a bundle. Substitutes `{name}` placeholders
32
+ * with values from `vars`. If the key is missing from the bundle (which
33
+ * should never happen when contributors supply a fully typed bundle, but can
34
+ * happen with a `Partial<IQAuthLocaleBundle>` override), the default `enUS`
35
+ * value is used as a graceful fallback.
36
+ */
37
+ declare function t(bundle: IQAuthLocaleBundle | IQAuthLocaleOverride | undefined | null, key: IQAuthLocaleKey, vars?: Record<string, string | number>): string;
38
+ /**
39
+ * Merge a partial override on top of the default bundle so callers always
40
+ * receive a fully populated `IQAuthLocaleBundle`. Used internally by
41
+ * `<IQAuthProvider localization={...}>`.
42
+ */
43
+ declare function resolveBundle(override?: IQAuthLocaleBundle | IQAuthLocaleOverride | null): IQAuthLocaleBundle;
44
+ declare function localizeErrorCode(bundle: IQAuthLocaleBundle | IQAuthLocaleOverride | undefined | null, code: string | undefined | null, vars?: Record<string, string | number>): string;
45
+ /**
46
+ * Negotiate the best matching locale tag from a comma-separated
47
+ * `Accept-Language` header (or `navigator.language` value), preferring exact
48
+ * matches and falling back to the language subtag (e.g. `fr-CA` -> `fr-FR`).
49
+ * Returns `"en-US"` when no candidate matches a built-in locale.
50
+ */
51
+ declare function negotiateLocale(acceptLanguage: string | null | undefined, available?: string[]): string;
52
+
53
+ export { IQAuthLocaleBundle, IQAuthLocaleKey, IQAuthLocaleOverride, builtInLocales, deDE, defaultBundle, enUS, esES, frFR, jaJP, localizeErrorCode, negotiateLocale, ptBR, resolveBundle, t };