@iqauth/sdk 2.1.0 → 2.2.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 (38) hide show
  1. package/README.md +19 -3
  2. package/dist/browser.d.mts +2 -2
  3. package/dist/browser.d.ts +2 -2
  4. package/dist/browser.js +57 -6
  5. package/dist/browser.mjs +2 -2
  6. package/dist/{chunk-ZESHDJDU.mjs → chunk-D72UL5HL.mjs} +3 -6
  7. package/dist/{chunk-JQRTY5MY.mjs → chunk-M4J6BPK7.mjs} +3 -8
  8. package/dist/chunk-QEJB7WEQ.mjs +119 -0
  9. package/dist/{chunk-S3M2IXCE.mjs → chunk-QZB745C2.mjs} +3 -8
  10. package/dist/cli/index.js +21 -0
  11. package/dist/cli/index.mjs +1 -1
  12. package/dist/{doctor-OHJRZBBT.mjs → doctor-XCI77BQS.mjs} +2 -1
  13. package/dist/express.js +54 -25
  14. package/dist/express.mjs +5 -8
  15. package/dist/fastify.js +53 -19
  16. package/dist/fastify.mjs +4 -5
  17. package/dist/hono.js +53 -19
  18. package/dist/hono.mjs +4 -5
  19. package/dist/index.d.mts +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.js +59 -4
  22. package/dist/index.mjs +4 -2
  23. package/dist/next.js +66 -34
  24. package/dist/next.mjs +6 -9
  25. package/dist/{publishableKey-B5DIK81A.d.mts → publishableKey-BaR0HoAH.d.mts} +10 -1
  26. package/dist/{publishableKey-B5DIK81A.d.ts → publishableKey-BaR0HoAH.d.ts} +10 -1
  27. package/dist/react.d.mts +35 -3
  28. package/dist/react.d.ts +35 -3
  29. package/dist/react.js +66 -17
  30. package/dist/react.mjs +14 -2
  31. package/dist/server/handlers.js +63 -17
  32. package/dist/server/handlers.mjs +3 -2
  33. package/dist/server.js +53 -21
  34. package/dist/server.mjs +3 -3
  35. package/dist/{signIn-VRNzlNyG.d.ts → signIn-BVDTIA_t.d.ts} +1 -1
  36. package/dist/{signIn-CEMdUAwd.d.mts → signIn-D_kP3v-c.d.mts} +1 -1
  37. package/package.json +1 -1
  38. package/dist/chunk-5WFR6Y33.mjs +0 -59
package/README.md CHANGED
@@ -56,18 +56,34 @@ Create both in one call from the admin Quickstart wizard, or run `npx iqauth ini
56
56
  ### React (browser)
57
57
 
58
58
  ```tsx
59
- import { IQAuthProvider, SignedIn, SignedOut, RedirectToSignIn } from "@iqauth/sdk/react";
59
+ import {
60
+ IQAuthProvider,
61
+ IQAuthLoading,
62
+ IQAuthLoaded,
63
+ SignedIn,
64
+ SignedOut,
65
+ RedirectToSignIn,
66
+ } from "@iqauth/sdk/react";
60
67
 
61
68
  export default function App() {
62
69
  return (
63
70
  <IQAuthProvider publishableKey={import.meta.env.VITE_IQAUTH_PUBLISHABLE_KEY}>
64
- <SignedIn><Dashboard /></SignedIn>
65
- <SignedOut><RedirectToSignIn /></SignedOut>
71
+ <IQAuthLoading><Spinner /></IQAuthLoading>
72
+ <IQAuthLoaded>
73
+ <SignedIn><Dashboard /></SignedIn>
74
+ <SignedOut><RedirectToSignIn /></SignedOut>
75
+ </IQAuthLoaded>
66
76
  </IQAuthProvider>
67
77
  );
68
78
  }
69
79
  ```
70
80
 
81
+ Wrapping the gating components in `<IQAuthLoading/>` / `<IQAuthLoaded/>` is
82
+ the slow-network-safe pattern: until `bootstrap()` finishes, both
83
+ `<SignedIn/>` and `<SignedOut/>` render `null`, which on a slow mobile
84
+ connection is several seconds of blank page. The loading slot fills that
85
+ gap, mirroring Clerk's `<ClerkLoading/>` / `<ClerkLoaded/>`.
86
+
71
87
  Available hooks: `useUser()`, `useSession()`, `useAuth()`, `useOrganization()`. Each returns `{ data, isLoading, error }`.
72
88
  Drop-in components: `<SignIn/>`, `<SignUp/>`, `<UserButton/>`, `<UserProfile/>`, `<OrganizationSwitcher/>`, `<AuthCallback/>`.
73
89
 
@@ -1,5 +1,5 @@
1
- export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-CEMdUAwd.mjs';
2
- export { K as KeyMode, b as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, a as isSecretKey, p as parsePublishableKey } from './publishableKey-B5DIK81A.mjs';
1
+ export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-D_kP3v-c.mjs';
2
+ 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';
3
3
  export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.mjs';
4
4
  import './types-Cxl3bQHt.mjs';
5
5
 
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-VRNzlNyG.js';
2
- export { K as KeyMode, b as ParsedPublishableKey, P as PublishableKeyPayload, e as encodePublishableKey, i as isPublishableKey, a as isSecretKey, p as parsePublishableKey } from './publishableKey-B5DIK81A.js';
1
+ export { C as CallbackResult, S as SessionManager, d as SessionManagerOptions, a as SessionSnapshot, e as SessionStatus, b as SignInOptions, c as SignOutOptions, f as buildSignInUrl, h as handleAuthCallback, r as redirectToSignIn, s as signIn, g as signOut } from './signIn-BVDTIA_t.js';
2
+ 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';
3
3
  export { a as ErrorCode, E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.js';
4
4
  import './types-Cxl3bQHt.js';
5
5
 
package/dist/browser.js CHANGED
@@ -116,6 +116,18 @@ function encodePublishableKey(mode, payload) {
116
116
  if (mode !== "test" && mode !== "live") throw new Error(`Invalid mode: ${mode}`);
117
117
  return `pk_${mode}_${b64urlEncode(JSON.stringify(payload))}`;
118
118
  }
119
+ function isValidIssuerUrl(iss) {
120
+ if (typeof iss !== "string" || iss.length === 0) return false;
121
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
122
+ try {
123
+ const u = new URL(iss);
124
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
125
+ if (!u.hostname) return false;
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
119
131
  function parsePublishableKey(raw) {
120
132
  if (typeof raw !== "string") return null;
121
133
  const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
@@ -126,11 +138,55 @@ function parsePublishableKey(raw) {
126
138
  if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
127
139
  return null;
128
140
  }
141
+ if (!isValidIssuerUrl(json.iss)) return null;
129
142
  return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
130
143
  } catch {
131
144
  return null;
132
145
  }
133
146
  }
147
+ function assertPublishableKey(raw, opts) {
148
+ const ctx = opts?.context ? `${opts.context}: ` : "";
149
+ if (typeof raw !== "string" || raw.length === 0) {
150
+ throw new IQAuthError(
151
+ "CONFIG_INVALID",
152
+ `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
153
+ );
154
+ }
155
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
156
+ if (!shapeMatch) {
157
+ throw new IQAuthError(
158
+ "CONFIG_INVALID",
159
+ `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
160
+ );
161
+ }
162
+ let decoded;
163
+ try {
164
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
165
+ } catch {
166
+ throw new IQAuthError(
167
+ "CONFIG_INVALID",
168
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
169
+ );
170
+ }
171
+ if (!isPublishableKeyPayload(decoded)) {
172
+ throw new IQAuthError(
173
+ "CONFIG_INVALID",
174
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
175
+ );
176
+ }
177
+ if (!isValidIssuerUrl(decoded.iss)) {
178
+ throw new IQAuthError(
179
+ "CONFIG_INVALID",
180
+ `${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.`
181
+ );
182
+ }
183
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
184
+ }
185
+ function isPublishableKeyPayload(value) {
186
+ if (!value || typeof value !== "object") return false;
187
+ const v = value;
188
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
189
+ }
134
190
  function isPublishableKey(raw) {
135
191
  return typeof raw === "string" && /^pk_(test|live)_/.test(raw);
136
192
  }
@@ -258,12 +314,7 @@ var SessionManager = class {
258
314
  this.remoteRefreshWaiters = [];
259
315
  /** Active claims by other tabs (keyed by source tabId). */
260
316
  this.foreignClaim = null;
261
- const parsed = parsePublishableKey(options.publishableKey);
262
- if (!parsed) {
263
- throw new Error(
264
- `Invalid IQAuth publishable key. Expected pk_test_\u2026 or pk_live_\u2026 (got ${options.publishableKey?.slice(0, 12) ?? "<empty>"}\u2026).`
265
- );
266
- }
317
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
267
318
  this.key = parsed;
268
319
  const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
269
320
  this.issuer = inferred.replace(/\/+$/, "");
package/dist/browser.mjs CHANGED
@@ -12,13 +12,13 @@ import {
12
12
  setCookie,
13
13
  signIn,
14
14
  signOut
15
- } from "./chunk-S3M2IXCE.mjs";
15
+ } from "./chunk-QZB745C2.mjs";
16
16
  import {
17
17
  encodePublishableKey,
18
18
  isPublishableKey,
19
19
  isSecretKey,
20
20
  parsePublishableKey
21
- } from "./chunk-5WFR6Y33.mjs";
21
+ } from "./chunk-QEJB7WEQ.mjs";
22
22
  import {
23
23
  ErrorCodes,
24
24
  IQAuthError
@@ -1,6 +1,6 @@
1
1
  import {
2
- parsePublishableKey
3
- } from "./chunk-5WFR6Y33.mjs";
2
+ assertPublishableKey
3
+ } from "./chunk-QEJB7WEQ.mjs";
4
4
  import {
5
5
  IQAuthClient
6
6
  } from "./chunk-MDUHPQMM.mjs";
@@ -44,10 +44,7 @@ function readCookie(req, name) {
44
44
  return void 0;
45
45
  }
46
46
  function clientFromPublishableKey(opts) {
47
- const parsed = parsePublishableKey(opts.publishableKey);
48
- if (!parsed) {
49
- throw new Error("iqAuthMiddleware: invalid publishable key");
50
- }
47
+ const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
51
48
  const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
52
49
  return new IQAuthClient({ baseUrl: issuer, environment: "server" });
53
50
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
- parsePublishableKey
3
- } from "./chunk-5WFR6Y33.mjs";
2
+ assertPublishableKey
3
+ } from "./chunk-QEJB7WEQ.mjs";
4
4
 
5
5
  // src/server/handlers.ts
6
6
  var TERMINAL_REFRESH_ERROR_CODES = /* @__PURE__ */ new Set([
@@ -22,12 +22,7 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
22
22
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
23
23
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
24
24
  function resolve(config) {
25
- const parsed = parsePublishableKey(config.publishableKey);
26
- if (!parsed) {
27
- throw new Error(
28
- "@iqauth/sdk: invalid publishable key passed to iqAuth helpers (expected pk_test_\u2026 or pk_live_\u2026)"
29
- );
30
- }
25
+ const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
31
26
  const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
32
27
  return {
33
28
  publishableKey: config.publishableKey,
@@ -0,0 +1,119 @@
1
+ import {
2
+ IQAuthError
3
+ } from "./chunk-6I6RM4MN.mjs";
4
+ import {
5
+ __require
6
+ } from "./chunk-Y6FXYEAI.mjs";
7
+
8
+ // src/publishableKey.ts
9
+ function b64urlEncode(input) {
10
+ if (typeof btoa === "function") {
11
+ const bytes = new TextEncoder().encode(input);
12
+ let bin = "";
13
+ for (let i = 0; i < bytes.byteLength; i++) bin += String.fromCharCode(bytes[i]);
14
+ return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
15
+ }
16
+ const { Buffer } = __require("buffer");
17
+ return Buffer.from(input, "utf8").toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
18
+ }
19
+ function b64urlDecode(input) {
20
+ const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
21
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
22
+ if (typeof atob === "function") {
23
+ const bin = atob(normalized);
24
+ const bytes = new Uint8Array(bin.length);
25
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
26
+ return new TextDecoder().decode(bytes);
27
+ }
28
+ const { Buffer } = __require("buffer");
29
+ return Buffer.from(normalized, "base64").toString("utf8");
30
+ }
31
+ function encodePublishableKey(mode, payload) {
32
+ if (mode !== "test" && mode !== "live") throw new Error(`Invalid mode: ${mode}`);
33
+ return `pk_${mode}_${b64urlEncode(JSON.stringify(payload))}`;
34
+ }
35
+ function isValidIssuerUrl(iss) {
36
+ if (typeof iss !== "string" || iss.length === 0) return false;
37
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
38
+ try {
39
+ const u = new URL(iss);
40
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
41
+ if (!u.hostname) return false;
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ function parsePublishableKey(raw) {
48
+ if (typeof raw !== "string") return null;
49
+ const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
50
+ if (!m) return null;
51
+ try {
52
+ const json = JSON.parse(b64urlDecode(m[2]));
53
+ if (!json || typeof json !== "object") return null;
54
+ if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
55
+ return null;
56
+ }
57
+ if (!isValidIssuerUrl(json.iss)) return null;
58
+ return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ function assertPublishableKey(raw, opts) {
64
+ const ctx = opts?.context ? `${opts.context}: ` : "";
65
+ if (typeof raw !== "string" || raw.length === 0) {
66
+ throw new IQAuthError(
67
+ "CONFIG_INVALID",
68
+ `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
69
+ );
70
+ }
71
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
72
+ if (!shapeMatch) {
73
+ throw new IQAuthError(
74
+ "CONFIG_INVALID",
75
+ `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
76
+ );
77
+ }
78
+ let decoded;
79
+ try {
80
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
81
+ } catch {
82
+ throw new IQAuthError(
83
+ "CONFIG_INVALID",
84
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
85
+ );
86
+ }
87
+ if (!isPublishableKeyPayload(decoded)) {
88
+ throw new IQAuthError(
89
+ "CONFIG_INVALID",
90
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
91
+ );
92
+ }
93
+ if (!isValidIssuerUrl(decoded.iss)) {
94
+ throw new IQAuthError(
95
+ "CONFIG_INVALID",
96
+ `${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.`
97
+ );
98
+ }
99
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
100
+ }
101
+ function isPublishableKeyPayload(value) {
102
+ if (!value || typeof value !== "object") return false;
103
+ const v = value;
104
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
105
+ }
106
+ function isPublishableKey(raw) {
107
+ return typeof raw === "string" && /^pk_(test|live)_/.test(raw);
108
+ }
109
+ function isSecretKey(raw) {
110
+ return typeof raw === "string" && /^sk_(test|live)_/.test(raw);
111
+ }
112
+
113
+ export {
114
+ encodePublishableKey,
115
+ parsePublishableKey,
116
+ assertPublishableKey,
117
+ isPublishableKey,
118
+ isSecretKey
119
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
- parsePublishableKey
3
- } from "./chunk-5WFR6Y33.mjs";
2
+ assertPublishableKey
3
+ } from "./chunk-QEJB7WEQ.mjs";
4
4
  import {
5
5
  IQAuthError
6
6
  } from "./chunk-6I6RM4MN.mjs";
@@ -125,12 +125,7 @@ var SessionManager = class {
125
125
  this.remoteRefreshWaiters = [];
126
126
  /** Active claims by other tabs (keyed by source tabId). */
127
127
  this.foreignClaim = null;
128
- const parsed = parsePublishableKey(options.publishableKey);
129
- if (!parsed) {
130
- throw new Error(
131
- `Invalid IQAuth publishable key. Expected pk_test_\u2026 or pk_live_\u2026 (got ${options.publishableKey?.slice(0, 12) ?? "<empty>"}\u2026).`
132
- );
133
- }
128
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
134
129
  this.key = parsed;
135
130
  const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
136
131
  this.issuer = inferred.replace(/\/+$/, "");
package/dist/cli/index.js CHANGED
@@ -278,6 +278,13 @@ var init_util = __esm({
278
278
  }
279
279
  });
280
280
 
281
+ // src/errors.ts
282
+ var init_errors = __esm({
283
+ "src/errors.ts"() {
284
+ "use strict";
285
+ }
286
+ });
287
+
281
288
  // src/publishableKey.ts
282
289
  function b64urlDecode(input) {
283
290
  const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
@@ -291,6 +298,18 @@ function b64urlDecode(input) {
291
298
  const { Buffer: Buffer2 } = require("buffer");
292
299
  return Buffer2.from(normalized, "base64").toString("utf8");
293
300
  }
301
+ function isValidIssuerUrl(iss) {
302
+ if (typeof iss !== "string" || iss.length === 0) return false;
303
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
304
+ try {
305
+ const u = new URL(iss);
306
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
307
+ if (!u.hostname) return false;
308
+ return true;
309
+ } catch {
310
+ return false;
311
+ }
312
+ }
294
313
  function parsePublishableKey(raw) {
295
314
  if (typeof raw !== "string") return null;
296
315
  const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
@@ -301,6 +320,7 @@ function parsePublishableKey(raw) {
301
320
  if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
302
321
  return null;
303
322
  }
323
+ if (!isValidIssuerUrl(json.iss)) return null;
304
324
  return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
305
325
  } catch {
306
326
  return null;
@@ -309,6 +329,7 @@ function parsePublishableKey(raw) {
309
329
  var init_publishableKey = __esm({
310
330
  "src/publishableKey.ts"() {
311
331
  "use strict";
332
+ init_errors();
312
333
  }
313
334
  });
314
335
 
@@ -17,7 +17,7 @@ async function run() {
17
17
  return;
18
18
  }
19
19
  case "doctor": {
20
- const { runDoctor } = await import("../doctor-OHJRZBBT.mjs");
20
+ const { runDoctor } = await import("../doctor-XCI77BQS.mjs");
21
21
  await runDoctor(rest);
22
22
  return;
23
23
  }
@@ -5,7 +5,8 @@ import {
5
5
  } from "./chunk-X3K3WOBR.mjs";
6
6
  import {
7
7
  parsePublishableKey
8
- } from "./chunk-5WFR6Y33.mjs";
8
+ } from "./chunk-QEJB7WEQ.mjs";
9
+ import "./chunk-6I6RM4MN.mjs";
9
10
  import "./chunk-Y6FXYEAI.mjs";
10
11
 
11
12
  // src/cli/doctor.ts
package/dist/express.js CHANGED
@@ -1805,21 +1805,61 @@ function b64urlDecode(input) {
1805
1805
  const { Buffer: Buffer2 } = require("buffer");
1806
1806
  return Buffer2.from(normalized, "base64").toString("utf8");
1807
1807
  }
1808
- function parsePublishableKey(raw) {
1809
- if (typeof raw !== "string") return null;
1810
- const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
1811
- if (!m) return null;
1808
+ function isValidIssuerUrl(iss) {
1809
+ if (typeof iss !== "string" || iss.length === 0) return false;
1810
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
1812
1811
  try {
1813
- const json = JSON.parse(b64urlDecode(m[2]));
1814
- if (!json || typeof json !== "object") return null;
1815
- if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
1816
- return null;
1817
- }
1818
- return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
1812
+ const u = new URL(iss);
1813
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
1814
+ if (!u.hostname) return false;
1815
+ return true;
1819
1816
  } catch {
1820
- return null;
1817
+ return false;
1821
1818
  }
1822
1819
  }
1820
+ function assertPublishableKey(raw, opts) {
1821
+ const ctx = opts?.context ? `${opts.context}: ` : "";
1822
+ if (typeof raw !== "string" || raw.length === 0) {
1823
+ throw new IQAuthError(
1824
+ "CONFIG_INVALID",
1825
+ `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
1826
+ );
1827
+ }
1828
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
1829
+ if (!shapeMatch) {
1830
+ throw new IQAuthError(
1831
+ "CONFIG_INVALID",
1832
+ `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
1833
+ );
1834
+ }
1835
+ let decoded;
1836
+ try {
1837
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
1838
+ } catch {
1839
+ throw new IQAuthError(
1840
+ "CONFIG_INVALID",
1841
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
1842
+ );
1843
+ }
1844
+ if (!isPublishableKeyPayload(decoded)) {
1845
+ throw new IQAuthError(
1846
+ "CONFIG_INVALID",
1847
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
1848
+ );
1849
+ }
1850
+ if (!isValidIssuerUrl(decoded.iss)) {
1851
+ throw new IQAuthError(
1852
+ "CONFIG_INVALID",
1853
+ `${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.`
1854
+ );
1855
+ }
1856
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
1857
+ }
1858
+ function isPublishableKeyPayload(value) {
1859
+ if (!value || typeof value !== "object") return false;
1860
+ const v = value;
1861
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
1862
+ }
1823
1863
 
1824
1864
  // src/middleware/express.ts
1825
1865
  var KNOWN_AUTH_ERROR_CODES = /* @__PURE__ */ new Set([
@@ -1857,10 +1897,7 @@ function readCookie(req, name) {
1857
1897
  return void 0;
1858
1898
  }
1859
1899
  function clientFromPublishableKey(opts) {
1860
- const parsed = parsePublishableKey(opts.publishableKey);
1861
- if (!parsed) {
1862
- throw new Error("iqAuthMiddleware: invalid publishable key");
1863
- }
1900
+ const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
1864
1901
  const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
1865
1902
  return new IQAuthClient({ baseUrl: issuer, environment: "server" });
1866
1903
  }
@@ -2002,12 +2039,7 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
2002
2039
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
2003
2040
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
2004
2041
  function resolve(config) {
2005
- const parsed = parsePublishableKey(config.publishableKey);
2006
- if (!parsed) {
2007
- throw new Error(
2008
- "@iqauth/sdk: invalid publishable key passed to iqAuth helpers (expected pk_test_\u2026 or pk_live_\u2026)"
2009
- );
2010
- }
2042
+ const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
2011
2043
  const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
2012
2044
  return {
2013
2045
  publishableKey: config.publishableKey,
@@ -2222,10 +2254,7 @@ function readCookieFromReq(req, name) {
2222
2254
  return void 0;
2223
2255
  }
2224
2256
  function iqAuth(options) {
2225
- const parsed = parsePublishableKey(options.publishableKey);
2226
- if (!parsed) {
2227
- throw new Error("@iqauth/sdk/express: invalid publishable key");
2228
- }
2257
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/express" });
2229
2258
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
2230
2259
  const client = new IQAuthClient({
2231
2260
  baseUrl: issuer,
package/dist/express.mjs CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  DEFAULT_REFRESH_COOKIE,
3
3
  iqAuthMiddleware
4
- } from "./chunk-ZESHDJDU.mjs";
4
+ } from "./chunk-D72UL5HL.mjs";
5
5
  import {
6
6
  handleCallback,
7
7
  handleRefresh,
8
8
  handleSignout
9
- } from "./chunk-JQRTY5MY.mjs";
9
+ } from "./chunk-M4J6BPK7.mjs";
10
10
  import {
11
- parsePublishableKey
12
- } from "./chunk-5WFR6Y33.mjs";
11
+ assertPublishableKey
12
+ } from "./chunk-QEJB7WEQ.mjs";
13
13
  import {
14
14
  IQAuthClient
15
15
  } from "./chunk-MDUHPQMM.mjs";
@@ -66,10 +66,7 @@ function readCookieFromReq(req, name) {
66
66
  return void 0;
67
67
  }
68
68
  function iqAuth(options) {
69
- const parsed = parsePublishableKey(options.publishableKey);
70
- if (!parsed) {
71
- throw new Error("@iqauth/sdk/express: invalid publishable key");
72
- }
69
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/express" });
73
70
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
74
71
  const client = new IQAuthClient({
75
72
  baseUrl: issuer,
package/dist/fastify.js CHANGED
@@ -1767,20 +1767,60 @@ function b64urlDecode(input) {
1767
1767
  const { Buffer: Buffer2 } = require("buffer");
1768
1768
  return Buffer2.from(normalized, "base64").toString("utf8");
1769
1769
  }
1770
- function parsePublishableKey(raw) {
1771
- if (typeof raw !== "string") return null;
1772
- const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
1773
- if (!m) return null;
1770
+ function isValidIssuerUrl(iss) {
1771
+ if (typeof iss !== "string" || iss.length === 0) return false;
1772
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
1774
1773
  try {
1775
- const json = JSON.parse(b64urlDecode(m[2]));
1776
- if (!json || typeof json !== "object") return null;
1777
- if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
1778
- return null;
1779
- }
1780
- return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
1774
+ const u = new URL(iss);
1775
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
1776
+ if (!u.hostname) return false;
1777
+ return true;
1781
1778
  } catch {
1782
- return null;
1779
+ return false;
1780
+ }
1781
+ }
1782
+ function assertPublishableKey(raw, opts) {
1783
+ const ctx = opts?.context ? `${opts.context}: ` : "";
1784
+ if (typeof raw !== "string" || raw.length === 0) {
1785
+ throw new IQAuthError(
1786
+ "CONFIG_INVALID",
1787
+ `${ctx}IQAuth publishable key is missing. Set IQAUTH_PUBLISHABLE_KEY (or pass publishableKey) to a pk_test_\u2026 or pk_live_\u2026 value from the IQAuth admin console.`
1788
+ );
1789
+ }
1790
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
1791
+ if (!shapeMatch) {
1792
+ throw new IQAuthError(
1793
+ "CONFIG_INVALID",
1794
+ `${ctx}IQAuth publishable key is malformed (got ${raw.slice(0, 12)}\u2026). Expected pk_test_\u2026 or pk_live_\u2026; regenerate the key from the IQAuth admin console.`
1795
+ );
1796
+ }
1797
+ let decoded;
1798
+ try {
1799
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
1800
+ } catch {
1801
+ throw new IQAuthError(
1802
+ "CONFIG_INVALID",
1803
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
1804
+ );
1805
+ }
1806
+ if (!isPublishableKeyPayload(decoded)) {
1807
+ throw new IQAuthError(
1808
+ "CONFIG_INVALID",
1809
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
1810
+ );
1783
1811
  }
1812
+ if (!isValidIssuerUrl(decoded.iss)) {
1813
+ throw new IQAuthError(
1814
+ "CONFIG_INVALID",
1815
+ `${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.`
1816
+ );
1817
+ }
1818
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
1819
+ }
1820
+ function isPublishableKeyPayload(value) {
1821
+ if (!value || typeof value !== "object") return false;
1822
+ const v = value;
1823
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
1784
1824
  }
1785
1825
 
1786
1826
  // src/server/handlers.ts
@@ -1803,12 +1843,7 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
1803
1843
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
1804
1844
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
1805
1845
  function resolve(config) {
1806
- const parsed = parsePublishableKey(config.publishableKey);
1807
- if (!parsed) {
1808
- throw new Error(
1809
- "@iqauth/sdk: invalid publishable key passed to iqAuth helpers (expected pk_test_\u2026 or pk_live_\u2026)"
1810
- );
1811
- }
1846
+ const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
1812
1847
  const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
1813
1848
  return {
1814
1849
  publishableKey: config.publishableKey,
@@ -2023,8 +2058,7 @@ function readCookie(req, name) {
2023
2058
  return void 0;
2024
2059
  }
2025
2060
  async function iqAuth(fastify, options) {
2026
- const parsed = parsePublishableKey(options.publishableKey);
2027
- if (!parsed) throw new Error("@iqauth/sdk/fastify: invalid publishable key");
2061
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/fastify" });
2028
2062
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
2029
2063
  const helperConfig = { ...options, issuer };
2030
2064
  const client = new IQAuthClient({