@luminaryworks/auth-react 0.3.1 → 0.4.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 CHANGED
@@ -1,64 +1,97 @@
1
- # `@luminaryworks/auth-react`
2
-
3
- LuminaryWorks unified OIDC SPA client + **AuthGate** (401 pause / single-flight reauth).
4
-
5
- ## AuthGate
6
-
7
- ```ts
8
- import { authGate, withAuthGateRetry, AuthGateProvider, ReauthOverlay } from "@luminaryworks/auth-react";
9
-
10
- authGate.configure({
11
- tryRefresh: async () => {
12
- /* refresh product JWT; return true if ok */
13
- return false;
14
- },
15
- });
16
-
17
- // In API client:
18
- await withAuthGateRetry(() => fetch(...), {
19
- isUnauthorized: (e) => e.status === 401,
20
- });
21
- ```
22
-
23
- On first 401: try refresh → else open reauth UI (overlay + OIDC **popup**, not IdP iframe) → retry queued callers.
24
-
25
- ## Headless login panel
26
-
27
- Branded panel with **social buttons** (auto-loaded from IdP `socialSignInConnectorTargets` — google / github / x / …) plus **unified account** password. Social uses `direct_sign_in=social:<target>`.
28
-
29
- Styles ship as **CSS Modules (SCSS)**. The built bundle auto-injects panel CSS in the browser. Optional explicit import (SSR / style control):
30
-
31
- ```ts
32
- import "@luminaryworks/auth-react/style.css";
33
- ```
34
-
35
- Override layout via `className` / `style` on the root.
36
-
37
- ```tsx
38
- // End-user product login (social on by default)
39
- <HeadlessLoginPanel
40
- config={idpConfig}
41
- productName="DataLuminary"
42
- mode="redirect"
43
- // socialProviders: "auto" | ["google","github"] | []
44
- />
45
-
46
- // Admin / internal console — hide Experience social connectors
47
- <HeadlessLoginPanel
48
- config={idpConfig}
49
- productName="DataLuminary Admin"
50
- mode="redirect"
51
- showSocialConnectors={false}
52
- />
53
- ```
54
-
55
- | Prop | Default | Notes |
56
- |------|---------|--------|
57
- | `showSocialConnectors` | `true` | `false` skips fetch and hides divider + social buttons |
58
- | `socialProviders` | `"auto"` | allowlist, or `[]` (same effect as `showSocialConnectors={false}`) |
59
-
60
- IdP hosted `/sign-in` social row layout: `node scripts/apply-branding.mjs` (customCss wrap). Enable connectors: `ensure-sign-in-experience.mjs` + `verify-social-direct-signin.mjs`.
61
-
62
- ## Popup callback
63
-
64
- Callback route must detect popup windows and call `handleSignInPopupCallback` (do not SSO-exchange inside the popup).
1
+ # `@luminaryworks/auth-react`
2
+
3
+ LuminaryWorks unified OIDC SPA client + **AuthGate** (401 pause / single-flight reauth).
4
+
5
+ ## AuthGate
6
+
7
+ ```ts
8
+ import { authGate, withAuthGateRetry, AuthGateProvider, ReauthOverlay } from "@luminaryworks/auth-react";
9
+
10
+ authGate.configure({
11
+ tryRefresh: async () => {
12
+ /* refresh product JWT; return true if ok */
13
+ return false;
14
+ },
15
+ });
16
+
17
+ // In API client:
18
+ await withAuthGateRetry(() => fetch(...), {
19
+ isUnauthorized: (e) => e.status === 401,
20
+ });
21
+ ```
22
+
23
+ On first 401: try refresh → else open reauth UI (overlay + OIDC **popup**, not IdP iframe) → retry queued callers.
24
+
25
+ ## Headless login panel
26
+
27
+ Branded panel with **social buttons** (auto-loaded from IdP Experience `socialConnectors` — google / github / x / …) plus **unified account** password. The default `LogtoExperienceAdapter` keeps Logto's `direct_sign_in=social:<target>` parameter inside provider-specific code. Buttons are hidden when the IdP has no social connectors (do not invent Google/GitHub — that dumped users on Logto `/sign-in`).
28
+
29
+ Styles ship as **CSS Modules (SCSS)**. The built bundle auto-injects panel CSS in the browser. Optional explicit import (SSR / style control):
30
+
31
+ ```ts
32
+ import "@luminaryworks/auth-react/style.css";
33
+ ```
34
+
35
+ Override layout via `className` / `style` on the root.
36
+
37
+ ```tsx
38
+ // End-user product login (social on by default)
39
+ <HeadlessLoginPanel
40
+ config={idpConfig}
41
+ productName="DataLuminary"
42
+ mode="redirect"
43
+ // socialProviders: "auto" | ["google","github"] | []
44
+ />
45
+
46
+ // Admin / internal console — hide Experience social connectors
47
+ <HeadlessLoginPanel
48
+ config={idpConfig}
49
+ productName="DataLuminary Admin"
50
+ mode="redirect"
51
+ showSocialConnectors={false}
52
+ />
53
+ ```
54
+
55
+ | Prop | Default | Notes |
56
+ |------|---------|--------|
57
+ | `showSocialConnectors` | `true` | `false` skips fetch and hides divider + social buttons |
58
+ | `socialProviders` | `"auto"` | allowlist, or `[]` (same effect as `showSocialConnectors={false}`) |
59
+ | `experienceAdapter` | `LogtoExperienceAdapter` | optional custom `LoginExperienceAdapter` |
60
+
61
+ IdP hosted `/sign-in` social row layout: `node scripts/apply-branding.mjs` (customCss wrap). Enable connectors: `ensure-sign-in-experience.mjs` + `verify-social-direct-signin.mjs`.
62
+
63
+ ### Login experience adapters
64
+
65
+ `HeadlessLoginPanel` delegates non-standard password and social-connector flows
66
+ to `LoginExperienceAdapter`; standard OIDC authorization code + PKCE remains in
67
+ the OIDC client. Adapter methods are optional and are used only when both the
68
+ matching capability and method are present. Without password support, the panel
69
+ renders `labels.submitSso` and starts a standard hosted OIDC flow. Logto is the
70
+ only built-in provider:
71
+
72
+ ```ts
73
+ import {
74
+ createLoginExperienceAdapter,
75
+ LogtoExperienceAdapter,
76
+ resolveLoginExperienceAdapter,
77
+ type LoginExperienceAdapter,
78
+ type LoginExperienceCapability,
79
+ } from "@luminaryworks/auth-react";
80
+
81
+ const logto = new LogtoExperienceAdapter();
82
+ const sameDefault = createLoginExperienceAdapter("logto");
83
+ const resolved = resolveLoginExperienceAdapter(logto);
84
+
85
+ // Hosted-only enterprise IdP: no Experience API methods are required.
86
+ const hostedOnly = {
87
+ provider: "enterprise",
88
+ capabilities: [],
89
+ } satisfies LoginExperienceAdapter;
90
+ ```
91
+
92
+ Existing `experiencePasswordSignIn` and `fetchSocialConnectors` imports remain
93
+ available as compatibility functions backed by the default Logto adapter.
94
+
95
+ ## Popup callback
96
+
97
+ Callback route must detect popup windows and call `handleSignInPopupCallback` (do not SSO-exchange inside the popup).
package/dist/index.d.ts CHANGED
@@ -45,9 +45,12 @@ declare function createUserManager(config: LuminaryIdpConfig): UserManager;
45
45
  declare function resetUserManager(): void;
46
46
  interface SignInOptions {
47
47
  returnUrl?: string;
48
+ /** Provider-specific authorize parameters supplied by an experience adapter. */
49
+ extraQueryParams?: Record<string, string>;
48
50
  /**
49
51
  * Logto direct sign-in, e.g. `social:google` / `social:github` / `sso:<connectorId>`.
50
52
  * Skips the hosted password page and opens the provider immediately.
53
+ * @deprecated Prefer a LoginExperienceAdapter, which supplies extraQueryParams.
51
54
  */
52
55
  directSignIn?: string;
53
56
  }
@@ -83,10 +86,17 @@ declare function clearStoredSession(config: LuminaryIdpConfig): void;
83
86
  declare function getCurrentUser(config: LuminaryIdpConfig): Promise<LuminaryAuthSession | null>;
84
87
 
85
88
  /**
86
- * Logto Experience API (Headless) client.
87
- * Prefer same-origin Experience base (SPA proxies /api/experience) so cookies work on HTTP localhost.
88
- * @see https://docs.logto.io/docs/recipes/customize-token-claims (Experience API recipes)
89
+ * Capabilities exposed by a headless login-experience provider.
90
+ *
91
+ * Consumers can use these flags to avoid assuming that every provider supports
92
+ * Logto's password, connector-discovery, or direct social-login flows.
89
93
  */
94
+ declare const LOGIN_EXPERIENCE_CAPABILITIES: {
95
+ readonly passwordSignIn: "password-sign-in";
96
+ readonly socialConnectors: "social-connectors";
97
+ readonly socialDirectSignIn: "social-direct-sign-in";
98
+ };
99
+ type LoginExperienceCapability = (typeof LOGIN_EXPERIENCE_CAPABILITIES)[keyof typeof LOGIN_EXPERIENCE_CAPABILITIES];
90
100
  type ExperienceIdentifierType = "email" | "username" | "phone";
91
101
  interface ExperiencePasswordSignInInput {
92
102
  /** Experience API origin (no trailing slash). Prefer SPA origin when proxied. */
@@ -108,39 +118,69 @@ interface ExperiencePasswordSignInResult {
108
118
  redirectTo?: string;
109
119
  raw?: unknown;
110
120
  }
111
- /**
112
- * Force authorize onto the Experience/SPA origin so Set-Cookie lands on the same
113
- * host as subsequent `/api/experience` calls (dev proxy strips Domain).
114
- */
115
- declare function sameOriginAuthorizeUrl(authorizeUrl: string, apiBase: string): string;
116
- /**
117
- * Headless password sign-in via Logto Experience API.
118
- * Requires an OIDC interaction cookie (see {@link bootstrapOidcInteraction}) and
119
- * same-site Experience calls (SPA proxy or Auth Gateway on the SPA origin).
120
- *
121
- * On success, follow {@link ExperiencePasswordSignInResult.redirectTo} in the same
122
- * window — that URL completes the PKCE round-trip started by createSigninRequest.
123
- * Do not start a second authorize (that shows Logto `/sign-in` again).
124
- *
125
- * Tries the guessed identifier type first, then the alternate email/username so
126
- * users can sign in with either when both methods are enabled on the IdP.
127
- */
128
- declare function experiencePasswordSignIn(input: ExperiencePasswordSignInInput): Promise<ExperiencePasswordSignInResult>;
129
121
  interface ExperienceSocialConnector {
130
122
  id: string;
131
123
  target: string;
132
124
  name: string;
133
125
  logo?: string;
134
126
  }
135
- /**
136
- * Public Logto sign-in experience (no auth). Returns enabled social connectors
137
- * in SIE order — use for Headless social buttons (google / github / x / …).
138
- */
139
- declare function fetchSocialConnectors(input: {
127
+ interface FetchSocialConnectorsInput {
140
128
  /** Same origin as Experience / IdP (e.g. SPA proxy or http://localhost:3001). */
141
129
  apiBase: string;
142
130
  appId?: string;
143
- }): Promise<ExperienceSocialConnector[]>;
131
+ }
132
+ interface SocialSignInRequest {
133
+ /** Provider-specific authorize parameters, passed through by the OIDC client. */
134
+ extraQueryParams?: Record<string, string>;
135
+ }
136
+ /**
137
+ * Provider boundary for non-standard login-experience APIs.
138
+ *
139
+ * Standard OIDC redirect/popup and PKCE handling intentionally remain in the
140
+ * OIDC client. Only provider-specific Experience operations belong here.
141
+ */
142
+ interface LoginExperienceAdapter {
143
+ readonly provider: string;
144
+ readonly capabilities: readonly LoginExperienceCapability[];
145
+ experiencePasswordSignIn?(input: ExperiencePasswordSignInInput): Promise<ExperiencePasswordSignInResult>;
146
+ fetchSocialConnectors?(input: FetchSocialConnectorsInput): Promise<ExperienceSocialConnector[]>;
147
+ createSocialSignInRequest?(target: string): SocialSignInRequest;
148
+ }
149
+ type LoginExperienceProvider = "logto";
150
+ /**
151
+ * Factory for built-in providers. Logto is intentionally the only built-in
152
+ * adapter; products can supply their own adapter without shipping fake stubs.
153
+ */
154
+ declare function createLoginExperienceAdapter(provider?: LoginExperienceProvider): LoginExperienceAdapter;
155
+ /** Resolve an explicit product adapter, or the backward-compatible Logto default. */
156
+ declare function resolveLoginExperienceAdapter(adapter?: LoginExperienceAdapter | null): LoginExperienceAdapter;
157
+
158
+ /**
159
+ * Logto Experience API (Headless) adapter.
160
+ * Prefer same-origin Experience base (SPA proxies /api/experience) so cookies
161
+ * work on HTTP localhost.
162
+ */
163
+
164
+ /**
165
+ * Force authorize onto the Experience/SPA origin so Set-Cookie lands on the
166
+ * same host as subsequent `/api/experience` calls (dev proxy strips Domain).
167
+ */
168
+ declare function sameOriginAuthorizeUrl(authorizeUrl: string, apiBase: string): string;
169
+ /** Build Logto's provider-specific direct sign-in authorize parameter. */
170
+ declare function createLogtoDirectSignInRequest(directSignIn: string): SocialSignInRequest;
171
+ /** Built-in adapter for Logto's non-standard Experience API. */
172
+ declare class LogtoExperienceAdapter implements LoginExperienceAdapter {
173
+ readonly provider = "logto";
174
+ readonly capabilities: readonly LoginExperienceCapability[];
175
+ experiencePasswordSignIn(input: ExperiencePasswordSignInInput): Promise<ExperiencePasswordSignInResult>;
176
+ fetchSocialConnectors(input: FetchSocialConnectorsInput): Promise<ExperienceSocialConnector[]>;
177
+ createSocialSignInRequest(target: string): SocialSignInRequest;
178
+ }
179
+
180
+ /** @deprecated Use a LoginExperienceAdapter instance. */
181
+ declare function experiencePasswordSignIn(input: ExperiencePasswordSignInInput): Promise<ExperiencePasswordSignInResult>;
182
+ /** @deprecated Use a LoginExperienceAdapter instance. */
183
+ declare function fetchSocialConnectors(input: FetchSocialConnectorsInput): Promise<ExperienceSocialConnector[]>;
144
184
 
145
185
  /**
146
186
  * AuthGate — single-flight 401 recovery with optional token refresh + reauth UI.
@@ -235,7 +275,7 @@ interface HeadlessLoginLabels {
235
275
  identifierPlaceholder?: string;
236
276
  passwordPlaceholder?: string;
237
277
  submitPassword?: string;
238
- /** @deprecated Use social buttons; kept for backward-compatible label overrides. */
278
+ /** Hosted OIDC button shown when the adapter has no password capability. */
239
279
  submitSso?: string;
240
280
  submitGoogle?: string;
241
281
  submitGithub?: string;
@@ -248,6 +288,8 @@ interface HeadlessLoginLabels {
248
288
  }
249
289
  interface HeadlessLoginPanelProps {
250
290
  config: Partial<LuminaryIdpConfig>;
291
+ /** Login-experience provider. Defaults to the built-in Logto adapter. */
292
+ experienceAdapter?: LoginExperienceAdapter;
251
293
  /** Product display name shown as brand signal */
252
294
  productName: string;
253
295
  logoSrc?: string;
@@ -290,7 +332,7 @@ interface HeadlessLoginPanelProps {
290
332
  }
291
333
  /** Shared default accent for product login panels. */
292
334
  declare const DEFAULT_LOGIN_THEME_COLOR = "#3a84ff";
293
- declare function HeadlessLoginPanel({ config, productName, logoSrc, labels: labelsProp, returnUrl, mode, showSocialConnectors, socialProviders, showCancel, onCancel, onOidcSession, onExperienceRedirect, footer, className, style, themeColor, }: HeadlessLoginPanelProps): react.JSX.Element;
335
+ declare function HeadlessLoginPanel({ config, experienceAdapter: experienceAdapterProp, productName, logoSrc, labels: labelsProp, returnUrl, mode, showSocialConnectors, socialProviders, showCancel, onCancel, onOidcSession, onExperienceRedirect, footer, className, style, themeColor, }: HeadlessLoginPanelProps): react.JSX.Element;
294
336
 
295
337
  interface ReauthOverlayProps {
296
338
  config: Partial<LuminaryIdpConfig>;
@@ -353,4 +395,4 @@ declare function createPostLoginPathHelpers(options: PostLoginPathOptions): {
353
395
  isUnsafeReturnPath: (path: string) => boolean;
354
396
  };
355
397
 
356
- export { type AuthGateConfig, type AuthGatePhase, AuthGateProvider, type AuthGateProviderProps, type AuthGateSnapshot, DEFAULT_LOGIN_THEME_COLOR, type ExperienceIdentifierType, type ExperiencePasswordSignInInput, type ExperiencePasswordSignInResult, type ExperienceSocialConnector, type HeadlessLoginLabels, HeadlessLoginPanel, type HeadlessLoginPanelProps, LuminaryAuthProvider, type LuminaryAuthProviderProps, type LuminaryAuthSession, type LuminaryIdpConfig, type PostLoginPathOptions, ReauthOverlay, type ReauthOverlayProps, type SignInOptions, type SocialProviderTarget, type UnauthorizedDisposition, authGate, clearStoredSession, createPostLoginPathHelpers, createUserManager, experiencePasswordSignIn, fetchSocialConnectors, getCurrentUser, getStoredAccessToken, handleSignInCallback, handleSignInPopupCallback, isIdpConfigured, isOidcPopupWindow, prepareSignInRequestUrl, readIdpConfigFromEnv, resetUserManager, resolveExperienceApiBase, resolveIssuer, sameOriginAuthorizeUrl, signInPopup, signInRedirect, signOutRedirect, useAuthGate, useAuthGateOptional, useLuminaryAuth, withAuthGateRetry };
398
+ export { type AuthGateConfig, type AuthGatePhase, AuthGateProvider, type AuthGateProviderProps, type AuthGateSnapshot, DEFAULT_LOGIN_THEME_COLOR, type ExperienceIdentifierType, type ExperiencePasswordSignInInput, type ExperiencePasswordSignInResult, type ExperienceSocialConnector, type FetchSocialConnectorsInput, type HeadlessLoginLabels, HeadlessLoginPanel, type HeadlessLoginPanelProps, LOGIN_EXPERIENCE_CAPABILITIES, type LoginExperienceAdapter, type LoginExperienceCapability, type LoginExperienceProvider, LogtoExperienceAdapter, LuminaryAuthProvider, type LuminaryAuthProviderProps, type LuminaryAuthSession, type LuminaryIdpConfig, type PostLoginPathOptions, ReauthOverlay, type ReauthOverlayProps, type SignInOptions, type SocialProviderTarget, type SocialSignInRequest, type UnauthorizedDisposition, authGate, clearStoredSession, createLoginExperienceAdapter, createLogtoDirectSignInRequest, createPostLoginPathHelpers, createUserManager, experiencePasswordSignIn, fetchSocialConnectors, getCurrentUser, getStoredAccessToken, handleSignInCallback, handleSignInPopupCallback, isIdpConfigured, isOidcPopupWindow, prepareSignInRequestUrl, readIdpConfigFromEnv, resetUserManager, resolveExperienceApiBase, resolveIssuer, resolveLoginExperienceAdapter, sameOriginAuthorizeUrl, signInPopup, signInRedirect, signOutRedirect, useAuthGate, useAuthGateOptional, useLuminaryAuth, withAuthGateRetry };