@civic/auth 0.8.2 → 0.9.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +9 -3
- package/dist/constants.d.ts +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/lib/oauth.d.ts +4 -2
- package/dist/lib/oauth.d.ts.map +1 -1
- package/dist/lib/oauth.js +4 -2
- package/dist/lib/oauth.js.map +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.d.ts +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.d.ts.map +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.js.map +1 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts +1 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts.map +1 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.js +3 -0
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.js.map +1 -1
- package/dist/nextjs/config.d.ts +3 -0
- package/dist/nextjs/config.d.ts.map +1 -1
- package/dist/nextjs/config.js +3 -0
- package/dist/nextjs/config.js.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.js +1 -1
- package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
- package/dist/nextjs/routeHandler.d.ts.map +1 -1
- package/dist/nextjs/routeHandler.js +2 -1
- package/dist/nextjs/routeHandler.js.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.d.ts +16 -0
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js +28 -1
- package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
- package/dist/reactjs/hooks/useUser.d.ts +3 -0
- package/dist/reactjs/hooks/useUser.d.ts.map +1 -1
- package/dist/reactjs/hooks/useUser.js +32 -0
- package/dist/reactjs/hooks/useUser.js.map +1 -1
- package/dist/reactjs/providers/CivicAuthContext.d.ts +4 -0
- package/dist/reactjs/providers/CivicAuthContext.d.ts.map +1 -1
- package/dist/reactjs/providers/CivicAuthContext.js +22 -13
- package/dist/reactjs/providers/CivicAuthContext.js.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.d.ts +2 -0
- package/dist/reactjs/providers/CivicAuthProvider.d.ts.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.js +5 -1
- package/dist/reactjs/providers/CivicAuthProvider.js.map +1 -1
- package/dist/server/config.d.ts +47 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/index.d.ts +8 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +5 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/login.d.ts +9 -0
- package/dist/server/login.d.ts.map +1 -1
- package/dist/server/login.js +4 -2
- package/dist/server/login.js.map +1 -1
- package/dist/server/refresh.d.ts +1 -1
- package/dist/server/refresh.d.ts.map +1 -1
- package/dist/server/refresh.js.map +1 -1
- package/dist/server/session.d.ts +60 -2
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +216 -5
- package/dist/server/session.js.map +1 -1
- package/dist/server/types/express.d.ts +97 -0
- package/dist/server/types/express.d.ts.map +1 -0
- package/dist/server/types/express.js +2 -0
- package/dist/server/types/express.js.map +1 -0
- package/dist/services/AuthenticationService.d.ts +12 -0
- package/dist/services/AuthenticationService.d.ts.map +1 -1
- package/dist/services/AuthenticationService.js +62 -6
- package/dist/services/AuthenticationService.js.map +1 -1
- package/dist/services/types.d.ts +1 -1
- package/dist/services/types.d.ts.map +1 -1
- package/dist/services/types.js.map +1 -1
- package/dist/shared/components/CivicAuthIframe.d.ts +1 -0
- package/dist/shared/components/CivicAuthIframe.d.ts.map +1 -1
- package/dist/shared/components/CivicAuthIframe.js +4 -4
- package/dist/shared/components/CivicAuthIframe.js.map +1 -1
- package/dist/shared/components/CivicAuthIframeContainer.d.ts +2 -1
- package/dist/shared/components/CivicAuthIframeContainer.d.ts.map +1 -1
- package/dist/shared/components/CivicAuthIframeContainer.js +10 -3
- package/dist/shared/components/CivicAuthIframeContainer.js.map +1 -1
- package/dist/shared/components/IFrameAndLoading.d.ts.map +1 -1
- package/dist/shared/components/IFrameAndLoading.js +1 -1
- package/dist/shared/components/IFrameAndLoading.js.map +1 -1
- package/dist/shared/hooks/useSignIn.d.ts.map +1 -1
- package/dist/shared/hooks/useSignIn.js +5 -3
- package/dist/shared/hooks/useSignIn.js.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.d.ts +2 -2
- package/dist/shared/lib/AuthenticationRefresherImpl.d.ts.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.js +3 -0
- package/dist/shared/lib/AuthenticationRefresherImpl.js.map +1 -1
- package/dist/shared/lib/GenericAuthenticationRefresher.d.ts +2 -2
- package/dist/shared/lib/GenericAuthenticationRefresher.d.ts.map +1 -1
- package/dist/shared/lib/GenericAuthenticationRefresher.js.map +1 -1
- package/dist/shared/lib/iframeUtils.d.ts +2 -0
- package/dist/shared/lib/iframeUtils.d.ts.map +1 -1
- package/dist/shared/lib/iframeUtils.js +12 -0
- package/dist/shared/lib/iframeUtils.js.map +1 -1
- package/dist/shared/lib/types.d.ts +1 -0
- package/dist/shared/lib/types.d.ts.map +1 -1
- package/dist/shared/lib/types.js.map +1 -1
- package/dist/shared/lib/util.d.ts +7 -0
- package/dist/shared/lib/util.d.ts.map +1 -1
- package/dist/shared/lib/util.js +12 -0
- package/dist/shared/lib/util.js.map +1 -1
- package/dist/shared/providers/CivicAuthConfigContext.d.ts +2 -1
- package/dist/shared/providers/CivicAuthConfigContext.d.ts.map +1 -1
- package/dist/shared/providers/CivicAuthConfigContext.js +3 -1
- package/dist/shared/providers/CivicAuthConfigContext.js.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.d.ts.map +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts +41 -0
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts.map +1 -0
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js +125 -0
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js.map +1 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts +67 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +310 -10
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/SessionManager.d.ts +31 -3
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +253 -22
- package/dist/vanillajs/auth/SessionManager.js.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.js +31 -18
- package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js +15 -8
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts +44 -0
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +163 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts +23 -0
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js +59 -2
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +20 -0
- package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.js +1 -0
- package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.d.ts +36 -0
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.js +216 -24
- package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
- package/dist/vanillajs/index.d.ts +2 -0
- package/dist/vanillajs/index.d.ts.map +1 -1
- package/dist/vanillajs/index.js +4 -0
- package/dist/vanillajs/index.js.map +1 -1
- package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -1
- package/dist/vanillajs/ui/LoadingComponents.js +1 -1
- package/dist/vanillajs/ui/LoadingComponents.js.map +1 -1
- package/package.json +7 -2
package/dist/server/session.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {} from "../types.js";
|
|
1
|
+
import { tokenKeys, } from "../types.js";
|
|
2
2
|
import { getUser as getUserFromShared, getTokens as getTokensFromShared, } from "../shared/lib/session.js";
|
|
3
3
|
import { clearTokens as clearTokensUtil } from "../shared/lib/util.js";
|
|
4
4
|
import { resolveOAuthAccessCode } from "../server/login.js";
|
|
@@ -7,7 +7,37 @@ import { buildLogoutRedirectUrl } from "../server/logout.js";
|
|
|
7
7
|
import { refreshTokens } from "../server/refresh.js";
|
|
8
8
|
import { getVersion } from "../shared/index.js";
|
|
9
9
|
import { ServerAuthenticationResolver } from "../server/ServerAuthenticationResolver.js";
|
|
10
|
-
import { DEFAULT_AUTH_SERVER } from "../constants.js";
|
|
10
|
+
import { DEFAULT_AUTH_SERVER, JWT_PAYLOAD_KNOWN_CLAIM_KEYS, } from "../constants.js";
|
|
11
|
+
import { displayModeFromState } from "../lib/oauth.js";
|
|
12
|
+
import { decodeJwt } from "jose";
|
|
13
|
+
import { generateOauthLogoutUrl } from "../shared/lib/util.js";
|
|
14
|
+
// Function to omit keys from an object
|
|
15
|
+
const omitKeys = (keys, obj) => {
|
|
16
|
+
const result = { ...obj };
|
|
17
|
+
keys.forEach((key) => {
|
|
18
|
+
delete result[key];
|
|
19
|
+
});
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Extract user information directly from OIDC tokens
|
|
24
|
+
* @param tokens The OIDC tokens response
|
|
25
|
+
* @returns The user object or null if no valid ID token
|
|
26
|
+
*/
|
|
27
|
+
function getUserFromTokens(tokens) {
|
|
28
|
+
if (!tokens.id_token)
|
|
29
|
+
return null;
|
|
30
|
+
const parsedToken = decodeJwt(tokens.id_token);
|
|
31
|
+
if (!parsedToken.sub)
|
|
32
|
+
return null;
|
|
33
|
+
// set the user ID from the token sub
|
|
34
|
+
const userWithAdditionalTokenFields = {
|
|
35
|
+
...parsedToken,
|
|
36
|
+
id: parsedToken.sub,
|
|
37
|
+
};
|
|
38
|
+
// Remove the token keys from the user object to stop it getting too large
|
|
39
|
+
return omitKeys([...JWT_PAYLOAD_KNOWN_CLAIM_KEYS, ...tokenKeys], userWithAdditionalTokenFields);
|
|
40
|
+
}
|
|
11
41
|
/**
|
|
12
42
|
* CivicAuth is the main entry point for server-side authentication operations.
|
|
13
43
|
* It provides a unified interface to all the authentication functions.
|
|
@@ -66,10 +96,11 @@ export class CivicAuth {
|
|
|
66
96
|
return null;
|
|
67
97
|
}
|
|
68
98
|
// If session is valid, use the shared implementation to get the tokens
|
|
69
|
-
|
|
99
|
+
const tokens = await getTokensFromShared(this.storage);
|
|
100
|
+
return tokens;
|
|
70
101
|
}
|
|
71
102
|
catch (error) {
|
|
72
|
-
console.error("Token validation failed during getTokens", error);
|
|
103
|
+
console.error("❌ Token validation failed during getTokens", error);
|
|
73
104
|
return null;
|
|
74
105
|
}
|
|
75
106
|
}
|
|
@@ -112,6 +143,30 @@ export class CivicAuth {
|
|
|
112
143
|
* @returns The logout URL
|
|
113
144
|
*/
|
|
114
145
|
async buildLogoutRedirectUrl(options) {
|
|
146
|
+
// For backend flows with HTTP-only cookies, try to get tokens directly
|
|
147
|
+
// For logout, we don't need valid/authenticated tokens - just the ID token to build logout URL
|
|
148
|
+
try {
|
|
149
|
+
// Use the shared getTokens function directly - this bypasses session validation
|
|
150
|
+
// since for logout we just need the raw ID token, not validated tokens
|
|
151
|
+
const tokens = await getTokensFromShared(this.storage);
|
|
152
|
+
if (tokens?.idToken) {
|
|
153
|
+
// We have access to the ID token from HTTP-only cookies
|
|
154
|
+
// Build the logout URL manually using the shared utility
|
|
155
|
+
const logoutUrl = await generateOauthLogoutUrl({
|
|
156
|
+
clientId: this.authConfig.clientId,
|
|
157
|
+
redirectUrl: this.authConfig.postLogoutRedirectUrl || "/",
|
|
158
|
+
idToken: tokens.idToken,
|
|
159
|
+
state: options?.state ?? Math.random().toString(36).substring(2),
|
|
160
|
+
oauthServer: this.oauthServer,
|
|
161
|
+
});
|
|
162
|
+
return logoutUrl;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// If direct token access fails, fall back to the generic function
|
|
167
|
+
console.warn("❌ Could not get tokens directly from storage, falling back to generic logout method:", error);
|
|
168
|
+
}
|
|
169
|
+
// Fallback to the generic function for other storage types or when tokens aren't accessible
|
|
115
170
|
return buildLogoutRedirectUrl({
|
|
116
171
|
...this.authConfig,
|
|
117
172
|
scopes: options?.scopes,
|
|
@@ -120,7 +175,7 @@ export class CivicAuth {
|
|
|
120
175
|
}
|
|
121
176
|
/**
|
|
122
177
|
* Refresh the current set of OIDC tokens
|
|
123
|
-
* @returns The refreshed tokens
|
|
178
|
+
* @returns The refreshed tokens or null for backend flows where tokens are managed in HTTP-only cookies
|
|
124
179
|
*/
|
|
125
180
|
async refreshTokens() {
|
|
126
181
|
return refreshTokens(this.storage, this.authConfig);
|
|
@@ -131,5 +186,161 @@ export class CivicAuth {
|
|
|
131
186
|
async clearTokens() {
|
|
132
187
|
return clearTokensUtil(this.storage);
|
|
133
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Smart callback handler that automatically detects frontend vs backend requests
|
|
191
|
+
* and redirects appropriately. Use this instead of resolveOAuthAccessCode + manual redirect.
|
|
192
|
+
*
|
|
193
|
+
* @param params An object containing the authorization code, state, and the incoming request.
|
|
194
|
+
* @param params.code The authorization code from query parameters.
|
|
195
|
+
* @param params.state The OAuth state parameter.
|
|
196
|
+
* @param params.req The incoming request object (e.g., from Express).
|
|
197
|
+
* @param options Configuration options (frontendUrl override, apiResponse flag).
|
|
198
|
+
* @returns Object with redirect information or HTML content for iframe completion.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* ```javascript
|
|
202
|
+
* app.get('/auth/callback', async (req, res) => {
|
|
203
|
+
* const { code, state } = req.query;
|
|
204
|
+
* // The request object 'req' is passed directly
|
|
205
|
+
* const result = await req.civicAuth.handleCallback({ code, state, req });
|
|
206
|
+
*
|
|
207
|
+
* if (result.htmlContent) {
|
|
208
|
+
* res.setHeader('Content-Type', 'text/html');
|
|
209
|
+
* res.send(result.htmlContent);
|
|
210
|
+
* } else if (result.redirectTo) {
|
|
211
|
+
* res.redirect(result.redirectTo);
|
|
212
|
+
* } else {
|
|
213
|
+
* res.json({ success: true, user: result.user });
|
|
214
|
+
* }
|
|
215
|
+
* });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
async handleCallback({ code, state, req }, options) {
|
|
219
|
+
// First, resolve the OAuth code and create session
|
|
220
|
+
const tokens = await this.resolveOAuthAccessCode(code, state);
|
|
221
|
+
// Extract user info directly from tokens
|
|
222
|
+
const user = getUserFromTokens(tokens);
|
|
223
|
+
const frontendUrl = options?.frontendUrl || this.authConfig.loginSuccessUrl;
|
|
224
|
+
// Priority 1: Check state for display mode configuration
|
|
225
|
+
const stateDisplayMode = displayModeFromState(state, undefined);
|
|
226
|
+
const isConfiguredForIframe = stateDisplayMode === "iframe";
|
|
227
|
+
// Determine if this should be treated as an iframe request
|
|
228
|
+
// Configuration (from state) takes precedence over auto-detection
|
|
229
|
+
const shouldTreatAsIframe = isConfiguredForIframe && !this.authConfig.disableIframeDetection;
|
|
230
|
+
const isTopLevelRedirect = req.headers["sec-fetch-dest"] === "document";
|
|
231
|
+
const isApiRequest = options?.apiResponse || req.headers.accept?.includes("application/json");
|
|
232
|
+
// Detect Safari or other browsers where iframe postMessage may fail due to cross-origin restrictions
|
|
233
|
+
const userAgent = req.headers["user-agent"] || "";
|
|
234
|
+
const isSafari = userAgent.includes("Safari") && !userAgent.includes("Chrome");
|
|
235
|
+
const isLikelyCrossOriginIframe = isSafari ||
|
|
236
|
+
(userAgent.includes("WebKit") && !userAgent.includes("Chrome"));
|
|
237
|
+
// Case 1: The request should be treated as iframe. Return HTML content.
|
|
238
|
+
// Unless iframe detection is disabled via configuration OR we detect cross-origin issues
|
|
239
|
+
if (shouldTreatAsIframe &&
|
|
240
|
+
user &&
|
|
241
|
+
frontendUrl &&
|
|
242
|
+
!isLikelyCrossOriginIframe) {
|
|
243
|
+
const completionHtml = this.generateIframeCompletionHtml(user);
|
|
244
|
+
return { content: completionHtml };
|
|
245
|
+
}
|
|
246
|
+
// Case 1b: Safari/cross-origin iframe case - redirect instead of HTML
|
|
247
|
+
if (shouldTreatAsIframe &&
|
|
248
|
+
user &&
|
|
249
|
+
frontendUrl &&
|
|
250
|
+
isLikelyCrossOriginIframe) {
|
|
251
|
+
return { redirectTo: frontendUrl };
|
|
252
|
+
}
|
|
253
|
+
// Case 2: The request is a top-level navigation. Return redirect URL.
|
|
254
|
+
if (isTopLevelRedirect && frontendUrl) {
|
|
255
|
+
return { redirectTo: frontendUrl };
|
|
256
|
+
}
|
|
257
|
+
// Case 3: The request is an API call. Return JSON content.
|
|
258
|
+
if (isApiRequest) {
|
|
259
|
+
return {
|
|
260
|
+
content: {
|
|
261
|
+
success: true,
|
|
262
|
+
user,
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
// Fallback for older browsers or other contexts: if a frontend URL is configured,
|
|
267
|
+
// assume a redirect to it.
|
|
268
|
+
if (frontendUrl) {
|
|
269
|
+
return { redirectTo: frontendUrl };
|
|
270
|
+
}
|
|
271
|
+
// Server-side fallback: if no frontend URL is configured but we have a postLogoutRedirectUrl,
|
|
272
|
+
// redirect there instead of returning JSON content
|
|
273
|
+
if (this.authConfig.postLogoutRedirectUrl) {
|
|
274
|
+
return { redirectTo: this.authConfig.postLogoutRedirectUrl };
|
|
275
|
+
}
|
|
276
|
+
// Absolute fallback: return success as JSON content if no other conditions are met.
|
|
277
|
+
// This could happen if no loginSuccessUrl or postLogoutRedirectUrl is configured.
|
|
278
|
+
return {
|
|
279
|
+
content: {
|
|
280
|
+
success: true,
|
|
281
|
+
user,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Generate HTML content for iframe completion that sends postMessage to parent
|
|
287
|
+
*/
|
|
288
|
+
generateIframeCompletionHtml(user) {
|
|
289
|
+
const escapedUser = JSON.stringify(user).replace(/'/g, "\\'");
|
|
290
|
+
const clientId = this.authConfig.clientId;
|
|
291
|
+
return `
|
|
292
|
+
<!DOCTYPE html>
|
|
293
|
+
<html>
|
|
294
|
+
<head>
|
|
295
|
+
<title>Authentication Complete</title>
|
|
296
|
+
<meta charset="utf-8">
|
|
297
|
+
</head>
|
|
298
|
+
<body>
|
|
299
|
+
<div style="text-align: center; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
|
|
300
|
+
<p>Authentication successful! Completing login...</p>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
<!-- Success signal for SignalObserver -->
|
|
304
|
+
<div id="civic-auth-success-signal" style="display: none;" data-user-info='${escapedUser}'>
|
|
305
|
+
Authentication successful!
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<script>
|
|
309
|
+
// Send postMessage to parent to resolve authentication promise
|
|
310
|
+
if (window.parent && window.parent !== window) {
|
|
311
|
+
console.log('📤 Sending auth success postMessage to parent');
|
|
312
|
+
try {
|
|
313
|
+
window.parent.postMessage({
|
|
314
|
+
type: 'auth_success',
|
|
315
|
+
detail: 'Authentication successful',
|
|
316
|
+
data: {
|
|
317
|
+
user: ${escapedUser}
|
|
318
|
+
}
|
|
319
|
+
}, '*');
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('❌ Failed to send postMessage:', error);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Also send civicloginApp format message for compatibility
|
|
325
|
+
try {
|
|
326
|
+
window.parent.postMessage({
|
|
327
|
+
source: 'civicloginApp',
|
|
328
|
+
type: 'auth_success',
|
|
329
|
+
clientId: '${clientId}',
|
|
330
|
+
data: {
|
|
331
|
+
user: ${escapedUser}
|
|
332
|
+
}
|
|
333
|
+
}, '*');
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('❌ Failed to send civicloginApp message:', error);
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
console.log('❌ Not in iframe context or no parent window');
|
|
339
|
+
}
|
|
340
|
+
</script>
|
|
341
|
+
</body>
|
|
342
|
+
</html>
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
134
345
|
}
|
|
135
346
|
//# sourceMappingURL=session.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AuthStorage,\n type OAuthTokens,\n type User,\n type EmptyObject,\n type UnknownObject,\n type OIDCTokenResponseBody,\n} from \"@/types.js\";\nimport type { AuthConfig } from \"@/server/config.js\";\nimport {\n getUser as getUserFromShared,\n getTokens as getTokensFromShared,\n} from \"@/shared/lib/session.js\";\nimport { clearTokens as clearTokensUtil } from \"@/shared/lib/util.js\";\nimport { resolveOAuthAccessCode } from \"@/server/login.js\";\nimport { buildLoginUrl } from \"@/server/login.js\";\nimport { buildLogoutRedirectUrl } from \"@/server/logout.js\";\nimport { refreshTokens } from \"@/server/refresh.js\";\nimport { getVersion } from \"@/shared/index.js\";\nimport { ServerAuthenticationResolver } from \"@/server/ServerAuthenticationResolver.js\";\nimport { DEFAULT_AUTH_SERVER } from \"@/constants.js\";\nimport type { AuthenticationResolver } from \"@/services/types.js\";\n\n/**\n * CivicAuth is the main entry point for server-side authentication operations.\n * It provides a unified interface to all the authentication functions.\n */\nexport class CivicAuth {\n _authResolver: AuthenticationResolver | null = null;\n constructor(\n readonly storage: AuthStorage,\n readonly authConfig: AuthConfig,\n ) {}\n\n get oauthServer(): string {\n return this.authConfig.oauthServer || DEFAULT_AUTH_SERVER;\n }\n\n async getAuthResolver(): Promise<AuthenticationResolver> {\n if (this._authResolver) {\n return Promise.resolve(this._authResolver);\n }\n this._authResolver = await ServerAuthenticationResolver.build(\n {\n ...this.authConfig,\n oauthServer: this.oauthServer,\n },\n this.storage,\n );\n return this._authResolver;\n }\n /**\n * Gets the authenticated user with token validation\n * @returns The user object if authenticated, null otherwise\n */\n async getUser<\n T extends UnknownObject = EmptyObject,\n >(): Promise<User<T> | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the user\n const session = await resolver.validateExistingSession();\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the user\n return getUserFromShared<T>(this.storage);\n } catch (error) {\n console.error(\"Token validation failed during getUser\", error);\n return null;\n }\n }\n\n /**\n * Gets the authentication tokens with token validation\n * @returns The tokens if authenticated, null otherwise\n */\n async getTokens(): Promise<OAuthTokens | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the tokens\n const session = await resolver.validateExistingSession();\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the tokens\n return getTokensFromShared(this.storage);\n } catch (error) {\n console.error(\"Token validation failed during getTokens\", error);\n return null;\n }\n }\n\n /**\n * Resolve an OAuth access code to a set of OIDC tokens\n * @param code The access code from the query parameter\n * @param state The OAuth state parameter\n * @returns OIDC tokens\n */\n async resolveOAuthAccessCode(\n code: string,\n state: string,\n ): Promise<OIDCTokenResponseBody> {\n return resolveOAuthAccessCode(code, state, this.storage, this.authConfig);\n }\n\n /**\n * Check if the user is currently logged in\n * @returns true if logged in, false otherwise\n */\n async isLoggedIn(): Promise<boolean> {\n const resolver = await this.getAuthResolver();\n const session = await resolver.validateExistingSession();\n return session?.authenticated ?? false;\n }\n\n /**\n * Build a login URL to redirect the user to\n * @param options Additional options for building the login URL\n * @returns The login URL\n */\n async buildLoginUrl(options?: {\n scopes?: string[];\n state?: string;\n nonce?: string;\n }): Promise<URL> {\n return buildLoginUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n nonce: options?.nonce,\n framework: \"server\",\n sdkVersion: getVersion(),\n },\n this.storage,\n );\n }\n\n /**\n * Build a logout URL to redirect the user to\n * @param options Additional options for building the logout URL\n * @returns The logout URL\n */\n async buildLogoutRedirectUrl(options?: {\n scopes?: string[];\n state?: string;\n }): Promise<URL> {\n return buildLogoutRedirectUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n },\n this.storage,\n );\n }\n\n /**\n * Refresh the current set of OIDC tokens\n * @returns The refreshed tokens\n */\n async refreshTokens(): Promise<OIDCTokenResponseBody> {\n return refreshTokens(this.storage, this.authConfig);\n }\n\n /**\n * Clear all authentication tokens from storage\n */\n async clearTokens(): Promise<void> {\n return clearTokensUtil(this.storage);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/server/session.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AuthStorage,\n type OAuthTokens,\n type User,\n type EmptyObject,\n type UnknownObject,\n type OIDCTokenResponseBody,\n tokenKeys,\n} from \"@/types.js\";\nimport type { AuthConfig } from \"@/server/config.js\";\nimport {\n getUser as getUserFromShared,\n getTokens as getTokensFromShared,\n} from \"@/shared/lib/session.js\";\nimport { clearTokens as clearTokensUtil } from \"@/shared/lib/util.js\";\nimport { resolveOAuthAccessCode } from \"@/server/login.js\";\nimport { buildLoginUrl } from \"@/server/login.js\";\nimport { buildLogoutRedirectUrl } from \"@/server/logout.js\";\nimport { refreshTokens } from \"@/server/refresh.js\";\nimport { getVersion } from \"@/shared/index.js\";\nimport { ServerAuthenticationResolver } from \"@/server/ServerAuthenticationResolver.js\";\nimport {\n DEFAULT_AUTH_SERVER,\n JWT_PAYLOAD_KNOWN_CLAIM_KEYS,\n} from \"@/constants.js\";\nimport type { AuthenticationResolver } from \"@/services/types.js\";\nimport { displayModeFromState } from \"@/lib/oauth.js\";\nimport { decodeJwt, type JWTPayload } from \"jose\";\nimport { generateOauthLogoutUrl } from \"@/shared/lib/util.js\";\nexport type HandleCallbackRequest = {\n headers: {\n [key: string]: string | string[] | undefined;\n referer?: string;\n origin?: string;\n \"user-agent\"?: string;\n accept?: string;\n \"sec-fetch-dest\"?: string;\n };\n};\n\nexport type HandleCallbackParams = {\n code: string;\n state: string;\n req: HandleCallbackRequest;\n};\n\n// Function to omit keys from an object\nconst omitKeys = <K extends keyof T, T extends Record<string, unknown>>(\n keys: K[],\n obj: T,\n): Omit<T, K> => {\n const result = { ...obj };\n keys.forEach((key) => {\n delete result[key];\n });\n return result;\n};\n\n/**\n * Extract user information directly from OIDC tokens\n * @param tokens The OIDC tokens response\n * @returns The user object or null if no valid ID token\n */\nfunction getUserFromTokens<T extends UnknownObject = EmptyObject>(\n tokens: OIDCTokenResponseBody,\n): User<T> | null {\n if (!tokens.id_token) return null;\n\n const parsedToken = decodeJwt(tokens.id_token) as JWTPayload & T;\n if (!parsedToken.sub) return null;\n\n // set the user ID from the token sub\n const userWithAdditionalTokenFields = {\n ...(parsedToken as T),\n id: parsedToken.sub,\n };\n\n // Remove the token keys from the user object to stop it getting too large\n return omitKeys(\n [...JWT_PAYLOAD_KNOWN_CLAIM_KEYS, ...tokenKeys],\n userWithAdditionalTokenFields,\n ) as User<T>;\n}\n\n/**\n * CivicAuth is the main entry point for server-side authentication operations.\n * It provides a unified interface to all the authentication functions.\n */\nexport class CivicAuth {\n _authResolver: AuthenticationResolver | null = null;\n constructor(\n readonly storage: AuthStorage,\n readonly authConfig: AuthConfig,\n ) {}\n\n get oauthServer(): string {\n return this.authConfig.oauthServer || DEFAULT_AUTH_SERVER;\n }\n\n async getAuthResolver(): Promise<AuthenticationResolver> {\n if (this._authResolver) {\n return Promise.resolve(this._authResolver);\n }\n this._authResolver = await ServerAuthenticationResolver.build(\n {\n ...this.authConfig,\n oauthServer: this.oauthServer,\n },\n this.storage,\n );\n return this._authResolver;\n }\n /**\n * Gets the authenticated user with token validation\n * @returns The user object if authenticated, null otherwise\n */\n async getUser<\n T extends UnknownObject = EmptyObject,\n >(): Promise<User<T> | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the user\n const session = await resolver.validateExistingSession();\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the user\n return getUserFromShared<T>(this.storage);\n } catch (error) {\n console.error(\"Token validation failed during getUser\", error);\n return null;\n }\n }\n\n /**\n * Gets the authentication tokens with token validation\n * @returns The tokens if authenticated, null otherwise\n */\n async getTokens(): Promise<OAuthTokens | null> {\n const resolver = await this.getAuthResolver();\n\n try {\n // Validate the session before returning the tokens\n const session = await resolver.validateExistingSession();\n\n if (!session?.authenticated) {\n return null;\n }\n\n // If session is valid, use the shared implementation to get the tokens\n const tokens = await getTokensFromShared(this.storage);\n return tokens;\n } catch (error) {\n console.error(\"❌ Token validation failed during getTokens\", error);\n return null;\n }\n }\n\n /**\n * Resolve an OAuth access code to a set of OIDC tokens\n * @param code The access code from the query parameter\n * @param state The OAuth state parameter\n * @returns OIDC tokens\n */\n async resolveOAuthAccessCode(\n code: string,\n state: string,\n ): Promise<OIDCTokenResponseBody> {\n return resolveOAuthAccessCode(code, state, this.storage, this.authConfig);\n }\n\n /**\n * Check if the user is currently logged in\n * @returns true if logged in, false otherwise\n */\n async isLoggedIn(): Promise<boolean> {\n const resolver = await this.getAuthResolver();\n const session = await resolver.validateExistingSession();\n return session?.authenticated ?? false;\n }\n\n /**\n * Build a login URL to redirect the user to\n * @param options Additional options for building the login URL\n * @returns The login URL\n */\n async buildLoginUrl(options?: {\n scopes?: string[];\n state?: string;\n nonce?: string;\n }): Promise<URL> {\n return buildLoginUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n nonce: options?.nonce,\n framework: \"server\",\n sdkVersion: getVersion(),\n },\n this.storage,\n );\n }\n\n /**\n * Build a logout URL to redirect the user to\n * @param options Additional options for building the logout URL\n * @returns The logout URL\n */\n async buildLogoutRedirectUrl(options?: {\n scopes?: string[];\n state?: string;\n }): Promise<URL> {\n // For backend flows with HTTP-only cookies, try to get tokens directly\n // For logout, we don't need valid/authenticated tokens - just the ID token to build logout URL\n try {\n // Use the shared getTokens function directly - this bypasses session validation\n // since for logout we just need the raw ID token, not validated tokens\n const tokens = await getTokensFromShared(this.storage);\n\n if (tokens?.idToken) {\n // We have access to the ID token from HTTP-only cookies\n // Build the logout URL manually using the shared utility\n\n const logoutUrl = await generateOauthLogoutUrl({\n clientId: this.authConfig.clientId,\n redirectUrl: this.authConfig.postLogoutRedirectUrl || \"/\",\n idToken: tokens.idToken,\n state: options?.state ?? Math.random().toString(36).substring(2),\n oauthServer: this.oauthServer,\n });\n\n return logoutUrl;\n }\n } catch (error) {\n // If direct token access fails, fall back to the generic function\n console.warn(\n \"❌ Could not get tokens directly from storage, falling back to generic logout method:\",\n error,\n );\n }\n\n // Fallback to the generic function for other storage types or when tokens aren't accessible\n return buildLogoutRedirectUrl(\n {\n ...this.authConfig,\n scopes: options?.scopes,\n state: options?.state,\n },\n this.storage,\n );\n }\n\n /**\n * Refresh the current set of OIDC tokens\n * @returns The refreshed tokens or null for backend flows where tokens are managed in HTTP-only cookies\n */\n async refreshTokens(): Promise<OIDCTokenResponseBody | null> {\n return refreshTokens(this.storage, this.authConfig);\n }\n\n /**\n * Clear all authentication tokens from storage\n */\n async clearTokens(): Promise<void> {\n return clearTokensUtil(this.storage);\n }\n\n /**\n * Smart callback handler that automatically detects frontend vs backend requests\n * and redirects appropriately. Use this instead of resolveOAuthAccessCode + manual redirect.\n *\n * @param params An object containing the authorization code, state, and the incoming request.\n * @param params.code The authorization code from query parameters.\n * @param params.state The OAuth state parameter.\n * @param params.req The incoming request object (e.g., from Express).\n * @param options Configuration options (frontendUrl override, apiResponse flag).\n * @returns Object with redirect information or HTML content for iframe completion.\n *\n * @example\n * ```javascript\n * app.get('/auth/callback', async (req, res) => {\n * const { code, state } = req.query;\n * // The request object 'req' is passed directly\n * const result = await req.civicAuth.handleCallback({ code, state, req });\n *\n * if (result.htmlContent) {\n * res.setHeader('Content-Type', 'text/html');\n * res.send(result.htmlContent);\n * } else if (result.redirectTo) {\n * res.redirect(result.redirectTo);\n * } else {\n * res.json({ success: true, user: result.user });\n * }\n * });\n * ```\n */\n async handleCallback(\n { code, state, req }: HandleCallbackParams,\n options?: {\n frontendUrl?: string;\n apiResponse?: boolean;\n },\n ): Promise<{\n redirectTo?: string;\n content?: string | { success: boolean; user?: User | null };\n }> {\n // First, resolve the OAuth code and create session\n const tokens = await this.resolveOAuthAccessCode(code, state);\n\n // Extract user info directly from tokens\n const user = getUserFromTokens(tokens);\n\n const frontendUrl = options?.frontendUrl || this.authConfig.loginSuccessUrl;\n\n // Priority 1: Check state for display mode configuration\n const stateDisplayMode = displayModeFromState(state, undefined);\n const isConfiguredForIframe = stateDisplayMode === \"iframe\";\n\n // Determine if this should be treated as an iframe request\n // Configuration (from state) takes precedence over auto-detection\n const shouldTreatAsIframe =\n isConfiguredForIframe && !this.authConfig.disableIframeDetection;\n\n const isTopLevelRedirect = req.headers[\"sec-fetch-dest\"] === \"document\";\n const isApiRequest =\n options?.apiResponse || req.headers.accept?.includes(\"application/json\");\n\n // Detect Safari or other browsers where iframe postMessage may fail due to cross-origin restrictions\n const userAgent = req.headers[\"user-agent\"] || \"\";\n const isSafari =\n userAgent.includes(\"Safari\") && !userAgent.includes(\"Chrome\");\n const isLikelyCrossOriginIframe =\n isSafari ||\n (userAgent.includes(\"WebKit\") && !userAgent.includes(\"Chrome\"));\n\n // Case 1: The request should be treated as iframe. Return HTML content.\n // Unless iframe detection is disabled via configuration OR we detect cross-origin issues\n if (\n shouldTreatAsIframe &&\n user &&\n frontendUrl &&\n !isLikelyCrossOriginIframe\n ) {\n const completionHtml = this.generateIframeCompletionHtml(user);\n return { content: completionHtml };\n }\n\n // Case 1b: Safari/cross-origin iframe case - redirect instead of HTML\n if (\n shouldTreatAsIframe &&\n user &&\n frontendUrl &&\n isLikelyCrossOriginIframe\n ) {\n return { redirectTo: frontendUrl };\n }\n\n // Case 2: The request is a top-level navigation. Return redirect URL.\n if (isTopLevelRedirect && frontendUrl) {\n return { redirectTo: frontendUrl };\n }\n\n // Case 3: The request is an API call. Return JSON content.\n if (isApiRequest) {\n return {\n content: {\n success: true,\n user,\n },\n };\n }\n\n // Fallback for older browsers or other contexts: if a frontend URL is configured,\n // assume a redirect to it.\n if (frontendUrl) {\n return { redirectTo: frontendUrl };\n }\n\n // Server-side fallback: if no frontend URL is configured but we have a postLogoutRedirectUrl,\n // redirect there instead of returning JSON content\n if (this.authConfig.postLogoutRedirectUrl) {\n return { redirectTo: this.authConfig.postLogoutRedirectUrl };\n }\n\n // Absolute fallback: return success as JSON content if no other conditions are met.\n // This could happen if no loginSuccessUrl or postLogoutRedirectUrl is configured.\n return {\n content: {\n success: true,\n user,\n },\n };\n }\n\n /**\n * Generate HTML content for iframe completion that sends postMessage to parent\n */\n private generateIframeCompletionHtml(user: User): string {\n const escapedUser = JSON.stringify(user).replace(/'/g, \"\\\\'\");\n const clientId = this.authConfig.clientId;\n\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <title>Authentication Complete</title>\n <meta charset=\"utf-8\">\n </head>\n <body>\n <div style=\"text-align: center; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\">\n <p>Authentication successful! Completing login...</p>\n </div>\n \n <!-- Success signal for SignalObserver -->\n <div id=\"civic-auth-success-signal\" style=\"display: none;\" data-user-info='${escapedUser}'>\n Authentication successful!\n </div>\n \n <script> \n // Send postMessage to parent to resolve authentication promise\n if (window.parent && window.parent !== window) {\n console.log('📤 Sending auth success postMessage to parent');\n try {\n window.parent.postMessage({\n type: 'auth_success',\n detail: 'Authentication successful',\n data: {\n user: ${escapedUser}\n }\n }, '*');\n } catch (error) {\n console.error('❌ Failed to send postMessage:', error);\n }\n \n // Also send civicloginApp format message for compatibility\n try {\n window.parent.postMessage({\n source: 'civicloginApp',\n type: 'auth_success',\n clientId: '${clientId}',\n data: {\n user: ${escapedUser}\n }\n }, '*');\n } catch (error) {\n console.error('❌ Failed to send civicloginApp message:', error);\n }\n } else {\n console.log('❌ Not in iframe context or no parent window');\n }\n </script>\n </body>\n </html>\n `;\n }\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AuthConfig } from "../../server/config.js";
|
|
2
|
+
export interface ExpressRequest {
|
|
3
|
+
cookies: Record<string, string>;
|
|
4
|
+
headers: Record<string, string | string[] | undefined>;
|
|
5
|
+
query: Record<string, string | string[] | undefined>;
|
|
6
|
+
method: string;
|
|
7
|
+
secure: boolean;
|
|
8
|
+
protocol: string;
|
|
9
|
+
storage?: unknown;
|
|
10
|
+
civicAuth?: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface ExpressResponse {
|
|
13
|
+
header: (name: string, value: string) => void;
|
|
14
|
+
status: (code: number) => ExpressResponse;
|
|
15
|
+
end: () => void;
|
|
16
|
+
redirect: (url: string) => void;
|
|
17
|
+
json: (data: unknown) => void;
|
|
18
|
+
send: (data: string) => void;
|
|
19
|
+
cookie: (name: string, value: string, options?: Record<string, unknown>) => void;
|
|
20
|
+
clearCookie: (name: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export interface ExpressNextFunction {
|
|
23
|
+
(): void;
|
|
24
|
+
}
|
|
25
|
+
export interface ExpressRouter {
|
|
26
|
+
use: (...handlers: Array<(req: ExpressRequest, res: ExpressResponse, next: ExpressNextFunction) => void>) => void;
|
|
27
|
+
get: (path: string, handler: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>) => void;
|
|
28
|
+
post: (path: string, handler: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface ConditionalLogger {
|
|
31
|
+
info: (message: string, ...args: unknown[]) => void;
|
|
32
|
+
debug: (message: string, ...args: unknown[]) => void;
|
|
33
|
+
error: (message: string, ...args: unknown[]) => void;
|
|
34
|
+
}
|
|
35
|
+
export interface CivicAuthServerOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Custom route overrides - allows users to override specific route handlers
|
|
38
|
+
*/
|
|
39
|
+
routes?: {
|
|
40
|
+
login?: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>;
|
|
41
|
+
callback?: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>;
|
|
42
|
+
logout?: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>;
|
|
43
|
+
logoutCallback?: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>;
|
|
44
|
+
refresh?: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>;
|
|
45
|
+
user?: (req: ExpressRequest, res: ExpressResponse, next?: ExpressNextFunction) => void | Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Custom storage class or factory function - allows users to provide their own storage implementation
|
|
49
|
+
*/
|
|
50
|
+
storage?: CookieStorageConstructor | ((req: ExpressRequest, res: ExpressResponse) => CookieStorageInterface);
|
|
51
|
+
}
|
|
52
|
+
export interface ExpressModule {
|
|
53
|
+
Router: () => ExpressRouter;
|
|
54
|
+
}
|
|
55
|
+
export interface CookieStorageConstructor {
|
|
56
|
+
new (options?: {
|
|
57
|
+
secure?: boolean;
|
|
58
|
+
sameSite?: string;
|
|
59
|
+
httpOnly?: boolean;
|
|
60
|
+
path?: string;
|
|
61
|
+
}): CookieStorageInterface;
|
|
62
|
+
}
|
|
63
|
+
export interface CookieStorageInterface {
|
|
64
|
+
settings: {
|
|
65
|
+
secure?: boolean;
|
|
66
|
+
sameSite?: string;
|
|
67
|
+
httpOnly?: boolean;
|
|
68
|
+
path?: string;
|
|
69
|
+
};
|
|
70
|
+
get(key: string): Promise<string | null>;
|
|
71
|
+
set(key: string, value: string): Promise<void>;
|
|
72
|
+
delete(key: string): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
export interface CivicAuthConstructor {
|
|
75
|
+
new (storage: CookieStorageInterface, config: AuthConfig): CivicAuthInterface;
|
|
76
|
+
}
|
|
77
|
+
export interface CivicAuthInterface {
|
|
78
|
+
buildLoginUrl(): Promise<URL>;
|
|
79
|
+
handleCallback(params: {
|
|
80
|
+
code: string;
|
|
81
|
+
state: string;
|
|
82
|
+
req: ExpressRequest;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
redirectTo?: string;
|
|
85
|
+
content?: string;
|
|
86
|
+
}>;
|
|
87
|
+
buildLogoutRedirectUrl(): Promise<URL>;
|
|
88
|
+
clearTokens(): Promise<void>;
|
|
89
|
+
isLoggedIn(): Promise<boolean>;
|
|
90
|
+
refreshTokens(): Promise<void>;
|
|
91
|
+
getUser(): Promise<unknown>;
|
|
92
|
+
}
|
|
93
|
+
export interface ServerModules {
|
|
94
|
+
CookieStorage: CookieStorageConstructor;
|
|
95
|
+
CivicAuth: CivicAuthConstructor;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/server/types/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../../src/server/types/express.ts"],"names":[],"mappings":"","sourcesContent":["import type { AuthConfig } from \"@/server/config.js\";\n\n// Express types - use Express types directly when available\nexport interface ExpressRequest {\n cookies: Record<string, string>;\n headers: Record<string, string | string[] | undefined>;\n query: Record<string, string | string[] | undefined>;\n method: string;\n secure: boolean;\n protocol: string;\n storage?: unknown;\n civicAuth?: unknown;\n}\n\nexport interface ExpressResponse {\n header: (name: string, value: string) => void;\n status: (code: number) => ExpressResponse;\n end: () => void;\n redirect: (url: string) => void;\n json: (data: unknown) => void;\n send: (data: string) => void;\n cookie: (\n name: string,\n value: string,\n options?: Record<string, unknown>,\n ) => void;\n clearCookie: (name: string) => void;\n}\n\nexport interface ExpressNextFunction {\n (): void;\n}\n\nexport interface ExpressRouter {\n use: (\n ...handlers: Array<\n (\n req: ExpressRequest,\n res: ExpressResponse,\n next: ExpressNextFunction,\n ) => void\n >\n ) => void;\n get: (\n path: string,\n handler: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>,\n ) => void;\n post: (\n path: string,\n handler: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>,\n ) => void;\n}\n\n// Logger interface that can be either a real logger or a no-op logger\nexport interface ConditionalLogger {\n info: (message: string, ...args: unknown[]) => void;\n debug: (message: string, ...args: unknown[]) => void;\n error: (message: string, ...args: unknown[]) => void;\n}\n\nexport interface CivicAuthServerOptions {\n /**\n * Custom route overrides - allows users to override specific route handlers\n */\n routes?: {\n login?: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>;\n callback?: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>;\n logout?: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>;\n logoutCallback?: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>;\n refresh?: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>;\n user?: (\n req: ExpressRequest,\n res: ExpressResponse,\n next?: ExpressNextFunction,\n ) => void | Promise<void>;\n };\n /**\n * Custom storage class or factory function - allows users to provide their own storage implementation\n */\n storage?:\n | CookieStorageConstructor\n | ((req: ExpressRequest, res: ExpressResponse) => CookieStorageInterface);\n}\n\n// Interfaces for dynamically imported modules\nexport interface ExpressModule {\n Router: () => ExpressRouter;\n}\n\nexport interface CookieStorageConstructor {\n new (options?: {\n secure?: boolean;\n sameSite?: string;\n httpOnly?: boolean;\n path?: string;\n }): CookieStorageInterface;\n}\n\nexport interface CookieStorageInterface {\n settings: {\n secure?: boolean;\n sameSite?: string;\n httpOnly?: boolean;\n path?: string;\n };\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}\n\nexport interface CivicAuthConstructor {\n new (storage: CookieStorageInterface, config: AuthConfig): CivicAuthInterface;\n}\n\nexport interface CivicAuthInterface {\n buildLoginUrl(): Promise<URL>;\n handleCallback(params: {\n code: string;\n state: string;\n req: ExpressRequest;\n }): Promise<{\n redirectTo?: string;\n content?: string;\n }>;\n buildLogoutRedirectUrl(): Promise<URL>;\n clearTokens(): Promise<void>;\n isLoggedIn(): Promise<boolean>;\n refreshTokens(): Promise<void>;\n getUser(): Promise<unknown>;\n}\n\nexport interface ServerModules {\n CookieStorage: CookieStorageConstructor;\n CivicAuth: CivicAuthConstructor;\n}\n"]}
|
|
@@ -15,9 +15,11 @@ export type BrowserAuthenticationInitiatorConfig = Omit<GenericAuthenticationIni
|
|
|
15
15
|
logoutUrl?: string;
|
|
16
16
|
logoutRedirectUrl: string;
|
|
17
17
|
displayMode: DisplayMode;
|
|
18
|
+
iframeDisplayMode?: "modal" | "embedded";
|
|
18
19
|
basePath?: string;
|
|
19
20
|
loginSuccessUrl?: string;
|
|
20
21
|
framework?: FrameworkType;
|
|
22
|
+
autoRedirect?: boolean;
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* An authentication initiator that works on a browser. Since this is just triggering
|
|
@@ -44,14 +46,24 @@ export declare class BrowserAuthenticationInitiator implements AuthenticationIni
|
|
|
44
46
|
readonly setDesignOptions: (value: LoginAppDesignOptions) => void;
|
|
45
47
|
private postMessageHandler;
|
|
46
48
|
protected config: BrowserAuthenticationInitiatorConfig;
|
|
49
|
+
static browserCorsFailsSilentlyRedirectUrl: string;
|
|
50
|
+
private _iframeRef;
|
|
47
51
|
setDisplayMode(displayMode: DisplayMode): void;
|
|
48
52
|
get displayMode(): DisplayMode;
|
|
49
53
|
get isServerTokenExchange(): boolean;
|
|
50
54
|
get state(): string;
|
|
51
55
|
instanceId: string;
|
|
56
|
+
browserCorsFailsSilentlyRedirectUrl: string;
|
|
52
57
|
constructor(config: typeof this.config, setDesignOptions?: (value: LoginAppDesignOptions) => void);
|
|
53
58
|
handleLoginAppPopupFailed(redirectUrl: string): Promise<void>;
|
|
59
|
+
handleBrowserCorsFailsSilently(redirectUrl: string): Promise<{
|
|
60
|
+
isRedirecting: boolean;
|
|
61
|
+
} | undefined>;
|
|
62
|
+
handleUserInteractionBrowserCorsFailsSilently(): Promise<{
|
|
63
|
+
isRedirecting: boolean;
|
|
64
|
+
}>;
|
|
54
65
|
handleLoginAppDesignUpdate(options: LoginAppDesignOptions): Promise<void>;
|
|
66
|
+
getSignInUrl(): Promise<URL>;
|
|
55
67
|
signIn(iframeRef: HTMLIFrameElement | null): Promise<URL>;
|
|
56
68
|
signOut(idToken: string | undefined, iframeRef: HTMLIFrameElement | null): Promise<URL>;
|
|
57
69
|
cleanup(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthenticationService.d.ts","sourceRoot":"","sources":["../../src/services/AuthenticationService.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,SAAS,EACT,aAAa,EACb,qBAAqB,EAErB,qBAAqB,EACrB,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,+BAA+B,EAEhC,MAAM,oBAAoB,CAAC;AAgB5B,OAAO,KAAK,EACV,uBAAuB,EACvB,sBAAsB,EACtB,YAAY,EACb,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"AuthenticationService.d.ts","sourceRoot":"","sources":["../../src/services/AuthenticationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ import { DEFAULT_AUTH_SERVER, DEFAULT_OAUTH_GET_PARAMS, LOGOUT_STATE, } from "..
|
|
|
11
11
|
import { validateLoginAppPostMessage } from "../lib/postMessage.js";
|
|
12
12
|
import { getUser } from "../shared/lib/session.js";
|
|
13
13
|
import { GenericUserSession } from "../shared/lib/UserSession.js";
|
|
14
|
-
import { getIframeRef } from "../shared/lib/iframeUtils.js";
|
|
14
|
+
import { getIframeRef, iframeIsVisible, isEmbeddedIframeMode, } from "../shared/lib/iframeUtils.js";
|
|
15
15
|
import { v4 as uuid } from "uuid";
|
|
16
16
|
import { CodeVerifier } from "../shared/lib/types.js";
|
|
17
17
|
import { BrowserAuthenticationRefresher } from "../shared/lib/BrowserAuthenticationRefresher.js";
|
|
@@ -50,6 +50,8 @@ export class BrowserAuthenticationInitiator {
|
|
|
50
50
|
setDesignOptions;
|
|
51
51
|
postMessageHandler = null;
|
|
52
52
|
config;
|
|
53
|
+
static browserCorsFailsSilentlyRedirectUrl;
|
|
54
|
+
_iframeRef = null;
|
|
53
55
|
setDisplayMode(displayMode) {
|
|
54
56
|
this.config.displayMode = displayMode;
|
|
55
57
|
}
|
|
@@ -62,6 +64,7 @@ export class BrowserAuthenticationInitiator {
|
|
|
62
64
|
get state() {
|
|
63
65
|
return generateState({
|
|
64
66
|
displayMode: this.config.displayMode,
|
|
67
|
+
iframeDisplayMode: this.config.iframeDisplayMode,
|
|
65
68
|
serverTokenExchange: this.isServerTokenExchange,
|
|
66
69
|
loginSuccessUrl: this.config.loginSuccessUrl,
|
|
67
70
|
framework: this.config.framework,
|
|
@@ -69,6 +72,7 @@ export class BrowserAuthenticationInitiator {
|
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
74
|
instanceId;
|
|
75
|
+
browserCorsFailsSilentlyRedirectUrl = "";
|
|
72
76
|
constructor(config, setDesignOptions = defaultSetDesignOptions) {
|
|
73
77
|
this.setDesignOptions = setDesignOptions;
|
|
74
78
|
this.instanceId = uuid();
|
|
@@ -85,6 +89,11 @@ export class BrowserAuthenticationInitiator {
|
|
|
85
89
|
this.handleLoginAppPopupFailed(loginMessage.data.url);
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
92
|
+
if (loginMessage.type === "browserCorsFailsSilently" &&
|
|
93
|
+
this.displayMode === "iframe") {
|
|
94
|
+
this.handleBrowserCorsFailsSilently(loginMessage.data.url);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
88
97
|
if (loginMessage.type === "design") {
|
|
89
98
|
// TODO handle the design message
|
|
90
99
|
this.handleLoginAppDesignUpdate(loginMessage.data);
|
|
@@ -98,19 +107,62 @@ export class BrowserAuthenticationInitiator {
|
|
|
98
107
|
console.warn("Login app popup failed open a popup, using redirect mode instead...", redirectUrl);
|
|
99
108
|
window.location.href = redirectUrl;
|
|
100
109
|
}
|
|
110
|
+
async handleBrowserCorsFailsSilently(redirectUrl) {
|
|
111
|
+
console.warn("Login app opened in a browser where popups fail silently...", {
|
|
112
|
+
redirectUrl,
|
|
113
|
+
iframeIsVisible: iframeIsVisible(),
|
|
114
|
+
autoRedirect: this.config.autoRedirect,
|
|
115
|
+
});
|
|
116
|
+
BrowserAuthenticationInitiator.browserCorsFailsSilentlyRedirectUrl =
|
|
117
|
+
redirectUrl;
|
|
118
|
+
// Check autoRedirect config before proceeding
|
|
119
|
+
if (this.config.autoRedirect !== false && iframeIsVisible()) {
|
|
120
|
+
// hide the iframe as we're in redirect mode
|
|
121
|
+
// to avoid it loading then immediately disappearing
|
|
122
|
+
const iframe = document.getElementById("civic-auth-iframe-container");
|
|
123
|
+
if (iframe) {
|
|
124
|
+
iframe.style.display = "none";
|
|
125
|
+
}
|
|
126
|
+
return this.handleUserInteractionBrowserCorsFailsSilently();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async handleUserInteractionBrowserCorsFailsSilently() {
|
|
130
|
+
const isInEmbeddedIframeMode = isEmbeddedIframeMode();
|
|
131
|
+
// check if the iframe is visible
|
|
132
|
+
if (!isInEmbeddedIframeMode &&
|
|
133
|
+
this.config.autoRedirect !== false && // Add this check
|
|
134
|
+
BrowserAuthenticationInitiator.browserCorsFailsSilentlyRedirectUrl) {
|
|
135
|
+
console.warn("Browser CORS failed silently, redirecting...", BrowserAuthenticationInitiator.browserCorsFailsSilentlyRedirectUrl);
|
|
136
|
+
this.config.displayMode = "redirect";
|
|
137
|
+
collectAndSendSDKAnalytics(this.config.clientId, this.config.oauthServer, this.config.framework);
|
|
138
|
+
const signInUrl = await this.getSignInUrl();
|
|
139
|
+
window.dispatchEvent(new CustomEvent("locationWillChange", {
|
|
140
|
+
detail: { newUrl: signInUrl.toString() },
|
|
141
|
+
}));
|
|
142
|
+
setTimeout(() => {
|
|
143
|
+
window.location.href = signInUrl.toString();
|
|
144
|
+
}, 100);
|
|
145
|
+
return { isRedirecting: true };
|
|
146
|
+
}
|
|
147
|
+
return { isRedirecting: false };
|
|
148
|
+
}
|
|
101
149
|
async handleLoginAppDesignUpdate(options) {
|
|
102
150
|
this.setDesignOptions(options);
|
|
103
151
|
}
|
|
152
|
+
async getSignInUrl() {
|
|
153
|
+
const val = await generateOauthLoginUrl({
|
|
154
|
+
...this.config,
|
|
155
|
+
state: this.state,
|
|
156
|
+
});
|
|
157
|
+
return val;
|
|
158
|
+
}
|
|
104
159
|
// Use the config (Client ID, scopes OAuth Server, Endpoints, PKCEConsumer) to generate a new login url
|
|
105
160
|
// and then use the display mode to decide how to send the user there
|
|
106
161
|
async signIn(iframeRef) {
|
|
107
162
|
// Send SDK analytics when authentication starts
|
|
108
163
|
// Fire and forget - don't block authentication if analytics fails
|
|
109
164
|
collectAndSendSDKAnalytics(this.config.clientId, this.config.oauthServer, this.config.framework);
|
|
110
|
-
const url = await
|
|
111
|
-
...this.config,
|
|
112
|
-
state: this.state,
|
|
113
|
-
});
|
|
165
|
+
const url = await this.getSignInUrl();
|
|
114
166
|
if (this.config.displayMode === "iframe") {
|
|
115
167
|
const ref = getIframeRef(iframeRef);
|
|
116
168
|
ref.setAttribute("src", url.toString());
|
|
@@ -312,10 +364,14 @@ export class BrowserAuthenticationService extends BrowserAuthenticationInitiator
|
|
|
312
364
|
try {
|
|
313
365
|
// Perform token refresh (no need to call init explicitly)
|
|
314
366
|
const tokenResponse = await refresher.refreshAccessToken();
|
|
367
|
+
// For backend flows, tokenResponse might be null since tokens are in HTTP-only cookies
|
|
368
|
+
if (tokenResponse) {
|
|
369
|
+
// Store tokens for SPA flows where tokens are accessible
|
|
370
|
+
await this.storeTokensOnLogin(tokenResponse);
|
|
371
|
+
}
|
|
315
372
|
// Return a new session with the refreshed tokens
|
|
316
373
|
const refreshedSession = await this.getSessionData();
|
|
317
374
|
if (refreshedSession && refreshedSession.authenticated) {
|
|
318
|
-
await this.storeTokensOnLogin(tokenResponse);
|
|
319
375
|
return {
|
|
320
376
|
...refreshedSession,
|
|
321
377
|
authenticated: true,
|