@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
|
@@ -1,88 +1,55 @@
|
|
|
1
|
-
import { createIframe, removeIframe } from "../iframe/domUtils.js";
|
|
2
1
|
import { AuthEvent } from "../types/index.js";
|
|
3
|
-
import { StorageAdapterToAuthStorage, } from "../storage/StorageAdapter.js";
|
|
4
|
-
import { BrowserCookieStorageAdapter } from "../storage/BrowserCookieStorageAdapter.js";
|
|
5
|
-
import { handleOAuthRedirectPage } from "../utils/page-handlers.js";
|
|
6
2
|
import { buildAuthUrl } from "../utils/auth-utils.js";
|
|
7
|
-
import {
|
|
8
|
-
import { SignalObserver } from "../iframe/SignalObserver.js";
|
|
3
|
+
import { createMainLogger, configureLogging, setCurrentLogger, } from "../utils/logger.js";
|
|
9
4
|
import { GenericPublicClientPKCEProducer } from "../../services/PKCE.js";
|
|
10
5
|
import { generateState } from "../../lib/oauth.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
}
|
|
6
|
+
import { SessionManager } from "./SessionManager.js";
|
|
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";
|
|
41
15
|
/**
|
|
42
16
|
* CivicAuth client for handling OAuth authentication
|
|
17
|
+
*
|
|
18
|
+
* This is a refactored version that uses a modular architecture for better maintainability.
|
|
43
19
|
*/
|
|
44
20
|
export class CivicAuth {
|
|
45
21
|
config;
|
|
46
|
-
|
|
47
|
-
|
|
22
|
+
storage;
|
|
23
|
+
endpoints;
|
|
24
|
+
logger;
|
|
25
|
+
sessionManager;
|
|
26
|
+
initialDisplayMode;
|
|
27
|
+
// Authentication state
|
|
48
28
|
authPromise;
|
|
49
29
|
authPromiseResolve;
|
|
50
30
|
authPromiseReject;
|
|
51
31
|
authProcessTimeoutHandle;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
popupFailureTimeoutHandle;
|
|
33
|
+
hasPopupFailed = false;
|
|
34
|
+
// Handlers
|
|
35
|
+
messageHandler;
|
|
36
|
+
popupHandler;
|
|
37
|
+
iframeAuthHandler;
|
|
56
38
|
/**
|
|
57
|
-
* Private constructor
|
|
39
|
+
* Private constructor - initializes configuration and handlers.
|
|
58
40
|
* Use {@link CivicAuth.create} to create a new instance.
|
|
59
|
-
* @param config - Configuration options for the auth client
|
|
60
|
-
* @throws {CivicAuthError} If required configuration is missing
|
|
61
|
-
* @private
|
|
62
41
|
*/
|
|
63
42
|
constructor(config) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.config = {
|
|
71
|
-
displayMode: "iframe",
|
|
72
|
-
authProcessTimeout: config.authProcessTimeout || CONSTANTS.DEFAULT_AUTH_PROCESS_TIMEOUT,
|
|
73
|
-
iframeId: config.iframeId || CONSTANTS.DEFAULT_IFRAME_ID,
|
|
74
|
-
logging: loggingConfig,
|
|
75
|
-
...config,
|
|
76
|
-
};
|
|
77
|
-
// Configure logging based on config
|
|
78
|
-
configureLogging(loggingConfig);
|
|
79
|
-
// Initialize logger based on config
|
|
43
|
+
// Process config with defaults and validation
|
|
44
|
+
this.config = processConfigWithDefaults(config);
|
|
45
|
+
this.initialDisplayMode = this.config.displayMode;
|
|
46
|
+
// Configure logging
|
|
47
|
+
configureLogging(this.config.logging);
|
|
48
|
+
// Initialize logger - always use "vanillajs" as base namespace
|
|
80
49
|
if (this.config.logging?.enabled) {
|
|
81
|
-
|
|
82
|
-
this.logger = createLogger(namespace);
|
|
50
|
+
this.logger = createMainLogger("vanillajs"); // Always use "vanillajs"
|
|
83
51
|
}
|
|
84
52
|
else {
|
|
85
|
-
// Create a no-op logger when logging is disabled
|
|
86
53
|
this.logger = {
|
|
87
54
|
debug: () => { },
|
|
88
55
|
info: () => { },
|
|
@@ -90,21 +57,14 @@ export class CivicAuth {
|
|
|
90
57
|
error: () => { },
|
|
91
58
|
};
|
|
92
59
|
}
|
|
93
|
-
// Set this logger as the current logger
|
|
94
60
|
setCurrentLogger(this.logger);
|
|
95
|
-
this.storage = config.storageAdapter
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
{ key: "targetContainerElement", value: config.targetContainerElement },
|
|
100
|
-
{ key: "textSignals.success", value: config.textSignals?.success },
|
|
101
|
-
];
|
|
102
|
-
for (const { key, value } of requiredConfigs) {
|
|
103
|
-
if (!value) {
|
|
104
|
-
throw new CivicAuthError(`CivicAuth: ${key} is required.`, CivicAuthErrorCode.CONFIG_REQUIRED);
|
|
105
|
-
}
|
|
61
|
+
this.storage = this.config.storageAdapter;
|
|
62
|
+
// Initialize SessionManager if events are provided
|
|
63
|
+
if (config.events) {
|
|
64
|
+
this.sessionManager = new SessionManager(this.storage, config.events);
|
|
106
65
|
}
|
|
107
|
-
|
|
66
|
+
// Initialize handlers
|
|
67
|
+
this.initializeHandlers();
|
|
108
68
|
}
|
|
109
69
|
/**
|
|
110
70
|
* Creates and initializes a new instance of CivicAuth.
|
|
@@ -118,9 +78,12 @@ export class CivicAuth {
|
|
|
118
78
|
* ```typescript
|
|
119
79
|
* const auth = await CivicAuth.create({
|
|
120
80
|
* clientId: "your-client-id",
|
|
121
|
-
* redirectUrl
|
|
122
|
-
*
|
|
123
|
-
*
|
|
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
|
|
124
87
|
* targetContainerElement: "auth-container",
|
|
125
88
|
* textSignals: {
|
|
126
89
|
* success: "Authentication successful!"
|
|
@@ -135,11 +98,16 @@ export class CivicAuth {
|
|
|
135
98
|
}
|
|
136
99
|
/**
|
|
137
100
|
* Initializes the auth client and checks for callback handling
|
|
138
|
-
* @throws {CivicAuthError} If initialization fails
|
|
139
101
|
*/
|
|
140
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
|
+
});
|
|
141
109
|
try {
|
|
142
|
-
// Get OAuth endpoints
|
|
110
|
+
// Get OAuth endpoints
|
|
143
111
|
this.endpoints = {
|
|
144
112
|
auth: `${this.config.oauthServerBaseUrl}auth`,
|
|
145
113
|
token: `${this.config.oauthServerBaseUrl}token`,
|
|
@@ -147,16 +115,44 @@ export class CivicAuth {
|
|
|
147
115
|
userinfo: `${this.config.oauthServerBaseUrl}userinfo`,
|
|
148
116
|
endsession: `${this.config.oauthServerBaseUrl}endsession`,
|
|
149
117
|
};
|
|
118
|
+
this.logger.info("🔗 OAuth endpoints configured", {
|
|
119
|
+
endpoints: this.endpoints,
|
|
120
|
+
});
|
|
121
|
+
// Initialize SessionManager with auth config
|
|
122
|
+
if (this.sessionManager) {
|
|
123
|
+
const authConfig = {
|
|
124
|
+
clientId: this.config.clientId,
|
|
125
|
+
redirectUrl: this.config.redirectUrl,
|
|
126
|
+
oauthServer: this.config.oauthServerBaseUrl,
|
|
127
|
+
scopes: this.config.scopes,
|
|
128
|
+
endpoints: this.endpoints,
|
|
129
|
+
};
|
|
130
|
+
this.logger.info("🔧 Initializing SessionManager", { authConfig });
|
|
131
|
+
await this.sessionManager.initializeWithAuthConfig(authConfig);
|
|
132
|
+
}
|
|
150
133
|
// Check if we're on the callback page
|
|
151
|
-
|
|
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");
|
|
152
142
|
await this.handleCallback();
|
|
153
143
|
}
|
|
144
|
+
else {
|
|
145
|
+
this.logger.info("🏠 Not a callback page, initialization complete");
|
|
146
|
+
}
|
|
154
147
|
}
|
|
155
148
|
catch (error) {
|
|
156
149
|
const errorMessage = error instanceof Error
|
|
157
150
|
? error.message
|
|
158
151
|
: "Failed to initialize authentication";
|
|
159
|
-
this.logger.error("
|
|
152
|
+
this.logger.error("❌ CivicAuth initialization failed", {
|
|
153
|
+
error: errorMessage,
|
|
154
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
155
|
+
});
|
|
160
156
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
161
157
|
detail: errorMessage,
|
|
162
158
|
});
|
|
@@ -164,30 +160,34 @@ export class CivicAuth {
|
|
|
164
160
|
}
|
|
165
161
|
}
|
|
166
162
|
/**
|
|
167
|
-
*
|
|
168
|
-
* @returns The container element or null if not found
|
|
163
|
+
* Initialize all handlers with proper configuration
|
|
169
164
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
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
|
+
});
|
|
179
182
|
}
|
|
180
183
|
/**
|
|
181
184
|
* Builds the authentication URL with PKCE challenge
|
|
182
|
-
* @returns The complete authentication URL
|
|
183
|
-
* @throws {CivicAuthError} If endpoints are not initialized
|
|
184
185
|
*/
|
|
185
186
|
async buildAuthUrl() {
|
|
186
187
|
if (!this.endpoints) {
|
|
187
188
|
throw new CivicAuthError("OAuth endpoints not initialized. Please wait for initialization to complete.", CivicAuthErrorCode.ENDPOINTS_NOT_INITIALIZED);
|
|
188
189
|
}
|
|
189
|
-
const
|
|
190
|
-
const pkceProducer = new GenericPublicClientPKCEProducer(authStorage);
|
|
190
|
+
const pkceProducer = new GenericPublicClientPKCEProducer(this.storage);
|
|
191
191
|
const codeChallenge = await pkceProducer.getCodeChallenge();
|
|
192
192
|
const state = this.config.initialState ||
|
|
193
193
|
generateState({
|
|
@@ -201,270 +201,265 @@ export class CivicAuth {
|
|
|
201
201
|
codeChallenge,
|
|
202
202
|
state,
|
|
203
203
|
prompt: this.config.prompt,
|
|
204
|
+
nonce: this.config.nonce,
|
|
204
205
|
});
|
|
205
206
|
}
|
|
206
|
-
handleIframeMessage(event) {
|
|
207
|
-
const expectedOrigin = new URL(this.config.oauthServerBaseUrl).origin;
|
|
208
|
-
this.logIncomingMessage(event, expectedOrigin);
|
|
209
|
-
if (!this.isValidMessageSource(event, expectedOrigin)) {
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
this.handleValidMessage(event);
|
|
213
|
-
}
|
|
214
|
-
logIncomingMessage(event, expectedOrigin) {
|
|
215
|
-
this.logger.debug("Global window received message:", {
|
|
216
|
-
data: event.data,
|
|
217
|
-
origin: event.origin,
|
|
218
|
-
sourceProvided: !!event.source,
|
|
219
|
-
iframeContentWindow: this.iframeElement?.contentWindow,
|
|
220
|
-
expectedIframeOrigin: expectedOrigin,
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
isValidMessageSource(event, expectedOrigin) {
|
|
224
|
-
const isValidOrigin = event.origin === expectedOrigin;
|
|
225
|
-
const isValidSource = event.source === this.iframeElement?.contentWindow;
|
|
226
|
-
if (!isValidOrigin) {
|
|
227
|
-
this.logger.warn("Ignored message from unexpected origin.", {
|
|
228
|
-
receivedOrigin: event.origin,
|
|
229
|
-
expectedOrigin,
|
|
230
|
-
iframeSrc: this.iframeElement?.src,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
if (!isValidSource) {
|
|
234
|
-
this.logger.warn("Ignored message from unexpected source.", {
|
|
235
|
-
isSourceProvided: !!event.source,
|
|
236
|
-
isIframeContentWindowAvailable: !!this.iframeElement?.contentWindow,
|
|
237
|
-
iframeSrc: this.iframeElement?.src,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
return isValidOrigin && isValidSource;
|
|
241
|
-
}
|
|
242
|
-
handleValidMessage(event) {
|
|
243
|
-
this.logger.info("Message from configured iframe source and origin received", {
|
|
244
|
-
data: event.data,
|
|
245
|
-
iframeSrc: this.iframeElement?.src,
|
|
246
|
-
});
|
|
247
|
-
const message = event.data;
|
|
248
|
-
const messageType = message?.type;
|
|
249
|
-
switch (messageType) {
|
|
250
|
-
case "auth_success":
|
|
251
|
-
this.handleAuthSuccess(message);
|
|
252
|
-
break;
|
|
253
|
-
case "auth_error":
|
|
254
|
-
this.handleAuthError(message);
|
|
255
|
-
break;
|
|
256
|
-
default:
|
|
257
|
-
this.logger.debug("Message from iframe did not match expected types (auth_success, auth_error)", { data: event.data });
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
handleAuthSuccess(data) {
|
|
261
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
|
|
262
|
-
detail: "Success signal received via postMessage",
|
|
263
|
-
data,
|
|
264
|
-
});
|
|
265
|
-
this.authPromiseResolve?.(data?.data || {});
|
|
266
|
-
this.cleanup();
|
|
267
|
-
}
|
|
268
|
-
handleAuthError(data) {
|
|
269
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
270
|
-
detail: "Error signal received via postMessage",
|
|
271
|
-
error: data,
|
|
272
|
-
});
|
|
273
|
-
this.authPromiseReject?.(new CivicAuthError(data?.detail || "Error signal received via postMessage", CivicAuthErrorCode.INVALID_MESSAGE));
|
|
274
|
-
this.cleanup();
|
|
275
|
-
}
|
|
276
|
-
setupSignalObserver(iframeDoc) {
|
|
277
|
-
const signalObserver = new SignalObserver({
|
|
278
|
-
textSignals: this.config.textSignals,
|
|
279
|
-
events: this.config.events,
|
|
280
|
-
logger: this.logger,
|
|
281
|
-
}, this.authPromiseResolve, this.authPromiseReject, () => this.cleanup());
|
|
282
|
-
signalObserver.setup(iframeDoc);
|
|
283
|
-
}
|
|
284
|
-
async handleNewTabAuth(fullAuthUrl, reject) {
|
|
285
|
-
const popupWindow = window.open(fullAuthUrl, "_blank");
|
|
286
|
-
if (!popupWindow) {
|
|
287
|
-
const error = new CivicAuthError("Failed to open popup window. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
|
|
288
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
289
|
-
detail: error.message,
|
|
290
|
-
});
|
|
291
|
-
reject(error);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
async handleIframeAuth(fullAuthUrl, reject) {
|
|
295
|
-
const container = this.getContainerElement();
|
|
296
|
-
if (!container) {
|
|
297
|
-
const error = new CivicAuthError("Target container element not found.", CivicAuthErrorCode.CONTAINER_NOT_FOUND);
|
|
298
|
-
this.logger.error(error.message);
|
|
299
|
-
reject(error);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
this.logger.debug("Creating iframe", {
|
|
303
|
-
url: fullAuthUrl,
|
|
304
|
-
containerId: container?.id,
|
|
305
|
-
iframeId: this.config.iframeId,
|
|
306
|
-
origin: window.location.origin,
|
|
307
|
-
});
|
|
308
|
-
this.iframeElement = createIframe(fullAuthUrl, container, this.config.iframeId);
|
|
309
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_STARTED, {
|
|
310
|
-
detail: "Iframe created",
|
|
311
|
-
});
|
|
312
|
-
this.iframeElement.onload = () => {
|
|
313
|
-
this.logger.info("Iframe loaded", {
|
|
314
|
-
iframeSrc: this.iframeElement?.src,
|
|
315
|
-
currentOrigin: window.location.origin,
|
|
316
|
-
expectedAuthServerOrigin: new URL(this.config.oauthServerBaseUrl)
|
|
317
|
-
.origin,
|
|
318
|
-
});
|
|
319
|
-
if (!this.iframeElement?.contentWindow) {
|
|
320
|
-
const errorMsg = "Iframe content window not available after load.";
|
|
321
|
-
this.logger.error(errorMsg, {
|
|
322
|
-
iframeSrc: this.iframeElement?.src,
|
|
323
|
-
});
|
|
324
|
-
reject(new Error(errorMsg));
|
|
325
|
-
this.cleanup();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
// Set up postMessage listener for cross-origin communication
|
|
329
|
-
if (this.messageEventHandler) {
|
|
330
|
-
window.addEventListener("message", this.messageEventHandler);
|
|
331
|
-
this.logger.info("Added cross-origin message event listener for auth server communication", {
|
|
332
|
-
parentOrigin: window.location.origin,
|
|
333
|
-
authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
this.logger.error("messageEventHandler was not defined when trying to add listener.");
|
|
338
|
-
}
|
|
339
|
-
// Try to detect redirect to our domain
|
|
340
|
-
try {
|
|
341
|
-
const currentIframeHref = this.iframeElement.contentWindow.location.href;
|
|
342
|
-
if (currentIframeHref.startsWith(this.config.redirectUrl)) {
|
|
343
|
-
this.logger.info("Iframe has navigated to redirectUrl (same-origin). Setting up DOM observer.");
|
|
344
|
-
if (this.iframeElement.contentDocument &&
|
|
345
|
-
this.iframeElement.contentDocument.body) {
|
|
346
|
-
this.setupSignalObserver(this.iframeElement.contentDocument);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
catch (error) {
|
|
351
|
-
this.logger.error("Error checking iframe href", {
|
|
352
|
-
error,
|
|
353
|
-
iframeSrc: this.iframeElement?.src,
|
|
354
|
-
});
|
|
355
|
-
// This is expected when the iframe is on the auth server domain
|
|
356
|
-
this.logger.info("Iframe is on auth server domain - using postMessage for communication", {
|
|
357
|
-
parentOrigin: window.location.origin,
|
|
358
|
-
authServerOrigin: new URL(this.config.oauthServerBaseUrl).origin,
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
this.iframeElement.onerror = (event) => {
|
|
363
|
-
this.logger.error("Iframe load error", {
|
|
364
|
-
event,
|
|
365
|
-
iframeSrc: this.iframeElement?.src,
|
|
366
|
-
currentOrigin: window.location.origin,
|
|
367
|
-
});
|
|
368
|
-
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
369
|
-
detail: "Iframe load error",
|
|
370
|
-
error: event,
|
|
371
|
-
});
|
|
372
|
-
reject(new Error("Iframe failed to load."));
|
|
373
|
-
this.cleanup();
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
207
|
/**
|
|
377
208
|
* Starts the authentication process
|
|
378
209
|
* @returns A promise that resolves with the authentication result
|
|
379
210
|
* @throws {CivicAuthError} If authentication fails or times out
|
|
380
211
|
*/
|
|
381
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
|
+
});
|
|
382
218
|
if (!this.endpoints) {
|
|
383
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
|
+
});
|
|
384
223
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
385
224
|
detail: error.message,
|
|
386
225
|
});
|
|
387
226
|
throw error;
|
|
388
227
|
}
|
|
389
228
|
if (this.authPromise) {
|
|
390
|
-
this.logger.info("Authentication already in progress, returning existing promise");
|
|
229
|
+
this.logger.info("⏳ Authentication already in progress, returning existing promise");
|
|
391
230
|
return this.authPromise;
|
|
392
231
|
}
|
|
393
232
|
const fullAuthUrl = await this.buildAuthUrl();
|
|
394
|
-
this.logger.info("
|
|
395
|
-
|
|
233
|
+
this.logger.info("🔗 Built authentication URL", {
|
|
234
|
+
url: fullAuthUrl,
|
|
396
235
|
displayMode: this.config.displayMode,
|
|
397
236
|
authProcessTimeout: this.config.authProcessTimeout,
|
|
398
237
|
});
|
|
399
238
|
this.authPromise = new Promise((resolve, reject) => {
|
|
400
239
|
this.authPromiseResolve = resolve;
|
|
401
240
|
this.authPromiseReject = reject;
|
|
402
|
-
|
|
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 {
|
|
403
254
|
switch (this.config.displayMode) {
|
|
404
255
|
case "redirect":
|
|
405
|
-
|
|
256
|
+
this.logger.info("🌐 Using redirect mode");
|
|
406
257
|
window.location.href = fullAuthUrl;
|
|
407
258
|
break;
|
|
408
259
|
case "new_tab":
|
|
409
|
-
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);
|
|
410
265
|
break;
|
|
411
266
|
case "iframe":
|
|
412
|
-
default:
|
|
413
|
-
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);
|
|
414
274
|
break;
|
|
275
|
+
}
|
|
415
276
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
this.
|
|
420
|
-
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
if (error instanceof PopupError) {
|
|
280
|
+
await this.handlePopupErrorWithFallback(fullAuthUrl, error);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
this.handleAuthError(error instanceof Error ? error : new Error(String(error)));
|
|
284
|
+
}
|
|
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,
|
|
294
|
+
});
|
|
295
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
296
|
+
detail: "Popup blocked, falling back to redirect mode",
|
|
297
|
+
});
|
|
298
|
+
try {
|
|
299
|
+
this.logger.info("🔄 Attempting redirect fallback");
|
|
300
|
+
// Clean up current authentication attempt
|
|
301
|
+
this.cleanup();
|
|
302
|
+
// Always switch to redirect mode for Safari compatibility
|
|
303
|
+
this.config.displayMode = "redirect";
|
|
304
|
+
// Regenerate the auth URL with updated display mode in state
|
|
305
|
+
const fallbackAuthUrl = await this.buildAuthUrl();
|
|
306
|
+
this.logger.info("🌐 Redirecting to auth URL", { url: fallbackAuthUrl });
|
|
307
|
+
window.location.href = fallbackAuthUrl;
|
|
308
|
+
this.logger.info("✅ Redirect initiated successfully");
|
|
309
|
+
}
|
|
310
|
+
catch (redirectError) {
|
|
311
|
+
this.logger.error("❌ Redirect fallback failed", {
|
|
312
|
+
error: redirectError,
|
|
313
|
+
redirectUrl: fullAuthUrl,
|
|
314
|
+
});
|
|
315
|
+
const fallbackError = new CivicAuthError("Failed to open popup window and redirect fallback failed. Please check your browser's popup settings.", CivicAuthErrorCode.INIT_FAILED);
|
|
316
|
+
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
317
|
+
detail: fallbackError.message,
|
|
318
|
+
});
|
|
319
|
+
this.handleAuthError(fallbackError);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Setup authentication timeout
|
|
324
|
+
*/
|
|
325
|
+
setupAuthenticationTimeout() {
|
|
326
|
+
if (this.config.authProcessTimeout && this.config.authProcessTimeout > 0) {
|
|
327
|
+
this.logger.debug("⏰ Setting up authentication timeout", {
|
|
328
|
+
authProcessTimeout: this.config.authProcessTimeout,
|
|
329
|
+
displayMode: this.config.displayMode,
|
|
330
|
+
});
|
|
331
|
+
this.authProcessTimeoutHandle = window.setTimeout(() => {
|
|
332
|
+
this.logger.error("⏰ Authentication timed out", {
|
|
421
333
|
displayMode: this.config.displayMode,
|
|
334
|
+
currentOrigin: window.location.origin,
|
|
335
|
+
authProcessTimeout: this.config.authProcessTimeout,
|
|
422
336
|
});
|
|
423
|
-
this.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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);
|
|
343
|
+
}
|
|
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,
|
|
435
368
|
});
|
|
436
|
-
|
|
369
|
+
// Clean up iframe if it exists
|
|
370
|
+
if (this.iframeAuthHandler) {
|
|
371
|
+
this.iframeAuthHandler.cleanupIframe();
|
|
372
|
+
}
|
|
373
|
+
// Always redirect to the failed URL or build a new one
|
|
374
|
+
if (failedUrl) {
|
|
375
|
+
window.location.href = failedUrl;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
this.buildAuthUrl()
|
|
379
|
+
.then((authUrl) => {
|
|
380
|
+
window.location.href = authUrl;
|
|
381
|
+
})
|
|
382
|
+
.catch((error) => {
|
|
383
|
+
this.logger.error("Failed to build auth URL for redirect fallback", {
|
|
384
|
+
error,
|
|
385
|
+
});
|
|
386
|
+
const fallbackError = new CivicAuthError("Failed to redirect for authentication", CivicAuthErrorCode.INIT_FAILED);
|
|
387
|
+
this.handleAuthError(fallbackError);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
437
390
|
}
|
|
438
391
|
/**
|
|
439
|
-
*
|
|
392
|
+
* Show popup failure message to user
|
|
440
393
|
*/
|
|
441
|
-
|
|
442
|
-
this.
|
|
443
|
-
if (
|
|
444
|
-
this.logger.
|
|
445
|
-
|
|
446
|
-
this.observer = undefined;
|
|
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;
|
|
447
399
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
400
|
+
const existingMessage = document.getElementById("civic-auth-popup-failure-message");
|
|
401
|
+
if (existingMessage && existingMessage.parentNode) {
|
|
402
|
+
existingMessage.parentNode.removeChild(existingMessage);
|
|
451
403
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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";
|
|
456
420
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
|
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;
|
|
461
452
|
}
|
|
462
|
-
this.
|
|
463
|
-
this.authPromise = undefined;
|
|
464
|
-
this.authPromiseResolve = undefined;
|
|
465
|
-
this.authPromiseReject = undefined;
|
|
453
|
+
return this.config.targetContainerElement ?? null;
|
|
466
454
|
}
|
|
455
|
+
/**
|
|
456
|
+
* Handle OAuth callback
|
|
457
|
+
*/
|
|
467
458
|
async handleCallback() {
|
|
459
|
+
this.logger.info("🔄 Handling OAuth callback", {
|
|
460
|
+
currentUrl: window.location.href,
|
|
461
|
+
redirectUrl: this.config.redirectUrl,
|
|
462
|
+
});
|
|
468
463
|
try {
|
|
469
464
|
const callbackHandled = await handleOAuthRedirectPage({
|
|
470
465
|
clientId: this.config.clientId,
|
|
@@ -474,11 +469,14 @@ export class CivicAuth {
|
|
|
474
469
|
success: this.config.textSignals.success,
|
|
475
470
|
error: this.config.textSignals.error || "Authentication failed",
|
|
476
471
|
},
|
|
472
|
+
storageAdapter: this.storage,
|
|
477
473
|
});
|
|
474
|
+
this.logger.info("📋 Callback processing result", { callbackHandled });
|
|
478
475
|
if (callbackHandled) {
|
|
479
|
-
const successSignal = document.getElementById(
|
|
480
|
-
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);
|
|
481
478
|
if (successSignal) {
|
|
479
|
+
this.logger.info("✅ Success signal found");
|
|
482
480
|
let userInfo = null;
|
|
483
481
|
const userInfoAttr = successSignal.getAttribute("data-user-info");
|
|
484
482
|
if (userInfoAttr) {
|
|
@@ -486,7 +484,7 @@ export class CivicAuth {
|
|
|
486
484
|
userInfo = JSON.parse(userInfoAttr);
|
|
487
485
|
}
|
|
488
486
|
catch (error) {
|
|
489
|
-
this.logger.error("Failed to parse user info
|
|
487
|
+
this.logger.error("❌ Failed to parse user info", { error });
|
|
490
488
|
}
|
|
491
489
|
}
|
|
492
490
|
this.config.events?.emit(AuthEvent.SIGN_IN_COMPLETE, {
|
|
@@ -495,6 +493,7 @@ export class CivicAuth {
|
|
|
495
493
|
});
|
|
496
494
|
}
|
|
497
495
|
else if (errorSignal) {
|
|
496
|
+
this.logger.error("❌ Error signal found");
|
|
498
497
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
499
498
|
detail: errorSignal.textContent || "Unknown error during callback",
|
|
500
499
|
});
|
|
@@ -502,6 +501,7 @@ export class CivicAuth {
|
|
|
502
501
|
}
|
|
503
502
|
}
|
|
504
503
|
catch (error) {
|
|
504
|
+
this.logger.error("❌ Callback handling failed", { error });
|
|
505
505
|
this.config.events?.emit(AuthEvent.SIGN_IN_ERROR, {
|
|
506
506
|
detail: error instanceof Error
|
|
507
507
|
? error.message
|
|
@@ -509,5 +509,158 @@ export class CivicAuth {
|
|
|
509
509
|
});
|
|
510
510
|
}
|
|
511
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
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Get the current session
|
|
540
|
+
*/
|
|
541
|
+
async getCurrentSession() {
|
|
542
|
+
return this.sessionManager?.getCurrentSession() || null;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Check if user is authenticated
|
|
546
|
+
*/
|
|
547
|
+
async isAuthenticated() {
|
|
548
|
+
return this.sessionManager?.isAuthenticated() || false;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Get the current user
|
|
552
|
+
*/
|
|
553
|
+
async getCurrentUser() {
|
|
554
|
+
return this.sessionManager?.getCurrentUser() || null;
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Clear the current session
|
|
558
|
+
*/
|
|
559
|
+
async clearSession() {
|
|
560
|
+
if (!this.sessionManager) {
|
|
561
|
+
throw new Error("SessionManager not initialized. Provide events in config.");
|
|
562
|
+
}
|
|
563
|
+
return this.sessionManager.clearSession();
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Manually refresh tokens
|
|
567
|
+
*/
|
|
568
|
+
async refreshTokens() {
|
|
569
|
+
if (!this.sessionManager) {
|
|
570
|
+
throw new Error("SessionManager not initialized. Provide events in config.");
|
|
571
|
+
}
|
|
572
|
+
return this.sessionManager.refreshTokens();
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Get token refresher state for debugging
|
|
576
|
+
*/
|
|
577
|
+
getTokenRefresherState() {
|
|
578
|
+
return this.sessionManager?.getTokenRefresherState() || null;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Update the iframe display mode
|
|
582
|
+
* @param mode - The display mode to use for the iframe
|
|
583
|
+
*/
|
|
584
|
+
setIframeDisplayMode(mode) {
|
|
585
|
+
this.config.iframeDisplayMode = mode;
|
|
586
|
+
this.logger.debug(`Iframe display mode updated to: ${mode}`);
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get the current iframe display mode
|
|
590
|
+
* @returns The current iframe display mode
|
|
591
|
+
*/
|
|
592
|
+
getIframeDisplayMode() {
|
|
593
|
+
return this.config.iframeDisplayMode;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Destroy the auth client and clean up all resources
|
|
597
|
+
*/
|
|
598
|
+
async destroy() {
|
|
599
|
+
this.cleanup();
|
|
600
|
+
await this.sessionManager?.destroy();
|
|
601
|
+
this.sessionManager = undefined;
|
|
602
|
+
this.logger.info("CivicAuth destroyed");
|
|
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
|
+
}
|
|
512
664
|
}
|
|
665
|
+
export { CivicAuthErrorCode } from "./types/AuthTypes.js";
|
|
513
666
|
//# sourceMappingURL=CivicAuth.js.map
|