@iqauth/sdk 2.5.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -0
- package/dist/react.d.mts +23 -1
- package/dist/react.d.ts +23 -1
- package/dist/react.js +41 -8
- package/dist/react.mjs +41 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ The canonical TypeScript SDK for **IQAuthService** — DispositionIQ's multi-ten
|
|
|
17
17
|
- [Install](#install)
|
|
18
18
|
- [Five-line integration](#five-line-integration)
|
|
19
19
|
- [Pick your environment](#pick-your-environment)
|
|
20
|
+
- [What's new in 2.6.1](#whats-new-in-261)
|
|
20
21
|
- [What's new in 2.0.3](#whats-new-in-203)
|
|
21
22
|
- [Browser apps with a backend (recommended)](#browser-apps-with-a-backend-recommended)
|
|
22
23
|
- [Server reference (Express, Fastify, Hono, Next.js)](#server-reference)
|
|
@@ -89,6 +90,13 @@ gap, mirroring Clerk's `<ClerkLoading/>` / `<ClerkLoaded/>`.
|
|
|
89
90
|
Available hooks: `useUser()`, `useSession()`, `useAuth()`, `useOrganization()`. Each returns `{ data, isLoading, error }`.
|
|
90
91
|
Drop-in components: `<SignIn/>`, `<SignUp/>`, `<UserButton/>`, `<UserProfile/>`, `<OrganizationSwitcher/>`, `<AuthCallback/>`.
|
|
91
92
|
|
|
93
|
+
> **Silent SSO is opt-in as of 2.6.1.** `<SignIn/>` always renders the
|
|
94
|
+
> form on first paint by default — even for returning users with an active
|
|
95
|
+
> issuer-side `iq_sso` session. To restore the old auto-resume behavior,
|
|
96
|
+
> add `silentSso` to the provider or a specific `<SignIn/>` instance:
|
|
97
|
+
> `<IQAuthProvider publishableKey={…} silentSso>` or `<SignIn silentSso />`.
|
|
98
|
+
> See "What's new in 2.6.1" below.
|
|
99
|
+
|
|
92
100
|
### Express
|
|
93
101
|
|
|
94
102
|
```ts
|
|
@@ -165,6 +173,55 @@ The SDK is not "one auth model for every environment". The safe pattern depends
|
|
|
165
173
|
|
|
166
174
|
---
|
|
167
175
|
|
|
176
|
+
## What's new in 2.6.1
|
|
177
|
+
|
|
178
|
+
### 1. Silent SSO is now opt-in (default off)
|
|
179
|
+
|
|
180
|
+
Previously, `<SignIn/>` would detect an active issuer-side `iq_sso` session
|
|
181
|
+
on mount and silently redirect through `/oidc/sso-resume` — users never saw
|
|
182
|
+
the form, never clicked anything, and were transparently signed back in.
|
|
183
|
+
That was surprising for embedded use cases and made it impossible to switch
|
|
184
|
+
accounts without reaching for `?prompt=login`. As of 2.6.1, silent SSO is
|
|
185
|
+
**disabled by default** and must be explicitly enabled:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// Provider-wide opt-in (covers every <SignIn/> below it)
|
|
189
|
+
<IQAuthProvider publishableKey={…} silentSso>
|
|
190
|
+
…
|
|
191
|
+
</IQAuthProvider>
|
|
192
|
+
|
|
193
|
+
// Per-instance opt-in (overrides the provider value)
|
|
194
|
+
<SignIn silentSso />
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
When silent SSO is off (default), `<SignIn/>` always renders the form on
|
|
198
|
+
first paint regardless of `iq_sso` cookie state. `prompt="login"` and
|
|
199
|
+
`?prompt=login` continue to work as additional force-form switches.
|
|
200
|
+
|
|
201
|
+
### 2. Embedded card no longer forces full-viewport height
|
|
202
|
+
|
|
203
|
+
The internal `.iqauth-sdk-pane` declared `min-height: 100vh` unconditionally,
|
|
204
|
+
which worked for hosted full-page sign-in but broke when `<SignIn/>` was
|
|
205
|
+
embedded in a card, modal, or sidebar — pushing content below the fold and
|
|
206
|
+
creating large empty areas. The 100vh height now applies only inside the
|
|
207
|
+
wide side-by-side layout (`@container iqauth-sdk (min-width: 768px)`) where
|
|
208
|
+
the hero pane needs height parity. Narrow embeds size naturally to their
|
|
209
|
+
content.
|
|
210
|
+
|
|
211
|
+
### 3. Better error reporting on misconfigured `<SignIn/>`
|
|
212
|
+
|
|
213
|
+
- `useIQAuthSignInContext` now detects HTML responses (CORS preflight, wrong
|
|
214
|
+
`iqAuthBaseUrl`, wrong `appKey`) and prints an actionable `console.error`
|
|
215
|
+
naming the three likely causes — instead of the cryptic
|
|
216
|
+
`Unexpected token '<' in JSON`.
|
|
217
|
+
- The "returnTo not in allowed origins" console error now also lists the
|
|
218
|
+
app's actual `allowedOrigins`, so the diff with the rejected `returnTo`
|
|
219
|
+
is visible at a glance instead of requiring a Network-tab spelunk.
|
|
220
|
+
|
|
221
|
+
For older versions see [`CHANGELOG.md`](./CHANGELOG.md).
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
168
225
|
## What's new in 2.0.3
|
|
169
226
|
|
|
170
227
|
### 1. `serverManagedSession: true` for `SessionManager` / `IQAuthProvider`
|
package/dist/react.d.mts
CHANGED
|
@@ -116,6 +116,13 @@ interface IQAuthContextValue {
|
|
|
116
116
|
roleMapper: ((claims: JwtClaims | null) => string | null) | null;
|
|
117
117
|
/** Task #95 — fully resolved localization bundle (default + override). */
|
|
118
118
|
localization: IQAuthLocaleBundle;
|
|
119
|
+
/**
|
|
120
|
+
* 2.6.1 — Whether `<SignIn/>` is allowed to silently resume an active SSO
|
|
121
|
+
* session and redirect the user without showing the form. Opt-in. Default
|
|
122
|
+
* `false` so consumers always see the sign-in UI on first paint unless they
|
|
123
|
+
* explicitly enable it. Per-instance `<SignIn silentSso>` overrides this.
|
|
124
|
+
*/
|
|
125
|
+
silentSso: boolean;
|
|
119
126
|
}
|
|
120
127
|
interface IQAuthProviderProps {
|
|
121
128
|
publishableKey: string;
|
|
@@ -154,6 +161,14 @@ interface IQAuthProviderProps {
|
|
|
154
161
|
* default `enUS` strings.
|
|
155
162
|
*/
|
|
156
163
|
localization?: IQAuthLocaleBundle | IQAuthLocaleOverride;
|
|
164
|
+
/**
|
|
165
|
+
* 2.6.1 — Opt into silent SSO resume in `<SignIn/>` (default `false`).
|
|
166
|
+
* When `true`, `<SignIn/>` will detect an active issuer-side `iq_sso`
|
|
167
|
+
* session and redirect through `/oidc/sso-resume` without rendering the
|
|
168
|
+
* form. When `false` (default) the form always renders and users must
|
|
169
|
+
* click to continue. Per-instance `<SignIn silentSso>` overrides this.
|
|
170
|
+
*/
|
|
171
|
+
silentSso?: boolean;
|
|
157
172
|
children?: ReactNode;
|
|
158
173
|
}
|
|
159
174
|
/**
|
|
@@ -162,7 +177,7 @@ interface IQAuthProviderProps {
|
|
|
162
177
|
* safe — a single SessionManager instance is created per provider and reused
|
|
163
178
|
* across remounts.
|
|
164
179
|
*/
|
|
165
|
-
declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
|
|
180
|
+
declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, silentSso, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
|
|
166
181
|
/**
|
|
167
182
|
* Task #95 — Returns the active localization bundle resolved from the
|
|
168
183
|
* `localization` prop on `<IQAuthProvider>` (merged on top of `enUS`). Safe
|
|
@@ -578,6 +593,13 @@ interface SignInProps extends Partial<SharedComponentProps> {
|
|
|
578
593
|
prompt?: "login";
|
|
579
594
|
/** F11 — Per-instance appearance overrides; merged on top of provider-level appearance. */
|
|
580
595
|
appearance?: IQAuthAppearance;
|
|
596
|
+
/**
|
|
597
|
+
* 2.6.1 — Per-instance opt-in to silent SSO resume. Overrides the
|
|
598
|
+
* `<IQAuthProvider silentSso>` value when supplied. Default (when both are
|
|
599
|
+
* unset): `false` — the form always renders and the user must click to
|
|
600
|
+
* continue.
|
|
601
|
+
*/
|
|
602
|
+
silentSso?: boolean;
|
|
581
603
|
}
|
|
582
604
|
/**
|
|
583
605
|
* Pure render-decision helper. When this returns `true`, `<SignIn/>` MUST
|
package/dist/react.d.ts
CHANGED
|
@@ -116,6 +116,13 @@ interface IQAuthContextValue {
|
|
|
116
116
|
roleMapper: ((claims: JwtClaims | null) => string | null) | null;
|
|
117
117
|
/** Task #95 — fully resolved localization bundle (default + override). */
|
|
118
118
|
localization: IQAuthLocaleBundle;
|
|
119
|
+
/**
|
|
120
|
+
* 2.6.1 — Whether `<SignIn/>` is allowed to silently resume an active SSO
|
|
121
|
+
* session and redirect the user without showing the form. Opt-in. Default
|
|
122
|
+
* `false` so consumers always see the sign-in UI on first paint unless they
|
|
123
|
+
* explicitly enable it. Per-instance `<SignIn silentSso>` overrides this.
|
|
124
|
+
*/
|
|
125
|
+
silentSso: boolean;
|
|
119
126
|
}
|
|
120
127
|
interface IQAuthProviderProps {
|
|
121
128
|
publishableKey: string;
|
|
@@ -154,6 +161,14 @@ interface IQAuthProviderProps {
|
|
|
154
161
|
* default `enUS` strings.
|
|
155
162
|
*/
|
|
156
163
|
localization?: IQAuthLocaleBundle | IQAuthLocaleOverride;
|
|
164
|
+
/**
|
|
165
|
+
* 2.6.1 — Opt into silent SSO resume in `<SignIn/>` (default `false`).
|
|
166
|
+
* When `true`, `<SignIn/>` will detect an active issuer-side `iq_sso`
|
|
167
|
+
* session and redirect through `/oidc/sso-resume` without rendering the
|
|
168
|
+
* form. When `false` (default) the form always renders and users must
|
|
169
|
+
* click to continue. Per-instance `<SignIn silentSso>` overrides this.
|
|
170
|
+
*/
|
|
171
|
+
silentSso?: boolean;
|
|
157
172
|
children?: ReactNode;
|
|
158
173
|
}
|
|
159
174
|
/**
|
|
@@ -162,7 +177,7 @@ interface IQAuthProviderProps {
|
|
|
162
177
|
* safe — a single SessionManager instance is created per provider and reused
|
|
163
178
|
* across remounts.
|
|
164
179
|
*/
|
|
165
|
-
declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
|
|
180
|
+
declare function IQAuthProvider({ publishableKey, issuer, channelName, proactiveRefresh, manager: externalManager, allowedReturnOrigins, appearance, roleMapper, cookieNames, localization, silentSso, children, }: IQAuthProviderProps): React.FunctionComponentElement<React.ProviderProps<IQAuthContextValue | null>>;
|
|
166
181
|
/**
|
|
167
182
|
* Task #95 — Returns the active localization bundle resolved from the
|
|
168
183
|
* `localization` prop on `<IQAuthProvider>` (merged on top of `enUS`). Safe
|
|
@@ -578,6 +593,13 @@ interface SignInProps extends Partial<SharedComponentProps> {
|
|
|
578
593
|
prompt?: "login";
|
|
579
594
|
/** F11 — Per-instance appearance overrides; merged on top of provider-level appearance. */
|
|
580
595
|
appearance?: IQAuthAppearance;
|
|
596
|
+
/**
|
|
597
|
+
* 2.6.1 — Per-instance opt-in to silent SSO resume. Overrides the
|
|
598
|
+
* `<IQAuthProvider silentSso>` value when supplied. Default (when both are
|
|
599
|
+
* unset): `false` — the form always renders and the user must click to
|
|
600
|
+
* continue.
|
|
601
|
+
*/
|
|
602
|
+
silentSso?: boolean;
|
|
581
603
|
}
|
|
582
604
|
/**
|
|
583
605
|
* Pure render-decision helper. When this returns `true`, `<SignIn/>` MUST
|
package/dist/react.js
CHANGED
|
@@ -1847,6 +1847,7 @@ function IQAuthProvider({
|
|
|
1847
1847
|
roleMapper,
|
|
1848
1848
|
cookieNames,
|
|
1849
1849
|
localization,
|
|
1850
|
+
silentSso,
|
|
1850
1851
|
children
|
|
1851
1852
|
}) {
|
|
1852
1853
|
const managerRef = (0, import_react.useRef)(null);
|
|
@@ -1895,9 +1896,10 @@ function IQAuthProvider({
|
|
|
1895
1896
|
allowedReturnOrigins: allowedReturnOrigins ?? [],
|
|
1896
1897
|
appearance: appearance ?? null,
|
|
1897
1898
|
roleMapper: roleMapper ?? null,
|
|
1898
|
-
localization: resolvedLocalization
|
|
1899
|
+
localization: resolvedLocalization,
|
|
1900
|
+
silentSso: silentSso ?? false
|
|
1899
1901
|
}),
|
|
1900
|
-
[manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization]
|
|
1902
|
+
[manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization, silentSso]
|
|
1901
1903
|
);
|
|
1902
1904
|
return (0, import_react.createElement)(IQAuthContext.Provider, { value }, children);
|
|
1903
1905
|
}
|
|
@@ -2374,7 +2376,19 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
|
|
|
2374
2376
|
let cancelled = false;
|
|
2375
2377
|
setLoading(true);
|
|
2376
2378
|
const url2 = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
|
|
2377
|
-
fetch(url2, { credentials: "include" }).then((r) =>
|
|
2379
|
+
fetch(url2, { credentials: "include" }).then(async (r) => {
|
|
2380
|
+
const contentType = r.headers.get("content-type") || "";
|
|
2381
|
+
if (!r.ok || !contentType.includes("json")) {
|
|
2382
|
+
const bodyPreview = await r.text().then((t2) => t2.slice(0, 160)).catch(() => "");
|
|
2383
|
+
console.error(
|
|
2384
|
+
`[IQAuth] sign-in-context request failed: ${r.status} ${r.statusText} (content-type: ${contentType || "\u2014"}). URL: ${url2}. Common causes: (1) iqAuthBaseUrl points at the wrong host (it should be your IQAuth issuer, e.g. https://auth.dispositioniq.com \u2014 NOT your own app's domain); (2) the app key "${appKey}" is wrong or revoked; (3) CORS preflight is blocked because this origin isn't in the app's allowed-origins list. Response body preview: ${bodyPreview}`
|
|
2385
|
+
);
|
|
2386
|
+
throw new Error(
|
|
2387
|
+
r.status >= 500 ? "Failed to load sign-in context (server error)" : `Failed to load sign-in context (HTTP ${r.status})`
|
|
2388
|
+
);
|
|
2389
|
+
}
|
|
2390
|
+
return r.json();
|
|
2391
|
+
}).then((payload) => {
|
|
2378
2392
|
if (cancelled) return;
|
|
2379
2393
|
if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
|
|
2380
2394
|
setCtx(payload.data);
|
|
@@ -2397,11 +2411,25 @@ var SHELL_CSS = `
|
|
|
2397
2411
|
grid-template-columns: 1fr;
|
|
2398
2412
|
background: var(--brand-bg, #f7f7f6);
|
|
2399
2413
|
color: var(--brand-text, #0f172a);
|
|
2414
|
+
/* Container queries so the two-pane layout responds to the SDK's
|
|
2415
|
+
RENDERED width, not the viewport. This keeps the form usable when
|
|
2416
|
+
a host app embeds <SignIn/> inside a narrower card or sidebar
|
|
2417
|
+
instead of full-screen \u2014 the previous viewport @media query would
|
|
2418
|
+
happily render the side-by-side hero+form into a 200px container
|
|
2419
|
+
and wrap text one character per line. */
|
|
2420
|
+
container-type: inline-size;
|
|
2421
|
+
container-name: iqauth-sdk;
|
|
2400
2422
|
}
|
|
2401
2423
|
.iqauth-sdk-hero { display: none; }
|
|
2402
2424
|
.iqauth-sdk-pane {
|
|
2403
2425
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
2404
|
-
padding:
|
|
2426
|
+
padding: 32px 20px;
|
|
2427
|
+
/* 2.6.1 \u2014 Default to natural height so embedded usage (inside a card,
|
|
2428
|
+
modal, or sidebar) sizes to its content instead of forcing a full
|
|
2429
|
+
viewport. The hosted/full-page layout re-introduces 100vh below the
|
|
2430
|
+
768px container-query threshold for the side-by-side hero variant. */
|
|
2431
|
+
min-height: auto;
|
|
2432
|
+
box-sizing: border-box;
|
|
2405
2433
|
}
|
|
2406
2434
|
.iqauth-sdk-card {
|
|
2407
2435
|
width: 100%; max-width: 460px;
|
|
@@ -2446,8 +2474,11 @@ var SHELL_CSS = `
|
|
|
2446
2474
|
.iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
|
|
2447
2475
|
.iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
|
|
2448
2476
|
|
|
2449
|
-
@
|
|
2477
|
+
@container iqauth-sdk (min-width: 768px) {
|
|
2450
2478
|
.iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
|
2479
|
+
/* 2.6.1 \u2014 Restore 100vh ONLY for the wide side-by-side layout where
|
|
2480
|
+
hero+pane need height parity. Embedded narrow uses keep natural height. */
|
|
2481
|
+
.iqauth-sdk-pane { padding: 48px 24px; min-height: 100vh; }
|
|
2451
2482
|
.iqauth-sdk-hero {
|
|
2452
2483
|
display: flex; flex-direction: column; justify-content: space-between;
|
|
2453
2484
|
padding: clamp(32px, 4vw, 56px); color: #ffffff;
|
|
@@ -2467,7 +2498,7 @@ var SHELL_CSS = `
|
|
|
2467
2498
|
.iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
|
|
2468
2499
|
.iqauth-sdk-mobile-brand { display: none; }
|
|
2469
2500
|
}
|
|
2470
|
-
@
|
|
2501
|
+
@container iqauth-sdk (min-width: 1280px) {
|
|
2471
2502
|
.iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
|
|
2472
2503
|
}
|
|
2473
2504
|
`;
|
|
@@ -2773,7 +2804,8 @@ function SignIn(props) {
|
|
|
2773
2804
|
const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
|
|
2774
2805
|
const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
|
|
2775
2806
|
const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
|
|
2776
|
-
const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
|
|
2807
|
+
const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
|
|
2808
|
+
const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
|
|
2777
2809
|
const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
|
|
2778
2810
|
if (!iqAuthBaseUrl || !appKey) {
|
|
2779
2811
|
console.error(
|
|
@@ -2803,6 +2835,7 @@ function SignIn(props) {
|
|
|
2803
2835
|
const [forcePrompt, setForcePrompt] = (0, import_react.useState)(false);
|
|
2804
2836
|
const effectivePrompt = (0, import_react.useMemo)(() => {
|
|
2805
2837
|
if (prompt === "login" || forcePrompt) return "login";
|
|
2838
|
+
if (!silentSsoEnabled) return "login";
|
|
2806
2839
|
if (typeof window !== "undefined") {
|
|
2807
2840
|
try {
|
|
2808
2841
|
if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
|
|
@@ -2810,7 +2843,7 @@ function SignIn(props) {
|
|
|
2810
2843
|
}
|
|
2811
2844
|
}
|
|
2812
2845
|
return void 0;
|
|
2813
|
-
}, [prompt, forcePrompt]);
|
|
2846
|
+
}, [prompt, forcePrompt, silentSsoEnabled]);
|
|
2814
2847
|
const oidcPayload = () => ({
|
|
2815
2848
|
client_id: ctx?.app.defaultClientId,
|
|
2816
2849
|
redirect_uri: returnTo,
|
package/dist/react.mjs
CHANGED
|
@@ -102,6 +102,7 @@ function IQAuthProvider({
|
|
|
102
102
|
roleMapper,
|
|
103
103
|
cookieNames,
|
|
104
104
|
localization,
|
|
105
|
+
silentSso,
|
|
105
106
|
children
|
|
106
107
|
}) {
|
|
107
108
|
const managerRef = useRef(null);
|
|
@@ -150,9 +151,10 @@ function IQAuthProvider({
|
|
|
150
151
|
allowedReturnOrigins: allowedReturnOrigins ?? [],
|
|
151
152
|
appearance: appearance ?? null,
|
|
152
153
|
roleMapper: roleMapper ?? null,
|
|
153
|
-
localization: resolvedLocalization
|
|
154
|
+
localization: resolvedLocalization,
|
|
155
|
+
silentSso: silentSso ?? false
|
|
154
156
|
}),
|
|
155
|
-
[manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization]
|
|
157
|
+
[manager, snapshot, allowedReturnOrigins, appearance, roleMapper, resolvedLocalization, silentSso]
|
|
156
158
|
);
|
|
157
159
|
return createElement(IQAuthContext.Provider, { value }, children);
|
|
158
160
|
}
|
|
@@ -629,7 +631,19 @@ function useIQAuthSignInContext(iqAuthBaseUrl, appKey, returnTo) {
|
|
|
629
631
|
let cancelled = false;
|
|
630
632
|
setLoading(true);
|
|
631
633
|
const url = `${iqAuthBaseUrl.replace(/\/$/, "")}/api/public/apps/${encodeURIComponent(appKey)}/sign-in-context?return_to=${encodeURIComponent(returnTo)}`;
|
|
632
|
-
fetch(url, { credentials: "include" }).then((r) =>
|
|
634
|
+
fetch(url, { credentials: "include" }).then(async (r) => {
|
|
635
|
+
const contentType = r.headers.get("content-type") || "";
|
|
636
|
+
if (!r.ok || !contentType.includes("json")) {
|
|
637
|
+
const bodyPreview = await r.text().then((t2) => t2.slice(0, 160)).catch(() => "");
|
|
638
|
+
console.error(
|
|
639
|
+
`[IQAuth] sign-in-context request failed: ${r.status} ${r.statusText} (content-type: ${contentType || "\u2014"}). URL: ${url}. Common causes: (1) iqAuthBaseUrl points at the wrong host (it should be your IQAuth issuer, e.g. https://auth.dispositioniq.com \u2014 NOT your own app's domain); (2) the app key "${appKey}" is wrong or revoked; (3) CORS preflight is blocked because this origin isn't in the app's allowed-origins list. Response body preview: ${bodyPreview}`
|
|
640
|
+
);
|
|
641
|
+
throw new Error(
|
|
642
|
+
r.status >= 500 ? "Failed to load sign-in context (server error)" : `Failed to load sign-in context (HTTP ${r.status})`
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
return r.json();
|
|
646
|
+
}).then((payload) => {
|
|
633
647
|
if (cancelled) return;
|
|
634
648
|
if (payload?.success === false) throw new Error(payload?.error?.message || "Failed to load sign-in context");
|
|
635
649
|
setCtx(payload.data);
|
|
@@ -652,11 +666,25 @@ var SHELL_CSS = `
|
|
|
652
666
|
grid-template-columns: 1fr;
|
|
653
667
|
background: var(--brand-bg, #f7f7f6);
|
|
654
668
|
color: var(--brand-text, #0f172a);
|
|
669
|
+
/* Container queries so the two-pane layout responds to the SDK's
|
|
670
|
+
RENDERED width, not the viewport. This keeps the form usable when
|
|
671
|
+
a host app embeds <SignIn/> inside a narrower card or sidebar
|
|
672
|
+
instead of full-screen \u2014 the previous viewport @media query would
|
|
673
|
+
happily render the side-by-side hero+form into a 200px container
|
|
674
|
+
and wrap text one character per line. */
|
|
675
|
+
container-type: inline-size;
|
|
676
|
+
container-name: iqauth-sdk;
|
|
655
677
|
}
|
|
656
678
|
.iqauth-sdk-hero { display: none; }
|
|
657
679
|
.iqauth-sdk-pane {
|
|
658
680
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
659
|
-
padding:
|
|
681
|
+
padding: 32px 20px;
|
|
682
|
+
/* 2.6.1 \u2014 Default to natural height so embedded usage (inside a card,
|
|
683
|
+
modal, or sidebar) sizes to its content instead of forcing a full
|
|
684
|
+
viewport. The hosted/full-page layout re-introduces 100vh below the
|
|
685
|
+
768px container-query threshold for the side-by-side hero variant. */
|
|
686
|
+
min-height: auto;
|
|
687
|
+
box-sizing: border-box;
|
|
660
688
|
}
|
|
661
689
|
.iqauth-sdk-card {
|
|
662
690
|
width: 100%; max-width: 460px;
|
|
@@ -701,8 +729,11 @@ var SHELL_CSS = `
|
|
|
701
729
|
.iqauth-sdk-google-btn:hover { background: #f8fafc; border-color: rgba(15,23,42,0.28); }
|
|
702
730
|
.iqauth-sdk-google-btn[disabled] { opacity: 0.6; cursor: not-allowed; }
|
|
703
731
|
|
|
704
|
-
@
|
|
732
|
+
@container iqauth-sdk (min-width: 768px) {
|
|
705
733
|
.iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
|
|
734
|
+
/* 2.6.1 \u2014 Restore 100vh ONLY for the wide side-by-side layout where
|
|
735
|
+
hero+pane need height parity. Embedded narrow uses keep natural height. */
|
|
736
|
+
.iqauth-sdk-pane { padding: 48px 24px; min-height: 100vh; }
|
|
706
737
|
.iqauth-sdk-hero {
|
|
707
738
|
display: flex; flex-direction: column; justify-content: space-between;
|
|
708
739
|
padding: clamp(32px, 4vw, 56px); color: #ffffff;
|
|
@@ -722,7 +753,7 @@ var SHELL_CSS = `
|
|
|
722
753
|
.iqauth-sdk-hero-foot { font-size: 12px; opacity: 0.7; }
|
|
723
754
|
.iqauth-sdk-mobile-brand { display: none; }
|
|
724
755
|
}
|
|
725
|
-
@
|
|
756
|
+
@container iqauth-sdk (min-width: 1280px) {
|
|
726
757
|
.iqauth-sdk-shell:not([data-layout="centered_card"]):not([data-layout="full_bleed"]) { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
|
|
727
758
|
}
|
|
728
759
|
`;
|
|
@@ -1028,7 +1059,8 @@ function SignIn(props) {
|
|
|
1028
1059
|
const iqAuthBaseUrl = props.iqAuthBaseUrl ?? providerCtx?.manager.issuerUrl ?? "";
|
|
1029
1060
|
const appKey = props.appKey ?? providerCtx?.manager.appKey ?? "";
|
|
1030
1061
|
const returnTo = props.returnTo ?? (typeof window !== "undefined" ? `${window.location.origin}/api/iqauth/callback` : "");
|
|
1031
|
-
const { onRedirect, className, prompt, appearance: instanceAppearance } = props;
|
|
1062
|
+
const { onRedirect, className, prompt, appearance: instanceAppearance, silentSso: instanceSilentSso } = props;
|
|
1063
|
+
const silentSsoEnabled = instanceSilentSso ?? providerCtx?.silentSso ?? false;
|
|
1032
1064
|
const appearance = instanceAppearance && providerCtx?.appearance ? { elements: { ...providerCtx.appearance.elements, ...instanceAppearance.elements } } : instanceAppearance ?? providerCtx?.appearance ?? null;
|
|
1033
1065
|
if (!iqAuthBaseUrl || !appKey) {
|
|
1034
1066
|
console.error(
|
|
@@ -1058,6 +1090,7 @@ function SignIn(props) {
|
|
|
1058
1090
|
const [forcePrompt, setForcePrompt] = useState(false);
|
|
1059
1091
|
const effectivePrompt = useMemo(() => {
|
|
1060
1092
|
if (prompt === "login" || forcePrompt) return "login";
|
|
1093
|
+
if (!silentSsoEnabled) return "login";
|
|
1061
1094
|
if (typeof window !== "undefined") {
|
|
1062
1095
|
try {
|
|
1063
1096
|
if (new URLSearchParams(window.location.search).get("prompt") === "login") return "login";
|
|
@@ -1065,7 +1098,7 @@ function SignIn(props) {
|
|
|
1065
1098
|
}
|
|
1066
1099
|
}
|
|
1067
1100
|
return void 0;
|
|
1068
|
-
}, [prompt, forcePrompt]);
|
|
1101
|
+
}, [prompt, forcePrompt, silentSsoEnabled]);
|
|
1069
1102
|
const oidcPayload = () => ({
|
|
1070
1103
|
client_id: ctx?.app.defaultClientId,
|
|
1071
1104
|
redirect_uri: returnTo,
|
package/package.json
CHANGED