@civic/auth 0.6.1-beta.3 → 0.6.1-beta.4
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 +3 -0
- package/README.md +7 -0
- package/dist/nextjs/config.d.ts.map +1 -1
- package/dist/nextjs/config.js +1 -5
- package/dist/nextjs/config.js.map +1 -1
- package/dist/nextjs/hooks/useUserCookie.d.ts.map +1 -1
- package/dist/nextjs/hooks/useUserCookie.js.map +1 -1
- package/dist/nextjs/middleware.d.ts.map +1 -1
- package/dist/nextjs/middleware.js +51 -18
- package/dist/nextjs/middleware.js.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.js +0 -1
- package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
- package/dist/nextjs/routeHandler.d.ts.map +1 -1
- package/dist/nextjs/routeHandler.js +8 -0
- package/dist/nextjs/routeHandler.js.map +1 -1
- package/dist/shared/hooks/useSignIn.d.ts +4 -9
- package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
- package/dist/shared/hooks/useSignIn.js +42 -75
- package/dist/shared/hooks/useSignIn.js.map +1 -1
- package/dist/shared/providers/AuthContext.d.ts +2 -7
- package/dist/shared/providers/AuthContext.d.ts.map +1 -1
- package/dist/shared/providers/AuthContext.js.map +1 -1
- package/dist/shared/providers/UserProvider.d.ts +1 -5
- package/dist/shared/providers/UserProvider.d.ts.map +1 -1
- package/dist/shared/providers/UserProvider.js.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/vanillajs/auth/AuthenticationEvents.d.ts.map +1 -1
- package/dist/vanillajs/auth/AuthenticationEvents.js +2 -2
- package/dist/vanillajs/auth/AuthenticationEvents.js.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.d.ts +107 -68
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +412 -389
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/{handlers/OAuthCallbackHandler.d.ts → OAuthCallbackHandler.d.ts} +2 -2
- package/dist/vanillajs/auth/OAuthCallbackHandler.d.ts.map +1 -0
- package/dist/vanillajs/auth/OAuthCallbackHandler.js +143 -0
- package/dist/vanillajs/auth/OAuthCallbackHandler.js.map +1 -0
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +2 -2
- 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 +2 -2
- package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.d.ts +0 -33
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.js +36 -163
- package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
- package/dist/vanillajs/index.d.ts +2 -2
- package/dist/vanillajs/index.d.ts.map +1 -1
- package/dist/vanillajs/index.js +2 -2
- package/dist/vanillajs/index.js.map +1 -1
- package/dist/vanillajs/services/ApiService.d.ts.map +1 -1
- package/dist/vanillajs/services/ApiService.js +2 -2
- package/dist/vanillajs/services/ApiService.js.map +1 -1
- package/dist/vanillajs/types/index.d.ts +10 -15
- package/dist/vanillajs/types/index.d.ts.map +1 -1
- package/dist/vanillajs/types/index.js +10 -15
- package/dist/vanillajs/types/index.js.map +1 -1
- package/dist/vanillajs/utils/auth-utils.d.ts +1 -2
- package/dist/vanillajs/utils/auth-utils.d.ts.map +1 -1
- package/dist/vanillajs/utils/auth-utils.js +3 -6
- package/dist/vanillajs/utils/auth-utils.js.map +1 -1
- package/dist/vanillajs/utils/logger.d.ts +15 -16
- package/dist/vanillajs/utils/logger.d.ts.map +1 -1
- package/dist/vanillajs/utils/logger.js +19 -35
- package/dist/vanillajs/utils/logger.js.map +1 -1
- package/package.json +1 -6
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts +0 -6
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +0 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js +0 -59
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +0 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +0 -40
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +0 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +0 -388
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +0 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +0 -170
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +0 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js +0 -367
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +0 -1
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.d.ts.map +0 -1
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js +0 -301
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js.map +0 -1
- package/dist/vanillajs/auth/handlers/PopupHandler.d.ts +0 -108
- package/dist/vanillajs/auth/handlers/PopupHandler.d.ts.map +0 -1
- package/dist/vanillajs/auth/handlers/PopupHandler.js +0 -333
- package/dist/vanillajs/auth/handlers/PopupHandler.js.map +0 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +0 -128
- package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +0 -1
- package/dist/vanillajs/auth/types/AuthTypes.js +0 -40
- package/dist/vanillajs/auth/types/AuthTypes.js.map +0 -1
|
@@ -1,55 +1,105 @@
|
|
|
1
1
|
import { AuthEvent } from "../types/index.js";
|
|
2
|
+
import { LocalStorageAdapter } from "../../browser/storage.js";
|
|
3
|
+
import { handleOAuthRedirectPage } from "./OAuthCallbackHandler.js";
|
|
2
4
|
import { buildAuthUrl } from "../utils/auth-utils.js";
|
|
3
|
-
import {
|
|
5
|
+
import { createLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
|
|
6
|
+
import { SignalObserver } from "../iframe/SignalObserver.js";
|
|
4
7
|
import { GenericPublicClientPKCEProducer } from "../../services/PKCE.js";
|
|
5
8
|
import { generateState } from "../../lib/oauth.js";
|
|
6
9
|
import { SessionManager } from "./SessionManager.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
import { IframeManager } from "../iframe/IframeManager.js";
|
|
11
|
+
/**
|
|
12
|
+
* Error codes for CivicAuth errors
|
|
13
|
+
*/
|
|
14
|
+
export var CivicAuthErrorCode;
|
|
15
|
+
(function (CivicAuthErrorCode) {
|
|
16
|
+
CivicAuthErrorCode["CONFIG_REQUIRED"] = "CONFIG_REQUIRED";
|
|
17
|
+
CivicAuthErrorCode["INIT_FAILED"] = "INIT_FAILED";
|
|
18
|
+
CivicAuthErrorCode["ENDPOINTS_NOT_INITIALIZED"] = "ENDPOINTS_NOT_INITIALIZED";
|
|
19
|
+
CivicAuthErrorCode["CONTAINER_NOT_FOUND"] = "CONTAINER_NOT_FOUND";
|
|
20
|
+
CivicAuthErrorCode["AUTH_PROCESS_TIMEOUT"] = "AUTH_PROCESS_TIMEOUT";
|
|
21
|
+
CivicAuthErrorCode["IFRAME_LOAD_ERROR"] = "IFRAME_LOAD_ERROR";
|
|
22
|
+
CivicAuthErrorCode["INVALID_MESSAGE"] = "INVALID_MESSAGE";
|
|
23
|
+
})(CivicAuthErrorCode || (CivicAuthErrorCode = {}));
|
|
24
|
+
/**
|
|
25
|
+
* Constants for the auth client
|
|
26
|
+
*/
|
|
27
|
+
const CONSTANTS = {
|
|
28
|
+
DEFAULT_IFRAME_ID: "civic-auth-iframe",
|
|
29
|
+
DEFAULT_AUTH_PROCESS_TIMEOUT: 60000, // 60 seconds
|
|
30
|
+
SUCCESS_SIGNAL_ID: "civic-auth-success-signal",
|
|
31
|
+
ERROR_SIGNAL_ID: "civic-auth-error-signal",
|
|
32
|
+
};
|
|
33
|
+
class CivicAuthError extends Error {
|
|
34
|
+
code;
|
|
35
|
+
constructor(message, code) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.name = "CivicAuthError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Process the configuration with defaults
|
|
43
|
+
*/
|
|
44
|
+
function processConfigWithDefaults(config) {
|
|
45
|
+
const loggingConfig = {
|
|
46
|
+
enabled: true,
|
|
47
|
+
namespace: "iframe",
|
|
48
|
+
level: "debug",
|
|
49
|
+
...config.logging,
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
...config,
|
|
53
|
+
displayMode: config.displayMode || "iframe",
|
|
54
|
+
authProcessTimeout: config.authProcessTimeout || CONSTANTS.DEFAULT_AUTH_PROCESS_TIMEOUT,
|
|
55
|
+
iframeId: config.iframeId || CONSTANTS.DEFAULT_IFRAME_ID,
|
|
56
|
+
logging: loggingConfig,
|
|
57
|
+
storageAdapter: config.storageAdapter || new LocalStorageAdapter(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
15
60
|
/**
|
|
16
61
|
* CivicAuth client for handling OAuth authentication
|
|
17
|
-
*
|
|
18
|
-
* This is a refactored version that uses a modular architecture for better maintainability.
|
|
19
62
|
*/
|
|
20
63
|
export class CivicAuth {
|
|
64
|
+
/**
|
|
65
|
+
* Internal configuration with all optional properties resolved to required ones.
|
|
66
|
+
*
|
|
67
|
+
* We extend CivicAuthClientConfig rather than making these properties required
|
|
68
|
+
* in the public interface to maintain better DX (optional properties) while
|
|
69
|
+
* ensuring type safety internally after defaults are applied.
|
|
70
|
+
*/
|
|
21
71
|
config;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
logger;
|
|
25
|
-
sessionManager;
|
|
26
|
-
initialDisplayMode;
|
|
27
|
-
// Authentication state
|
|
72
|
+
iframeElement;
|
|
73
|
+
observer;
|
|
28
74
|
authPromise;
|
|
29
75
|
authPromiseResolve;
|
|
30
76
|
authPromiseReject;
|
|
31
77
|
authProcessTimeoutHandle;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
78
|
+
messageEventHandler;
|
|
79
|
+
storage;
|
|
80
|
+
endpoints;
|
|
81
|
+
logger;
|
|
82
|
+
sessionManager;
|
|
83
|
+
iframeManager;
|
|
38
84
|
/**
|
|
39
|
-
* Private constructor
|
|
85
|
+
* Private constructor for CivicAuth.
|
|
40
86
|
* Use {@link CivicAuth.create} to create a new instance.
|
|
87
|
+
* @param config - Configuration options for the auth client
|
|
88
|
+
* @throws {CivicAuthError} If required configuration is missing
|
|
89
|
+
* @private
|
|
41
90
|
*/
|
|
42
91
|
constructor(config) {
|
|
43
|
-
// Process config with defaults
|
|
92
|
+
// Process config with defaults
|
|
44
93
|
this.config = processConfigWithDefaults(config);
|
|
45
|
-
|
|
46
|
-
// Configure logging
|
|
94
|
+
// Configure logging based on config
|
|
47
95
|
configureLogging(this.config.logging);
|
|
48
|
-
// Initialize logger
|
|
96
|
+
// Initialize logger based on config
|
|
49
97
|
if (this.config.logging?.enabled) {
|
|
50
|
-
this.
|
|
98
|
+
const namespace = this.config.logging.namespace || "iframe";
|
|
99
|
+
this.logger = createLogger(namespace);
|
|
51
100
|
}
|
|
52
101
|
else {
|
|
102
|
+
// Create a no-op logger when logging is disabled
|
|
53
103
|
this.logger = {
|
|
54
104
|
debug: () => { },
|
|
55
105
|
info: () => { },
|
|
@@ -57,14 +107,26 @@ export class CivicAuth {
|
|
|
57
107
|
error: () => { },
|
|
58
108
|
};
|
|
59
109
|
}
|
|
110
|
+
// Set this logger as the current logger
|
|
60
111
|
setCurrentLogger(this.logger);
|
|
112
|
+
// Use the storage adapter from processed config (guaranteed to be present)
|
|
61
113
|
this.storage = this.config.storageAdapter;
|
|
62
114
|
// Initialize SessionManager if events are provided
|
|
63
115
|
if (config.events) {
|
|
64
116
|
this.sessionManager = new SessionManager(this.storage, config.events);
|
|
65
117
|
}
|
|
66
|
-
//
|
|
67
|
-
|
|
118
|
+
// Validate required configuration
|
|
119
|
+
const requiredConfigs = [
|
|
120
|
+
{ key: "clientId", value: config.clientId },
|
|
121
|
+
{ key: "targetContainerElement", value: config.targetContainerElement },
|
|
122
|
+
{ key: "textSignals.success", value: config.textSignals?.success },
|
|
123
|
+
];
|
|
124
|
+
for (const { key, value } of requiredConfigs) {
|
|
125
|
+
if (!value) {
|
|
126
|
+
throw new CivicAuthError(`CivicAuth: ${key} is required.`, CivicAuthErrorCode.CONFIG_REQUIRED);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
this.messageEventHandler = this.handleIframeMessage.bind(this);
|
|
68
130
|
}
|
|
69
131
|
/**
|
|
70
132
|
* Creates and initializes a new instance of CivicAuth.
|
|
@@ -78,12 +140,9 @@ export class CivicAuth {
|
|
|
78
140
|
* ```typescript
|
|
79
141
|
* const auth = await CivicAuth.create({
|
|
80
142
|
* clientId: "your-client-id",
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* oauthServerBaseUrl: "https://auth-server.com/", // optional
|
|
85
|
-
* // scopes is optional - defaults to ['openid', 'profile', 'email', 'offline_access']
|
|
86
|
-
* scopes: ["openid", "profile"], // optional
|
|
143
|
+
* redirectUrl: "https://your-app.com/callback",
|
|
144
|
+
* oauthServerBaseUrl: "https://auth-server.com/",
|
|
145
|
+
* scopes: ["openid", "profile"],
|
|
87
146
|
* targetContainerElement: "auth-container",
|
|
88
147
|
* textSignals: {
|
|
89
148
|
* success: "Authentication successful!"
|
|
@@ -98,16 +157,11 @@ export class CivicAuth {
|
|
|
98
157
|
}
|
|
99
158
|
/**
|
|
100
159
|
* Initializes the auth client and checks for callback handling
|
|
160
|
+
* @throws {CivicAuthError} If initialization fails
|
|
101
161
|
*/
|
|
102
162
|
async init() {
|
|
103
|
-
this.logger.info("🚀 Initializing CivicAuth", {
|
|
104
|
-
currentUrl: window.location.href,
|
|
105
|
-
redirectUrl: this.config.redirectUrl,
|
|
106
|
-
oauthServerBaseUrl: this.config.oauthServerBaseUrl,
|
|
107
|
-
isCallbackUrl: window.location.href.startsWith(this.config.redirectUrl),
|
|
108
|
-
});
|
|
109
163
|
try {
|
|
110
|
-
// Get OAuth endpoints
|
|
164
|
+
// Get OAuth endpoints from well-known configuration
|
|
111
165
|
this.endpoints = {
|
|
112
166
|
auth: `${this.config.oauthServerBaseUrl}auth`,
|
|
113
167
|
token: `${this.config.oauthServerBaseUrl}token`,
|
|
@@ -115,10 +169,7 @@ export class CivicAuth {
|
|
|
115
169
|
userinfo: `${this.config.oauthServerBaseUrl}userinfo`,
|
|
116
170
|
endsession: `${this.config.oauthServerBaseUrl}endsession`,
|
|
117
171
|
};
|
|
118
|
-
|
|
119
|
-
endpoints: this.endpoints,
|
|
120
|
-
});
|
|
121
|
-
// Initialize SessionManager with auth config
|
|
172
|
+
// Initialize SessionManager with auth config for token refresh
|
|
122
173
|
if (this.sessionManager) {
|
|
123
174
|
const authConfig = {
|
|
124
175
|
clientId: this.config.clientId,
|
|
@@ -127,32 +178,18 @@ export class CivicAuth {
|
|
|
127
178
|
scopes: this.config.scopes,
|
|
128
179
|
endpoints: this.endpoints,
|
|
129
180
|
};
|
|
130
|
-
this.logger.info("🔧 Initializing SessionManager", { authConfig });
|
|
131
181
|
await this.sessionManager.initializeWithAuthConfig(authConfig);
|
|
132
182
|
}
|
|
133
183
|
// Check if we're on the callback page
|
|
134
|
-
|
|
135
|
-
this.logger.info("🔍 Callback page check", {
|
|
136
|
-
isCallbackPage,
|
|
137
|
-
currentUrl: window.location.href,
|
|
138
|
-
redirectUrl: this.config.redirectUrl,
|
|
139
|
-
});
|
|
140
|
-
if (isCallbackPage) {
|
|
141
|
-
this.logger.info("📞 Processing callback page");
|
|
184
|
+
if (window.location.href.startsWith(this.config.redirectUrl)) {
|
|
142
185
|
await this.handleCallback();
|
|
143
186
|
}
|
|
144
|
-
else {
|
|
145
|
-
this.logger.info("🏠 Not a callback page, initialization complete");
|
|
146
|
-
}
|
|
147
187
|
}
|
|
148
188
|
catch (error) {
|
|
149
189
|
const errorMessage = error instanceof Error
|
|
150
190
|
? error.message
|
|
151
191
|
: "Failed to initialize authentication";
|
|
152
|
-
this.logger.error("
|
|
153
|
-
error: errorMessage,
|
|
154
|
-
stack: error instanceof Error ? error.stack : undefined,
|
|
155
|
-
});
|
|
192
|
+
this.logger.error("Failed to initialize CivicAuth:", { error });
|
|
156
193
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
157
194
|
detail: errorMessage,
|
|
158
195
|
});
|
|
@@ -160,33 +197,82 @@ export class CivicAuth {
|
|
|
160
197
|
}
|
|
161
198
|
}
|
|
162
199
|
/**
|
|
163
|
-
*
|
|
200
|
+
* Gets the container element for the auth iframe
|
|
201
|
+
* @returns The container element or null if not found
|
|
164
202
|
*/
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
this.
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
getContainerElement() {
|
|
204
|
+
if (typeof this.config.targetContainerElement === "string") {
|
|
205
|
+
const element = document.getElementById(this.config.targetContainerElement);
|
|
206
|
+
if (!element) {
|
|
207
|
+
this.logger.warn(`Container element with ID "${this.config.targetContainerElement}" not found`);
|
|
208
|
+
}
|
|
209
|
+
return element;
|
|
210
|
+
}
|
|
211
|
+
return this.config.targetContainerElement;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Determines the appropriate iframe display mode based on container characteristics
|
|
215
|
+
* @param container The HTML element that will contain the iframe
|
|
216
|
+
* @returns "modal" for full-screen overlay or "embedded" for in-container display
|
|
217
|
+
*/
|
|
218
|
+
determineIframeDisplayMode(container) {
|
|
219
|
+
// Use explicit config if provided
|
|
220
|
+
if (this.config.iframeDisplayMode) {
|
|
221
|
+
this.logger.debug(`Using configured iframe display mode: ${this.config.iframeDisplayMode}`);
|
|
222
|
+
return this.config.iframeDisplayMode;
|
|
223
|
+
}
|
|
224
|
+
// Analyze container characteristics to determine best display mode
|
|
225
|
+
const containerRect = container.getBoundingClientRect();
|
|
226
|
+
const containerStyles = window.getComputedStyle(container);
|
|
227
|
+
// Get container dimensions
|
|
228
|
+
const containerWidth = containerRect.width;
|
|
229
|
+
const containerHeight = containerRect.height;
|
|
230
|
+
// Check if container is positioned in a way that suggests it's meant for modal display
|
|
231
|
+
const isFullScreenContainer = containerStyles.position === "fixed" &&
|
|
232
|
+
(containerWidth >= window.innerWidth * 0.9 ||
|
|
233
|
+
containerHeight >= window.innerHeight * 0.9);
|
|
234
|
+
// Modal mode thresholds - if container is too small, use modal for better UX
|
|
235
|
+
const MIN_EMBEDDED_WIDTH = 320; // Same as modal content wrapper width
|
|
236
|
+
const MIN_EMBEDDED_HEIGHT = 400; // Reasonable minimum for embedded auth flow
|
|
237
|
+
// Determine mode based on container characteristics
|
|
238
|
+
if (isFullScreenContainer) {
|
|
239
|
+
this.logger.debug("Container appears to be full-screen positioned, using modal mode", { containerWidth, containerHeight, position: containerStyles.position });
|
|
240
|
+
return "modal";
|
|
241
|
+
}
|
|
242
|
+
if (containerWidth < MIN_EMBEDDED_WIDTH ||
|
|
243
|
+
containerHeight < MIN_EMBEDDED_HEIGHT) {
|
|
244
|
+
this.logger.debug(`Container too small for embedded mode (${containerWidth}x${containerHeight}), using modal mode`, {
|
|
245
|
+
containerWidth,
|
|
246
|
+
containerHeight,
|
|
247
|
+
MIN_EMBEDDED_WIDTH,
|
|
248
|
+
MIN_EMBEDDED_HEIGHT,
|
|
249
|
+
});
|
|
250
|
+
return "modal";
|
|
251
|
+
}
|
|
252
|
+
// Check if container has sufficient space and is properly positioned for embedding
|
|
253
|
+
const hasAdequateSpace = containerWidth >= MIN_EMBEDDED_WIDTH &&
|
|
254
|
+
containerHeight >= MIN_EMBEDDED_HEIGHT;
|
|
255
|
+
const isEmbeddablePosition = containerStyles.position === "relative" ||
|
|
256
|
+
containerStyles.position === "static" ||
|
|
257
|
+
containerStyles.position === "";
|
|
258
|
+
if (hasAdequateSpace && isEmbeddablePosition) {
|
|
259
|
+
this.logger.debug("Container has adequate space and positioning for embedded mode", { containerWidth, containerHeight, position: containerStyles.position });
|
|
260
|
+
return "embedded";
|
|
261
|
+
}
|
|
262
|
+
// Default to modal mode if container characteristics are unclear
|
|
263
|
+
this.logger.debug("Container characteristics unclear, defaulting to modal mode for better UX", { containerWidth, containerHeight, position: containerStyles.position });
|
|
264
|
+
return "modal";
|
|
182
265
|
}
|
|
183
266
|
/**
|
|
184
267
|
* Builds the authentication URL with PKCE challenge
|
|
268
|
+
* @returns The complete authentication URL
|
|
269
|
+
* @throws {CivicAuthError} If endpoints are not initialized
|
|
185
270
|
*/
|
|
186
271
|
async buildAuthUrl() {
|
|
187
272
|
if (!this.endpoints) {
|
|
188
273
|
throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
189
274
|
}
|
|
275
|
+
// Use storage directly since it's now AuthStorage
|
|
190
276
|
const pkceProducer = new GenericPublicClientPKCEProducer(this.storage);
|
|
191
277
|
const codeChallenge = await pkceProducer.getCodeChallenge();
|
|
192
278
|
const state = this.config.initialState ||
|
|
@@ -201,265 +287,293 @@ export class CivicAuth {
|
|
|
201
287
|
codeChallenge,
|
|
202
288
|
state,
|
|
203
289
|
prompt: this.config.prompt,
|
|
204
|
-
nonce: this.config.nonce,
|
|
205
290
|
});
|
|
206
291
|
}
|
|
292
|
+
handleIframeMessage(event) {
|
|
293
|
+
const expectedOrigin = new URL(this.config.oauthServerBaseUrl).origin;
|
|
294
|
+
this.logIncomingMessage(event, expectedOrigin);
|
|
295
|
+
if (!this.isValidMessageSource(event, expectedOrigin)) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
this.handleValidMessage(event);
|
|
299
|
+
}
|
|
300
|
+
logIncomingMessage(event, expectedOrigin) {
|
|
301
|
+
this.logger.debug("Global window received message:", {
|
|
302
|
+
data: event.data,
|
|
303
|
+
origin: event.origin,
|
|
304
|
+
sourceProvided: !!event.source,
|
|
305
|
+
iframeContentWindow: this.iframeElement?.contentWindow,
|
|
306
|
+
expectedIframeOrigin: expectedOrigin,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
isValidMessageSource(event, expectedOrigin) {
|
|
310
|
+
const isValidOrigin = event.origin === expectedOrigin;
|
|
311
|
+
const isValidSource = event.source === this.iframeElement?.contentWindow;
|
|
312
|
+
if (!isValidOrigin) {
|
|
313
|
+
this.logger.warn("Ignored message from unexpected origin.", {
|
|
314
|
+
receivedOrigin: event.origin,
|
|
315
|
+
expectedOrigin,
|
|
316
|
+
iframeSrc: this.iframeElement?.src,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (!isValidSource) {
|
|
320
|
+
this.logger.warn("Ignored message from unexpected source.", {
|
|
321
|
+
isSourceProvided: !!event.source,
|
|
322
|
+
isIframeContentWindowAvailable: !!this.iframeElement?.contentWindow,
|
|
323
|
+
iframeSrc: this.iframeElement?.src,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return isValidOrigin && isValidSource;
|
|
327
|
+
}
|
|
328
|
+
handleValidMessage(event) {
|
|
329
|
+
this.logger.info("Message from configured iframe source and origin received", {
|
|
330
|
+
data: event.data,
|
|
331
|
+
iframeSrc: this.iframeElement?.src,
|
|
332
|
+
});
|
|
333
|
+
const message = event.data;
|
|
334
|
+
const messageType = message?.type;
|
|
335
|
+
switch (messageType) {
|
|
336
|
+
case "auth_success":
|
|
337
|
+
this.handleAuthSuccess(message);
|
|
338
|
+
break;
|
|
339
|
+
case "auth_error":
|
|
340
|
+
this.handleAuthError(message);
|
|
341
|
+
break;
|
|
342
|
+
default:
|
|
343
|
+
this.logger.debug("Message from iframe did not match expected types (auth_success, auth_error)", { data: event.data });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
handleAuthSuccess(data) {
|
|
347
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
|
|
348
|
+
detail: "Success signal received via postMessage",
|
|
349
|
+
data,
|
|
350
|
+
});
|
|
351
|
+
this.authPromiseResolve?.(data?.data || {});
|
|
352
|
+
this.cleanup();
|
|
353
|
+
}
|
|
354
|
+
handleAuthError(data) {
|
|
355
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
356
|
+
detail: "Error signal received via postMessage",
|
|
357
|
+
error: data,
|
|
358
|
+
});
|
|
359
|
+
this.authPromiseReject?.(new CivicAuthError(data?.detail || "Error signal received via postMessage", CivicAuthErrorCode.INVALID_MESSAGE));
|
|
360
|
+
this.cleanup();
|
|
361
|
+
}
|
|
362
|
+
setupSignalObserver(iframeDoc) {
|
|
363
|
+
const signalObserver = new SignalObserver({
|
|
364
|
+
textSignals: this.config.textSignals,
|
|
365
|
+
events: this.config.events,
|
|
366
|
+
logger: this.logger,
|
|
367
|
+
}, this.authPromiseResolve, this.authPromiseReject, () => this.cleanup());
|
|
368
|
+
signalObserver.setup(iframeDoc);
|
|
369
|
+
}
|
|
370
|
+
async handleNewTabAuth(fullAuthUrl, reject) {
|
|
371
|
+
const popupWindow = window.open(fullAuthUrl, "_blank");
|
|
372
|
+
if (!popupWindow) {
|
|
373
|
+
const error = new CivicAuthError("Failed to open popup window. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
|
|
374
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
375
|
+
detail: error.message,
|
|
376
|
+
});
|
|
377
|
+
reject(error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async handleIframeAuth(fullAuthUrl, reject) {
|
|
381
|
+
const container = this.getContainerElement();
|
|
382
|
+
if (!container) {
|
|
383
|
+
const error = new CivicAuthError("Target container element not found.", CivicAuthErrorCode.CONTAINER_NOT_FOUND);
|
|
384
|
+
this.logger.error(error.message);
|
|
385
|
+
reject(error);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
this.logger.debug("Creating iframe with modal backdrop", {
|
|
389
|
+
url: fullAuthUrl,
|
|
390
|
+
containerId: container?.id,
|
|
391
|
+
iframeId: this.config.iframeId,
|
|
392
|
+
origin: window.location.origin,
|
|
393
|
+
});
|
|
394
|
+
// Determine the actual display mode for IframeManager
|
|
395
|
+
// For iframe displayMode, we need to decide between modal and embedded based on container
|
|
396
|
+
const iframeDisplayMode = this.determineIframeDisplayMode(container);
|
|
397
|
+
this.logger.debug(`🎯 CivicAuth: Creating IframeManager with display mode: ${iframeDisplayMode}`);
|
|
398
|
+
// Create IframeManager with appropriate display mode
|
|
399
|
+
this.iframeManager = new IframeManager({
|
|
400
|
+
container: container,
|
|
401
|
+
displayMode: iframeDisplayMode,
|
|
402
|
+
iframeId: this.config.iframeId,
|
|
403
|
+
onClose: () => {
|
|
404
|
+
this.logger.debug("Authentication close requested by user (backdrop click, close button, or Escape key)");
|
|
405
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
406
|
+
detail: "Authentication cancelled by user",
|
|
407
|
+
});
|
|
408
|
+
reject(new CivicAuthError("Authentication cancelled by user", CivicAuthErrorCode.AUTH_PROCESS_TIMEOUT));
|
|
409
|
+
this.cleanup();
|
|
410
|
+
},
|
|
411
|
+
});
|
|
412
|
+
// Create the iframe using IframeManager
|
|
413
|
+
this.iframeElement = this.iframeManager.createIframe(fullAuthUrl);
|
|
414
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_STARTED, {
|
|
415
|
+
detail: "Iframe created with modal backdrop",
|
|
416
|
+
});
|
|
417
|
+
this.iframeElement.onload = () => {
|
|
418
|
+
this.logger.info("Iframe loaded", {
|
|
419
|
+
iframeSrc: this.iframeElement?.src,
|
|
420
|
+
currentOrigin: window.location.origin,
|
|
421
|
+
expectedAuthServerOrigin: new URL(this.config.oauthServerBaseUrl)
|
|
422
|
+
.origin,
|
|
423
|
+
});
|
|
424
|
+
if (!this.iframeElement?.contentWindow) {
|
|
425
|
+
const errorMsg = "Iframe content window not available after load.";
|
|
426
|
+
this.logger.error(errorMsg, {
|
|
427
|
+
iframeSrc: this.iframeElement?.src,
|
|
428
|
+
});
|
|
429
|
+
reject(new Error(errorMsg));
|
|
430
|
+
this.cleanup();
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
// Set up postMessage listener for cross-origin communication
|
|
434
|
+
if (this.messageEventHandler) {
|
|
435
|
+
window.addEventListener("message", this.messageEventHandler);
|
|
436
|
+
this.logger.info("Added cross-origin message event listener for auth server communication", {
|
|
437
|
+
parentOrigin: window.location.origin,
|
|
438
|
+
authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
this.logger.error("messageEventHandler was not defined when trying to add listener.");
|
|
443
|
+
}
|
|
444
|
+
// Try to detect redirect to our domain
|
|
445
|
+
try {
|
|
446
|
+
const currentIframeHref = this.iframeElement.contentWindow.location.href;
|
|
447
|
+
if (currentIframeHref.startsWith(this.config.redirectUrl)) {
|
|
448
|
+
this.logger.info("Iframe has navigated to redirectUrl (same-origin). Setting up DOM observer.");
|
|
449
|
+
if (this.iframeElement.contentDocument &&
|
|
450
|
+
this.iframeElement.contentDocument.body) {
|
|
451
|
+
this.setupSignalObserver(this.iframeElement.contentDocument);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
catch (error) {
|
|
456
|
+
this.logger.error("Error checking iframe href", {
|
|
457
|
+
error,
|
|
458
|
+
iframeSrc: this.iframeElement?.src,
|
|
459
|
+
});
|
|
460
|
+
// This is expected when the iframe is on the auth server domain
|
|
461
|
+
this.logger.info("Iframe is on auth server domain - using postMessage for communication", {
|
|
462
|
+
parentOrigin: window.location.origin,
|
|
463
|
+
authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
this.iframeElement.onerror = (event) => {
|
|
468
|
+
this.logger.error("Iframe load error", {
|
|
469
|
+
event,
|
|
470
|
+
iframeSrc: this.iframeElement?.src,
|
|
471
|
+
currentOrigin: window.location.origin,
|
|
472
|
+
});
|
|
473
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
474
|
+
detail: "Iframe load error",
|
|
475
|
+
error: event,
|
|
476
|
+
});
|
|
477
|
+
reject(new Error("Iframe failed to load."));
|
|
478
|
+
this.cleanup();
|
|
479
|
+
};
|
|
480
|
+
}
|
|
207
481
|
/**
|
|
208
482
|
* Starts the authentication process
|
|
209
483
|
* @returns A promise that resolves with the authentication result
|
|
210
484
|
* @throws {CivicAuthError} If authentication fails or times out
|
|
211
485
|
*/
|
|
212
486
|
async startAuthentication() {
|
|
213
|
-
this.logger.info("🎬 Starting authentication process", {
|
|
214
|
-
displayMode: this.config.displayMode,
|
|
215
|
-
userAgent: navigator.userAgent,
|
|
216
|
-
currentUrl: window.location.href,
|
|
217
|
-
});
|
|
218
487
|
if (!this.endpoints) {
|
|
219
488
|
const error = new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
220
|
-
this.logger.error("❌ Endpoints not initialized", {
|
|
221
|
-
error: error.message,
|
|
222
|
-
});
|
|
223
489
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
224
490
|
detail: error.message,
|
|
225
491
|
});
|
|
226
492
|
throw error;
|
|
227
493
|
}
|
|
228
494
|
if (this.authPromise) {
|
|
229
|
-
this.logger.info("
|
|
495
|
+
this.logger.info("Authentication already in progress, returning existing promise");
|
|
230
496
|
return this.authPromise;
|
|
231
497
|
}
|
|
232
498
|
const fullAuthUrl = await this.buildAuthUrl();
|
|
233
|
-
this.logger.info("
|
|
234
|
-
|
|
499
|
+
this.logger.info("Starting authentication process", {
|
|
500
|
+
constructedIframeUrl: fullAuthUrl,
|
|
235
501
|
displayMode: this.config.displayMode,
|
|
236
502
|
authProcessTimeout: this.config.authProcessTimeout,
|
|
237
503
|
});
|
|
238
504
|
this.authPromise = new Promise((resolve, reject) => {
|
|
239
505
|
this.authPromiseResolve = resolve;
|
|
240
506
|
this.authPromiseReject = reject;
|
|
241
|
-
|
|
242
|
-
this.setupAuthenticationTimeout();
|
|
243
|
-
});
|
|
244
|
-
return this.authPromise;
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Handle authentication based on display mode
|
|
248
|
-
*/
|
|
249
|
-
async handleAuthenticationByDisplayMode(fullAuthUrl) {
|
|
250
|
-
this.logger.info("🎯 Handling authentication with display mode", {
|
|
251
|
-
displayMode: this.config.displayMode,
|
|
252
|
-
});
|
|
253
|
-
try {
|
|
507
|
+
// Handle different display modes
|
|
254
508
|
switch (this.config.displayMode) {
|
|
255
509
|
case "redirect":
|
|
256
|
-
|
|
510
|
+
// For redirect mode, just navigate to the auth URL
|
|
257
511
|
window.location.href = fullAuthUrl;
|
|
258
512
|
break;
|
|
259
513
|
case "new_tab":
|
|
260
|
-
this.
|
|
261
|
-
if (!this.popupHandler) {
|
|
262
|
-
throw new Error("Popup handler not initialized");
|
|
263
|
-
}
|
|
264
|
-
await this.popupHandler.handleNewTabAuth(fullAuthUrl);
|
|
514
|
+
this.handleNewTabAuth(fullAuthUrl, reject);
|
|
265
515
|
break;
|
|
266
516
|
case "iframe":
|
|
267
|
-
default:
|
|
268
|
-
this.
|
|
269
|
-
if (!this.iframeAuthHandler || !this.messageHandler) {
|
|
270
|
-
throw new Error("Iframe handler not initialized");
|
|
271
|
-
}
|
|
272
|
-
const iframeElement = await this.iframeAuthHandler.handleIframeAuth(fullAuthUrl);
|
|
273
|
-
this.messageHandler.updateIframeElement(iframeElement);
|
|
517
|
+
default:
|
|
518
|
+
this.handleIframeAuth(fullAuthUrl, reject);
|
|
274
519
|
break;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
catch (error) {
|
|
279
|
-
if (error instanceof PopupError) {
|
|
280
|
-
await this.handlePopupErrorWithFallback(fullAuthUrl, error);
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
this.handleAuthError(error instanceof Error ? error : new Error(String(error)));
|
|
284
520
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
*/
|
|
290
|
-
async handlePopupErrorWithFallback(fullAuthUrl, error) {
|
|
291
|
-
this.logger.warn("🚫 Popup failed, falling back to redirect mode", {
|
|
292
|
-
originalDisplayMode: this.config.displayMode,
|
|
293
|
-
error: error.message,
|
|
294
|
-
});
|
|
295
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
296
|
-
detail: "Popup blocked, falling back to redirect mode",
|
|
297
|
-
});
|
|
298
|
-
try {
|
|
299
|
-
this.logger.info("🔄 Attempting redirect fallback");
|
|
300
|
-
// Clean up current authentication attempt
|
|
301
|
-
this.cleanup();
|
|
302
|
-
// Always switch to redirect mode for Safari compatibility
|
|
303
|
-
this.config.displayMode = "redirect";
|
|
304
|
-
// Regenerate the auth URL with updated display mode in state
|
|
305
|
-
const fallbackAuthUrl = await this.buildAuthUrl();
|
|
306
|
-
this.logger.info("🌐 Redirecting to auth URL", { url: fallbackAuthUrl });
|
|
307
|
-
window.location.href = fallbackAuthUrl;
|
|
308
|
-
this.logger.info("✅ Redirect initiated successfully");
|
|
309
|
-
}
|
|
310
|
-
catch (redirectError) {
|
|
311
|
-
this.logger.error("❌ Redirect fallback failed", {
|
|
312
|
-
error: redirectError,
|
|
313
|
-
redirectUrl: fullAuthUrl,
|
|
314
|
-
});
|
|
315
|
-
const fallbackError = new CivicAuthError("Failed to open popup window and redirect fallback failed. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
|
|
316
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
317
|
-
detail: fallbackError.message,
|
|
318
|
-
});
|
|
319
|
-
this.handleAuthError(fallbackError);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Setup authentication timeout
|
|
324
|
-
*/
|
|
325
|
-
setupAuthenticationTimeout() {
|
|
326
|
-
if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
|
|
327
|
-
this.logger.debug("⏰ Setting up authentication timeout", {
|
|
328
|
-
authProcessTimeout: this.config.authProcessTimeout,
|
|
329
|
-
displayMode: this.config.displayMode,
|
|
330
|
-
});
|
|
331
|
-
this.authProcessTimeoutHandle = window.setTimeout(() => {
|
|
332
|
-
this.logger.error("⏰ Authentication timed out", {
|
|
333
|
-
displayMode: this.config.displayMode,
|
|
334
|
-
currentOrigin: window.location.origin,
|
|
521
|
+
// Set up timeout for all display modes
|
|
522
|
+
if (this.config.authProcessTimeout &&
|
|
523
|
+
this.config.authProcessTimeout > 0) {
|
|
524
|
+
this.logger.debug("Setting up authentication timeout", {
|
|
335
525
|
authProcessTimeout: this.config.authProcessTimeout,
|
|
526
|
+
displayMode: this.config.displayMode,
|
|
336
527
|
});
|
|
337
|
-
this.
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this.logger.info("✅ Authentication successful");
|
|
350
|
-
this.authPromiseResolve?.(result);
|
|
351
|
-
this.cleanup();
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Handle authentication error
|
|
355
|
-
*/
|
|
356
|
-
handleAuthError(error) {
|
|
357
|
-
this.logger.error("❌ Authentication failed", { error: error.message });
|
|
358
|
-
this.authPromiseReject?.(error);
|
|
359
|
-
this.cleanup();
|
|
360
|
-
}
|
|
361
|
-
/**
|
|
362
|
-
* Handle popup failure - simplified like React implementation
|
|
363
|
-
*/
|
|
364
|
-
handlePopupFailure(failedUrl) {
|
|
365
|
-
this.hasPopupFailed = true;
|
|
366
|
-
this.logger.warn("Popup failed, using redirect mode instead...", {
|
|
367
|
-
failedUrl,
|
|
528
|
+
this.authProcessTimeoutHandle = window.setTimeout(() => {
|
|
529
|
+
this.logger.warn("Authentication timed out", {
|
|
530
|
+
displayMode: this.config.displayMode,
|
|
531
|
+
currentOrigin: window.location.origin,
|
|
532
|
+
});
|
|
533
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
534
|
+
detail: "Authentication timed out",
|
|
535
|
+
});
|
|
536
|
+
reject(new Error("Authentication timed out."));
|
|
537
|
+
this.cleanup();
|
|
538
|
+
}, this.config.authProcessTimeout);
|
|
539
|
+
}
|
|
368
540
|
});
|
|
369
|
-
|
|
370
|
-
if (this.iframeAuthHandler) {
|
|
371
|
-
this.iframeAuthHandler.cleanupIframe();
|
|
372
|
-
}
|
|
373
|
-
// Always redirect to the failed URL or build a new one
|
|
374
|
-
if (failedUrl) {
|
|
375
|
-
window.location.href = failedUrl;
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
this.buildAuthUrl()
|
|
379
|
-
.then((authUrl) => {
|
|
380
|
-
window.location.href = authUrl;
|
|
381
|
-
})
|
|
382
|
-
.catch((error) => {
|
|
383
|
-
this.logger.error("Failed to build auth URL for redirect fallback", {
|
|
384
|
-
error,
|
|
385
|
-
});
|
|
386
|
-
const fallbackError = new CivicAuthError("Failed to redirect for authentication", CivicAuthErrorCode.INIT_FAILED);
|
|
387
|
-
this.handleAuthError(fallbackError);
|
|
388
|
-
});
|
|
389
|
-
}
|
|
541
|
+
return this.authPromise;
|
|
390
542
|
}
|
|
391
543
|
/**
|
|
392
|
-
*
|
|
544
|
+
* Cleans up resources and event listeners
|
|
393
545
|
*/
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
this.logger.
|
|
398
|
-
|
|
546
|
+
cleanup() {
|
|
547
|
+
this.logger.info("Cleaning up iframe authentication client");
|
|
548
|
+
if (this.observer) {
|
|
549
|
+
this.logger.debug("Disconnecting mutation observer");
|
|
550
|
+
this.observer.disconnect();
|
|
551
|
+
this.observer = undefined;
|
|
399
552
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
553
|
+
if (this.messageEventHandler) {
|
|
554
|
+
window.removeEventListener("message", this.messageEventHandler);
|
|
555
|
+
this.logger.debug("Removed 'message' event listener from window.");
|
|
403
556
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
messageOverlay.innerHTML =
|
|
409
|
-
'<div style="display:flex;align-items:center;gap:8px;">' +
|
|
410
|
-
'<span style="font-size:16px;">⚠️</span>' +
|
|
411
|
-
"<div>" +
|
|
412
|
-
"<strong>Popup blocked</strong><br>" +
|
|
413
|
-
'<span style="font-size:12px;">' +
|
|
414
|
-
customMessage +
|
|
415
|
-
"</span>" +
|
|
416
|
-
"</div>" +
|
|
417
|
-
"</div>";
|
|
418
|
-
if (getComputedStyle(container).position === "static") {
|
|
419
|
-
container.style.position = "relative";
|
|
557
|
+
if (this.iframeManager) {
|
|
558
|
+
this.logger.debug("Cleaning up iframe manager");
|
|
559
|
+
this.iframeManager.cleanup();
|
|
560
|
+
this.iframeManager = undefined;
|
|
420
561
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
messageOverlay.parentNode.removeChild(messageOverlay);
|
|
425
|
-
}
|
|
426
|
-
}, 10000);
|
|
427
|
-
this.logger.info("Popup failure message displayed to user");
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* Setup popup failure timeout
|
|
431
|
-
*/
|
|
432
|
-
setupPopupFailureTimeout() {
|
|
433
|
-
this.popupFailureTimeoutHandle = window.setTimeout(() => {
|
|
434
|
-
this.logger.info("⏰ Popup failure timeout reached");
|
|
435
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
436
|
-
detail: "Authentication timeout - popup failure scenario",
|
|
437
|
-
});
|
|
438
|
-
const error = new CivicAuthError("Authentication timeout - popup failure scenario", CivicAuthErrorCode.AUTH_PROCESS_TIMEOUT);
|
|
439
|
-
this.handleAuthError(error);
|
|
440
|
-
}, 20000); // 20 seconds
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Gets the container element for the auth iframe
|
|
444
|
-
*/
|
|
445
|
-
getContainerElement() {
|
|
446
|
-
if (typeof this.config.targetContainerElement === "string") {
|
|
447
|
-
const element = document.getElementById(this.config.targetContainerElement);
|
|
448
|
-
if (!element) {
|
|
449
|
-
this.logger.warn(`Container element with ID "${this.config.targetContainerElement}" not found`);
|
|
450
|
-
}
|
|
451
|
-
return element;
|
|
562
|
+
// Reset iframe element reference (actual removal handled by IframeManager)
|
|
563
|
+
if (this.iframeElement) {
|
|
564
|
+
this.iframeElement = undefined;
|
|
452
565
|
}
|
|
453
|
-
|
|
566
|
+
if (this.authProcessTimeoutHandle) {
|
|
567
|
+
this.logger.debug("Clearing authentication process timeout");
|
|
568
|
+
window.clearTimeout(this.authProcessTimeoutHandle);
|
|
569
|
+
this.authProcessTimeoutHandle = undefined;
|
|
570
|
+
}
|
|
571
|
+
this.logger.debug("Resetting promise state");
|
|
572
|
+
this.authPromise = undefined;
|
|
573
|
+
this.authPromiseResolve = undefined;
|
|
574
|
+
this.authPromiseReject = undefined;
|
|
454
575
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Handle OAuth callback
|
|
457
|
-
*/
|
|
458
576
|
async handleCallback() {
|
|
459
|
-
this.logger.info("🔄 Handling OAuth callback", {
|
|
460
|
-
currentUrl: window.location.href,
|
|
461
|
-
redirectUrl: this.config.redirectUrl,
|
|
462
|
-
});
|
|
463
577
|
try {
|
|
464
578
|
const callbackHandled = await handleOAuthRedirectPage({
|
|
465
579
|
clientId: this.config.clientId,
|
|
@@ -471,12 +585,10 @@ export class CivicAuth {
|
|
|
471
585
|
},
|
|
472
586
|
storageAdapter: this.storage,
|
|
473
587
|
});
|
|
474
|
-
this.logger.info("📋 Callback processing result", { callbackHandled });
|
|
475
588
|
if (callbackHandled) {
|
|
476
|
-
const successSignal = document.getElementById(
|
|
477
|
-
const errorSignal = document.getElementById(
|
|
589
|
+
const successSignal = document.getElementById(CONSTANTS.SUCCESS_SIGNAL_ID);
|
|
590
|
+
const errorSignal = document.getElementById(CONSTANTS.ERROR_SIGNAL_ID);
|
|
478
591
|
if (successSignal) {
|
|
479
|
-
this.logger.info("✅ Success signal found");
|
|
480
592
|
let userInfo = null;
|
|
481
593
|
const userInfoAttr = successSignal.getAttribute("data-user-info");
|
|
482
594
|
if (userInfoAttr) {
|
|
@@ -484,7 +596,7 @@ export class CivicAuth {
|
|
|
484
596
|
userInfo = JSON.parse(userInfoAttr);
|
|
485
597
|
}
|
|
486
598
|
catch (error) {
|
|
487
|
-
this.logger.error("
|
|
599
|
+
this.logger.error("Failed to parse user info:", { error });
|
|
488
600
|
}
|
|
489
601
|
}
|
|
490
602
|
this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
|
|
@@ -493,7 +605,6 @@ export class CivicAuth {
|
|
|
493
605
|
});
|
|
494
606
|
}
|
|
495
607
|
else if (errorSignal) {
|
|
496
|
-
this.logger.error("❌ Error signal found");
|
|
497
608
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
498
609
|
detail: errorSignal.textContent || "Unknown error during callback",
|
|
499
610
|
});
|
|
@@ -501,7 +612,6 @@ export class CivicAuth {
|
|
|
501
612
|
}
|
|
502
613
|
}
|
|
503
614
|
catch (error) {
|
|
504
|
-
this.logger.error("❌ Callback handling failed", { error });
|
|
505
615
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
506
616
|
detail: error instanceof Error
|
|
507
617
|
? error.message
|
|
@@ -509,32 +619,6 @@ export class CivicAuth {
|
|
|
509
619
|
});
|
|
510
620
|
}
|
|
511
621
|
}
|
|
512
|
-
/**
|
|
513
|
-
* Cleans up resources and event listeners
|
|
514
|
-
*/
|
|
515
|
-
cleanup() {
|
|
516
|
-
this.logger.info("Cleaning up authentication client");
|
|
517
|
-
// Clean up handlers
|
|
518
|
-
this.iframeAuthHandler?.cleanupIframe();
|
|
519
|
-
// Clean up timeouts
|
|
520
|
-
if (this.authProcessTimeoutHandle) {
|
|
521
|
-
window.clearTimeout(this.authProcessTimeoutHandle);
|
|
522
|
-
this.authProcessTimeoutHandle = undefined;
|
|
523
|
-
}
|
|
524
|
-
if (this.popupFailureTimeoutHandle) {
|
|
525
|
-
window.clearTimeout(this.popupFailureTimeoutHandle);
|
|
526
|
-
this.popupFailureTimeoutHandle = undefined;
|
|
527
|
-
}
|
|
528
|
-
// Reset state
|
|
529
|
-
this.hasPopupFailed = false;
|
|
530
|
-
this.authPromise = undefined;
|
|
531
|
-
this.authPromiseResolve = undefined;
|
|
532
|
-
this.authPromiseReject = undefined;
|
|
533
|
-
// Remove message event listener
|
|
534
|
-
if (this.messageHandler) {
|
|
535
|
-
window.removeEventListener("message", this.messageHandler.handleMessage);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
622
|
/**
|
|
539
623
|
* Get the current session
|
|
540
624
|
*/
|
|
@@ -601,66 +685,5 @@ export class CivicAuth {
|
|
|
601
685
|
this.sessionManager = undefined;
|
|
602
686
|
this.logger.info("CivicAuth destroyed");
|
|
603
687
|
}
|
|
604
|
-
/**
|
|
605
|
-
* Handle logout
|
|
606
|
-
*/
|
|
607
|
-
async logout() {
|
|
608
|
-
if (!this.sessionManager) {
|
|
609
|
-
throw new Error("SessionManager not initialized. Provide events in config.");
|
|
610
|
-
}
|
|
611
|
-
try {
|
|
612
|
-
this.logger.info("🔄 Starting logout process");
|
|
613
|
-
this.config.events?.emit(AuthEvent.SIGN_OUT_STARTED, {
|
|
614
|
-
detail: "Logout process started",
|
|
615
|
-
});
|
|
616
|
-
// Get current tokens before clearing them
|
|
617
|
-
const tokens = await retrieveTokens(this.storage);
|
|
618
|
-
if (!tokens?.id_token) {
|
|
619
|
-
this.logger.warn("⚠️ No ID token found, clearing local session only");
|
|
620
|
-
await clearTokens(this.storage);
|
|
621
|
-
await this.sessionManager.clearSession();
|
|
622
|
-
this.config.events?.emit(AuthEvent.SIGN_OUT_COMPLETE, {
|
|
623
|
-
detail: "Local session cleared",
|
|
624
|
-
});
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
|
-
if (!this.endpoints) {
|
|
628
|
-
throw new Error("OAuth endpoints not initialized");
|
|
629
|
-
}
|
|
630
|
-
// Generate a state for logout
|
|
631
|
-
const state = generateState({
|
|
632
|
-
displayMode: this.config.displayMode || "iframe",
|
|
633
|
-
});
|
|
634
|
-
// Generate logout URL
|
|
635
|
-
const logoutUrl = await generateOauthLogoutUrl({
|
|
636
|
-
clientId: this.config.clientId,
|
|
637
|
-
redirectUrl: this.config.redirectUrl,
|
|
638
|
-
idToken: tokens.id_token,
|
|
639
|
-
state: state,
|
|
640
|
-
oauthServer: this.config.oauthServerBaseUrl,
|
|
641
|
-
});
|
|
642
|
-
this.logger.info("🔗 Generated logout URL", {
|
|
643
|
-
logoutUrl: logoutUrl.toString(),
|
|
644
|
-
});
|
|
645
|
-
// Clear local tokens and session
|
|
646
|
-
await clearTokens(this.storage);
|
|
647
|
-
await this.sessionManager.clearSession();
|
|
648
|
-
// Emit logout complete event before redirect
|
|
649
|
-
this.config.events?.emit(AuthEvent.SIGN_OUT_COMPLETE, {
|
|
650
|
-
detail: "Logout successful",
|
|
651
|
-
});
|
|
652
|
-
// Redirect to logout URL
|
|
653
|
-
this.logger.info("🌐 Redirecting to logout URL");
|
|
654
|
-
window.location.href = logoutUrl.toString();
|
|
655
|
-
}
|
|
656
|
-
catch (error) {
|
|
657
|
-
this.logger.error("❌ Logout failed", { error });
|
|
658
|
-
this.config.events?.emit(AuthEvent.SIGN_OUT_ERROR, {
|
|
659
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
660
|
-
});
|
|
661
|
-
throw new CivicAuthError("Logout failed", CivicAuthErrorCode.LOGOUT_FAILED);
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
688
|
}
|
|
665
|
-
export { CivicAuthErrorCode } from "./types/AuthTypes.js";
|
|
666
689
|
//# sourceMappingURL=CivicAuth.js.map
|