@iqauth/sdk 2.1.0 → 2.3.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 (64) hide show
  1. package/README.md +43 -3
  2. package/dist/browser-session.d.mts +1 -2
  3. package/dist/browser-session.d.ts +1 -2
  4. package/dist/browser-session.js +89 -68
  5. package/dist/browser-session.mjs +2 -1
  6. package/dist/browser.d.mts +2 -2
  7. package/dist/browser.d.ts +2 -2
  8. package/dist/browser.js +69 -7
  9. package/dist/browser.mjs +2 -2
  10. package/dist/{chunk-ZESHDJDU.mjs → chunk-EKTNEZIH.mjs} +5 -8
  11. package/dist/{chunk-JQRTY5MY.mjs → chunk-KGEPDXHU.mjs} +12 -8
  12. package/dist/{chunk-S3M2IXCE.mjs → chunk-RACIPVLD.mjs} +15 -9
  13. package/dist/chunk-UNYDG2L4.mjs +209 -0
  14. package/dist/{chunk-MDUHPQMM.mjs → chunk-W3F4JYGP.mjs} +8 -180
  15. package/dist/chunk-WQWBJSSS.mjs +119 -0
  16. package/dist/cli/index.js +21 -0
  17. package/dist/cli/index.mjs +1 -1
  18. package/dist/{client-DXbHb2ul.d.ts → client-DTX4hNdS.d.ts} +16 -21
  19. package/dist/{client-Dv4v92Mj.d.mts → client-vdh2a9fJ.d.mts} +16 -21
  20. package/dist/{doctor-OHJRZBBT.mjs → doctor-A5E7LSFW.mjs} +2 -1
  21. package/dist/{express-BZmF1llh.d.mts → express-A0-dWEMy.d.mts} +1 -1
  22. package/dist/{express-B4o3P8vK.d.ts → express-Bo_pJKHN.d.ts} +1 -1
  23. package/dist/express.d.mts +75 -5
  24. package/dist/express.d.ts +75 -5
  25. package/dist/express.js +353 -94
  26. package/dist/express.mjs +210 -12
  27. package/dist/fastify.js +153 -88
  28. package/dist/fastify.mjs +10 -9
  29. package/dist/hono.js +152 -88
  30. package/dist/hono.mjs +9 -9
  31. package/dist/index.d.mts +3 -4
  32. package/dist/index.d.ts +3 -4
  33. package/dist/index.js +148 -72
  34. package/dist/index.mjs +16 -12
  35. package/dist/mobile.d.mts +1 -2
  36. package/dist/mobile.d.ts +1 -2
  37. package/dist/mobile.js +89 -68
  38. package/dist/mobile.mjs +2 -1
  39. package/dist/next.d.mts +9 -0
  40. package/dist/next.d.ts +9 -0
  41. package/dist/next.js +164 -1649
  42. package/dist/next.mjs +13 -16
  43. package/dist/{publishableKey-B5DIK81A.d.mts → publishableKey-BaR0HoAH.d.mts} +10 -1
  44. package/dist/{publishableKey-B5DIK81A.d.ts → publishableKey-BaR0HoAH.d.ts} +10 -1
  45. package/dist/react.d.mts +35 -3
  46. package/dist/react.d.ts +35 -3
  47. package/dist/react.js +78 -18
  48. package/dist/react.mjs +14 -2
  49. package/dist/server/handlers.d.mts +2 -0
  50. package/dist/server/handlers.d.ts +2 -0
  51. package/dist/server/handlers.js +72 -17
  52. package/dist/server/handlers.mjs +3 -2
  53. package/dist/server.d.mts +2 -3
  54. package/dist/server.d.ts +2 -3
  55. package/dist/server.js +151 -89
  56. package/dist/server.mjs +7 -6
  57. package/dist/service.d.mts +1 -2
  58. package/dist/service.d.ts +1 -2
  59. package/dist/service.js +89 -68
  60. package/dist/service.mjs +2 -1
  61. package/dist/{signIn-CEMdUAwd.d.mts → signIn-Cd0P4y9d.d.mts} +9 -1
  62. package/dist/{signIn-VRNzlNyG.d.ts → signIn-DKakyzeu.d.ts} +9 -1
  63. package/package.json +3 -2
  64. package/dist/chunk-5WFR6Y33.mjs +0 -59
package/dist/next.mjs CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  handleRefresh,
4
4
  handleSignout,
5
5
  serializeCookie
6
- } from "./chunk-JQRTY5MY.mjs";
6
+ } from "./chunk-KGEPDXHU.mjs";
7
7
  import {
8
- parsePublishableKey
9
- } from "./chunk-5WFR6Y33.mjs";
8
+ assertPublishableKey
9
+ } from "./chunk-WQWBJSSS.mjs";
10
10
  import {
11
- IQAuthClient
12
- } from "./chunk-MDUHPQMM.mjs";
11
+ TokensModule
12
+ } from "./chunk-UNYDG2L4.mjs";
13
13
  import "./chunk-6I6RM4MN.mjs";
14
14
  import "./chunk-Y6FXYEAI.mjs";
15
15
 
@@ -35,8 +35,7 @@ function toResponse(hr) {
35
35
  return new Response(JSON.stringify(hr.body), { status: hr.status, headers });
36
36
  }
37
37
  function handler(options) {
38
- const parsed = parsePublishableKey(options.publishableKey);
39
- if (!parsed) throw new Error("@iqauth/sdk/next: invalid publishable key");
38
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/next handler" });
40
39
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
41
40
  const helperConfig = { ...options, issuer };
42
41
  const accessCookie = options.accessCookieName ?? "iqauth_at";
@@ -60,7 +59,7 @@ function handler(options) {
60
59
  if (action === "signout") {
61
60
  const auth = req.headers.get("authorization");
62
61
  const accessToken = auth && auth.replace(/^Bearer /i, "") || readCookieFromHeader(cookieHeader, accessCookie);
63
- return toResponse(await handleSignout(helperConfig, { accessToken }));
62
+ return toResponse(await handleSignout(helperConfig, { accessToken, ssoCookieHeader: cookieHeader ?? void 0 }));
64
63
  }
65
64
  return new Response(JSON.stringify({ success: false, error: { code: "NOT_FOUND", message: `Unknown action: ${action}` } }), {
66
65
  status: 404,
@@ -69,11 +68,10 @@ function handler(options) {
69
68
  };
70
69
  }
71
70
  function createMiddleware(options) {
72
- const parsed = parsePublishableKey(options.publishableKey);
73
- if (!parsed) throw new Error("@iqauth/sdk/next: invalid publishable key");
71
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/next createMiddleware" });
74
72
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
75
73
  const accessCookie = options.accessCookieName ?? "iqauth_at";
76
- const client = new IQAuthClient({ baseUrl: issuer, environment: "server" });
74
+ const tokens = new TokensModule(issuer);
77
75
  return async (req) => {
78
76
  const auth = req.headers.get("authorization");
79
77
  let token;
@@ -86,7 +84,7 @@ function createMiddleware(options) {
86
84
  });
87
85
  }
88
86
  try {
89
- await client.tokens.verify(token);
87
+ await tokens.verify(token);
90
88
  return void 0;
91
89
  } catch (err) {
92
90
  const code = err.code || "TOKEN_INVALID";
@@ -98,8 +96,7 @@ function createMiddleware(options) {
98
96
  };
99
97
  }
100
98
  async function getAuth(options) {
101
- const parsed = parsePublishableKey(options.publishableKey);
102
- if (!parsed) throw new Error("@iqauth/sdk/next: invalid publishable key");
99
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/next getAuth" });
103
100
  const issuer = (options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
104
101
  const accessCookie = options.accessCookieName ?? "iqauth_at";
105
102
  let cookieJar = null;
@@ -116,9 +113,9 @@ async function getAuth(options) {
116
113
  }
117
114
  const token = cookieJar?.get(accessCookie)?.value;
118
115
  if (!token) return null;
119
- const client = new IQAuthClient({ baseUrl: issuer, environment: "server" });
116
+ const tokens = new TokensModule(issuer);
120
117
  try {
121
- return await client.tokens.verify(token);
118
+ return await tokens.verify(token);
122
119
  } catch {
123
120
  return null;
124
121
  }
@@ -18,7 +18,16 @@ interface ParsedPublishableKey extends PublishableKeyPayload {
18
18
  }
19
19
  declare function encodePublishableKey(mode: KeyMode, payload: PublishableKeyPayload): string;
20
20
  declare function parsePublishableKey(raw: string): ParsedPublishableKey | null;
21
+ /**
22
+ * Strict counterpart to `parsePublishableKey` — throws a typed `IQAuthError`
23
+ * (`code: "CONFIG_INVALID"`) with an actionable message when the key is
24
+ * missing, malformed, or encodes a non-URL `iss`. Use this at SDK init so a
25
+ * bad key fails loudly at boot instead of cryptically at first verify.
26
+ */
27
+ declare function assertPublishableKey(raw: string | undefined | null, opts?: {
28
+ context?: string;
29
+ }): ParsedPublishableKey;
21
30
  declare function isPublishableKey(raw: string): boolean;
22
31
  declare function isSecretKey(raw: string): boolean;
23
32
 
24
- export { type KeyMode as K, type PublishableKeyPayload as P, isSecretKey as a, type ParsedPublishableKey as b, encodePublishableKey as e, isPublishableKey as i, parsePublishableKey as p };
33
+ export { type KeyMode as K, type PublishableKeyPayload as P, assertPublishableKey as a, isSecretKey as b, type ParsedPublishableKey as c, encodePublishableKey as e, isPublishableKey as i, parsePublishableKey as p };
@@ -18,7 +18,16 @@ interface ParsedPublishableKey extends PublishableKeyPayload {
18
18
  }
19
19
  declare function encodePublishableKey(mode: KeyMode, payload: PublishableKeyPayload): string;
20
20
  declare function parsePublishableKey(raw: string): ParsedPublishableKey | null;
21
+ /**
22
+ * Strict counterpart to `parsePublishableKey` — throws a typed `IQAuthError`
23
+ * (`code: "CONFIG_INVALID"`) with an actionable message when the key is
24
+ * missing, malformed, or encodes a non-URL `iss`. Use this at SDK init so a
25
+ * bad key fails loudly at boot instead of cryptically at first verify.
26
+ */
27
+ declare function assertPublishableKey(raw: string | undefined | null, opts?: {
28
+ context?: string;
29
+ }): ParsedPublishableKey;
21
30
  declare function isPublishableKey(raw: string): boolean;
22
31
  declare function isSecretKey(raw: string): boolean;
23
32
 
24
- export { type KeyMode as K, type PublishableKeyPayload as P, isSecretKey as a, type ParsedPublishableKey as b, encodePublishableKey as e, isPublishableKey as i, parsePublishableKey as p };
33
+ export { type KeyMode as K, type PublishableKeyPayload as P, assertPublishableKey as a, isSecretKey as b, type ParsedPublishableKey as c, encodePublishableKey as e, isPublishableKey as i, parsePublishableKey as p };
package/dist/react.d.mts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-CEMdUAwd.mjs';
4
+ import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-Cd0P4y9d.mjs';
5
5
  import { d as SessionUser, J as JwtClaims } from './types-Cxl3bQHt.mjs';
6
- import './publishableKey-B5DIK81A.mjs';
6
+ import './publishableKey-BaR0HoAH.mjs';
7
7
 
8
8
  interface IQAuthContextValue {
9
9
  manager: SessionManager;
@@ -89,6 +89,38 @@ declare function SignedOut({ children }: {
89
89
  }): React.FunctionComponentElement<{
90
90
  children?: ReactNode | undefined;
91
91
  }> | null;
92
+ /**
93
+ * Renders its children only while the SDK is still bootstrapping (i.e.
94
+ * `useAuth().isLoaded === false`). Mirrors Clerk's `<ClerkLoading/>`.
95
+ *
96
+ * Recommended pattern (slow-network-safe):
97
+ * ```tsx
98
+ * <IQAuthProvider publishableKey={…}>
99
+ * <IQAuthLoading><Spinner /></IQAuthLoading>
100
+ * <IQAuthLoaded>
101
+ * <SignedIn><App /></SignedIn>
102
+ * <SignedOut><SignIn /></SignedOut>
103
+ * </IQAuthLoaded>
104
+ * </IQAuthProvider>
105
+ * ```
106
+ */
107
+ declare function IQAuthLoading({ children }: {
108
+ children?: ReactNode;
109
+ }): React.FunctionComponentElement<{
110
+ children?: ReactNode | undefined;
111
+ }> | null;
112
+ /**
113
+ * Renders its children only after the SDK has finished bootstrapping (i.e.
114
+ * `useAuth().isLoaded === true`). Mirrors Clerk's `<ClerkLoaded/>`. Wrap
115
+ * `<SignedIn/>` / `<SignedOut/>` in this to avoid a flash of blank UI on
116
+ * slow connections — see `<IQAuthLoading/>` above for the recommended
117
+ * full pattern.
118
+ */
119
+ declare function IQAuthLoaded({ children }: {
120
+ children?: ReactNode;
121
+ }): React.FunctionComponentElement<{
122
+ children?: ReactNode | undefined;
123
+ }> | null;
92
124
  interface RedirectToSignInProps extends SignInOptions {
93
125
  }
94
126
  declare function RedirectToSignIn(props?: RedirectToSignInProps): React.DetailedReactHTMLElement<{
@@ -268,4 +300,4 @@ interface OrganizationSwitcherProps {
268
300
  declare function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }: OrganizationSwitcherProps): react_jsx_runtime.JSX.Element;
269
301
  declare const __version__ = "phase-bc-1.0.0";
270
302
 
271
- export { AuthCallback, type AuthCallbackProps, type IQAuthBranding, IQAuthProvider, type IQAuthProviderProps, type IQAuthSignInContext, OrganizationSwitcher, type OrganizationSwitcherProps, RedirectToSignIn, type RedirectToSignInProps, type SessionError, type SharedComponentProps, SignIn, type SignInProps, SignUp, type SignUpProps, SignedIn, SignedOut, type UseAuthResult, type UseOrganizationResult, type UseSessionResult, type UseUserResult, UserButton, type UserButtonProps, UserProfile, type UserProfileProps, type UserSummary, __version__, isSilentSsoEligible, sanitizeBrandCss, useAuth, useAuthFetch, useIQAuthSignInContext, useOrganization, useResolvedSdkBranding, useSession, useUser };
303
+ export { AuthCallback, type AuthCallbackProps, type IQAuthBranding, IQAuthLoaded, IQAuthLoading, IQAuthProvider, type IQAuthProviderProps, type IQAuthSignInContext, OrganizationSwitcher, type OrganizationSwitcherProps, RedirectToSignIn, type RedirectToSignInProps, type SessionError, type SharedComponentProps, SignIn, type SignInProps, SignUp, type SignUpProps, SignedIn, SignedOut, type UseAuthResult, type UseOrganizationResult, type UseSessionResult, type UseUserResult, UserButton, type UserButtonProps, UserProfile, type UserProfileProps, type UserSummary, __version__, isSilentSsoEligible, sanitizeBrandCss, useAuth, useAuthFetch, useIQAuthSignInContext, useOrganization, useResolvedSdkBranding, useSession, useUser };
package/dist/react.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
  import { ReactNode } from 'react';
4
- import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-VRNzlNyG.js';
4
+ import { S as SessionManager, a as SessionSnapshot, b as SignInOptions, c as SignOutOptions, C as CallbackResult } from './signIn-DKakyzeu.js';
5
5
  import { d as SessionUser, J as JwtClaims } from './types-Cxl3bQHt.js';
6
- import './publishableKey-B5DIK81A.js';
6
+ import './publishableKey-BaR0HoAH.js';
7
7
 
8
8
  interface IQAuthContextValue {
9
9
  manager: SessionManager;
@@ -89,6 +89,38 @@ declare function SignedOut({ children }: {
89
89
  }): React.FunctionComponentElement<{
90
90
  children?: ReactNode | undefined;
91
91
  }> | null;
92
+ /**
93
+ * Renders its children only while the SDK is still bootstrapping (i.e.
94
+ * `useAuth().isLoaded === false`). Mirrors Clerk's `<ClerkLoading/>`.
95
+ *
96
+ * Recommended pattern (slow-network-safe):
97
+ * ```tsx
98
+ * <IQAuthProvider publishableKey={…}>
99
+ * <IQAuthLoading><Spinner /></IQAuthLoading>
100
+ * <IQAuthLoaded>
101
+ * <SignedIn><App /></SignedIn>
102
+ * <SignedOut><SignIn /></SignedOut>
103
+ * </IQAuthLoaded>
104
+ * </IQAuthProvider>
105
+ * ```
106
+ */
107
+ declare function IQAuthLoading({ children }: {
108
+ children?: ReactNode;
109
+ }): React.FunctionComponentElement<{
110
+ children?: ReactNode | undefined;
111
+ }> | null;
112
+ /**
113
+ * Renders its children only after the SDK has finished bootstrapping (i.e.
114
+ * `useAuth().isLoaded === true`). Mirrors Clerk's `<ClerkLoaded/>`. Wrap
115
+ * `<SignedIn/>` / `<SignedOut/>` in this to avoid a flash of blank UI on
116
+ * slow connections — see `<IQAuthLoading/>` above for the recommended
117
+ * full pattern.
118
+ */
119
+ declare function IQAuthLoaded({ children }: {
120
+ children?: ReactNode;
121
+ }): React.FunctionComponentElement<{
122
+ children?: ReactNode | undefined;
123
+ }> | null;
92
124
  interface RedirectToSignInProps extends SignInOptions {
93
125
  }
94
126
  declare function RedirectToSignIn(props?: RedirectToSignInProps): React.DetailedReactHTMLElement<{
@@ -268,4 +300,4 @@ interface OrganizationSwitcherProps {
268
300
  declare function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }: OrganizationSwitcherProps): react_jsx_runtime.JSX.Element;
269
301
  declare const __version__ = "phase-bc-1.0.0";
270
302
 
271
- export { AuthCallback, type AuthCallbackProps, type IQAuthBranding, IQAuthProvider, type IQAuthProviderProps, type IQAuthSignInContext, OrganizationSwitcher, type OrganizationSwitcherProps, RedirectToSignIn, type RedirectToSignInProps, type SessionError, type SharedComponentProps, SignIn, type SignInProps, SignUp, type SignUpProps, SignedIn, SignedOut, type UseAuthResult, type UseOrganizationResult, type UseSessionResult, type UseUserResult, UserButton, type UserButtonProps, UserProfile, type UserProfileProps, type UserSummary, __version__, isSilentSsoEligible, sanitizeBrandCss, useAuth, useAuthFetch, useIQAuthSignInContext, useOrganization, useResolvedSdkBranding, useSession, useUser };
303
+ export { AuthCallback, type AuthCallbackProps, type IQAuthBranding, IQAuthLoaded, IQAuthLoading, IQAuthProvider, type IQAuthProviderProps, type IQAuthSignInContext, OrganizationSwitcher, type OrganizationSwitcherProps, RedirectToSignIn, type RedirectToSignInProps, type SessionError, type SharedComponentProps, SignIn, type SignInProps, SignUp, type SignUpProps, SignedIn, SignedOut, type UseAuthResult, type UseOrganizationResult, type UseSessionResult, type UseUserResult, UserButton, type UserButtonProps, UserProfile, type UserProfileProps, type UserSummary, __version__, isSilentSsoEligible, sanitizeBrandCss, useAuth, useAuthFetch, useIQAuthSignInContext, useOrganization, useResolvedSdkBranding, useSession, useUser };
package/dist/react.js CHANGED
@@ -21,6 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var react_exports = {};
22
22
  __export(react_exports, {
23
23
  AuthCallback: () => AuthCallback,
24
+ IQAuthLoaded: () => IQAuthLoaded,
25
+ IQAuthLoading: () => IQAuthLoading,
24
26
  IQAuthProvider: () => IQAuthProvider,
25
27
  OrganizationSwitcher: () => OrganizationSwitcher,
26
28
  RedirectToSignIn: () => RedirectToSignIn,
@@ -70,20 +72,60 @@ function b64urlDecode(input) {
70
72
  const { Buffer: Buffer2 } = require("buffer");
71
73
  return Buffer2.from(normalized, "base64").toString("utf8");
72
74
  }
73
- function parsePublishableKey(raw) {
74
- if (typeof raw !== "string") return null;
75
- const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
76
- if (!m) return null;
75
+ function isValidIssuerUrl(iss) {
76
+ if (typeof iss !== "string" || iss.length === 0) return false;
77
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
77
78
  try {
78
- const json = JSON.parse(b64urlDecode(m[2]));
79
- if (!json || typeof json !== "object") return null;
80
- if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
81
- return null;
82
- }
83
- return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
79
+ const u = new URL(iss);
80
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
81
+ if (!u.hostname) return false;
82
+ return true;
84
83
  } catch {
85
- return null;
84
+ return false;
85
+ }
86
+ }
87
+ function assertPublishableKey(raw, opts) {
88
+ const ctx = opts?.context ? `${opts.context}: ` : "";
89
+ if (typeof raw !== "string" || raw.length === 0) {
90
+ throw new IQAuthError(
91
+ "CONFIG_INVALID",
92
+ `${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.`
93
+ );
94
+ }
95
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
96
+ if (!shapeMatch) {
97
+ throw new IQAuthError(
98
+ "CONFIG_INVALID",
99
+ `${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.`
100
+ );
101
+ }
102
+ let decoded;
103
+ try {
104
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
105
+ } catch {
106
+ throw new IQAuthError(
107
+ "CONFIG_INVALID",
108
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
109
+ );
110
+ }
111
+ if (!isPublishableKeyPayload(decoded)) {
112
+ throw new IQAuthError(
113
+ "CONFIG_INVALID",
114
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
115
+ );
86
116
  }
117
+ if (!isValidIssuerUrl(decoded.iss)) {
118
+ throw new IQAuthError(
119
+ "CONFIG_INVALID",
120
+ `${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.`
121
+ );
122
+ }
123
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
124
+ }
125
+ function isPublishableKeyPayload(value) {
126
+ if (!value || typeof value !== "object") return false;
127
+ const v = value;
128
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
87
129
  }
88
130
 
89
131
  // src/browser/storage.ts
@@ -206,12 +248,7 @@ var SessionManager = class {
206
248
  this.remoteRefreshWaiters = [];
207
249
  /** Active claims by other tabs (keyed by source tabId). */
208
250
  this.foreignClaim = null;
209
- const parsed = parsePublishableKey(options.publishableKey);
210
- if (!parsed) {
211
- throw new Error(
212
- `Invalid IQAuth publishable key. Expected pk_test_\u2026 or pk_live_\u2026 (got ${options.publishableKey?.slice(0, 12) ?? "<empty>"}\u2026).`
213
- );
214
- }
251
+ const parsed = assertPublishableKey(options.publishableKey, { context: "@iqauth/sdk/browser SessionManager" });
215
252
  this.key = parsed;
216
253
  const inferred = options.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`);
217
254
  this.issuer = inferred.replace(/\/+$/, "");
@@ -612,6 +649,7 @@ async function createPkcePair() {
612
649
  // src/browser/signIn.ts
613
650
  var DEFAULT_SIGN_IN_PATH = "/sign-in";
614
651
  var DEFAULT_LOGOUT_PATH = "/api/v1/auth/logout";
652
+ var DEFAULT_SSO_LOGOUT_PATH = "/oidc/sso-logout";
615
653
  var DEFAULT_TOKEN_PATH = "/oidc/token";
616
654
  var DEFAULT_CALLBACK_PATH = "/auth/callback";
617
655
  function defaultRedirectUri() {
@@ -710,11 +748,21 @@ async function handleAuthCallback(manager, options = {}) {
710
748
  }
711
749
  async function signOut(manager, opts = {}) {
712
750
  if (!opts.localOnly) {
751
+ const issuer = manager.issuerUrl.replace(/\/$/, "");
713
752
  try {
714
- const url = `${manager.issuerUrl}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
753
+ const url = `${issuer}${opts.logoutPath ?? DEFAULT_LOGOUT_PATH}`;
715
754
  await manager.fetch(url, { method: "POST" }).catch(() => void 0);
716
755
  } catch {
717
756
  }
757
+ if (opts.endSsoSession !== false) {
758
+ try {
759
+ await fetch(`${issuer}${DEFAULT_SSO_LOGOUT_PATH}`, {
760
+ method: "POST",
761
+ credentials: "include"
762
+ }).catch(() => void 0);
763
+ } catch {
764
+ }
765
+ }
718
766
  }
719
767
  clearCookie(REFRESH_COOKIE);
720
768
  manager.signOutLocal();
@@ -846,6 +894,16 @@ function SignedOut({ children }) {
846
894
  if (!isLoaded || isSignedIn) return null;
847
895
  return (0, import_react.createElement)(import_react.Fragment, null, children);
848
896
  }
897
+ function IQAuthLoading({ children }) {
898
+ const { isLoaded } = useAuth();
899
+ if (isLoaded) return null;
900
+ return (0, import_react.createElement)(import_react.Fragment, null, children);
901
+ }
902
+ function IQAuthLoaded({ children }) {
903
+ const { isLoaded } = useAuth();
904
+ if (!isLoaded) return null;
905
+ return (0, import_react.createElement)(import_react.Fragment, null, children);
906
+ }
849
907
  function RedirectToSignIn(props = {}) {
850
908
  const { manager, snapshot } = useCtx();
851
909
  const [error, setError] = (0, import_react.useState)(null);
@@ -1868,6 +1926,8 @@ var __version__ = "phase-bc-1.0.0";
1868
1926
  // Annotate the CommonJS export names for ESM import in node:
1869
1927
  0 && (module.exports = {
1870
1928
  AuthCallback,
1929
+ IQAuthLoaded,
1930
+ IQAuthLoading,
1871
1931
  IQAuthProvider,
1872
1932
  OrganizationSwitcher,
1873
1933
  RedirectToSignIn,
package/dist/react.mjs CHANGED
@@ -4,8 +4,8 @@ import {
4
4
  redirectToSignIn,
5
5
  signIn,
6
6
  signOut
7
- } from "./chunk-S3M2IXCE.mjs";
8
- import "./chunk-5WFR6Y33.mjs";
7
+ } from "./chunk-RACIPVLD.mjs";
8
+ import "./chunk-WQWBJSSS.mjs";
9
9
  import "./chunk-6I6RM4MN.mjs";
10
10
  import "./chunk-Y6FXYEAI.mjs";
11
11
 
@@ -144,6 +144,16 @@ function SignedOut({ children }) {
144
144
  if (!isLoaded || isSignedIn) return null;
145
145
  return createElement(Fragment, null, children);
146
146
  }
147
+ function IQAuthLoading({ children }) {
148
+ const { isLoaded } = useAuth();
149
+ if (isLoaded) return null;
150
+ return createElement(Fragment, null, children);
151
+ }
152
+ function IQAuthLoaded({ children }) {
153
+ const { isLoaded } = useAuth();
154
+ if (!isLoaded) return null;
155
+ return createElement(Fragment, null, children);
156
+ }
147
157
  function RedirectToSignIn(props = {}) {
148
158
  const { manager, snapshot } = useCtx();
149
159
  const [error, setError] = useState(null);
@@ -1165,6 +1175,8 @@ function OrganizationSwitcher({ iqAuthBaseUrl, onSwitched, className }) {
1165
1175
  var __version__ = "phase-bc-1.0.0";
1166
1176
  export {
1167
1177
  AuthCallback,
1178
+ IQAuthLoaded,
1179
+ IQAuthLoading,
1168
1180
  IQAuthProvider,
1169
1181
  OrganizationSwitcher,
1170
1182
  RedirectToSignIn,
@@ -118,6 +118,8 @@ declare function handleRefresh(config: IQAuthHelperConfig, input: {
118
118
  /** POST /api/iqauth/signout — clear cookies and best-effort revoke at issuer. */
119
119
  declare function handleSignout(config: IQAuthHelperConfig, input: {
120
120
  accessToken?: string;
121
+ ssoCookieHeader?: string;
122
+ endSsoSession?: boolean;
121
123
  }): Promise<HandlerResponse>;
122
124
 
123
125
  export { type HandlerResponse, type IQAuthHelperConfig, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie };
@@ -118,6 +118,8 @@ declare function handleRefresh(config: IQAuthHelperConfig, input: {
118
118
  /** POST /api/iqauth/signout — clear cookies and best-effort revoke at issuer. */
119
119
  declare function handleSignout(config: IQAuthHelperConfig, input: {
120
120
  accessToken?: string;
121
+ ssoCookieHeader?: string;
122
+ endSsoSession?: boolean;
121
123
  }): Promise<HandlerResponse>;
122
124
 
123
125
  export { type HandlerResponse, type IQAuthHelperConfig, type ResolvedConfig as ResolvedIQAuthHelperConfig, type SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie };
@@ -27,6 +27,17 @@ __export(handlers_exports, {
27
27
  });
28
28
  module.exports = __toCommonJS(handlers_exports);
29
29
 
30
+ // src/errors.ts
31
+ var IQAuthError = class extends Error {
32
+ constructor(code, message, status, raw) {
33
+ super(message);
34
+ this.name = "IQAuthError";
35
+ this.code = code;
36
+ this.status = status;
37
+ this.raw = raw;
38
+ }
39
+ };
40
+
30
41
  // src/publishableKey.ts
31
42
  function b64urlDecode(input) {
32
43
  const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - input.length % 4);
@@ -40,20 +51,60 @@ function b64urlDecode(input) {
40
51
  const { Buffer: Buffer2 } = require("buffer");
41
52
  return Buffer2.from(normalized, "base64").toString("utf8");
42
53
  }
43
- function parsePublishableKey(raw) {
44
- if (typeof raw !== "string") return null;
45
- const m = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
46
- if (!m) return null;
54
+ function isValidIssuerUrl(iss) {
55
+ if (typeof iss !== "string" || iss.length === 0) return false;
56
+ if (!iss.startsWith("http://") && !iss.startsWith("https://")) return false;
47
57
  try {
48
- const json = JSON.parse(b64urlDecode(m[2]));
49
- if (!json || typeof json !== "object") return null;
50
- if (typeof json.iss !== "string" || typeof json.appId !== "string" || typeof json.tenantId !== "string" || typeof json.kid !== "string") {
51
- return null;
52
- }
53
- return { mode: m[1], iss: json.iss, appId: json.appId, tenantId: json.tenantId, kid: json.kid, raw };
58
+ const u = new URL(iss);
59
+ if (u.protocol !== "http:" && u.protocol !== "https:") return false;
60
+ if (!u.hostname) return false;
61
+ return true;
54
62
  } catch {
55
- return null;
63
+ return false;
64
+ }
65
+ }
66
+ function assertPublishableKey(raw, opts) {
67
+ const ctx = opts?.context ? `${opts.context}: ` : "";
68
+ if (typeof raw !== "string" || raw.length === 0) {
69
+ throw new IQAuthError(
70
+ "CONFIG_INVALID",
71
+ `${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.`
72
+ );
73
+ }
74
+ const shapeMatch = raw.match(/^pk_(test|live)_([A-Za-z0-9_-]+)$/);
75
+ if (!shapeMatch) {
76
+ throw new IQAuthError(
77
+ "CONFIG_INVALID",
78
+ `${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.`
79
+ );
80
+ }
81
+ let decoded;
82
+ try {
83
+ decoded = JSON.parse(b64urlDecode(shapeMatch[2]));
84
+ } catch {
85
+ throw new IQAuthError(
86
+ "CONFIG_INVALID",
87
+ `${ctx}IQAuth publishable key payload is not valid base64url JSON. Regenerate the key from the IQAuth admin console.`
88
+ );
56
89
  }
90
+ if (!isPublishableKeyPayload(decoded)) {
91
+ throw new IQAuthError(
92
+ "CONFIG_INVALID",
93
+ `${ctx}IQAuth publishable key payload is missing required fields {iss, appId, tenantId, kid}. Regenerate the key from the IQAuth admin console.`
94
+ );
95
+ }
96
+ if (!isValidIssuerUrl(decoded.iss)) {
97
+ throw new IQAuthError(
98
+ "CONFIG_INVALID",
99
+ `${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.`
100
+ );
101
+ }
102
+ return { mode: shapeMatch[1], iss: decoded.iss, appId: decoded.appId, tenantId: decoded.tenantId, kid: decoded.kid, raw };
103
+ }
104
+ function isPublishableKeyPayload(value) {
105
+ if (!value || typeof value !== "object") return false;
106
+ const v = value;
107
+ return typeof v.iss === "string" && typeof v.appId === "string" && typeof v.tenantId === "string" && typeof v.kid === "string";
57
108
  }
58
109
 
59
110
  // src/server/handlers.ts
@@ -76,12 +127,7 @@ function shouldClearCookiesOnFailure(policy, status, errorCode) {
76
127
  var ACCESS_TOKEN_TTL_SECONDS = 60 * 15;
77
128
  var REFRESH_TOKEN_TTL_SECONDS = 60 * 60 * 24 * 30;
78
129
  function resolve(config) {
79
- const parsed = parsePublishableKey(config.publishableKey);
80
- if (!parsed) {
81
- throw new Error(
82
- "@iqauth/sdk: invalid publishable key passed to iqAuth helpers (expected pk_test_\u2026 or pk_live_\u2026)"
83
- );
84
- }
130
+ const parsed = assertPublishableKey(config.publishableKey, { context: "@iqauth/sdk helpers" });
85
131
  const inferredIssuer = parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`;
86
132
  return {
87
133
  publishableKey: config.publishableKey,
@@ -252,6 +298,15 @@ async function handleSignout(config, input) {
252
298
  } catch {
253
299
  }
254
300
  }
301
+ if (input.endSsoSession !== false && input.ssoCookieHeader) {
302
+ try {
303
+ await cfg.fetchImpl(`${cfg.issuer}/oidc/sso-logout`, {
304
+ method: "POST",
305
+ headers: { Cookie: input.ssoCookieHeader }
306
+ });
307
+ } catch {
308
+ }
309
+ }
255
310
  return {
256
311
  status: 200,
257
312
  body: { success: true, data: { signedOut: true } },
@@ -3,8 +3,9 @@ import {
3
3
  handleRefresh,
4
4
  handleSignout,
5
5
  serializeCookie
6
- } from "../chunk-JQRTY5MY.mjs";
7
- import "../chunk-5WFR6Y33.mjs";
6
+ } from "../chunk-KGEPDXHU.mjs";
7
+ import "../chunk-WQWBJSSS.mjs";
8
+ import "../chunk-6I6RM4MN.mjs";
8
9
  import "../chunk-Y6FXYEAI.mjs";
9
10
  export {
10
11
  handleCallback,
package/dist/server.d.mts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { b as IQAuthTokenClientConfig, N as ExpressMiddlewareOptions, Q as IQAuthRequestLike, R as IQAuthResponseLike, V as IQAuthNextFunction } from './types-Cxl3bQHt.mjs';
2
- import { I as IQAuthClient } from './client-Dv4v92Mj.mjs';
2
+ import { I as IQAuthClient } from './client-vdh2a9fJ.mjs';
3
3
  export { E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.mjs';
4
- export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-BZmF1llh.mjs';
4
+ export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-A0-dWEMy.mjs';
5
5
  export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie } from './server/handlers.mjs';
6
- import 'jsonwebtoken';
7
6
 
8
7
  declare class ServerIQAuthClient extends IQAuthClient {
9
8
  constructor(config: IQAuthTokenClientConfig);
package/dist/server.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import { b as IQAuthTokenClientConfig, N as ExpressMiddlewareOptions, Q as IQAuthRequestLike, R as IQAuthResponseLike, V as IQAuthNextFunction } from './types-Cxl3bQHt.js';
2
- import { I as IQAuthClient } from './client-DXbHb2ul.js';
2
+ import { I as IQAuthClient } from './client-DTX4hNdS.js';
3
3
  export { E as ErrorCodes, I as IQAuthError } from './errors-CDdl24MP.js';
4
- export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-B4o3P8vK.js';
4
+ export { C as CookieAwareMiddlewareOptions, D as DEFAULT_ACCESS_COOKIE, a as DEFAULT_REFRESH_COOKIE, i as iqAuthMiddleware } from './express-Bo_pJKHN.js';
5
5
  export { HandlerResponse, IQAuthHelperConfig, SetCookieDirective, handleCallback, handleRefresh, handleSignout, serializeCookie } from './server/handlers.js';
6
- import 'jsonwebtoken';
7
6
 
8
7
  declare class ServerIQAuthClient extends IQAuthClient {
9
8
  constructor(config: IQAuthTokenClientConfig);