@civic/auth 0.6.0 → 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/components/CivicAuthIframeContainer.js +1 -1
- package/dist/shared/components/CivicAuthIframeContainer.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/lib/BrowserAuthenticationRefresher.d.ts +7 -1
- package/dist/shared/lib/BrowserAuthenticationRefresher.d.ts.map +1 -1
- package/dist/shared/lib/BrowserAuthenticationRefresher.js +15 -2
- package/dist/shared/lib/BrowserAuthenticationRefresher.js.map +1 -1
- package/dist/shared/lib/util.d.ts +1 -1
- package/dist/shared/lib/util.d.ts.map +1 -1
- package/dist/shared/lib/util.js +6 -1
- package/dist/shared/lib/util.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.d.ts.map +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 +111 -92
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +474 -321
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/SessionManager.d.ts +40 -9
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +96 -61
- package/dist/vanillajs/auth/SessionManager.js.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.d.ts +54 -0
- package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -0
- package/dist/vanillajs/auth/TokenRefresher.js +166 -0
- package/dist/vanillajs/auth/TokenRefresher.js.map +1 -0
- 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/handlers/OAuthCallbackHandler.d.ts +90 -0
- 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 +115 -0
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -0
- package/dist/vanillajs/iframe/IframeManager.js +614 -0
- package/dist/vanillajs/iframe/IframeManager.js.map +1 -0
- package/dist/vanillajs/iframe/IframeResizer.d.ts +15 -0
- package/dist/vanillajs/iframe/IframeResizer.d.ts.map +1 -0
- package/dist/vanillajs/iframe/IframeResizer.js +127 -0
- package/dist/vanillajs/iframe/IframeResizer.js.map +1 -0
- package/dist/vanillajs/index.d.ts +8 -7
- package/dist/vanillajs/index.d.ts.map +1 -1
- package/dist/vanillajs/index.js +10 -7
- 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 +17 -12
- 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/ui/LoadingComponents.d.ts +51 -0
- package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -0
- package/dist/vanillajs/ui/LoadingComponents.js +363 -0
- package/dist/vanillajs/ui/LoadingComponents.js.map +1 -0
- 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/iframe/domUtils.d.ts +0 -4
- package/dist/vanillajs/iframe/domUtils.d.ts.map +0 -1
- package/dist/vanillajs/iframe/domUtils.js +0 -25
- package/dist/vanillajs/iframe/domUtils.js.map +0 -1
- package/dist/vanillajs/storage/BrowserCookieStorageAdapter.d.ts +0 -19
- package/dist/vanillajs/storage/BrowserCookieStorageAdapter.d.ts.map +0 -1
- package/dist/vanillajs/storage/BrowserCookieStorageAdapter.js +0 -101
- package/dist/vanillajs/storage/BrowserCookieStorageAdapter.js.map +0 -1
- package/dist/vanillajs/storage/BrowserLocalStorageAdapter.d.ts +0 -9
- package/dist/vanillajs/storage/BrowserLocalStorageAdapter.d.ts.map +0 -1
- package/dist/vanillajs/storage/BrowserLocalStorageAdapter.js +0 -36
- package/dist/vanillajs/storage/BrowserLocalStorageAdapter.js.map +0 -1
- package/dist/vanillajs/storage/InMemoryStorageAdapter.d.ts +0 -9
- package/dist/vanillajs/storage/InMemoryStorageAdapter.d.ts.map +0 -1
- package/dist/vanillajs/storage/InMemoryStorageAdapter.js +0 -16
- package/dist/vanillajs/storage/InMemoryStorageAdapter.js.map +0 -1
- package/dist/vanillajs/storage/StorageAdapter.d.ts +0 -15
- package/dist/vanillajs/storage/StorageAdapter.d.ts.map +0 -1
- package/dist/vanillajs/storage/StorageAdapter.js +0 -16
- package/dist/vanillajs/storage/StorageAdapter.js.map +0 -1
- package/dist/vanillajs/utils/page-handlers.d.ts +0 -29
- package/dist/vanillajs/utils/page-handlers.d.ts.map +0 -1
- package/dist/vanillajs/utils/page-handlers.js +0 -165
- package/dist/vanillajs/utils/page-handlers.js.map +0 -1
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { AuthEvent } from "../../types/index.js";
|
|
2
|
+
import { CivicAuthError, CivicAuthErrorCode } from "../types/AuthTypes.js";
|
|
3
|
+
import { SignalObserver } from "../../iframe/SignalObserver.js";
|
|
4
|
+
import { IframeManager } from "../../iframe/IframeManager.js";
|
|
5
|
+
import { createLogger as createLoggerFn } from "../../utils/logger.js";
|
|
6
|
+
export class IframeAuthHandler {
|
|
7
|
+
config;
|
|
8
|
+
logger = createLoggerFn("iframe-auth");
|
|
9
|
+
onAuthSuccess;
|
|
10
|
+
onAuthError;
|
|
11
|
+
cleanup;
|
|
12
|
+
messageHandler;
|
|
13
|
+
iframeManager;
|
|
14
|
+
iframeElement;
|
|
15
|
+
signalObserver;
|
|
16
|
+
constructor(handlerConfig) {
|
|
17
|
+
this.config = handlerConfig.config;
|
|
18
|
+
this.onAuthSuccess = handlerConfig.onAuthSuccess;
|
|
19
|
+
this.onAuthError = handlerConfig.onAuthError;
|
|
20
|
+
this.cleanup = handlerConfig.cleanup;
|
|
21
|
+
this.messageHandler = handlerConfig.messageHandler;
|
|
22
|
+
}
|
|
23
|
+
async handleIframeAuth(fullAuthUrl) {
|
|
24
|
+
// Determine the actual display mode for IframeManager first
|
|
25
|
+
const iframeDisplayMode = this.determineIframeDisplayMode();
|
|
26
|
+
let container = this.getContainerElement();
|
|
27
|
+
// For modal mode, if no container is provided, create one dynamically
|
|
28
|
+
if (iframeDisplayMode === "modal" && !container) {
|
|
29
|
+
container = this.createModalContainer();
|
|
30
|
+
}
|
|
31
|
+
if (!container) {
|
|
32
|
+
const error = new CivicAuthError("Target container element not found.", CivicAuthErrorCode.CONTAINER_NOT_FOUND);
|
|
33
|
+
this.logger.error(error.message);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
this.logger.debug("Creating iframe with modal backdrop", {
|
|
37
|
+
url: fullAuthUrl,
|
|
38
|
+
containerId: container?.id,
|
|
39
|
+
iframeId: this.config.iframeId,
|
|
40
|
+
origin: window.location.origin,
|
|
41
|
+
iframeDisplayMode,
|
|
42
|
+
containerCreated: iframeDisplayMode === "modal" && !this.config.targetContainerElement,
|
|
43
|
+
});
|
|
44
|
+
this.logger.debug(`🎯 CivicAuth: Creating IframeManager with display mode: ${iframeDisplayMode}`);
|
|
45
|
+
// Create IframeManager with appropriate display mode
|
|
46
|
+
this.iframeManager = new IframeManager({
|
|
47
|
+
container: container,
|
|
48
|
+
displayMode: iframeDisplayMode,
|
|
49
|
+
iframeId: this.config.iframeId,
|
|
50
|
+
/**
|
|
51
|
+
* Handles iframe closure events initiated by the user.
|
|
52
|
+
* This includes backdrop clicks, close button clicks, or Escape key presses.
|
|
53
|
+
* Emits an error event and cleans up the authentication process.
|
|
54
|
+
*/
|
|
55
|
+
onClose: () => {
|
|
56
|
+
this.logger.debug("Authentication close requested by user (backdrop click, close button, or Escape key)");
|
|
57
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
58
|
+
detail: "Authentication cancelled by user",
|
|
59
|
+
});
|
|
60
|
+
const error = new CivicAuthError("Authentication cancelled by user", CivicAuthErrorCode.USER_CANCELLED);
|
|
61
|
+
this.onAuthError(error);
|
|
62
|
+
this.cleanup();
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
// Create the iframe using IframeManager
|
|
66
|
+
this.iframeElement = this.iframeManager.createIframe(fullAuthUrl);
|
|
67
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_STARTED, {
|
|
68
|
+
detail: "Iframe created with modal backdrop",
|
|
69
|
+
});
|
|
70
|
+
this.setupIframeEventHandlers();
|
|
71
|
+
this.setupIframeNavigationMonitoring();
|
|
72
|
+
return this.iframeElement;
|
|
73
|
+
}
|
|
74
|
+
getIframeManager() {
|
|
75
|
+
return this.iframeManager;
|
|
76
|
+
}
|
|
77
|
+
getIframeElement() {
|
|
78
|
+
return this.iframeElement;
|
|
79
|
+
}
|
|
80
|
+
cleanupIframe() {
|
|
81
|
+
if (this.iframeManager) {
|
|
82
|
+
this.logger.debug("Cleaning up iframe manager");
|
|
83
|
+
this.iframeManager.cleanup();
|
|
84
|
+
this.iframeManager = undefined;
|
|
85
|
+
}
|
|
86
|
+
if (this.iframeElement) {
|
|
87
|
+
this.iframeElement = undefined;
|
|
88
|
+
}
|
|
89
|
+
// Clean up dynamically created modal containers
|
|
90
|
+
this.cleanupDynamicModalContainer();
|
|
91
|
+
}
|
|
92
|
+
cleanupDynamicModalContainer() {
|
|
93
|
+
// Only clean up containers we created dynamically (not user-provided ones)
|
|
94
|
+
if (!this.config.targetContainerElement) {
|
|
95
|
+
const dynamicContainer = document.querySelector(`[data-civic-auth-modal="true"]#${this.config.iframeId}-modal-container`);
|
|
96
|
+
if (dynamicContainer && dynamicContainer.parentNode) {
|
|
97
|
+
this.logger.debug("Removing dynamic modal container", {
|
|
98
|
+
containerId: dynamicContainer.id,
|
|
99
|
+
});
|
|
100
|
+
dynamicContainer.parentNode.removeChild(dynamicContainer);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
createModalContainer() {
|
|
105
|
+
const container = document.createElement("div");
|
|
106
|
+
container.id = `${this.config.iframeId}-modal-container`;
|
|
107
|
+
container.setAttribute("data-civic-auth-modal", "true");
|
|
108
|
+
// Append to body for modal overlay
|
|
109
|
+
document.body.appendChild(container);
|
|
110
|
+
this.logger.debug("Created dynamic modal container", {
|
|
111
|
+
containerId: container.id,
|
|
112
|
+
});
|
|
113
|
+
return container;
|
|
114
|
+
}
|
|
115
|
+
getContainerElement() {
|
|
116
|
+
if (!this.config.targetContainerElement) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
if (typeof this.config.targetContainerElement === "string") {
|
|
120
|
+
const element = document.getElementById(this.config.targetContainerElement);
|
|
121
|
+
if (!element) {
|
|
122
|
+
this.logger.warn(`Container element with ID "${this.config.targetContainerElement}" not found`);
|
|
123
|
+
}
|
|
124
|
+
return element;
|
|
125
|
+
}
|
|
126
|
+
return this.config.targetContainerElement;
|
|
127
|
+
}
|
|
128
|
+
determineIframeDisplayMode() {
|
|
129
|
+
// Priority 1: Explicit iframeDisplayMode setting from config
|
|
130
|
+
// This is the most specific instruction for how the iframe itself should be styled.
|
|
131
|
+
if (this.config.iframeDisplayMode) {
|
|
132
|
+
this.logger.debug(`Using configured iframeDisplayMode: ${this.config.iframeDisplayMode}`);
|
|
133
|
+
return this.config.iframeDisplayMode;
|
|
134
|
+
}
|
|
135
|
+
// Priority 2: If iframeDisplayMode is NOT set, and the overall displayMode is "iframe",
|
|
136
|
+
// default the iframe's own rendering style to "modal" (user-friendly default).
|
|
137
|
+
// To get an embedded iframe, iframeDisplayMode: "embedded" should be set explicitly.
|
|
138
|
+
if (this.config.displayMode === "iframe") {
|
|
139
|
+
this.logger.debug("Overall displayMode is 'iframe' and iframeDisplayMode is not set, defaulting iframe style to 'modal'.");
|
|
140
|
+
return "modal";
|
|
141
|
+
}
|
|
142
|
+
// Fallback for unexpected scenarios or if IframeAuthHandler is invoked with other displayModes.
|
|
143
|
+
this.logger.warn(`determineIframeDisplayMode called with overall displayMode: '${this.config.displayMode}' ` +
|
|
144
|
+
`and no explicit iframeDisplayMode. Defaulting iframe style to 'modal'.`);
|
|
145
|
+
return "modal";
|
|
146
|
+
}
|
|
147
|
+
setupIframeEventHandlers() {
|
|
148
|
+
if (!this.iframeElement)
|
|
149
|
+
return;
|
|
150
|
+
this.iframeElement.onload = () => {
|
|
151
|
+
this.logger.info("Iframe loaded", {
|
|
152
|
+
iframeSrc: this.iframeElement?.src,
|
|
153
|
+
currentOrigin: window.location.origin,
|
|
154
|
+
expectedAuthServerOrigin: new URL(this.config.oauthServerBaseUrl)
|
|
155
|
+
.origin,
|
|
156
|
+
});
|
|
157
|
+
if (!this.iframeElement?.contentWindow) {
|
|
158
|
+
const errorMsg = "Iframe content window not available after load.";
|
|
159
|
+
this.logger.error(errorMsg, {
|
|
160
|
+
iframeSrc: this.iframeElement?.src,
|
|
161
|
+
});
|
|
162
|
+
const error = new Error(errorMsg);
|
|
163
|
+
this.onAuthError(error);
|
|
164
|
+
this.cleanup();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
// Set up postMessage listener for cross-origin communication
|
|
168
|
+
window.addEventListener("message", this.messageHandler);
|
|
169
|
+
this.logger.info("Added cross-origin message event listener for auth server communication", {
|
|
170
|
+
parentOrigin: window.location.origin,
|
|
171
|
+
authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
|
|
172
|
+
});
|
|
173
|
+
// Hide iframe content if it's not the login app
|
|
174
|
+
this.checkAndHideNonLoginContent();
|
|
175
|
+
// Try to detect redirect to our domain
|
|
176
|
+
this.checkIframeRedirect();
|
|
177
|
+
};
|
|
178
|
+
this.iframeElement.onerror = (event) => {
|
|
179
|
+
this.logger.error("Iframe load error", {
|
|
180
|
+
event,
|
|
181
|
+
iframeSrc: this.iframeElement?.src,
|
|
182
|
+
currentOrigin: window.location.origin,
|
|
183
|
+
});
|
|
184
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
185
|
+
detail: "Iframe load error",
|
|
186
|
+
error: event,
|
|
187
|
+
});
|
|
188
|
+
const error = new Error("Iframe failed to load.");
|
|
189
|
+
this.onAuthError(error);
|
|
190
|
+
this.cleanup();
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
checkAndHideNonLoginContent() {
|
|
194
|
+
try {
|
|
195
|
+
const currentUrl = this.iframeElement?.contentWindow?.location.href;
|
|
196
|
+
if (currentUrl) {
|
|
197
|
+
const currentOrigin = new URL(currentUrl).origin;
|
|
198
|
+
const expectedAuthServerOrigin = this.config.oauthServerBaseUrl
|
|
199
|
+
? new URL(this.config.oauthServerBaseUrl).origin
|
|
200
|
+
: null;
|
|
201
|
+
const isOnAuthServer = expectedAuthServerOrigin
|
|
202
|
+
? currentOrigin === expectedAuthServerOrigin
|
|
203
|
+
: false;
|
|
204
|
+
const isCallbackUrl = this.config.redirectUrl
|
|
205
|
+
? currentUrl.startsWith(this.config.redirectUrl)
|
|
206
|
+
: false;
|
|
207
|
+
if (isOnAuthServer && !isCallbackUrl) {
|
|
208
|
+
this.logger.info("👀 Showing iframe content - confirmed login app on auth server origin", {
|
|
209
|
+
currentUrl,
|
|
210
|
+
isOnAuthServer,
|
|
211
|
+
isCallbackUrl,
|
|
212
|
+
currentOrigin,
|
|
213
|
+
expectedAuthServerOrigin,
|
|
214
|
+
});
|
|
215
|
+
if (this.iframeManager) {
|
|
216
|
+
this.iframeManager.forceHideLoader();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.logger.info("🙈 Masking iframe content with loader - not login app or on callback URL", {
|
|
221
|
+
currentUrl,
|
|
222
|
+
isOnAuthServer,
|
|
223
|
+
isCallbackUrl,
|
|
224
|
+
currentOrigin,
|
|
225
|
+
expectedAuthServerOrigin,
|
|
226
|
+
reason: !isOnAuthServer
|
|
227
|
+
? expectedAuthServerOrigin
|
|
228
|
+
? "not on auth server (origin mismatch)"
|
|
229
|
+
: "auth server origin unknown"
|
|
230
|
+
: "on callback URL (or origin mismatch for login page)",
|
|
231
|
+
});
|
|
232
|
+
if (this.iframeManager) {
|
|
233
|
+
this.iframeManager.forceShowLoader();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
this.logger.debug("Cannot access iframe URL (likely cross-origin) - assuming login app, showing content.", { error: error instanceof Error ? error.message : String(error) });
|
|
240
|
+
if (this.iframeManager) {
|
|
241
|
+
this.iframeManager.forceHideLoader();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
checkIframeRedirect() {
|
|
246
|
+
try {
|
|
247
|
+
const currentIframeHref = this.iframeElement?.contentWindow?.location.href;
|
|
248
|
+
if (currentIframeHref) {
|
|
249
|
+
this.logger.debug("Iframe current href accessible", {
|
|
250
|
+
href: currentIframeHref,
|
|
251
|
+
redirectUrl: this.config.redirectUrl,
|
|
252
|
+
startsWithRedirect: currentIframeHref.startsWith(this.config.redirectUrl),
|
|
253
|
+
});
|
|
254
|
+
if (currentIframeHref.startsWith(this.config.redirectUrl)) {
|
|
255
|
+
this.logger.info("Iframe has navigated to redirectUrl (same-origin). Setting up DOM observer.");
|
|
256
|
+
// Hide content since we're on callback page now
|
|
257
|
+
this.checkAndHideNonLoginContent();
|
|
258
|
+
// Set up signal observer for same-origin callback page
|
|
259
|
+
if (this.iframeElement?.contentDocument &&
|
|
260
|
+
this.iframeElement.contentDocument.body) {
|
|
261
|
+
this.setupSignalObserver(this.iframeElement.contentDocument);
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
this.logger.warn("Iframe content document or body not available for signal observer");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
this.logger.debug("Error checking iframe href (expected for cross-origin)", {
|
|
271
|
+
error: error instanceof Error ? error.message : String(error),
|
|
272
|
+
iframeSrc: this.iframeElement?.src,
|
|
273
|
+
});
|
|
274
|
+
// This is expected when the iframe is on the auth server domain
|
|
275
|
+
this.logger.info("Iframe is on auth server domain - using postMessage for communication", {
|
|
276
|
+
parentOrigin: window.location.origin,
|
|
277
|
+
authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
setupSignalObserver(iframeDoc) {
|
|
282
|
+
const signalObserver = new SignalObserver({
|
|
283
|
+
textSignals: this.config.textSignals,
|
|
284
|
+
events: this.config.events,
|
|
285
|
+
logger: this.logger,
|
|
286
|
+
}, this.onAuthSuccess, (error) => this.onAuthError(error || new Error("Signal observer error")), () => this.cleanup());
|
|
287
|
+
signalObserver.setup(iframeDoc);
|
|
288
|
+
}
|
|
289
|
+
setupIframeNavigationMonitoring() {
|
|
290
|
+
// Monitor iframe navigation to detect when it redirects to our callback URL
|
|
291
|
+
let monitoringInterval = undefined;
|
|
292
|
+
let lastKnownUrl = "";
|
|
293
|
+
const checkIframeNavigation = () => {
|
|
294
|
+
if (!this.iframeElement?.contentWindow) {
|
|
295
|
+
if (monitoringInterval) {
|
|
296
|
+
clearInterval(monitoringInterval);
|
|
297
|
+
}
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const currentUrl = this.iframeElement.contentWindow.location.href;
|
|
302
|
+
if (currentUrl !== lastKnownUrl) {
|
|
303
|
+
lastKnownUrl = currentUrl;
|
|
304
|
+
this.logger.debug("Iframe navigation detected", {
|
|
305
|
+
newUrl: currentUrl,
|
|
306
|
+
redirectUrl: this.config.redirectUrl,
|
|
307
|
+
isCallbackUrl: currentUrl.startsWith(this.config.redirectUrl),
|
|
308
|
+
});
|
|
309
|
+
// Hide content if not on login app
|
|
310
|
+
this.checkAndHideNonLoginContent();
|
|
311
|
+
// Check if iframe has navigated to our callback URL
|
|
312
|
+
if (currentUrl.startsWith(this.config.redirectUrl)) {
|
|
313
|
+
this.logger.info("Iframe navigated to callback URL - setting up signal observer");
|
|
314
|
+
if (monitoringInterval) {
|
|
315
|
+
clearInterval(monitoringInterval);
|
|
316
|
+
}
|
|
317
|
+
// Set up signal observer for same-origin callback page
|
|
318
|
+
if (this.iframeElement.contentDocument &&
|
|
319
|
+
this.iframeElement.contentDocument.body) {
|
|
320
|
+
this.setupSignalObserver(this.iframeElement.contentDocument);
|
|
321
|
+
}
|
|
322
|
+
// Also check for URL parameters (code, error) in case of direct callback
|
|
323
|
+
this.processCallbackUrl(currentUrl);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Expected when iframe is on different origin
|
|
329
|
+
// Only log if we haven't seen this before
|
|
330
|
+
if (lastKnownUrl !== "cross-origin") {
|
|
331
|
+
lastKnownUrl = "cross-origin";
|
|
332
|
+
this.logger.debug("Iframe on cross-origin domain (expected during auth flow)");
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
// Check immediately and then every 500ms
|
|
337
|
+
checkIframeNavigation();
|
|
338
|
+
monitoringInterval = window.setInterval(checkIframeNavigation, 500);
|
|
339
|
+
// Store cleanup function to clear monitoring
|
|
340
|
+
const originalCleanup = this.cleanup;
|
|
341
|
+
this.cleanup = () => {
|
|
342
|
+
if (monitoringInterval) {
|
|
343
|
+
clearInterval(monitoringInterval);
|
|
344
|
+
}
|
|
345
|
+
originalCleanup();
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
processCallbackUrl(currentUrl) {
|
|
349
|
+
const urlParams = new URLSearchParams(new URL(currentUrl).search);
|
|
350
|
+
const code = urlParams.get("code");
|
|
351
|
+
const error = urlParams.get("error");
|
|
352
|
+
if (code) {
|
|
353
|
+
this.logger.info("Authorization code detected in iframe URL", {
|
|
354
|
+
code: code.substring(0, 10) + "...",
|
|
355
|
+
hasState: !!urlParams.get("state"),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (error) {
|
|
359
|
+
this.logger.error("OAuth error detected in iframe URL", {
|
|
360
|
+
error,
|
|
361
|
+
errorDescription: urlParams.get("error_description"),
|
|
362
|
+
});
|
|
363
|
+
const authError = new CivicAuthError(`OAuth error: ${error}`, CivicAuthErrorCode.INVALID_MESSAGE);
|
|
364
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
365
|
+
detail: authError.message,
|
|
366
|
+
error: urlParams.get("error_description") || error,
|
|
367
|
+
});
|
|
368
|
+
this.onAuthError(authError);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
navigateIframe(url) {
|
|
372
|
+
if (!this.iframeElement) {
|
|
373
|
+
this.logger.error("Cannot navigate iframe, iframeElement is not available.");
|
|
374
|
+
this.onAuthError(new CivicAuthError("Iframe element not found for navigation.", CivicAuthErrorCode.IFRAME_NOT_FOUND));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (!this.iframeManager) {
|
|
378
|
+
this.logger.error("Cannot navigate iframe, iframeManager is not available.");
|
|
379
|
+
this.onAuthError(new CivicAuthError("Iframe manager not found for navigation.", CivicAuthErrorCode.INTERNAL_ERROR));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
this.logger.info("Navigating iframe to new URL", { url });
|
|
383
|
+
this.iframeElement.src = url;
|
|
384
|
+
// After changing src, existing onload/onmessage handlers in IframeManager
|
|
385
|
+
// and navigation monitoring in this class should manage visibility and state.
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=IframeAuthHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IframeAuthHandler.js","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/IframeAuthHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthEvent } from \"../../types/index.js\";\nimport type { AuthResult } from \"../../types/index.js\";\nimport type { ProcessedCivicAuthConfig } from \"../types/AuthTypes.js\";\nimport { CivicAuthError, CivicAuthErrorCode } from \"../types/AuthTypes.js\";\nimport { SignalObserver } from \"../../iframe/SignalObserver.js\";\nimport { IframeManager } from \"../../iframe/IframeManager.js\";\nimport type { createLogger } from \"../../utils/logger.js\";\nimport { createLogger as createLoggerFn } from \"../../utils/logger.js\";\n\nexport interface IframeAuthHandlerConfig {\n config: ProcessedCivicAuthConfig;\n logger: ReturnType<typeof createLogger>;\n onAuthSuccess: (result: AuthResult) => void;\n onAuthError: (error: Error) => void;\n cleanup: () => void;\n messageHandler: (event: MessageEvent) => void;\n}\n\nexport class IframeAuthHandler {\n private config: ProcessedCivicAuthConfig;\n private logger = createLoggerFn(\"iframe-auth\");\n private onAuthSuccess: (result: AuthResult) => void;\n private onAuthError: (error: Error) => void;\n private cleanup: () => void;\n private messageHandler: (event: MessageEvent) => void;\n private iframeManager?: IframeManager;\n private iframeElement?: HTMLIFrameElement;\n private signalObserver?: SignalObserver;\n\n constructor(handlerConfig: IframeAuthHandlerConfig) {\n this.config = handlerConfig.config;\n this.onAuthSuccess = handlerConfig.onAuthSuccess;\n this.onAuthError = handlerConfig.onAuthError;\n this.cleanup = handlerConfig.cleanup;\n this.messageHandler = handlerConfig.messageHandler;\n }\n\n public async handleIframeAuth(\n fullAuthUrl: string,\n ): Promise<HTMLIFrameElement> {\n // Determine the actual display mode for IframeManager first\n const iframeDisplayMode = this.determineIframeDisplayMode();\n\n let container = this.getContainerElement();\n\n // For modal mode, if no container is provided, create one dynamically\n if (iframeDisplayMode === \"modal\" && !container) {\n container = this.createModalContainer();\n }\n\n if (!container) {\n const error = new CivicAuthError(\n \"Target container element not found.\",\n CivicAuthErrorCode.CONTAINER_NOT_FOUND,\n );\n this.logger.error(error.message);\n throw error;\n }\n\n this.logger.debug(\"Creating iframe with modal backdrop\", {\n url: fullAuthUrl,\n containerId: container?.id,\n iframeId: this.config.iframeId,\n origin: window.location.origin,\n iframeDisplayMode,\n containerCreated:\n iframeDisplayMode === \"modal\" && !this.config.targetContainerElement,\n });\n\n this.logger.debug(\n `🎯 CivicAuth: Creating IframeManager with display mode: ${iframeDisplayMode}`,\n );\n\n // Create IframeManager with appropriate display mode\n this.iframeManager = new IframeManager({\n container: container,\n displayMode: iframeDisplayMode,\n iframeId: this.config.iframeId,\n /**\n * Handles iframe closure events initiated by the user.\n * This includes backdrop clicks, close button clicks, or Escape key presses.\n * Emits an error event and cleans up the authentication process.\n */\n onClose: () => {\n this.logger.debug(\n \"Authentication close requested by user (backdrop click, close button, or Escape key)\",\n );\n this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {\n detail: \"Authentication cancelled by user\",\n });\n\n const error = new CivicAuthError(\n \"Authentication cancelled by user\",\n CivicAuthErrorCode.USER_CANCELLED,\n );\n\n this.onAuthError(error);\n this.cleanup();\n },\n });\n\n // Create the iframe using IframeManager\n this.iframeElement = this.iframeManager.createIframe(fullAuthUrl);\n\n this.config.events?.emit(AuthEvent.SIGN_IN_STARTED, {\n detail: \"Iframe created with modal backdrop\",\n });\n\n this.setupIframeEventHandlers();\n this.setupIframeNavigationMonitoring();\n\n return this.iframeElement;\n }\n\n public getIframeManager(): IframeManager | undefined {\n return this.iframeManager;\n }\n\n public getIframeElement(): HTMLIFrameElement | undefined {\n return this.iframeElement;\n }\n\n public cleanupIframe(): void {\n if (this.iframeManager) {\n this.logger.debug(\"Cleaning up iframe manager\");\n this.iframeManager.cleanup();\n this.iframeManager = undefined;\n }\n\n if (this.iframeElement) {\n this.iframeElement = undefined;\n }\n\n // Clean up dynamically created modal containers\n this.cleanupDynamicModalContainer();\n }\n\n private cleanupDynamicModalContainer(): void {\n // Only clean up containers we created dynamically (not user-provided ones)\n if (!this.config.targetContainerElement) {\n const dynamicContainer = document.querySelector(\n `[data-civic-auth-modal=\"true\"]#${this.config.iframeId}-modal-container`,\n );\n\n if (dynamicContainer && dynamicContainer.parentNode) {\n this.logger.debug(\"Removing dynamic modal container\", {\n containerId: dynamicContainer.id,\n });\n dynamicContainer.parentNode.removeChild(dynamicContainer);\n }\n }\n }\n\n private createModalContainer(): HTMLElement {\n const container = document.createElement(\"div\");\n container.id = `${this.config.iframeId}-modal-container`;\n container.setAttribute(\"data-civic-auth-modal\", \"true\");\n\n // Append to body for modal overlay\n document.body.appendChild(container);\n\n this.logger.debug(\"Created dynamic modal container\", {\n containerId: container.id,\n });\n\n return container;\n }\n\n private getContainerElement(): HTMLElement | null {\n if (!this.config.targetContainerElement) {\n return null;\n }\n\n if (typeof this.config.targetContainerElement === \"string\") {\n const element = document.getElementById(\n this.config.targetContainerElement,\n );\n if (!element) {\n this.logger.warn(\n `Container element with ID \"${this.config.targetContainerElement}\" not found`,\n );\n }\n return element;\n }\n return this.config.targetContainerElement;\n }\n\n private determineIframeDisplayMode(): \"modal\" | \"embedded\" {\n // Priority 1: Explicit iframeDisplayMode setting from config\n // This is the most specific instruction for how the iframe itself should be styled.\n if (this.config.iframeDisplayMode) {\n this.logger.debug(\n `Using configured iframeDisplayMode: ${this.config.iframeDisplayMode}`,\n );\n return this.config.iframeDisplayMode;\n }\n\n // Priority 2: If iframeDisplayMode is NOT set, and the overall displayMode is \"iframe\",\n // default the iframe's own rendering style to \"modal\" (user-friendly default).\n // To get an embedded iframe, iframeDisplayMode: \"embedded\" should be set explicitly.\n if (this.config.displayMode === \"iframe\") {\n this.logger.debug(\n \"Overall displayMode is 'iframe' and iframeDisplayMode is not set, defaulting iframe style to 'modal'.\",\n );\n return \"modal\";\n }\n\n // Fallback for unexpected scenarios or if IframeAuthHandler is invoked with other displayModes.\n this.logger.warn(\n `determineIframeDisplayMode called with overall displayMode: '${this.config.displayMode}' ` +\n `and no explicit iframeDisplayMode. Defaulting iframe style to 'modal'.`,\n );\n return \"modal\";\n }\n\n private setupIframeEventHandlers(): void {\n if (!this.iframeElement) return;\n\n this.iframeElement.onload = () => {\n this.logger.info(\"Iframe loaded\", {\n iframeSrc: this.iframeElement?.src,\n currentOrigin: window.location.origin,\n expectedAuthServerOrigin: new URL(this.config.oauthServerBaseUrl)\n .origin,\n });\n\n if (!this.iframeElement?.contentWindow) {\n const errorMsg = \"Iframe content window not available after load.\";\n this.logger.error(errorMsg, {\n iframeSrc: this.iframeElement?.src,\n });\n\n const error = new Error(errorMsg);\n this.onAuthError(error);\n this.cleanup();\n return;\n }\n\n // Set up postMessage listener for cross-origin communication\n window.addEventListener(\"message\", this.messageHandler);\n this.logger.info(\n \"Added cross-origin message event listener for auth server communication\",\n {\n parentOrigin: window.location.origin,\n authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,\n },\n );\n\n // Hide iframe content if it's not the login app\n this.checkAndHideNonLoginContent();\n\n // Try to detect redirect to our domain\n this.checkIframeRedirect();\n };\n\n this.iframeElement.onerror = (event) => {\n this.logger.error(\"Iframe load error\", {\n event,\n iframeSrc: this.iframeElement?.src,\n currentOrigin: window.location.origin,\n });\n this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {\n detail: \"Iframe load error\",\n error: event,\n });\n\n const error = new Error(\"Iframe failed to load.\");\n this.onAuthError(error);\n this.cleanup();\n };\n }\n\n private checkAndHideNonLoginContent(): void {\n try {\n const currentUrl = this.iframeElement?.contentWindow?.location.href;\n if (currentUrl) {\n const currentOrigin = new URL(currentUrl).origin;\n const expectedAuthServerOrigin = this.config.oauthServerBaseUrl\n ? new URL(this.config.oauthServerBaseUrl).origin\n : null;\n const isOnAuthServer = expectedAuthServerOrigin\n ? currentOrigin === expectedAuthServerOrigin\n : false;\n const isCallbackUrl = this.config.redirectUrl\n ? currentUrl.startsWith(this.config.redirectUrl)\n : false;\n\n if (isOnAuthServer && !isCallbackUrl) {\n this.logger.info(\n \"👀 Showing iframe content - confirmed login app on auth server origin\",\n {\n currentUrl,\n isOnAuthServer,\n isCallbackUrl,\n currentOrigin,\n expectedAuthServerOrigin,\n },\n );\n if (this.iframeManager) {\n this.iframeManager.forceHideLoader();\n }\n } else {\n this.logger.info(\n \"🙈 Masking iframe content with loader - not login app or on callback URL\",\n {\n currentUrl,\n isOnAuthServer,\n isCallbackUrl,\n currentOrigin,\n expectedAuthServerOrigin,\n reason: !isOnAuthServer\n ? expectedAuthServerOrigin\n ? \"not on auth server (origin mismatch)\"\n : \"auth server origin unknown\"\n : \"on callback URL (or origin mismatch for login page)\",\n },\n );\n if (this.iframeManager) {\n this.iframeManager.forceShowLoader();\n }\n }\n }\n } catch (error) {\n this.logger.debug(\n \"Cannot access iframe URL (likely cross-origin) - assuming login app, showing content.\",\n { error: error instanceof Error ? error.message : String(error) },\n );\n if (this.iframeManager) {\n this.iframeManager.forceHideLoader();\n }\n }\n }\n\n private checkIframeRedirect(): void {\n try {\n const currentIframeHref =\n this.iframeElement?.contentWindow?.location.href;\n\n if (currentIframeHref) {\n this.logger.debug(\"Iframe current href accessible\", {\n href: currentIframeHref,\n redirectUrl: this.config.redirectUrl,\n startsWithRedirect: currentIframeHref.startsWith(\n this.config.redirectUrl,\n ),\n });\n\n if (currentIframeHref.startsWith(this.config.redirectUrl)) {\n this.logger.info(\n \"Iframe has navigated to redirectUrl (same-origin). Setting up DOM observer.\",\n );\n\n // Hide content since we're on callback page now\n this.checkAndHideNonLoginContent();\n\n // Set up signal observer for same-origin callback page\n if (\n this.iframeElement?.contentDocument &&\n this.iframeElement.contentDocument.body\n ) {\n this.setupSignalObserver(this.iframeElement.contentDocument);\n } else {\n this.logger.warn(\n \"Iframe content document or body not available for signal observer\",\n );\n }\n }\n }\n } catch (error) {\n this.logger.debug(\n \"Error checking iframe href (expected for cross-origin)\",\n {\n error: error instanceof Error ? error.message : String(error),\n iframeSrc: this.iframeElement?.src,\n },\n );\n // This is expected when the iframe is on the auth server domain\n this.logger.info(\n \"Iframe is on auth server domain - using postMessage for communication\",\n {\n parentOrigin: window.location.origin,\n authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,\n },\n );\n }\n }\n\n private setupSignalObserver(iframeDoc: Document): void {\n const signalObserver = new SignalObserver(\n {\n textSignals: this.config.textSignals,\n events: this.config.events,\n logger: this.logger,\n },\n this.onAuthSuccess,\n (error?: Error) =>\n this.onAuthError(error || new Error(\"Signal observer error\")),\n () => this.cleanup(),\n );\n\n signalObserver.setup(iframeDoc);\n }\n\n private setupIframeNavigationMonitoring(): void {\n // Monitor iframe navigation to detect when it redirects to our callback URL\n let monitoringInterval: number | undefined = undefined;\n let lastKnownUrl = \"\";\n\n const checkIframeNavigation = () => {\n if (!this.iframeElement?.contentWindow) {\n if (monitoringInterval) {\n clearInterval(monitoringInterval);\n }\n return;\n }\n\n try {\n const currentUrl = this.iframeElement.contentWindow.location.href;\n\n if (currentUrl !== lastKnownUrl) {\n lastKnownUrl = currentUrl;\n this.logger.debug(\"Iframe navigation detected\", {\n newUrl: currentUrl,\n redirectUrl: this.config.redirectUrl,\n isCallbackUrl: currentUrl.startsWith(this.config.redirectUrl),\n });\n\n // Hide content if not on login app\n this.checkAndHideNonLoginContent();\n\n // Check if iframe has navigated to our callback URL\n if (currentUrl.startsWith(this.config.redirectUrl)) {\n this.logger.info(\n \"Iframe navigated to callback URL - setting up signal observer\",\n );\n\n if (monitoringInterval) {\n clearInterval(monitoringInterval);\n }\n\n // Set up signal observer for same-origin callback page\n if (\n this.iframeElement.contentDocument &&\n this.iframeElement.contentDocument.body\n ) {\n this.setupSignalObserver(this.iframeElement.contentDocument);\n }\n\n // Also check for URL parameters (code, error) in case of direct callback\n this.processCallbackUrl(currentUrl);\n }\n }\n } catch {\n // Expected when iframe is on different origin\n // Only log if we haven't seen this before\n if (lastKnownUrl !== \"cross-origin\") {\n lastKnownUrl = \"cross-origin\";\n this.logger.debug(\n \"Iframe on cross-origin domain (expected during auth flow)\",\n );\n }\n }\n };\n\n // Check immediately and then every 500ms\n checkIframeNavigation();\n monitoringInterval = window.setInterval(checkIframeNavigation, 500);\n\n // Store cleanup function to clear monitoring\n const originalCleanup = this.cleanup;\n this.cleanup = () => {\n if (monitoringInterval) {\n clearInterval(monitoringInterval);\n }\n originalCleanup();\n };\n }\n\n private processCallbackUrl(currentUrl: string): void {\n const urlParams = new URLSearchParams(new URL(currentUrl).search);\n const code = urlParams.get(\"code\");\n const error = urlParams.get(\"error\");\n\n if (code) {\n this.logger.info(\"Authorization code detected in iframe URL\", {\n code: code.substring(0, 10) + \"...\",\n hasState: !!urlParams.get(\"state\"),\n });\n }\n\n if (error) {\n this.logger.error(\"OAuth error detected in iframe URL\", {\n error,\n errorDescription: urlParams.get(\"error_description\"),\n });\n\n const authError = new CivicAuthError(\n `OAuth error: ${error}`,\n CivicAuthErrorCode.INVALID_MESSAGE,\n );\n\n this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {\n detail: authError.message,\n error: urlParams.get(\"error_description\") || error,\n });\n\n this.onAuthError(authError);\n }\n }\n\n public navigateIframe(url: string): void {\n if (!this.iframeElement) {\n this.logger.error(\n \"Cannot navigate iframe, iframeElement is not available.\",\n );\n this.onAuthError(\n new CivicAuthError(\n \"Iframe element not found for navigation.\",\n CivicAuthErrorCode.IFRAME_NOT_FOUND,\n ),\n );\n return;\n }\n if (!this.iframeManager) {\n this.logger.error(\n \"Cannot navigate iframe, iframeManager is not available.\",\n );\n this.onAuthError(\n new CivicAuthError(\n \"Iframe manager not found for navigation.\",\n CivicAuthErrorCode.INTERNAL_ERROR,\n ),\n );\n return;\n }\n this.logger.info(\"Navigating iframe to new URL\", { url });\n this.iframeElement.src = url;\n // After changing src, existing onload/onmessage handlers in IframeManager\n // and navigation monitoring in this class should manage visibility and state.\n }\n}\n"]}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import type { AuthResult } from "../../types/index.js";
|
|
2
|
+
import type { ProcessedCivicAuthConfig } from "../types/AuthTypes.js";
|
|
3
|
+
import type { createLogger } from "../../utils/logger.js";
|
|
4
|
+
export interface MessageHandlerConfig {
|
|
5
|
+
config: ProcessedCivicAuthConfig;
|
|
6
|
+
logger: ReturnType<typeof createLogger>;
|
|
7
|
+
iframeElement?: HTMLIFrameElement;
|
|
8
|
+
onAuthSuccess: (result: AuthResult) => void;
|
|
9
|
+
onAuthError: (error: Error) => void;
|
|
10
|
+
onPopupFailure: (failedUrl?: string) => void;
|
|
11
|
+
cleanup: () => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* MessageHandler - Handles postMessage communication and authentication flow logic
|
|
15
|
+
* Processes messages from iframe, validates origins, and manages auth state transitions
|
|
16
|
+
*/
|
|
17
|
+
export declare class MessageHandler {
|
|
18
|
+
private config;
|
|
19
|
+
private logger;
|
|
20
|
+
private iframeElement?;
|
|
21
|
+
private onAuthSuccess;
|
|
22
|
+
private onAuthError;
|
|
23
|
+
private onPopupFailure;
|
|
24
|
+
private cleanup;
|
|
25
|
+
constructor(handlerConfig: MessageHandlerConfig);
|
|
26
|
+
/**
|
|
27
|
+
* Updates the iframe element reference used for message validation.
|
|
28
|
+
*
|
|
29
|
+
* This method allows updating the iframe element after the MessageHandler
|
|
30
|
+
* has been instantiated, which is useful when the iframe is created
|
|
31
|
+
* dynamically after the handler setup.
|
|
32
|
+
*
|
|
33
|
+
* @param iframeElement - The new iframe element to associate with this handler
|
|
34
|
+
*/
|
|
35
|
+
updateIframeElement(iframeElement: HTMLIFrameElement): void;
|
|
36
|
+
/**
|
|
37
|
+
* Main message handler for processing postMessage events.
|
|
38
|
+
*
|
|
39
|
+
* Validates message origin and source, then routes valid messages to
|
|
40
|
+
* appropriate handlers. This is the entry point for all iframe communication.
|
|
41
|
+
*
|
|
42
|
+
* @param event - The MessageEvent received from the iframe or other sources
|
|
43
|
+
*/
|
|
44
|
+
handleMessage: (event: MessageEvent) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Logs incoming message details for debugging purposes.
|
|
47
|
+
*
|
|
48
|
+
* Provides comprehensive logging of message properties including origin,
|
|
49
|
+
* source validation, and iframe state for troubleshooting communication issues.
|
|
50
|
+
*
|
|
51
|
+
* @param event - The MessageEvent to log
|
|
52
|
+
* @param expectedOrigin - The expected origin for comparison
|
|
53
|
+
*/
|
|
54
|
+
private logIncomingMessage;
|
|
55
|
+
/**
|
|
56
|
+
* Validates that a message comes from the expected origin and source.
|
|
57
|
+
*
|
|
58
|
+
* Performs security checks to ensure messages are only processed from
|
|
59
|
+
* the configured OAuth server origin and the designated iframe element.
|
|
60
|
+
*
|
|
61
|
+
* @param event - The MessageEvent to validate
|
|
62
|
+
* @param expectedOrigin - The expected origin URL for the message
|
|
63
|
+
* @returns True if the message source and origin are valid, false otherwise
|
|
64
|
+
*/
|
|
65
|
+
private isValidMessageSource;
|
|
66
|
+
/**
|
|
67
|
+
* Processes messages that have passed origin and source validation.
|
|
68
|
+
*
|
|
69
|
+
* Routes validated messages to specific handlers based on message type,
|
|
70
|
+
* including civicloginApp messages, iframe resizer messages, and standard auth messages.
|
|
71
|
+
*
|
|
72
|
+
* @param event - The validated MessageEvent to process
|
|
73
|
+
*/
|
|
74
|
+
private handleValidMessage;
|
|
75
|
+
/**
|
|
76
|
+
* Type guard to identify civicloginApp messages.
|
|
77
|
+
*
|
|
78
|
+
* Checks if a message object has the structure and source property
|
|
79
|
+
* that identifies it as coming from the civicloginApp.
|
|
80
|
+
*
|
|
81
|
+
* @param message - The message object to check
|
|
82
|
+
* @returns True if the message is a LoginAppMessage, false otherwise
|
|
83
|
+
*/
|
|
84
|
+
private isCivicLoginAppMessage;
|
|
85
|
+
/**
|
|
86
|
+
* Handles messages originating from the civicloginApp.
|
|
87
|
+
*
|
|
88
|
+
* Processes various civicloginApp message types including authentication errors,
|
|
89
|
+
* popup failures, design updates, and other app-specific communications.
|
|
90
|
+
* Validates client ID matches before processing.
|
|
91
|
+
*
|
|
92
|
+
* @param message - The validated civicloginApp message to process
|
|
93
|
+
*/
|
|
94
|
+
private handleCivicLoginAppMessage;
|
|
95
|
+
/**
|
|
96
|
+
* Handles authentication error messages from civicloginApp.
|
|
97
|
+
*
|
|
98
|
+
* Processes auth_error and auth_error_try_again messages, creates
|
|
99
|
+
* appropriate error objects, emits error events, and triggers cleanup.
|
|
100
|
+
*
|
|
101
|
+
* @param message - The civicloginApp error message to process
|
|
102
|
+
*/
|
|
103
|
+
private handleCivicLoginAppError;
|
|
104
|
+
/**
|
|
105
|
+
* Handles popup generation failure messages from civicloginApp.
|
|
106
|
+
*
|
|
107
|
+
* Processes generatePopupFailed messages, extracts the failed URL,
|
|
108
|
+
* emits appropriate events, and triggers the popup failure callback
|
|
109
|
+
* to enable fallback authentication methods.
|
|
110
|
+
*
|
|
111
|
+
* @param message - The civicloginApp popup failure message to process
|
|
112
|
+
*/
|
|
113
|
+
private handlePopupFailure;
|
|
114
|
+
/**
|
|
115
|
+
* Handles unknown or unrecognized civicloginApp message types.
|
|
116
|
+
*
|
|
117
|
+
* Provides fallback handling for unexpected message types, with special
|
|
118
|
+
* logic to detect potential success messages that don't match standard types.
|
|
119
|
+
*
|
|
120
|
+
* @param message - The unrecognized civicloginApp message to process
|
|
121
|
+
*/
|
|
122
|
+
private handleUnknownCivicLoginAppMessage;
|
|
123
|
+
/**
|
|
124
|
+
* Type guard to identify iframe resizer messages.
|
|
125
|
+
*
|
|
126
|
+
* Checks if a message is related to iframe resizing functionality,
|
|
127
|
+
* including both civic-specific resize messages and iFrameResizerChild messages.
|
|
128
|
+
*
|
|
129
|
+
* @param message - The message object to check
|
|
130
|
+
* @returns True if the message is an iframe resizer message, false otherwise
|
|
131
|
+
*/
|
|
132
|
+
private isIframeResizerMessage;
|
|
133
|
+
/**
|
|
134
|
+
* Handles iframe resizer messages for dynamic iframe sizing.
|
|
135
|
+
*
|
|
136
|
+
* Processes messages related to iframe resizing, including height adjustments
|
|
137
|
+
* and ready state notifications from the iframe resizer library.
|
|
138
|
+
*
|
|
139
|
+
* @param message - The iframe resizer message to process
|
|
140
|
+
*/
|
|
141
|
+
private handleIframeResizerMessage;
|
|
142
|
+
/**
|
|
143
|
+
* Handles standard authentication messages.
|
|
144
|
+
*
|
|
145
|
+
* Processes auth_success and auth_error messages that follow the standard
|
|
146
|
+
* authentication message format, routing them to appropriate success or error handlers.
|
|
147
|
+
*
|
|
148
|
+
* @param message - The standard auth message to process
|
|
149
|
+
*/
|
|
150
|
+
private handleStandardAuthMessage;
|
|
151
|
+
/**
|
|
152
|
+
* Handles successful authentication completion.
|
|
153
|
+
*
|
|
154
|
+
* Processes authentication success messages, emits success events,
|
|
155
|
+
* triggers the success callback with authentication results, and performs cleanup.
|
|
156
|
+
*
|
|
157
|
+
* @param data - The authentication success message containing result data
|
|
158
|
+
*/
|
|
159
|
+
private handleAuthSuccess;
|
|
160
|
+
/**
|
|
161
|
+
* Handles authentication errors.
|
|
162
|
+
*
|
|
163
|
+
* Processes authentication error messages, creates appropriate error objects,
|
|
164
|
+
* emits error events, triggers the error callback, and performs cleanup.
|
|
165
|
+
*
|
|
166
|
+
* @param data - The authentication error message containing error details
|
|
167
|
+
*/
|
|
168
|
+
private handleAuthError;
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=MessageHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageHandler.d.ts","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/MessageHandler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAIV,wBAAwB,EACzB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAG1D,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,wBAAwB,CAAC;IACjC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,aAAa,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IAC5C,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACpC,cAAc,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAqC;IACnD,OAAO,CAAC,aAAa,CAAC,CAAoB;IAC1C,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,OAAO,CAAa;gBAEhB,aAAa,EAAE,oBAAoB;IAS/C;;;;;;;;OAQG;IACI,mBAAmB,CAAC,aAAa,EAAE,iBAAiB,GAAG,IAAI;IAIlE;;;;;;;OAOG;IACI,aAAa,UAAW,YAAY,KAAG,IAAI,CAShD;IAEF;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;;;;;;;;OASG;IACH,OAAO,CAAC,oBAAoB;IA0B5B;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAoC1B;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAS9B;;;;;;;;OAQG;IACH,OAAO,CAAC,0BAA0B;IAmClC;;;;;;;OAOG;IACH,OAAO,CAAC,wBAAwB;IAoBhC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;;;;;;OAOG;IACH,OAAO,CAAC,iCAAiC;IAqBzC;;;;;;;;OAQG;IACH,OAAO,CAAC,sBAAsB;IAY9B;;;;;;;OAOG;IACH,OAAO,CAAC,0BAA0B;IAYlC;;;;;;;OAOG;IACH,OAAO,CAAC,yBAAyB;IA0BjC;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;CAcxB"}
|