@civic/auth 0.8.2 → 0.9.0-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/CHANGELOG.md +5 -0
- package/README.md +9 -3
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- 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/config.d.ts +3 -0
- package/dist/nextjs/config.d.ts.map +1 -1
- package/dist/nextjs/config.js +3 -0
- package/dist/nextjs/config.js.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.js +1 -1
- package/dist/nextjs/providers/NextAuthProvider.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 +16 -0
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js +28 -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 +2 -0
- package/dist/reactjs/providers/CivicAuthProvider.d.ts.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.js +5 -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 +12 -0
- package/dist/services/AuthenticationService.d.ts.map +1 -1
- package/dist/services/AuthenticationService.js +62 -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/components/IFrameAndLoading.d.ts.map +1 -1
- package/dist/shared/components/IFrameAndLoading.js +1 -1
- package/dist/shared/components/IFrameAndLoading.js.map +1 -1
- package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
- package/dist/shared/hooks/useSignIn.js +5 -3
- 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 +2 -0
- package/dist/shared/lib/iframeUtils.d.ts.map +1 -1
- package/dist/shared/lib/iframeUtils.js +12 -0
- package/dist/shared/lib/iframeUtils.js.map +1 -1
- package/dist/shared/lib/types.d.ts +1 -0
- package/dist/shared/lib/types.d.ts.map +1 -1
- package/dist/shared/lib/types.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/providers/CivicAuthConfigContext.d.ts +2 -1
- package/dist/shared/providers/CivicAuthConfigContext.d.ts.map +1 -1
- package/dist/shared/providers/CivicAuthConfigContext.js +3 -1
- package/dist/shared/providers/CivicAuthConfigContext.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 +67 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +310 -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 +15 -8
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +44 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +163 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +23 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js +59 -2
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +20 -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 +216 -24
- 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,9 @@ export class CivicAuth {
|
|
|
37
38
|
authProcessTimeoutHandle;
|
|
38
39
|
popupFailureTimeoutHandle;
|
|
39
40
|
hasPopupFailed = false;
|
|
41
|
+
hasSignInStarted = false;
|
|
42
|
+
// Backend integration - custom login URL
|
|
43
|
+
loginUrl;
|
|
40
44
|
// Handlers
|
|
41
45
|
messageHandler;
|
|
42
46
|
popupHandler;
|
|
@@ -49,6 +53,8 @@ export class CivicAuth {
|
|
|
49
53
|
// Process config with defaults and validation
|
|
50
54
|
this.config = processConfigWithDefaults(config);
|
|
51
55
|
this.initialDisplayMode = this.config.displayMode;
|
|
56
|
+
// Set loginUrl from config if provided
|
|
57
|
+
this.loginUrl = this.config.loginUrl;
|
|
52
58
|
// Configure logging
|
|
53
59
|
configureLogging(this.config.logging);
|
|
54
60
|
// Initialize logger - always use "vanillajs" as base namespace
|
|
@@ -68,7 +74,7 @@ export class CivicAuth {
|
|
|
68
74
|
// Always initialize events - use provided events or create default instance
|
|
69
75
|
this.events = config.events || new AuthenticationEvents();
|
|
70
76
|
// Always initialize SessionManager
|
|
71
|
-
this.sessionManager = new SessionManager(this.storage, this.events);
|
|
77
|
+
this.sessionManager = new SessionManager(this.storage, this.events, this.config);
|
|
72
78
|
// Initialize handlers
|
|
73
79
|
this.initializeHandlers();
|
|
74
80
|
}
|
|
@@ -82,6 +88,7 @@ export class CivicAuth {
|
|
|
82
88
|
*
|
|
83
89
|
* @example
|
|
84
90
|
* ```typescript
|
|
91
|
+
* // Standard SPA authentication
|
|
85
92
|
* const auth = await CivicAuth.create({
|
|
86
93
|
* clientId: "your-client-id",
|
|
87
94
|
* // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
|
|
@@ -97,6 +104,12 @@ export class CivicAuth {
|
|
|
97
104
|
* success: "Authentication successful!"
|
|
98
105
|
* }
|
|
99
106
|
* });
|
|
107
|
+
*
|
|
108
|
+
* // Backend integration authentication
|
|
109
|
+
* const authWithBackend = await CivicAuth.create({
|
|
110
|
+
* clientId: "your-client-id",
|
|
111
|
+
* loginUrl: "http://example.com/custom-backendurl" // Automatically uses BrowserCookieStorage
|
|
112
|
+
* });
|
|
100
113
|
* ```
|
|
101
114
|
*/
|
|
102
115
|
static async create(config) {
|
|
@@ -128,6 +141,8 @@ export class CivicAuth {
|
|
|
128
141
|
oauthServer: this.config.oauthServerBaseUrl,
|
|
129
142
|
scopes: this.config.scopes,
|
|
130
143
|
endpoints: this.endpoints,
|
|
144
|
+
loginUrl: this.config.loginUrl || this.loginUrl, // Include loginUrl for backend flows
|
|
145
|
+
backendEndpoints: this.config.backendEndpoints, // Pass backend endpoints config
|
|
131
146
|
};
|
|
132
147
|
this.logger.info("🔧 Initializing SessionManager", { authConfig });
|
|
133
148
|
await this.sessionManager.initializeWithAuthConfig(authConfig);
|
|
@@ -148,6 +163,43 @@ export class CivicAuth {
|
|
|
148
163
|
else {
|
|
149
164
|
this.logger.info("🏠 Not a callback page, initialization complete");
|
|
150
165
|
}
|
|
166
|
+
// Automatically preload iframe if enabled and user is not authenticated
|
|
167
|
+
// This runs after both callback processing and normal initialization
|
|
168
|
+
this.logger.info("🔍 Checking auto-preload conditions", {
|
|
169
|
+
preloadIframe: this.config.preloadIframe,
|
|
170
|
+
displayMode: this.config.displayMode,
|
|
171
|
+
});
|
|
172
|
+
if (this.config.preloadIframe && this.config.displayMode === "iframe") {
|
|
173
|
+
try {
|
|
174
|
+
const isAuthenticated = await this.isAuthenticated();
|
|
175
|
+
this.logger.info("🔍 Authentication check for preload", {
|
|
176
|
+
isAuthenticated,
|
|
177
|
+
});
|
|
178
|
+
if (!isAuthenticated) {
|
|
179
|
+
this.logger.info("🔄 Auto-preloading iframe for instant sign-in...");
|
|
180
|
+
await this.preloadAuthentication();
|
|
181
|
+
this.logger.info("✅ Iframe auto-preloaded successfully");
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
this.logger.debug("ℹ️ User already authenticated, skipping preload");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
// Don't fail initialization if preloading fails - graceful degradation
|
|
189
|
+
this.logger.warn("⚠️ Auto-preloading failed (graceful degradation):", {
|
|
190
|
+
error: error instanceof Error ? error.message : String(error),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.logger.info("ℹ️ Auto-preload conditions not met", {
|
|
196
|
+
preloadIframe: this.config.preloadIframe,
|
|
197
|
+
displayMode: this.config.displayMode,
|
|
198
|
+
reason: !this.config.preloadIframe
|
|
199
|
+
? "preloadIframe is disabled"
|
|
200
|
+
: "displayMode is not iframe",
|
|
201
|
+
});
|
|
202
|
+
}
|
|
151
203
|
}
|
|
152
204
|
catch (error) {
|
|
153
205
|
const errorMessage = error instanceof Error
|
|
@@ -177,17 +229,77 @@ export class CivicAuth {
|
|
|
177
229
|
this.messageHandler = new MessageHandler({
|
|
178
230
|
...handlerConfig,
|
|
179
231
|
onPopupFailure: this.handlePopupFailure.bind(this),
|
|
232
|
+
onBrowserCorsFailsSilently: async () => {
|
|
233
|
+
if (this.hasSignInStarted) {
|
|
234
|
+
await this.handleBrowserCorsFailsSilently();
|
|
235
|
+
}
|
|
236
|
+
},
|
|
180
237
|
});
|
|
181
238
|
this.popupHandler = new PopupHandler(handlerConfig);
|
|
182
239
|
this.iframeAuthHandler = new IframeAuthHandler({
|
|
183
240
|
...handlerConfig,
|
|
184
241
|
messageHandler: this.messageHandler.handleMessage,
|
|
185
242
|
});
|
|
243
|
+
// Set up automatic re-preloading when authentication is cancelled
|
|
244
|
+
this.setupAutoRepreload();
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Set up automatic re-preloading when authentication is cancelled by user
|
|
248
|
+
* This maintains instant sign-in experience for subsequent attempts
|
|
249
|
+
*/
|
|
250
|
+
setupAutoRepreload() {
|
|
251
|
+
this.events.on(AuthEvent.SIGN_IN_STARTED, () => {
|
|
252
|
+
this.hasSignInStarted = true;
|
|
253
|
+
});
|
|
254
|
+
this.events.on(AuthEvent.SIGN_IN_ERROR, (event) => {
|
|
255
|
+
// Only re-preload for iframe mode with user cancellation and preload enabled
|
|
256
|
+
if (this.config.displayMode === "iframe" &&
|
|
257
|
+
this.config.preloadIframe &&
|
|
258
|
+
event?.detail === "Authentication cancelled by user") {
|
|
259
|
+
this.logger.debug("🔄 Authentication cancelled, scheduling re-preload for instant subsequent sign-in");
|
|
260
|
+
// Small delay to ensure cleanup is complete
|
|
261
|
+
setTimeout(async () => {
|
|
262
|
+
try {
|
|
263
|
+
await this.preloadAuthentication();
|
|
264
|
+
this.logger.debug("✅ Re-preloaded iframe after cancellation for instant sign-in");
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
// Don't fail silently, but don't crash either - just log the issue
|
|
268
|
+
this.logger.warn("⚠️ Failed to re-preload iframe after cancellation:", {
|
|
269
|
+
error: error instanceof Error ? error.message : String(error),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}, 200); // Small delay to ensure cleanup completion
|
|
273
|
+
}
|
|
274
|
+
});
|
|
186
275
|
}
|
|
187
276
|
/**
|
|
188
277
|
* Builds the authentication URL with PKCE challenge
|
|
189
278
|
*/
|
|
190
279
|
async buildAuthUrl() {
|
|
280
|
+
// If a login URL is set (for backend integration), use that instead
|
|
281
|
+
if (this.loginUrl) {
|
|
282
|
+
this.logger.info("🔗 Using login URL for backend integration", {
|
|
283
|
+
loginUrl: this.loginUrl,
|
|
284
|
+
});
|
|
285
|
+
// Generate state with display mode information for backend integration
|
|
286
|
+
const state = this.config.initialState ||
|
|
287
|
+
generateState({
|
|
288
|
+
displayMode: this.config.displayMode || "iframe",
|
|
289
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
290
|
+
framework: this.config.framework || "vanillajs",
|
|
291
|
+
sdkVersion: getVersion(),
|
|
292
|
+
});
|
|
293
|
+
// Append state as query parameter to loginUrl
|
|
294
|
+
const url = new URL(this.loginUrl, window.location.origin);
|
|
295
|
+
url.searchParams.set("state", state);
|
|
296
|
+
this.logger.info("🔗 Built login URL with display mode state", {
|
|
297
|
+
loginUrl: url.toString(),
|
|
298
|
+
displayMode: this.config.displayMode,
|
|
299
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
300
|
+
});
|
|
301
|
+
return url.toString();
|
|
302
|
+
}
|
|
191
303
|
if (!this.endpoints) {
|
|
192
304
|
throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
193
305
|
}
|
|
@@ -196,6 +308,7 @@ export class CivicAuth {
|
|
|
196
308
|
const state = this.config.initialState ||
|
|
197
309
|
generateState({
|
|
198
310
|
displayMode: this.config.displayMode || "iframe",
|
|
311
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
199
312
|
framework: this.config.framework || "vanillajs",
|
|
200
313
|
sdkVersion: getVersion(),
|
|
201
314
|
});
|
|
@@ -210,6 +323,102 @@ export class CivicAuth {
|
|
|
210
323
|
nonce: this.config.nonce,
|
|
211
324
|
});
|
|
212
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Preloads the authentication iframe for instant sign-in
|
|
328
|
+
* This creates the iframe in the background but keeps it hidden until startAuthentication() is called
|
|
329
|
+
* @throws {CivicAuthError} If preloading fails or is not supported
|
|
330
|
+
* @private - This method is used internally for automatic preloading
|
|
331
|
+
*/
|
|
332
|
+
async preloadAuthentication() {
|
|
333
|
+
this.logger.info("🎬 Preloading authentication for instant sign-in", {
|
|
334
|
+
displayMode: this.config.displayMode,
|
|
335
|
+
userAgent: navigator.userAgent,
|
|
336
|
+
currentUrl: window.location.href,
|
|
337
|
+
});
|
|
338
|
+
if (!this.endpoints) {
|
|
339
|
+
const error = new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
340
|
+
this.logger.error("❌ Endpoints not initialized for preload", {
|
|
341
|
+
error: error.message,
|
|
342
|
+
});
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
// Only support preloading for iframe mode
|
|
346
|
+
if (this.config.displayMode !== "iframe") {
|
|
347
|
+
this.logger.warn("⚠️ Iframe preloading only supported for iframe display mode", {
|
|
348
|
+
displayMode: this.config.displayMode,
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!this.iframeAuthHandler) {
|
|
353
|
+
const error = new CivicAuthError("Iframe handler not initialized for preloading", CivicAuthErrorCode.INIT_FAILED);
|
|
354
|
+
this.logger.error("❌ Iframe handler not initialized", {
|
|
355
|
+
error: error.message,
|
|
356
|
+
});
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
const fullAuthUrl = await this.buildAuthUrl();
|
|
360
|
+
this.logger.info("🔗 Built authentication URL for preload", {
|
|
361
|
+
url: fullAuthUrl,
|
|
362
|
+
displayMode: this.config.displayMode,
|
|
363
|
+
});
|
|
364
|
+
try {
|
|
365
|
+
await this.iframeAuthHandler.preloadIframe(fullAuthUrl);
|
|
366
|
+
const iframeElement = this.iframeAuthHandler.getIframeElement();
|
|
367
|
+
if (iframeElement) {
|
|
368
|
+
this.messageHandler?.updateIframeElement(iframeElement);
|
|
369
|
+
}
|
|
370
|
+
this.logger.info("✅ Authentication iframe preloaded successfully");
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to preload iframe";
|
|
374
|
+
this.logger.error("❌ Failed to preload authentication iframe", {
|
|
375
|
+
error: errorMessage,
|
|
376
|
+
});
|
|
377
|
+
throw new CivicAuthError(errorMessage, CivicAuthErrorCode.IFRAME_PRELOAD_FAILED);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Check if authentication is preloaded and ready for instant sign-in
|
|
382
|
+
* @returns True if an iframe is preloaded and ready
|
|
383
|
+
*/
|
|
384
|
+
isAuthenticationPreloaded() {
|
|
385
|
+
if (this.config.displayMode !== "iframe" || !this.iframeAuthHandler) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
return this.iframeAuthHandler.hasPreloadedIframe();
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Enable or disable iframe preloading
|
|
392
|
+
* @param enabled Whether to enable iframe preloading
|
|
393
|
+
*/
|
|
394
|
+
setPreloadEnabled(enabled) {
|
|
395
|
+
// Preloading only makes sense for iframe mode
|
|
396
|
+
if (this.config.displayMode !== "iframe") {
|
|
397
|
+
this.logger.debug("🎯 Iframe preloading not applicable for non-iframe mode", {
|
|
398
|
+
displayMode: this.config.displayMode,
|
|
399
|
+
enabled,
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (this.iframeAuthHandler) {
|
|
404
|
+
this.iframeAuthHandler.setPreloadEnabled(enabled);
|
|
405
|
+
this.logger.info("🎯 Iframe preloading", { enabled });
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
this.logger.warn("⚠️ Cannot set preload enabled: iframe handler not initialized");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Check if iframe preloading is enabled
|
|
413
|
+
* @returns True if iframe preloading is enabled
|
|
414
|
+
*/
|
|
415
|
+
getPreloadEnabled() {
|
|
416
|
+
// Preloading only makes sense for iframe mode
|
|
417
|
+
if (this.config.displayMode !== "iframe") {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
return this.iframeAuthHandler?.getPreloadEnabled() ?? false;
|
|
421
|
+
}
|
|
213
422
|
/**
|
|
214
423
|
* Starts the authentication process
|
|
215
424
|
* @returns A promise that resolves with the authentication result
|
|
@@ -252,6 +461,30 @@ export class CivicAuth {
|
|
|
252
461
|
});
|
|
253
462
|
return this.authPromise;
|
|
254
463
|
}
|
|
464
|
+
async handleBrowserCorsFailsSilently() {
|
|
465
|
+
this.logger.warn("🚨 Browser CORS fails silently - switching to redirect mode", {
|
|
466
|
+
displayMode: this.config.displayMode,
|
|
467
|
+
iframeAuthHandler: !!this.iframeAuthHandler,
|
|
468
|
+
messageHandler: !!this.messageHandler,
|
|
469
|
+
});
|
|
470
|
+
if (!this.iframeAuthHandler || !this.messageHandler) {
|
|
471
|
+
throw new Error("Iframe handler not initialized");
|
|
472
|
+
}
|
|
473
|
+
this.iframeAuthHandler.forceHideIframe();
|
|
474
|
+
const rebuiltUrl = await this.buildAuthUrl();
|
|
475
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
476
|
+
detail: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
|
|
477
|
+
error: {
|
|
478
|
+
type: "browser_cors_fails_silently",
|
|
479
|
+
failedUrl: IframeManager.browserCorsFailsSilentlyUrl,
|
|
480
|
+
suggestion: "Browser doesn't support popups and passkey with CORS in iframe - switching to redirect mode",
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
// use a small delay to ensure the loader is shown
|
|
484
|
+
setTimeout(() => {
|
|
485
|
+
window.location.href = rebuiltUrl;
|
|
486
|
+
}, 100);
|
|
487
|
+
}
|
|
255
488
|
/**
|
|
256
489
|
* Handle authentication based on display mode
|
|
257
490
|
*/
|
|
@@ -278,6 +511,9 @@ export class CivicAuth {
|
|
|
278
511
|
if (!this.iframeAuthHandler || !this.messageHandler) {
|
|
279
512
|
throw new Error("Iframe handler not initialized");
|
|
280
513
|
}
|
|
514
|
+
if (IframeManager.browserCorsFailsSilentlyUrl) {
|
|
515
|
+
return this.handleBrowserCorsFailsSilently();
|
|
516
|
+
}
|
|
281
517
|
const iframeElement = await this.iframeAuthHandler.handleIframeAuth(fullAuthUrl);
|
|
282
518
|
this.messageHandler.updateIframeElement(iframeElement);
|
|
283
519
|
break;
|
|
@@ -341,19 +577,15 @@ export class CivicAuth {
|
|
|
341
577
|
});
|
|
342
578
|
return;
|
|
343
579
|
}
|
|
580
|
+
// Set up timeout for other modes
|
|
344
581
|
if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
|
|
345
582
|
this.logger.debug("⏰ Setting up authentication timeout", {
|
|
346
|
-
|
|
583
|
+
timeout: this.config.authProcessTimeout,
|
|
347
584
|
displayMode: this.config.displayMode,
|
|
348
585
|
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
349
586
|
});
|
|
350
587
|
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
|
-
});
|
|
588
|
+
this.logger.info("⏰ Authentication timeout reached");
|
|
357
589
|
this.events.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
358
590
|
detail: "Authentication timed out",
|
|
359
591
|
});
|
|
@@ -599,6 +831,54 @@ export class CivicAuth {
|
|
|
599
831
|
getTokenRefresherState() {
|
|
600
832
|
return this.sessionManager.getTokenRefresherState() || null;
|
|
601
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* Set a custom login URL for backend integration.
|
|
836
|
+
* This is useful when integrating with a backend that handles the OAuth flow.
|
|
837
|
+
* Alternatively, you can configure this directly in CivicAuth.create().
|
|
838
|
+
*
|
|
839
|
+
* @param loginUrl - The custom login URL to use (e.g., "http://localhost:3020/auth/login")
|
|
840
|
+
*
|
|
841
|
+
* @example
|
|
842
|
+
* ```typescript
|
|
843
|
+
* // Option 1: Configure in create()
|
|
844
|
+
* const authClient = await CivicAuth.create({
|
|
845
|
+
* clientId: "YOUR_CLIENT_ID",
|
|
846
|
+
* loginUrl: "http://example.com/custom-backendurl"
|
|
847
|
+
* });
|
|
848
|
+
*
|
|
849
|
+
* // Option 2: Set after creation
|
|
850
|
+
* civicAuth.setLoginUrl("http://localhost:3020/auth/login");
|
|
851
|
+
* await civicAuth.startAuthentication();
|
|
852
|
+
* ```
|
|
853
|
+
*/
|
|
854
|
+
setLoginUrl(loginUrl) {
|
|
855
|
+
this.loginUrl = loginUrl;
|
|
856
|
+
// Update MessageHandler to expect messages from the custom origin
|
|
857
|
+
if (this.messageHandler) {
|
|
858
|
+
this.messageHandler.setCustomExpectedOrigin(loginUrl);
|
|
859
|
+
}
|
|
860
|
+
this.logger.info("🔗 Custom login URL set for backend integration", {
|
|
861
|
+
loginUrl: this.loginUrl,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Clear the login URL and return to standard OAuth flow
|
|
866
|
+
*/
|
|
867
|
+
clearLoginUrl() {
|
|
868
|
+
this.loginUrl = undefined;
|
|
869
|
+
// Clear custom expected origin from MessageHandler
|
|
870
|
+
if (this.messageHandler) {
|
|
871
|
+
this.messageHandler.clearCustomExpectedOrigin();
|
|
872
|
+
}
|
|
873
|
+
this.logger.info("🔗 Login URL cleared, returning to standard OAuth flow");
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Get the current login URL
|
|
877
|
+
* @returns The current login URL or undefined if not set
|
|
878
|
+
*/
|
|
879
|
+
getLoginUrl() {
|
|
880
|
+
return this.loginUrl;
|
|
881
|
+
}
|
|
602
882
|
/**
|
|
603
883
|
* Update the iframe display mode
|
|
604
884
|
* @param mode - The display mode to use for the iframe
|
|
@@ -631,7 +911,27 @@ export class CivicAuth {
|
|
|
631
911
|
this.events.emit(AuthEvent.SIGN_OUT_STARTED, {
|
|
632
912
|
detail: "Logout process started",
|
|
633
913
|
});
|
|
634
|
-
//
|
|
914
|
+
// If a loginUrl is configured, redirect to the backend's logout endpoint.
|
|
915
|
+
// The backend will then handle redirecting to the OIDC provider.
|
|
916
|
+
if (this.loginUrl) {
|
|
917
|
+
this.logger.info("🚪 Redirecting to backend for logout in backend-integrated flow");
|
|
918
|
+
const backendUrl = new URL(this.loginUrl).origin;
|
|
919
|
+
const endpoints = getBackendEndpoints(this.config.backendEndpoints);
|
|
920
|
+
const backendLogoutUrl = `${backendUrl}${endpoints.logout}`;
|
|
921
|
+
// Clear local SDK session state before redirecting
|
|
922
|
+
await this.sessionManager.clearSession();
|
|
923
|
+
this.events.emit(AuthEvent.SIGN_OUT_COMPLETE, {
|
|
924
|
+
detail: "Local session cleared, redirecting to backend for logout.",
|
|
925
|
+
});
|
|
926
|
+
// Perform top-level redirect to the backend logout endpoint
|
|
927
|
+
this.logger.info("🌐 Redirecting to backend logout endpoint", {
|
|
928
|
+
url: backendLogoutUrl,
|
|
929
|
+
});
|
|
930
|
+
window.location.href = backendLogoutUrl;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
// --- Existing SPA redirect-based logout flow ---
|
|
934
|
+
this.logger.info("🚪 Using redirect-based logout for SPA flow");
|
|
635
935
|
const tokens = await retrieveTokens(this.storage);
|
|
636
936
|
if (!tokens?.id_token) {
|
|
637
937
|
this.logger.warn("⚠️ No ID token found, clearing local session only");
|