@civic/auth 0.6.1-beta.1 → 0.6.1-beta.2
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/dist/nextjs/config.d.ts.map +1 -1
- package/dist/nextjs/config.js +3 -1
- 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/providers/NextAuthProvider.d.ts.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.js +1 -0
- package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
- package/dist/shared/hooks/useSignIn.d.ts +9 -4
- package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
- package/dist/shared/hooks/useSignIn.js +75 -42
- package/dist/shared/hooks/useSignIn.js.map +1 -1
- package/dist/shared/providers/AuthContext.d.ts +7 -2
- 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 +5 -1
- 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 +68 -107
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +389 -412
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- 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/auth/config/ConfigProcessor.d.ts +6 -0
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -0
- package/dist/vanillajs/auth/config/ConfigProcessor.js +59 -0
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +40 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +388 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +170 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.js +367 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -0
- package/dist/vanillajs/auth/{OAuthCallbackHandler.d.ts → handlers/OAuthCallbackHandler.d.ts} +2 -2
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.d.ts.map +1 -0
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js +301 -0
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js.map +1 -0
- package/dist/vanillajs/auth/handlers/PopupHandler.d.ts +108 -0
- package/dist/vanillajs/auth/handlers/PopupHandler.d.ts.map +1 -0
- package/dist/vanillajs/auth/handlers/PopupHandler.js +333 -0
- package/dist/vanillajs/auth/handlers/PopupHandler.js.map +1 -0
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +128 -0
- package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -0
- package/dist/vanillajs/auth/types/AuthTypes.js +40 -0
- package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -0
- package/dist/vanillajs/iframe/IframeManager.d.ts +33 -0
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.js +163 -36
- 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 +15 -10
- package/dist/vanillajs/types/index.d.ts.map +1 -1
- package/dist/vanillajs/types/index.js +15 -10
- package/dist/vanillajs/types/index.js.map +1 -1
- package/dist/vanillajs/utils/auth-utils.d.ts +2 -1
- package/dist/vanillajs/utils/auth-utils.d.ts.map +1 -1
- package/dist/vanillajs/utils/auth-utils.js +6 -3
- package/dist/vanillajs/utils/auth-utils.js.map +1 -1
- package/dist/vanillajs/utils/logger.d.ts +16 -15
- package/dist/vanillajs/utils/logger.d.ts.map +1 -1
- package/dist/vanillajs/utils/logger.js +35 -19
- package/dist/vanillajs/utils/logger.js.map +1 -1
- package/package.json +1 -1
- package/dist/vanillajs/auth/OAuthCallbackHandler.d.ts.map +0 -1
- package/dist/vanillajs/auth/OAuthCallbackHandler.js +0 -143
- package/dist/vanillajs/auth/OAuthCallbackHandler.js.map +0 -1
|
@@ -1,105 +1,55 @@
|
|
|
1
1
|
import { AuthEvent } from "../types/index.js";
|
|
2
|
-
import { LocalStorageAdapter } from "../../browser/storage.js";
|
|
3
|
-
import { handleOAuthRedirectPage } from "./OAuthCallbackHandler.js";
|
|
4
2
|
import { buildAuthUrl } from "../utils/auth-utils.js";
|
|
5
|
-
import {
|
|
6
|
-
import { SignalObserver } from "../iframe/SignalObserver.js";
|
|
3
|
+
import { createMainLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
|
|
7
4
|
import { GenericPublicClientPKCEProducer } from "../../services/PKCE.js";
|
|
8
5
|
import { generateState } from "../../lib/oauth.js";
|
|
9
6
|
import { SessionManager } from "./SessionManager.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
}
|
|
7
|
+
import { PopupError } from "../../services/types.js";
|
|
8
|
+
import { handleOAuthRedirectPage } from "./handlers/OAuthCallbackHandler.js";
|
|
9
|
+
import { generateOauthLogoutUrl, clearTokens, retrieveTokens, } from "../../shared/lib/util.js";
|
|
10
|
+
import { CivicAuthError, CivicAuthErrorCode, CIVIC_AUTH_CONSTANTS, } from "./types/AuthTypes.js";
|
|
11
|
+
import { processConfigWithDefaults } from "./config/ConfigProcessor.js";
|
|
12
|
+
import { MessageHandler } from "./handlers/MessageHandler.js";
|
|
13
|
+
import { PopupHandler } from "./handlers/PopupHandler.js";
|
|
14
|
+
import { IframeAuthHandler } from "./handlers/IframeAuthHandler.js";
|
|
60
15
|
/**
|
|
61
16
|
* CivicAuth client for handling OAuth authentication
|
|
17
|
+
*
|
|
18
|
+
* This is a refactored version that uses a modular architecture for better maintainability.
|
|
62
19
|
*/
|
|
63
20
|
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
|
-
*/
|
|
71
21
|
config;
|
|
72
|
-
iframeElement;
|
|
73
|
-
observer;
|
|
74
|
-
authPromise;
|
|
75
|
-
authPromiseResolve;
|
|
76
|
-
authPromiseReject;
|
|
77
|
-
authProcessTimeoutHandle;
|
|
78
|
-
messageEventHandler;
|
|
79
22
|
storage;
|
|
80
23
|
endpoints;
|
|
81
24
|
logger;
|
|
82
25
|
sessionManager;
|
|
83
|
-
|
|
26
|
+
initialDisplayMode;
|
|
27
|
+
// Authentication state
|
|
28
|
+
authPromise;
|
|
29
|
+
authPromiseResolve;
|
|
30
|
+
authPromiseReject;
|
|
31
|
+
authProcessTimeoutHandle;
|
|
32
|
+
popupFailureTimeoutHandle;
|
|
33
|
+
hasPopupFailed = false;
|
|
34
|
+
// Handlers
|
|
35
|
+
messageHandler;
|
|
36
|
+
popupHandler;
|
|
37
|
+
iframeAuthHandler;
|
|
84
38
|
/**
|
|
85
|
-
* Private constructor
|
|
39
|
+
* Private constructor - initializes configuration and handlers.
|
|
86
40
|
* 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
|
|
90
41
|
*/
|
|
91
42
|
constructor(config) {
|
|
92
|
-
// Process config with defaults
|
|
43
|
+
// Process config with defaults and validation
|
|
93
44
|
this.config = processConfigWithDefaults(config);
|
|
94
|
-
|
|
45
|
+
this.initialDisplayMode = this.config.displayMode;
|
|
46
|
+
// Configure logging
|
|
95
47
|
configureLogging(this.config.logging);
|
|
96
|
-
// Initialize logger
|
|
48
|
+
// Initialize logger - always use "vanillajs" as base namespace
|
|
97
49
|
if (this.config.logging?.enabled) {
|
|
98
|
-
|
|
99
|
-
this.logger = createLogger(namespace);
|
|
50
|
+
this.logger = createMainLogger("vanillajs"); // Always use "vanillajs"
|
|
100
51
|
}
|
|
101
52
|
else {
|
|
102
|
-
// Create a no-op logger when logging is disabled
|
|
103
53
|
this.logger = {
|
|
104
54
|
debug: () => { },
|
|
105
55
|
info: () => { },
|
|
@@ -107,26 +57,14 @@ export class CivicAuth {
|
|
|
107
57
|
error: () => { },
|
|
108
58
|
};
|
|
109
59
|
}
|
|
110
|
-
// Set this logger as the current logger
|
|
111
60
|
setCurrentLogger(this.logger);
|
|
112
|
-
// Use the storage adapter from processed config (guaranteed to be present)
|
|
113
61
|
this.storage = this.config.storageAdapter;
|
|
114
62
|
// Initialize SessionManager if events are provided
|
|
115
63
|
if (config.events) {
|
|
116
64
|
this.sessionManager = new SessionManager(this.storage, config.events);
|
|
117
65
|
}
|
|
118
|
-
//
|
|
119
|
-
|
|
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);
|
|
66
|
+
// Initialize handlers
|
|
67
|
+
this.initializeHandlers();
|
|
130
68
|
}
|
|
131
69
|
/**
|
|
132
70
|
* Creates and initializes a new instance of CivicAuth.
|
|
@@ -140,9 +78,12 @@ export class CivicAuth {
|
|
|
140
78
|
* ```typescript
|
|
141
79
|
* const auth = await CivicAuth.create({
|
|
142
80
|
* clientId: "your-client-id",
|
|
143
|
-
* redirectUrl
|
|
144
|
-
*
|
|
145
|
-
*
|
|
81
|
+
* // redirectUrl is optional - defaults to current page (window.location.origin + window.location.pathname)
|
|
82
|
+
* redirectUrl: "https://your-app.com/callback", // optional
|
|
83
|
+
* // oauthServerBaseUrl is optional - defaults to "https://auth.civic.com/oauth/"
|
|
84
|
+
* oauthServerBaseUrl: "https://auth-server.com/", // optional
|
|
85
|
+
* // scopes is optional - defaults to ['openid', 'profile', 'email', 'offline_access']
|
|
86
|
+
* scopes: ["openid", "profile"], // optional
|
|
146
87
|
* targetContainerElement: "auth-container",
|
|
147
88
|
* textSignals: {
|
|
148
89
|
* success: "Authentication successful!"
|
|
@@ -157,11 +98,16 @@ export class CivicAuth {
|
|
|
157
98
|
}
|
|
158
99
|
/**
|
|
159
100
|
* Initializes the auth client and checks for callback handling
|
|
160
|
-
* @throws {CivicAuthError} If initialization fails
|
|
161
101
|
*/
|
|
162
102
|
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
|
+
});
|
|
163
109
|
try {
|
|
164
|
-
// Get OAuth endpoints
|
|
110
|
+
// Get OAuth endpoints
|
|
165
111
|
this.endpoints = {
|
|
166
112
|
auth: `${this.config.oauthServerBaseUrl}auth`,
|
|
167
113
|
token: `${this.config.oauthServerBaseUrl}token`,
|
|
@@ -169,7 +115,10 @@ export class CivicAuth {
|
|
|
169
115
|
userinfo: `${this.config.oauthServerBaseUrl}userinfo`,
|
|
170
116
|
endsession: `${this.config.oauthServerBaseUrl}endsession`,
|
|
171
117
|
};
|
|
172
|
-
|
|
118
|
+
this.logger.info("🔗 OAuth endpoints configured", {
|
|
119
|
+
endpoints: this.endpoints,
|
|
120
|
+
});
|
|
121
|
+
// Initialize SessionManager with auth config
|
|
173
122
|
if (this.sessionManager) {
|
|
174
123
|
const authConfig = {
|
|
175
124
|
clientId: this.config.clientId,
|
|
@@ -178,18 +127,32 @@ export class CivicAuth {
|
|
|
178
127
|
scopes: this.config.scopes,
|
|
179
128
|
endpoints: this.endpoints,
|
|
180
129
|
};
|
|
130
|
+
this.logger.info("🔧 Initializing SessionManager", { authConfig });
|
|
181
131
|
await this.sessionManager.initializeWithAuthConfig(authConfig);
|
|
182
132
|
}
|
|
183
133
|
// Check if we're on the callback page
|
|
184
|
-
|
|
134
|
+
const isCallbackPage = window.location.href.startsWith(this.config.redirectUrl);
|
|
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");
|
|
185
142
|
await this.handleCallback();
|
|
186
143
|
}
|
|
144
|
+
else {
|
|
145
|
+
this.logger.info("🏠 Not a callback page, initialization complete");
|
|
146
|
+
}
|
|
187
147
|
}
|
|
188
148
|
catch (error) {
|
|
189
149
|
const errorMessage = error instanceof Error
|
|
190
150
|
? error.message
|
|
191
151
|
: "Failed to initialize authentication";
|
|
192
|
-
this.logger.error("
|
|
152
|
+
this.logger.error("❌ CivicAuth initialization failed", {
|
|
153
|
+
error: errorMessage,
|
|
154
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
155
|
+
});
|
|
193
156
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
194
157
|
detail: errorMessage,
|
|
195
158
|
});
|
|
@@ -197,82 +160,33 @@ export class CivicAuth {
|
|
|
197
160
|
}
|
|
198
161
|
}
|
|
199
162
|
/**
|
|
200
|
-
*
|
|
201
|
-
* @returns The container element or null if not found
|
|
202
|
-
*/
|
|
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
|
|
163
|
+
* Initialize all handlers with proper configuration
|
|
217
164
|
*/
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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";
|
|
165
|
+
initializeHandlers() {
|
|
166
|
+
const handlerConfig = {
|
|
167
|
+
config: this.config,
|
|
168
|
+
logger: this.logger,
|
|
169
|
+
onAuthSuccess: this.handleAuthSuccess.bind(this),
|
|
170
|
+
onAuthError: this.handleAuthError.bind(this),
|
|
171
|
+
cleanup: this.cleanup.bind(this),
|
|
172
|
+
};
|
|
173
|
+
this.messageHandler = new MessageHandler({
|
|
174
|
+
...handlerConfig,
|
|
175
|
+
onPopupFailure: this.handlePopupFailure.bind(this),
|
|
176
|
+
});
|
|
177
|
+
this.popupHandler = new PopupHandler(handlerConfig);
|
|
178
|
+
this.iframeAuthHandler = new IframeAuthHandler({
|
|
179
|
+
...handlerConfig,
|
|
180
|
+
messageHandler: this.messageHandler.handleMessage,
|
|
181
|
+
});
|
|
265
182
|
}
|
|
266
183
|
/**
|
|
267
184
|
* Builds the authentication URL with PKCE challenge
|
|
268
|
-
* @returns The complete authentication URL
|
|
269
|
-
* @throws {CivicAuthError} If endpoints are not initialized
|
|
270
185
|
*/
|
|
271
186
|
async buildAuthUrl() {
|
|
272
187
|
if (!this.endpoints) {
|
|
273
188
|
throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
274
189
|
}
|
|
275
|
-
// Use storage directly since it's now AuthStorage
|
|
276
190
|
const pkceProducer = new GenericPublicClientPKCEProducer(this.storage);
|
|
277
191
|
const codeChallenge = await pkceProducer.getCodeChallenge();
|
|
278
192
|
const state = this.config.initialState ||
|
|
@@ -287,293 +201,265 @@ export class CivicAuth {
|
|
|
287
201
|
codeChallenge,
|
|
288
202
|
state,
|
|
289
203
|
prompt: this.config.prompt,
|
|
204
|
+
nonce: this.config.nonce,
|
|
290
205
|
});
|
|
291
206
|
}
|
|
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
|
-
}
|
|
481
207
|
/**
|
|
482
208
|
* Starts the authentication process
|
|
483
209
|
* @returns A promise that resolves with the authentication result
|
|
484
210
|
* @throws {CivicAuthError} If authentication fails or times out
|
|
485
211
|
*/
|
|
486
212
|
async startAuthentication() {
|
|
213
|
+
this.logger.info("🎬 Starting authentication process", {
|
|
214
|
+
displayMode: this.config.displayMode,
|
|
215
|
+
userAgent: navigator.userAgent,
|
|
216
|
+
currentUrl: window.location.href,
|
|
217
|
+
});
|
|
487
218
|
if (!this.endpoints) {
|
|
488
219
|
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
|
+
});
|
|
489
223
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
490
224
|
detail: error.message,
|
|
491
225
|
});
|
|
492
226
|
throw error;
|
|
493
227
|
}
|
|
494
228
|
if (this.authPromise) {
|
|
495
|
-
this.logger.info("Authentication already in progress, returning existing promise");
|
|
229
|
+
this.logger.info("⏳ Authentication already in progress, returning existing promise");
|
|
496
230
|
return this.authPromise;
|
|
497
231
|
}
|
|
498
232
|
const fullAuthUrl = await this.buildAuthUrl();
|
|
499
|
-
this.logger.info("
|
|
500
|
-
|
|
233
|
+
this.logger.info("🔗 Built authentication URL", {
|
|
234
|
+
url: fullAuthUrl,
|
|
501
235
|
displayMode: this.config.displayMode,
|
|
502
236
|
authProcessTimeout: this.config.authProcessTimeout,
|
|
503
237
|
});
|
|
504
238
|
this.authPromise = new Promise((resolve, reject) => {
|
|
505
239
|
this.authPromiseResolve = resolve;
|
|
506
240
|
this.authPromiseReject = reject;
|
|
507
|
-
|
|
241
|
+
this.handleAuthenticationByDisplayMode(fullAuthUrl);
|
|
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 {
|
|
508
254
|
switch (this.config.displayMode) {
|
|
509
255
|
case "redirect":
|
|
510
|
-
|
|
256
|
+
this.logger.info("🌐 Using redirect mode");
|
|
511
257
|
window.location.href = fullAuthUrl;
|
|
512
258
|
break;
|
|
513
259
|
case "new_tab":
|
|
514
|
-
this.
|
|
260
|
+
this.logger.info("📱 Using new_tab mode");
|
|
261
|
+
if (!this.popupHandler) {
|
|
262
|
+
throw new Error("Popup handler not initialized");
|
|
263
|
+
}
|
|
264
|
+
await this.popupHandler.handleNewTabAuth(fullAuthUrl);
|
|
515
265
|
break;
|
|
516
266
|
case "iframe":
|
|
517
|
-
default:
|
|
518
|
-
this.
|
|
267
|
+
default: {
|
|
268
|
+
this.logger.info("🖼️ Using iframe mode");
|
|
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);
|
|
519
274
|
break;
|
|
275
|
+
}
|
|
520
276
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
this.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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);
|
|
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)));
|
|
539
284
|
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Handle popup error with redirect fallback
|
|
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,
|
|
540
294
|
});
|
|
541
|
-
|
|
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
|
+
}
|
|
542
321
|
}
|
|
543
322
|
/**
|
|
544
|
-
*
|
|
323
|
+
* Setup authentication timeout
|
|
545
324
|
*/
|
|
546
|
-
|
|
547
|
-
this.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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,
|
|
335
|
+
authProcessTimeout: this.config.authProcessTimeout,
|
|
336
|
+
});
|
|
337
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
338
|
+
detail: "Authentication timed out",
|
|
339
|
+
});
|
|
340
|
+
const error = new CivicAuthError("Authentication timed out", CivicAuthErrorCode.AUTH_PROCESS_TIMEOUT);
|
|
341
|
+
this.handleAuthError(error);
|
|
342
|
+
}, this.config.authProcessTimeout);
|
|
552
343
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Handle successful authentication
|
|
347
|
+
*/
|
|
348
|
+
handleAuthSuccess(result) {
|
|
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,
|
|
368
|
+
});
|
|
369
|
+
// Clean up iframe if it exists
|
|
370
|
+
if (this.iframeAuthHandler) {
|
|
371
|
+
this.iframeAuthHandler.cleanupIframe();
|
|
556
372
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
this.iframeManager = undefined;
|
|
373
|
+
// Always redirect to the failed URL or build a new one
|
|
374
|
+
if (failedUrl) {
|
|
375
|
+
window.location.href = failedUrl;
|
|
561
376
|
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
+
});
|
|
565
389
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Show popup failure message to user
|
|
393
|
+
*/
|
|
394
|
+
showPopupFailureMessage(customMessage = "Authentication will continue in this window. Please wait...") {
|
|
395
|
+
const container = this.getContainerElement();
|
|
396
|
+
if (!container) {
|
|
397
|
+
this.logger.warn("Cannot show popup failure message - container not found");
|
|
398
|
+
return;
|
|
570
399
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
400
|
+
const existingMessage = document.getElementById("civic-auth-popup-failure-message");
|
|
401
|
+
if (existingMessage && existingMessage.parentNode) {
|
|
402
|
+
existingMessage.parentNode.removeChild(existingMessage);
|
|
403
|
+
}
|
|
404
|
+
const messageOverlay = document.createElement("div");
|
|
405
|
+
messageOverlay.id = "civic-auth-popup-failure-message";
|
|
406
|
+
messageOverlay.style.cssText =
|
|
407
|
+
"position:absolute;top:0;left:0;right:0;background:rgba(255,249,196,.95);border:1px solid #f59e0b;border-radius:6px;padding:12px;margin:8px;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;color:#92400e;z-index:1000;box-shadow:0 2px 4px rgba(0,0,0,.1);";
|
|
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";
|
|
420
|
+
}
|
|
421
|
+
container.appendChild(messageOverlay);
|
|
422
|
+
setTimeout(() => {
|
|
423
|
+
if (messageOverlay.parentNode) {
|
|
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
|
|
575
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;
|
|
452
|
+
}
|
|
453
|
+
return this.config.targetContainerElement ?? null;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Handle OAuth callback
|
|
457
|
+
*/
|
|
576
458
|
async handleCallback() {
|
|
459
|
+
this.logger.info("🔄 Handling OAuth callback", {
|
|
460
|
+
currentUrl: window.location.href,
|
|
461
|
+
redirectUrl: this.config.redirectUrl,
|
|
462
|
+
});
|
|
577
463
|
try {
|
|
578
464
|
const callbackHandled = await handleOAuthRedirectPage({
|
|
579
465
|
clientId: this.config.clientId,
|
|
@@ -585,10 +471,12 @@ export class CivicAuth {
|
|
|
585
471
|
},
|
|
586
472
|
storageAdapter: this.storage,
|
|
587
473
|
});
|
|
474
|
+
this.logger.info("📋 Callback processing result", { callbackHandled });
|
|
588
475
|
if (callbackHandled) {
|
|
589
|
-
const successSignal = document.getElementById(
|
|
590
|
-
const errorSignal = document.getElementById(
|
|
476
|
+
const successSignal = document.getElementById(CIVIC_AUTH_CONSTANTS.SUCCESS_SIGNAL_ID);
|
|
477
|
+
const errorSignal = document.getElementById(CIVIC_AUTH_CONSTANTS.ERROR_SIGNAL_ID);
|
|
591
478
|
if (successSignal) {
|
|
479
|
+
this.logger.info("✅ Success signal found");
|
|
592
480
|
let userInfo = null;
|
|
593
481
|
const userInfoAttr = successSignal.getAttribute("data-user-info");
|
|
594
482
|
if (userInfoAttr) {
|
|
@@ -596,7 +484,7 @@ export class CivicAuth {
|
|
|
596
484
|
userInfo = JSON.parse(userInfoAttr);
|
|
597
485
|
}
|
|
598
486
|
catch (error) {
|
|
599
|
-
this.logger.error("Failed to parse user info
|
|
487
|
+
this.logger.error("❌ Failed to parse user info", { error });
|
|
600
488
|
}
|
|
601
489
|
}
|
|
602
490
|
this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
|
|
@@ -605,6 +493,7 @@ export class CivicAuth {
|
|
|
605
493
|
});
|
|
606
494
|
}
|
|
607
495
|
else if (errorSignal) {
|
|
496
|
+
this.logger.error("❌ Error signal found");
|
|
608
497
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
609
498
|
detail: errorSignal.textContent || "Unknown error during callback",
|
|
610
499
|
});
|
|
@@ -612,6 +501,7 @@ export class CivicAuth {
|
|
|
612
501
|
}
|
|
613
502
|
}
|
|
614
503
|
catch (error) {
|
|
504
|
+
this.logger.error("❌ Callback handling failed", { error });
|
|
615
505
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
616
506
|
detail: error instanceof Error
|
|
617
507
|
? error.message
|
|
@@ -619,6 +509,32 @@ export class CivicAuth {
|
|
|
619
509
|
});
|
|
620
510
|
}
|
|
621
511
|
}
|
|
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
|
+
}
|
|
622
538
|
/**
|
|
623
539
|
* Get the current session
|
|
624
540
|
*/
|
|
@@ -685,5 +601,66 @@ export class CivicAuth {
|
|
|
685
601
|
this.sessionManager = undefined;
|
|
686
602
|
this.logger.info("CivicAuth destroyed");
|
|
687
603
|
}
|
|
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
|
+
}
|
|
688
664
|
}
|
|
665
|
+
export { CivicAuthErrorCode } from "./types/AuthTypes.js";
|
|
689
666
|
//# sourceMappingURL=CivicAuth.js.map
|