@civic/auth 0.8.2 → 0.8.3-beta.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 +6 -0
- package/dist/lib/oauth.d.ts +4 -2
- package/dist/lib/oauth.d.ts.map +1 -1
- package/dist/lib/oauth.js +4 -2
- package/dist/lib/oauth.js.map +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.d.ts +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.d.ts.map +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.js.map +1 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts +1 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts.map +1 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.js +3 -0
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.js.map +1 -1
- package/dist/nextjs/routeHandler.d.ts.map +1 -1
- package/dist/nextjs/routeHandler.js +2 -1
- package/dist/nextjs/routeHandler.js.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.d.ts +15 -0
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js +26 -1
- package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
- package/dist/reactjs/hooks/useUser.d.ts +3 -0
- package/dist/reactjs/hooks/useUser.d.ts.map +1 -1
- package/dist/reactjs/hooks/useUser.js +32 -0
- package/dist/reactjs/hooks/useUser.js.map +1 -1
- package/dist/reactjs/providers/CivicAuthContext.d.ts +4 -0
- package/dist/reactjs/providers/CivicAuthContext.d.ts.map +1 -1
- package/dist/reactjs/providers/CivicAuthContext.js +22 -13
- package/dist/reactjs/providers/CivicAuthContext.js.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.d.ts +1 -0
- package/dist/reactjs/providers/CivicAuthProvider.d.ts.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.js +3 -1
- package/dist/reactjs/providers/CivicAuthProvider.js.map +1 -1
- package/dist/server/config.d.ts +47 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/index.d.ts +8 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/login.d.ts +9 -0
- package/dist/server/login.d.ts.map +1 -1
- package/dist/server/login.js +4 -2
- package/dist/server/login.js.map +1 -1
- package/dist/server/refresh.d.ts +1 -1
- package/dist/server/refresh.d.ts.map +1 -1
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/session.d.ts +60 -2
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +216 -5
- package/dist/server/session.js.map +1 -1
- package/dist/server/types/express.d.ts +97 -0
- package/dist/server/types/express.d.ts.map +1 -0
- package/dist/server/types/express.js +2 -0
- package/dist/server/types/express.js.map +1 -0
- package/dist/services/AuthenticationService.d.ts +6 -0
- package/dist/services/AuthenticationService.d.ts.map +1 -1
- package/dist/services/AuthenticationService.js +48 -6
- package/dist/services/AuthenticationService.js.map +1 -1
- package/dist/services/types.d.ts +1 -1
- package/dist/services/types.d.ts.map +1 -1
- package/dist/services/types.js.map +1 -1
- package/dist/shared/components/CivicAuthIframe.d.ts +1 -0
- package/dist/shared/components/CivicAuthIframe.d.ts.map +1 -1
- package/dist/shared/components/CivicAuthIframe.js +4 -4
- package/dist/shared/components/CivicAuthIframe.js.map +1 -1
- package/dist/shared/components/CivicAuthIframeContainer.d.ts +2 -1
- package/dist/shared/components/CivicAuthIframeContainer.d.ts.map +1 -1
- package/dist/shared/components/CivicAuthIframeContainer.js +10 -3
- package/dist/shared/components/CivicAuthIframeContainer.js.map +1 -1
- package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
- package/dist/shared/hooks/useSignIn.js +2 -1
- package/dist/shared/hooks/useSignIn.js.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.d.ts +2 -2
- package/dist/shared/lib/AuthenticationRefresherImpl.d.ts.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.js +3 -0
- package/dist/shared/lib/AuthenticationRefresherImpl.js.map +1 -1
- package/dist/shared/lib/GenericAuthenticationRefresher.d.ts +2 -2
- package/dist/shared/lib/GenericAuthenticationRefresher.d.ts.map +1 -1
- package/dist/shared/lib/GenericAuthenticationRefresher.js.map +1 -1
- package/dist/shared/lib/iframeUtils.d.ts +1 -0
- package/dist/shared/lib/iframeUtils.d.ts.map +1 -1
- package/dist/shared/lib/iframeUtils.js +3 -0
- package/dist/shared/lib/iframeUtils.js.map +1 -1
- package/dist/shared/lib/util.d.ts +7 -0
- package/dist/shared/lib/util.d.ts.map +1 -1
- package/dist/shared/lib/util.js +12 -0
- package/dist/shared/lib/util.js.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.d.ts.map +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts +41 -0
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts.map +1 -0
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js +125 -0
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js.map +1 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts +66 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +296 -10
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/SessionManager.d.ts +31 -3
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +253 -22
- package/dist/vanillajs/auth/SessionManager.js.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.js +31 -18
- package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js +14 -8
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +34 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +139 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +21 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js +52 -2
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +17 -0
- package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.js +1 -0
- package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.d.ts +36 -0
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.js +205 -18
- package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
- package/dist/vanillajs/index.d.ts +2 -0
- package/dist/vanillajs/index.d.ts.map +1 -1
- package/dist/vanillajs/index.js +4 -0
- package/dist/vanillajs/index.js.map +1 -1
- package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -1
- package/dist/vanillajs/ui/LoadingComponents.js +1 -1
- package/dist/vanillajs/ui/LoadingComponents.js.map +1 -1
- package/package.json +7 -2
|
@@ -8,7 +8,7 @@ import { AuthenticationEvents } from "./AuthenticationEvents.js";
|
|
|
8
8
|
import { PopupError } from "../../services/types.js";
|
|
9
9
|
import { getVersion } from "../../shared/index.js";
|
|
10
10
|
import { handleOAuthRedirectPage } from "./handlers/OAuthCallbackHandler.js";
|
|
11
|
-
import { generateOauthLogoutUrl, clearTokens, retrieveTokens, } from "../../shared/lib/util.js";
|
|
11
|
+
import { generateOauthLogoutUrl, clearTokens, retrieveTokens, getBackendEndpoints, } from "../../shared/lib/util.js";
|
|
12
12
|
import { LOGOUT_STATE } from "../../constants.js";
|
|
13
13
|
import { getOauthEndpoints } from "../../lib/oauth.js";
|
|
14
14
|
import { collectAndSendSDKAnalytics } from "../../lib/analytics.js";
|
|
@@ -17,6 +17,7 @@ import { processConfigWithDefaults } from "./config/ConfigProcessor.js";
|
|
|
17
17
|
import { MessageHandler } from "./handlers/MessageHandler.js";
|
|
18
18
|
import { PopupHandler } from "./handlers/PopupHandler.js";
|
|
19
19
|
import { IframeAuthHandler } from "./handlers/IframeAuthHandler.js";
|
|
20
|
+
import { IframeManager } from "../iframe/IframeManager.js";
|
|
20
21
|
/**
|
|
21
22
|
* CivicAuth client for handling OAuth authentication
|
|
22
23
|
*
|
|
@@ -37,6 +38,8 @@ export class CivicAuth {
|
|
|
37
38
|
authProcessTimeoutHandle;
|
|
38
39
|
popupFailureTimeoutHandle;
|
|
39
40
|
hasPopupFailed = false;
|
|
41
|
+
// Backend integration - custom login URL
|
|
42
|
+
loginUrl;
|
|
40
43
|
// Handlers
|
|
41
44
|
messageHandler;
|
|
42
45
|
popupHandler;
|
|
@@ -49,6 +52,8 @@ export class CivicAuth {
|
|
|
49
52
|
// Process config with defaults and validation
|
|
50
53
|
this.config = processConfigWithDefaults(config);
|
|
51
54
|
this.initialDisplayMode = this.config.displayMode;
|
|
55
|
+
// Set loginUrl from config if provided
|
|
56
|
+
this.loginUrl = this.config.loginUrl;
|
|
52
57
|
// Configure logging
|
|
53
58
|
configureLogging(this.config.logging);
|
|
54
59
|
// Initialize logger - always use "vanillajs" as base namespace
|
|
@@ -68,7 +73,7 @@ export class CivicAuth {
|
|
|
68
73
|
// Always initialize events - use provided events or create default instance
|
|
69
74
|
this.events = config.events || new AuthenticationEvents();
|
|
70
75
|
// Always initialize SessionManager
|
|
71
|
-
this.sessionManager = new SessionManager(this.storage, this.events);
|
|
76
|
+
this.sessionManager = new SessionManager(this.storage, this.events, this.config);
|
|
72
77
|
// Initialize handlers
|
|
73
78
|
this.initializeHandlers();
|
|
74
79
|
}
|
|
@@ -82,6 +87,7 @@ export class CivicAuth {
|
|
|
82
87
|
*
|
|
83
88
|
* @example
|
|
84
89
|
* ```typescript
|
|
90
|
+
* // Standard SPA authentication
|
|
85
91
|
* const auth = await CivicAuth.create({
|
|
86
92
|
* clientId: "your-client-id",
|
|
87
93
|
* // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
|
|
@@ -97,6 +103,12 @@ export class CivicAuth {
|
|
|
97
103
|
* success: "Authentication successful!"
|
|
98
104
|
* }
|
|
99
105
|
* });
|
|
106
|
+
*
|
|
107
|
+
* // Backend integration authentication
|
|
108
|
+
* const authWithBackend = await CivicAuth.create({
|
|
109
|
+
* clientId: "your-client-id",
|
|
110
|
+
* loginUrl: "http://example.com/custom-backendurl" // Automatically uses BrowserCookieStorage
|
|
111
|
+
* });
|
|
100
112
|
* ```
|
|
101
113
|
*/
|
|
102
114
|
static async create(config) {
|
|
@@ -128,6 +140,8 @@ export class CivicAuth {
|
|
|
128
140
|
oauthServer: this.config.oauthServerBaseUrl,
|
|
129
141
|
scopes: this.config.scopes,
|
|
130
142
|
endpoints: this.endpoints,
|
|
143
|
+
loginUrl: this.config.loginUrl || this.loginUrl, // Include loginUrl for backend flows
|
|
144
|
+
backendEndpoints: this.config.backendEndpoints, // Pass backend endpoints config
|
|
131
145
|
};
|
|
132
146
|
this.logger.info("🔧 Initializing SessionManager", { authConfig });
|
|
133
147
|
await this.sessionManager.initializeWithAuthConfig(authConfig);
|
|
@@ -148,6 +162,43 @@ export class CivicAuth {
|
|
|
148
162
|
else {
|
|
149
163
|
this.logger.info("🏠 Not a callback page, initialization complete");
|
|
150
164
|
}
|
|
165
|
+
// Automatically preload iframe if enabled and user is not authenticated
|
|
166
|
+
// This runs after both callback processing and normal initialization
|
|
167
|
+
this.logger.info("🔍 Checking auto-preload conditions", {
|
|
168
|
+
preloadIframe: this.config.preloadIframe,
|
|
169
|
+
displayMode: this.config.displayMode,
|
|
170
|
+
});
|
|
171
|
+
if (this.config.preloadIframe && this.config.displayMode === "iframe") {
|
|
172
|
+
try {
|
|
173
|
+
const isAuthenticated = await this.isAuthenticated();
|
|
174
|
+
this.logger.info("🔍 Authentication check for preload", {
|
|
175
|
+
isAuthenticated,
|
|
176
|
+
});
|
|
177
|
+
if (!isAuthenticated) {
|
|
178
|
+
this.logger.info("🔄 Auto-preloading iframe for instant sign-in...");
|
|
179
|
+
await this.preloadAuthentication();
|
|
180
|
+
this.logger.info("✅ Iframe auto-preloaded successfully");
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
this.logger.debug("ℹ️ User already authenticated, skipping preload");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
// Don't fail initialization if preloading fails - graceful degradation
|
|
188
|
+
this.logger.warn("⚠️ Auto-preloading failed (graceful degradation):", {
|
|
189
|
+
error: error instanceof Error ? error.message : String(error),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this.logger.info("ℹ️ Auto-preload conditions not met", {
|
|
195
|
+
preloadIframe: this.config.preloadIframe,
|
|
196
|
+
displayMode: this.config.displayMode,
|
|
197
|
+
reason: !this.config.preloadIframe
|
|
198
|
+
? "preloadIframe is disabled"
|
|
199
|
+
: "displayMode is not iframe",
|
|
200
|
+
});
|
|
201
|
+
}
|
|
151
202
|
}
|
|
152
203
|
catch (error) {
|
|
153
204
|
const errorMessage = error instanceof Error
|
|
@@ -183,11 +234,63 @@ export class CivicAuth {
|
|
|
183
234
|
...handlerConfig,
|
|
184
235
|
messageHandler: this.messageHandler.handleMessage,
|
|
185
236
|
});
|
|
237
|
+
// Set up automatic re-preloading when authentication is cancelled
|
|
238
|
+
this.setupAutoRepreload();
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Set up automatic re-preloading when authentication is cancelled by user
|
|
242
|
+
* This maintains instant sign-in experience for subsequent attempts
|
|
243
|
+
*/
|
|
244
|
+
setupAutoRepreload() {
|
|
245
|
+
this.events.on(AuthEvent.SIGN_IN_ERROR, (event) => {
|
|
246
|
+
// Only re-preload for iframe mode with user cancellation and preload enabled
|
|
247
|
+
if (this.config.displayMode === "iframe" &&
|
|
248
|
+
this.config.preloadIframe &&
|
|
249
|
+
event?.detail === "Authentication cancelled by user") {
|
|
250
|
+
this.logger.debug("🔄 Authentication cancelled, scheduling re-preload for instant subsequent sign-in");
|
|
251
|
+
// Small delay to ensure cleanup is complete
|
|
252
|
+
setTimeout(async () => {
|
|
253
|
+
try {
|
|
254
|
+
await this.preloadAuthentication();
|
|
255
|
+
this.logger.debug("✅ Re-preloaded iframe after cancellation for instant sign-in");
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
// Don't fail silently, but don't crash either - just log the issue
|
|
259
|
+
this.logger.warn("⚠️ Failed to re-preload iframe after cancellation:", {
|
|
260
|
+
error: error instanceof Error ? error.message : String(error),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}, 200); // Small delay to ensure cleanup completion
|
|
264
|
+
}
|
|
265
|
+
});
|
|
186
266
|
}
|
|
187
267
|
/**
|
|
188
268
|
* Builds the authentication URL with PKCE challenge
|
|
189
269
|
*/
|
|
190
270
|
async buildAuthUrl() {
|
|
271
|
+
// If a login URL is set (for backend integration), use that instead
|
|
272
|
+
if (this.loginUrl) {
|
|
273
|
+
this.logger.info("🔗 Using login URL for backend integration", {
|
|
274
|
+
loginUrl: this.loginUrl,
|
|
275
|
+
});
|
|
276
|
+
// Generate state with display mode information for backend integration
|
|
277
|
+
const state = this.config.initialState ||
|
|
278
|
+
generateState({
|
|
279
|
+
displayMode: this.config.displayMode || "iframe",
|
|
280
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
281
|
+
framework: this.config.framework || "vanillajs",
|
|
282
|
+
sdkVersion: getVersion(),
|
|
283
|
+
});
|
|
284
|
+
// Append state as query parameter to loginUrl
|
|
285
|
+
const url = new URL(this.loginUrl, window.location.origin);
|
|
286
|
+
url.searchParams.set("state", state);
|
|
287
|
+
this.logger.info("🔗 Built login URL with display mode state", {
|
|
288
|
+
loginUrl: url.toString(),
|
|
289
|
+
displayMode: this.config.displayMode,
|
|
290
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
291
|
+
});
|
|
292
|
+
return url.toString();
|
|
293
|
+
}
|
|
191
294
|
if (!this.endpoints) {
|
|
192
295
|
throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
193
296
|
}
|
|
@@ -196,6 +299,7 @@ export class CivicAuth {
|
|
|
196
299
|
const state = this.config.initialState ||
|
|
197
300
|
generateState({
|
|
198
301
|
displayMode: this.config.displayMode || "iframe",
|
|
302
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
199
303
|
framework: this.config.framework || "vanillajs",
|
|
200
304
|
sdkVersion: getVersion(),
|
|
201
305
|
});
|
|
@@ -210,6 +314,102 @@ export class CivicAuth {
|
|
|
210
314
|
nonce: this.config.nonce,
|
|
211
315
|
});
|
|
212
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Preloads the authentication iframe for instant sign-in
|
|
319
|
+
* This creates the iframe in the background but keeps it hidden until startAuthentication() is called
|
|
320
|
+
* @throws {CivicAuthError} If preloading fails or is not supported
|
|
321
|
+
* @private - This method is used internally for automatic preloading
|
|
322
|
+
*/
|
|
323
|
+
async preloadAuthentication() {
|
|
324
|
+
this.logger.info("🎬 Preloading authentication for instant sign-in", {
|
|
325
|
+
displayMode: this.config.displayMode,
|
|
326
|
+
userAgent: navigator.userAgent,
|
|
327
|
+
currentUrl: window.location.href,
|
|
328
|
+
});
|
|
329
|
+
if (!this.endpoints) {
|
|
330
|
+
const error = new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
331
|
+
this.logger.error("❌ Endpoints not initialized for preload", {
|
|
332
|
+
error: error.message,
|
|
333
|
+
});
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
// Only support preloading for iframe mode
|
|
337
|
+
if (this.config.displayMode !== "iframe") {
|
|
338
|
+
this.logger.warn("⚠️ Iframe preloading only supported for iframe display mode", {
|
|
339
|
+
displayMode: this.config.displayMode,
|
|
340
|
+
});
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (!this.iframeAuthHandler) {
|
|
344
|
+
const error = new CivicAuthError("Iframe handler not initialized for preloading", CivicAuthErrorCode.INIT_FAILED);
|
|
345
|
+
this.logger.error("❌ Iframe handler not initialized", {
|
|
346
|
+
error: error.message,
|
|
347
|
+
});
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
const fullAuthUrl = await this.buildAuthUrl();
|
|
351
|
+
this.logger.info("🔗 Built authentication URL for preload", {
|
|
352
|
+
url: fullAuthUrl,
|
|
353
|
+
displayMode: this.config.displayMode,
|
|
354
|
+
});
|
|
355
|
+
try {
|
|
356
|
+
await this.iframeAuthHandler.preloadIframe(fullAuthUrl);
|
|
357
|
+
const iframeElement = this.iframeAuthHandler.getIframeElement();
|
|
358
|
+
if (iframeElement) {
|
|
359
|
+
this.messageHandler?.updateIframeElement(iframeElement);
|
|
360
|
+
}
|
|
361
|
+
this.logger.info("✅ Authentication iframe preloaded successfully");
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to preload iframe";
|
|
365
|
+
this.logger.error("❌ Failed to preload authentication iframe", {
|
|
366
|
+
error: errorMessage,
|
|
367
|
+
});
|
|
368
|
+
throw new CivicAuthError(errorMessage, CivicAuthErrorCode.IFRAME_PRELOAD_FAILED);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Check if authentication is preloaded and ready for instant sign-in
|
|
373
|
+
* @returns True if an iframe is preloaded and ready
|
|
374
|
+
*/
|
|
375
|
+
isAuthenticationPreloaded() {
|
|
376
|
+
if (this.config.displayMode !== "iframe" || !this.iframeAuthHandler) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
return this.iframeAuthHandler.hasPreloadedIframe();
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Enable or disable iframe preloading
|
|
383
|
+
* @param enabled Whether to enable iframe preloading
|
|
384
|
+
*/
|
|
385
|
+
setPreloadEnabled(enabled) {
|
|
386
|
+
// Preloading only makes sense for iframe mode
|
|
387
|
+
if (this.config.displayMode !== "iframe") {
|
|
388
|
+
this.logger.debug("🎯 Iframe preloading not applicable for non-iframe mode", {
|
|
389
|
+
displayMode: this.config.displayMode,
|
|
390
|
+
enabled,
|
|
391
|
+
});
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (this.iframeAuthHandler) {
|
|
395
|
+
this.iframeAuthHandler.setPreloadEnabled(enabled);
|
|
396
|
+
this.logger.info("🎯 Iframe preloading", { enabled });
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
this.logger.warn("⚠️ Cannot set preload enabled: iframe handler not initialized");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Check if iframe preloading is enabled
|
|
404
|
+
* @returns True if iframe preloading is enabled
|
|
405
|
+
*/
|
|
406
|
+
getPreloadEnabled() {
|
|
407
|
+
// Preloading only makes sense for iframe mode
|
|
408
|
+
if (this.config.displayMode !== "iframe") {
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
return this.iframeAuthHandler?.getPreloadEnabled() ?? false;
|
|
412
|
+
}
|
|
213
413
|
/**
|
|
214
414
|
* Starts the authentication process
|
|
215
415
|
* @returns A promise that resolves with the authentication result
|
|
@@ -252,6 +452,25 @@ export class CivicAuth {
|
|
|
252
452
|
});
|
|
253
453
|
return this.authPromise;
|
|
254
454
|
}
|
|
455
|
+
async handleBrowserCorsFailsSilently() {
|
|
456
|
+
if (!this.iframeAuthHandler || !this.messageHandler) {
|
|
457
|
+
throw new Error("Iframe handler not initialized");
|
|
458
|
+
}
|
|
459
|
+
const rebuiltUrl = await this.buildAuthUrl();
|
|
460
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
461
|
+
detail: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
|
|
462
|
+
error: {
|
|
463
|
+
type: "browser_cors_fails_silently",
|
|
464
|
+
failedUrl: IframeManager.browserCorsFailsSilentlyUrl,
|
|
465
|
+
suggestion: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
this.iframeAuthHandler.forceShowLoader();
|
|
469
|
+
// use a small delay to ensure the loader is shown
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
window.location.href = rebuiltUrl;
|
|
472
|
+
}, 100);
|
|
473
|
+
}
|
|
255
474
|
/**
|
|
256
475
|
* Handle authentication based on display mode
|
|
257
476
|
*/
|
|
@@ -278,6 +497,9 @@ export class CivicAuth {
|
|
|
278
497
|
if (!this.iframeAuthHandler || !this.messageHandler) {
|
|
279
498
|
throw new Error("Iframe handler not initialized");
|
|
280
499
|
}
|
|
500
|
+
if (IframeManager.browserCorsFailsSilentlyUrl) {
|
|
501
|
+
return this.handleBrowserCorsFailsSilently();
|
|
502
|
+
}
|
|
281
503
|
const iframeElement = await this.iframeAuthHandler.handleIframeAuth(fullAuthUrl);
|
|
282
504
|
this.messageHandler.updateIframeElement(iframeElement);
|
|
283
505
|
break;
|
|
@@ -341,19 +563,15 @@ export class CivicAuth {
|
|
|
341
563
|
});
|
|
342
564
|
return;
|
|
343
565
|
}
|
|
566
|
+
// Set up timeout for other modes
|
|
344
567
|
if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
|
|
345
568
|
this.logger.debug("⏰ Setting up authentication timeout", {
|
|
346
|
-
|
|
569
|
+
timeout: this.config.authProcessTimeout,
|
|
347
570
|
displayMode: this.config.displayMode,
|
|
348
571
|
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
349
572
|
});
|
|
350
573
|
this.authProcessTimeoutHandle = window.setTimeout(() => {
|
|
351
|
-
this.logger.
|
|
352
|
-
displayMode: this.config.displayMode,
|
|
353
|
-
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
354
|
-
currentOrigin: window.location.origin,
|
|
355
|
-
authProcessTimeout: this.config.authProcessTimeout,
|
|
356
|
-
});
|
|
574
|
+
this.logger.info("⏰ Authentication timeout reached");
|
|
357
575
|
this.events.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
358
576
|
detail: "Authentication timed out",
|
|
359
577
|
});
|
|
@@ -599,6 +817,54 @@ export class CivicAuth {
|
|
|
599
817
|
getTokenRefresherState() {
|
|
600
818
|
return this.sessionManager.getTokenRefresherState() || null;
|
|
601
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Set a custom login URL for backend integration.
|
|
822
|
+
* This is useful when integrating with a backend that handles the OAuth flow.
|
|
823
|
+
* Alternatively, you can configure this directly in CivicAuth.create().
|
|
824
|
+
*
|
|
825
|
+
* @param loginUrl - The custom login URL to use (e.g., "http://localhost:3020/auth/login")
|
|
826
|
+
*
|
|
827
|
+
* @example
|
|
828
|
+
* ```typescript
|
|
829
|
+
* // Option 1: Configure in create()
|
|
830
|
+
* const authClient = await CivicAuth.create({
|
|
831
|
+
* clientId: "YOUR_CLIENT_ID",
|
|
832
|
+
* loginUrl: "http://example.com/custom-backendurl"
|
|
833
|
+
* });
|
|
834
|
+
*
|
|
835
|
+
* // Option 2: Set after creation
|
|
836
|
+
* civicAuth.setLoginUrl("http://localhost:3020/auth/login");
|
|
837
|
+
* await civicAuth.startAuthentication();
|
|
838
|
+
* ```
|
|
839
|
+
*/
|
|
840
|
+
setLoginUrl(loginUrl) {
|
|
841
|
+
this.loginUrl = loginUrl;
|
|
842
|
+
// Update MessageHandler to expect messages from the custom origin
|
|
843
|
+
if (this.messageHandler) {
|
|
844
|
+
this.messageHandler.setCustomExpectedOrigin(loginUrl);
|
|
845
|
+
}
|
|
846
|
+
this.logger.info("🔗 Custom login URL set for backend integration", {
|
|
847
|
+
loginUrl: this.loginUrl,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Clear the login URL and return to standard OAuth flow
|
|
852
|
+
*/
|
|
853
|
+
clearLoginUrl() {
|
|
854
|
+
this.loginUrl = undefined;
|
|
855
|
+
// Clear custom expected origin from MessageHandler
|
|
856
|
+
if (this.messageHandler) {
|
|
857
|
+
this.messageHandler.clearCustomExpectedOrigin();
|
|
858
|
+
}
|
|
859
|
+
this.logger.info("🔗 Login URL cleared, returning to standard OAuth flow");
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Get the current login URL
|
|
863
|
+
* @returns The current login URL or undefined if not set
|
|
864
|
+
*/
|
|
865
|
+
getLoginUrl() {
|
|
866
|
+
return this.loginUrl;
|
|
867
|
+
}
|
|
602
868
|
/**
|
|
603
869
|
* Update the iframe display mode
|
|
604
870
|
* @param mode - The display mode to use for the iframe
|
|
@@ -631,7 +897,27 @@ export class CivicAuth {
|
|
|
631
897
|
this.events.emit(AuthEvent.SIGN_OUT_STARTED, {
|
|
632
898
|
detail: "Logout process started",
|
|
633
899
|
});
|
|
634
|
-
//
|
|
900
|
+
// If a loginUrl is configured, redirect to the backend's logout endpoint.
|
|
901
|
+
// The backend will then handle redirecting to the OIDC provider.
|
|
902
|
+
if (this.loginUrl) {
|
|
903
|
+
this.logger.info("🚪 Redirecting to backend for logout in backend-integrated flow");
|
|
904
|
+
const backendUrl = new URL(this.loginUrl).origin;
|
|
905
|
+
const endpoints = getBackendEndpoints(this.config.backendEndpoints);
|
|
906
|
+
const backendLogoutUrl = `${backendUrl}${endpoints.logout}`;
|
|
907
|
+
// Clear local SDK session state before redirecting
|
|
908
|
+
await this.sessionManager.clearSession();
|
|
909
|
+
this.events.emit(AuthEvent.SIGN_OUT_COMPLETE, {
|
|
910
|
+
detail: "Local session cleared, redirecting to backend for logout.",
|
|
911
|
+
});
|
|
912
|
+
// Perform top-level redirect to the backend logout endpoint
|
|
913
|
+
this.logger.info("🌐 Redirecting to backend logout endpoint", {
|
|
914
|
+
url: backendLogoutUrl,
|
|
915
|
+
});
|
|
916
|
+
window.location.href = backendLogoutUrl;
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
// --- Existing SPA redirect-based logout flow ---
|
|
920
|
+
this.logger.info("🚪 Using redirect-based logout for SPA flow");
|
|
635
921
|
const tokens = await retrieveTokens(this.storage);
|
|
636
922
|
if (!tokens?.id_token) {
|
|
637
923
|
this.logger.warn("⚠️ No ID token found, clearing local session only");
|