@civic/auth 0.11.0 โ 0.11.1-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 +1 -0
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +2 -0
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/oauth.d.ts +2 -1
- package/dist/lib/oauth.d.ts.map +1 -1
- package/dist/lib/oauth.js +2 -1
- package/dist/lib/oauth.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 +2 -0
- package/dist/nextjs/config.js.map +1 -1
- package/dist/nextjs/hooks/useInitialAuthConfig.d.ts +1 -0
- package/dist/nextjs/hooks/useInitialAuthConfig.d.ts.map +1 -1
- package/dist/nextjs/hooks/useInitialAuthConfig.js +7 -5
- package/dist/nextjs/hooks/useInitialAuthConfig.js.map +1 -1
- package/dist/nextjs/providers/NextAuthProviderClient.d.ts.map +1 -1
- package/dist/nextjs/providers/NextAuthProviderClient.js +2 -1
- package/dist/nextjs/providers/NextAuthProviderClient.js.map +1 -1
- package/dist/nextjs/routeHandler.d.ts.map +1 -1
- package/dist/nextjs/routeHandler.js +3 -0
- package/dist/nextjs/routeHandler.js.map +1 -1
- package/dist/reactjs/components/Button.d.ts.map +1 -1
- package/dist/reactjs/components/Button.js +4 -0
- package/dist/reactjs/components/Button.js.map +1 -1
- package/dist/reactjs/components/ButtonContentOrLoader.d.ts.map +1 -1
- package/dist/reactjs/components/ButtonContentOrLoader.js +9 -2
- package/dist/reactjs/components/ButtonContentOrLoader.js.map +1 -1
- package/dist/reactjs/components/SignInButton.d.ts +12 -2
- package/dist/reactjs/components/SignInButton.d.ts.map +1 -1
- package/dist/reactjs/components/SignInButton.js +52 -16
- package/dist/reactjs/components/SignInButton.js.map +1 -1
- package/dist/reactjs/components/SignOutButton.d.ts +8 -2
- package/dist/reactjs/components/SignOutButton.d.ts.map +1 -1
- package/dist/reactjs/components/SignOutButton.js +33 -8
- package/dist/reactjs/components/SignOutButton.js.map +1 -1
- package/dist/reactjs/components/UserButton.d.ts.map +1 -1
- package/dist/reactjs/components/UserButton.js +9 -29
- package/dist/reactjs/components/UserButton.js.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.d.ts +1 -0
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
- package/dist/reactjs/styles/colors.d.ts +4 -0
- package/dist/reactjs/styles/colors.d.ts.map +1 -1
- package/dist/reactjs/styles/colors.js +4 -2
- package/dist/reactjs/styles/colors.js.map +1 -1
- package/dist/server/ServerAuthenticationResolver.d.ts.map +1 -1
- package/dist/server/ServerAuthenticationResolver.js +49 -54
- package/dist/server/ServerAuthenticationResolver.js.map +1 -1
- package/dist/server/config.d.ts +7 -0
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +3 -0
- package/dist/server/session.js.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.d.ts.map +1 -1
- package/dist/shared/lib/AuthenticationRefresherImpl.js +28 -21
- package/dist/shared/lib/AuthenticationRefresherImpl.js.map +1 -1
- package/dist/shared/lib/BrowserAuthenticationRefresher.d.ts.map +1 -1
- package/dist/shared/lib/BrowserAuthenticationRefresher.js +6 -0
- package/dist/shared/lib/BrowserAuthenticationRefresher.js.map +1 -1
- package/dist/shared/lib/cookieConfig.d.ts.map +1 -1
- package/dist/shared/lib/cookieConfig.js +1 -0
- package/dist/shared/lib/cookieConfig.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.map +1 -1
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js +7 -1
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.d.ts +23 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +157 -2
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +3 -8
- package/dist/vanillajs/auth/SessionManager.js.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.d.ts +10 -0
- package/dist/vanillajs/auth/TokenRefresher.d.ts.map +1 -1
- package/dist/vanillajs/auth/TokenRefresher.js +80 -5
- package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
- package/package.json +1 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.d.ts +0 -8
- package/dist/nextjs/NextClientAuthenticationRefresher.d.ts.map +0 -1
- package/dist/nextjs/NextClientAuthenticationRefresher.js +0 -24
- package/dist/nextjs/NextClientAuthenticationRefresher.js.map +0 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts +0 -12
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.d.ts.map +0 -1
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.js +0 -25
- package/dist/nextjs/NextServerAuthenticationRefresherImpl.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BackendAuthenticationRefresher.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/BackendAuthenticationRefresher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthConfig } from \"../../server/config.js\";\nimport type { AuthStorage, OIDCTokenResponseBody } from \"../../types.js\";\nimport { GenericAuthenticationRefresher } from \"../../shared/lib/GenericAuthenticationRefresher.js\";\nimport {\n getBackendEndpoints,\n resolveEndpointUrl,\n retrieveOidcSessionExpiredAtSeconds,\n} from \"../../shared/lib/util.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport { AuthEvent } from \"../types/index.js\";\n\n/**\n * BackendAuthenticationRefresher handles token refresh for backend authentication flows\n * by calling the backend's refresh API endpoint instead of accessing browser storage.\n * This is used when loginUrl is configured, indicating backend integration.\n */\nexport class BackendAuthenticationRefresher extends GenericAuthenticationRefresher {\n private logger = createLogger(\"backend-auth-refresher\");\n private loginUrl: string;\n private autoRefreshTimeoutId?: number;\n private events?: AuthenticationEvents;\n constructor(\n authConfig: AuthConfig,\n storage: AuthStorage,\n loginUrl: string,\n onError: (error: Error) => Promise<void>,\n events?: AuthenticationEvents,\n ) {\n super(onError);\n this.storage = storage;\n this.authConfig = authConfig;\n this.loginUrl = loginUrl;\n this.events = events;\n this.logger.info(\"BackendAuthenticationRefresher initialized\", {\n loginUrl: this.loginUrl,\n storage: this.storage,\n });\n }\n\n static async build(\n authConfig: AuthConfig,\n storage: AuthStorage,\n loginUrl: string,\n onError: (error: Error) => Promise<void>,\n events?: AuthenticationEvents,\n ): Promise<BackendAuthenticationRefresher> {\n return new BackendAuthenticationRefresher(\n authConfig,\n storage,\n loginUrl,\n onError,\n events,\n );\n }\n\n /**\n * Override getRefreshToken to indicate that backend flows don't need browser-accessible refresh tokens\n */\n override async getRefreshToken(): Promise<string> {\n // For backend flows, we don't need to retrieve the refresh token from browser storage\n // The backend handles the refresh token internally via HTTP-only cookies\n this.logger.debug(\"Backend flow: refresh token managed server-side\");\n return \"backend-managed\"; // Placeholder token\n }\n\n /**\n * Refresh tokens by calling the backend's refresh API endpoint\n */\n override async refreshAccessToken(): Promise<OIDCTokenResponseBody | null> {\n try {\n // Emit refresh started event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n\n const backendUrl = new URL(this.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.authConfig?.backendEndpoints);\n const refreshEndpoint = resolveEndpointUrl(backendUrl, endpoints.refresh);\n\n this.logger.info(\"Calling backend refresh endpoint\", {\n endpoint: refreshEndpoint,\n });\n\n const response = await fetch(refreshEndpoint, {\n method: \"POST\",\n credentials: \"include\", // Include HTTP-only cookies\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n const error = new Error(\n `Backend refresh failed: ${response.status} ${response.statusText} - ${errorText}`,\n );\n\n // Emit refresh error event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n throw error;\n }\n\n this.logger.info(\"Backend token refresh successful\");\n\n // Emit refresh complete event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n\n // For backend flows, tokens are managed in HTTP-only cookies\n // and are not accessible to JavaScript, so we return null\n return null;\n } catch (error) {\n this.logger.error(\"Backend token refresh failed\", { error });\n\n // Emit refresh error event if not already emitted\n if (\n error instanceof Error &&\n !error.message.includes(\"Backend refresh failed\")\n ) {\n this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n\n throw error;\n }\n }\n\n /**\n * For backend flows, we don't need to store tokens in browser storage\n * since they're managed server-side in HTTP-only cookies\n */\n async storeTokens(\n tokenResponseBody: OIDCTokenResponseBody | null,\n ): Promise<void> {\n this.logger.debug(\n \"Backend flow: tokens stored server-side, skipping browser storage\",\n { tokenResponseBody },\n );\n // No-op for backend flows - tokens are stored server-side\n }\n\n async handleAutoRefresh() {\n try {\n if (this.abortController?.signal.aborted) {\n this.logger.warn(\n \"Auto-refresh aborted, skipping token refresh attempt\",\n );\n return;\n }\n this.logger.info(\"Auto-refreshing backend tokens\");\n await this.refreshTokens();\n // Schedule next refresh\n this.setupAutorefresh();\n } catch (error) {\n this.logger.error(\"Auto-refresh failed\", { error });\n await this.onError(error as Error);\n }\n }\n\n /**\n * Setup auto-refresh for backend flows\n * Since we can't access token expiration from HTTP-only cookies,\n * we'll use a conservative refresh interval\n */\n async setupAutorefresh() {\n const nowSeconds = Math.floor(Date.now() / 1000);\n // default the refresh period to 50 minutes in case storage isn't available\n let expiresAtSeconds = nowSeconds + 50 * 60; // 50 minutes;\n if (this.storage) {\n const retrievedExpiresAt = await retrieveOidcSessionExpiredAtSeconds(\n this.storage,\n );\n expiresAtSeconds = retrievedExpiresAt || expiresAtSeconds;\n }\n // Clear any existing timeout\n this.clearAutorefresh();\n\n // Calculate time until expiry (subtract 30 seconds as buffer)\n const bufferTime = 30; // 30 seconds\n // calculate the refresh time based on expires at. If expiresAt is in the past, default to 50 minutes\n // as the backend should have already rehydrated and this case shouldn't occur\n const refreshTimeoutSeconds = Math.max(\n 0,\n expiresAtSeconds - bufferTime - nowSeconds,\n );\n\n // setup an abort controller so we can cancel any in-flight requests if needed\n this.abortController = new AbortController();\n this.autoRefreshTimeoutId = window.setTimeout(() => {\n this.handleAutoRefresh();\n }, 1000 * refreshTimeoutSeconds);\n this.logger.debug(\n `Set auto-refresh timeout with duration ${refreshTimeoutSeconds} seconds`,\n );\n }\n\n /**\n * Clear auto-refresh for backend flows\n */\n clearAutorefresh(): void {\n if (this.autoRefreshTimeoutId) {\n this.logger.debug(\"Clearing auto-refresh timeout for backend flow\");\n // Abort any in-flight requests\n this.abortController?.abort();\n window.clearTimeout(this.autoRefreshTimeoutId);\n this.autoRefreshTimeoutId = undefined;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"BackendAuthenticationRefresher.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/BackendAuthenticationRefresher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthConfig } from \"../../server/config.js\";\nimport type { AuthStorage, OIDCTokenResponseBody } from \"../../types.js\";\nimport { GenericAuthenticationRefresher } from \"../../shared/lib/GenericAuthenticationRefresher.js\";\nimport {\n getBackendEndpoints,\n resolveEndpointUrl,\n retrieveOidcSessionExpiredAtSeconds,\n} from \"../../shared/lib/util.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport { AuthEvent } from \"../types/index.js\";\n\n/**\n * BackendAuthenticationRefresher handles token refresh for backend authentication flows\n * by calling the backend's refresh API endpoint instead of accessing browser storage.\n * This is used when loginUrl is configured, indicating backend integration.\n */\nexport class BackendAuthenticationRefresher extends GenericAuthenticationRefresher {\n private logger = createLogger(\"backend-auth-refresher\");\n private loginUrl: string;\n private autoRefreshTimeoutId?: number;\n private events?: AuthenticationEvents;\n constructor(\n authConfig: AuthConfig,\n storage: AuthStorage,\n loginUrl: string,\n onError: (error: Error) => Promise<void>,\n events?: AuthenticationEvents,\n ) {\n super(onError);\n this.storage = storage;\n this.authConfig = authConfig;\n this.loginUrl = loginUrl;\n this.events = events;\n this.logger.info(\"BackendAuthenticationRefresher initialized\", {\n loginUrl: this.loginUrl,\n storage: this.storage,\n });\n }\n\n static async build(\n authConfig: AuthConfig,\n storage: AuthStorage,\n loginUrl: string,\n onError: (error: Error) => Promise<void>,\n events?: AuthenticationEvents,\n ): Promise<BackendAuthenticationRefresher> {\n return new BackendAuthenticationRefresher(\n authConfig,\n storage,\n loginUrl,\n onError,\n events,\n );\n }\n\n /**\n * Override getRefreshToken to indicate that backend flows don't need browser-accessible refresh tokens\n */\n override async getRefreshToken(): Promise<string> {\n // For backend flows, we don't need to retrieve the refresh token from browser storage\n // The backend handles the refresh token internally via HTTP-only cookies\n this.logger.debug(\"Backend flow: refresh token managed server-side\");\n return \"backend-managed\"; // Placeholder token\n }\n\n /**\n * Refresh tokens by calling the backend's refresh API endpoint\n */\n override async refreshAccessToken(): Promise<OIDCTokenResponseBody | null> {\n try {\n // Emit refresh started event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n\n const backendUrl = new URL(this.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.authConfig?.backendEndpoints);\n const refreshEndpoint = resolveEndpointUrl(backendUrl, endpoints.refresh);\n\n this.logger.info(\"Calling backend refresh endpoint\", {\n endpoint: refreshEndpoint,\n });\n\n const response = await fetch(refreshEndpoint, {\n method: \"POST\",\n credentials: \"include\", // Include HTTP-only cookies\n headers: {\n \"Content-Type\": \"application/json\",\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => \"Unknown error\");\n const error = new Error(\n `Backend refresh failed: ${response.status} ${response.statusText} - ${errorText}`,\n );\n\n // Emit refresh error event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n throw error;\n }\n\n this.logger.info(\"Backend token refresh successful\");\n\n // Emit refresh complete event\n this.events?.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n\n // For backend flows, tokens are managed in HTTP-only cookies\n // and are not accessible to JavaScript, so we return null\n return null;\n } catch (error) {\n this.logger.error(\"Backend token refresh failed\", { error });\n\n // Emit refresh error event if not already emitted\n if (\n error instanceof Error &&\n !error.message.includes(\"Backend refresh failed\")\n ) {\n this.events?.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n\n throw error;\n }\n }\n\n /**\n * For backend flows, we don't need to store tokens in browser storage\n * since they're managed server-side in HTTP-only cookies\n */\n async storeTokens(\n tokenResponseBody: OIDCTokenResponseBody | null,\n ): Promise<void> {\n this.logger.debug(\n \"Backend flow: tokens stored server-side, skipping browser storage\",\n { tokenResponseBody },\n );\n // No-op for backend flows - tokens are stored server-side\n }\n\n async handleAutoRefresh() {\n try {\n if (this.abortController?.signal.aborted) {\n this.logger.warn(\n \"Auto-refresh aborted, skipping token refresh attempt\",\n );\n return;\n }\n\n // Check if page is hidden/frozen to prevent cookie-less refresh attempts\n if (\n typeof document !== \"undefined\" &&\n (document.hidden || document.visibilityState === \"hidden\")\n ) {\n this.logger.info(\n \"Page is hidden/frozen, skipping auto-refresh to prevent cookie-less requests\",\n );\n return;\n }\n this.logger.info(\"Auto-refreshing backend tokens\", new Date());\n await this.refreshTokens();\n // Schedule next refresh\n this.setupAutorefresh();\n } catch (error) {\n this.logger.error(\"Auto-refresh failed\", { error });\n await this.onError(error as Error);\n }\n }\n\n /**\n * Setup auto-refresh for backend flows\n * Since we can't access token expiration from HTTP-only cookies,\n * we'll use a conservative refresh interval\n */\n async setupAutorefresh() {\n const nowSeconds = Math.floor(Date.now() / 1000);\n // default the refresh period to 50 minutes in case storage isn't available\n let expiresAtSeconds = nowSeconds + 50 * 60; // 50 minutes;\n if (this.storage) {\n const retrievedExpiresAt = await retrieveOidcSessionExpiredAtSeconds(\n this.storage,\n );\n expiresAtSeconds = retrievedExpiresAt || expiresAtSeconds;\n }\n // Clear any existing timeout\n this.clearAutorefresh();\n\n // Calculate time until expiry (subtract 30 seconds as buffer)\n const bufferTime = 30; // 30 seconds\n // calculate the refresh time based on expires at. If expiresAt is in the past, default to 50 minutes\n // as the backend should have already rehydrated and this case shouldn't occur\n const refreshTimeoutSeconds = Math.max(\n 0,\n expiresAtSeconds - bufferTime - nowSeconds,\n );\n\n // setup an abort controller so we can cancel any in-flight requests if needed\n this.abortController = new AbortController();\n this.autoRefreshTimeoutId = window.setTimeout(() => {\n this.handleAutoRefresh();\n }, 1000 * refreshTimeoutSeconds);\n this.logger.debug(\n `Set auto-refresh timeout with duration ${refreshTimeoutSeconds} seconds`,\n );\n }\n\n /**\n * Clear auto-refresh for backend flows\n */\n clearAutorefresh(): void {\n if (this.autoRefreshTimeoutId) {\n this.logger.debug(\"Clearing auto-refresh timeout for backend flow\");\n // Abort any in-flight requests\n this.abortController?.abort();\n window.clearTimeout(this.autoRefreshTimeoutId);\n this.autoRefreshTimeoutId = undefined;\n }\n }\n}\n"]}
|
|
@@ -103,6 +103,29 @@ export declare class CivicAuth {
|
|
|
103
103
|
* @returns True if iframe preloading is enabled
|
|
104
104
|
*/
|
|
105
105
|
getPreloadEnabled(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Check if the authentication session is stale (older than timeout)
|
|
108
|
+
* @returns True if session is stale and needs re-initialization
|
|
109
|
+
*/
|
|
110
|
+
private isAuthenticationSessionStale;
|
|
111
|
+
/**
|
|
112
|
+
* Get the authentication started timestamp from storage
|
|
113
|
+
* @returns The timestamp in seconds or null if not set
|
|
114
|
+
*/
|
|
115
|
+
private getAuthenticationStartedAtSeconds;
|
|
116
|
+
/**
|
|
117
|
+
* Set the authentication started timestamp in storage
|
|
118
|
+
*/
|
|
119
|
+
private setAuthenticationStartedAtSeconds;
|
|
120
|
+
/**
|
|
121
|
+
* Clear the authentication started timestamp from storage
|
|
122
|
+
*/
|
|
123
|
+
private clearAuthenticationStartedAtSeconds;
|
|
124
|
+
/**
|
|
125
|
+
* Re-initialize the auth client when session is stale
|
|
126
|
+
* Preserves configuration but resets authentication state
|
|
127
|
+
*/
|
|
128
|
+
private reinitializeForStaleSession;
|
|
106
129
|
/**
|
|
107
130
|
* Starts the authentication process
|
|
108
131
|
* @returns A promise that resolves with the authentication result
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CivicAuth.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/CivicAuth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AA2B7D,OAAO,KAAK,EACV,qBAAqB,EAEtB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"CivicAuth.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/CivicAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,11 @@ import { MessageHandler } from "./handlers/MessageHandler.js";
|
|
|
18
18
|
import { PopupHandler } from "./handlers/PopupHandler.js";
|
|
19
19
|
import { IframeAuthHandler } from "./handlers/IframeAuthHandler.js";
|
|
20
20
|
import { IframeManager } from "../iframe/IframeManager.js";
|
|
21
|
+
// Authentication session timeout in seconds (30 minutes)
|
|
22
|
+
const AUTH_SESSION_TIMEOUT_SECONDS = 30 * 60;
|
|
23
|
+
const AUTH_SESSION_TIMESTAMP_KEY = "civic_auth_authentication_started_at_seconds";
|
|
24
|
+
// In-memory fallback store for environments without localStorage
|
|
25
|
+
const inMemoryStore = new Map();
|
|
21
26
|
/**
|
|
22
27
|
* CivicAuth client for handling OAuth authentication
|
|
23
28
|
*
|
|
@@ -139,6 +144,7 @@ export class CivicAuth {
|
|
|
139
144
|
// Construct authConfig based on our discriminated union pattern
|
|
140
145
|
const authConfig = loginUrl && !this.config.clientId
|
|
141
146
|
? {
|
|
147
|
+
...this.config,
|
|
142
148
|
// Backend integration mode: loginUrl required, clientId optional
|
|
143
149
|
loginUrl,
|
|
144
150
|
redirectUrl: this.config.redirectUrl,
|
|
@@ -146,6 +152,7 @@ export class CivicAuth {
|
|
|
146
152
|
backendEndpoints: this.config.backendEndpoints,
|
|
147
153
|
}
|
|
148
154
|
: {
|
|
155
|
+
...this.config,
|
|
149
156
|
// Standard OAuth mode: clientId required, loginUrl optional
|
|
150
157
|
clientId: this.config.clientId,
|
|
151
158
|
loginUrl,
|
|
@@ -308,7 +315,7 @@ export class CivicAuth {
|
|
|
308
315
|
/**
|
|
309
316
|
* Builds the authentication URL with PKCE challenge
|
|
310
317
|
*/
|
|
311
|
-
async buildAuthUrl() {
|
|
318
|
+
async buildAuthUrl(isStale) {
|
|
312
319
|
// If a login URL is set (for backend integration), use that instead
|
|
313
320
|
if (this.loginUrl) {
|
|
314
321
|
this.logger.info("๐ Using login URL for backend integration", {
|
|
@@ -322,6 +329,7 @@ export class CivicAuth {
|
|
|
322
329
|
framework: this.config.framework || "vanillajs",
|
|
323
330
|
sdkVersion: getVersion(),
|
|
324
331
|
loginSuccessUrl: this.config.loginSuccessUrl,
|
|
332
|
+
previousSessionStale: isStale,
|
|
325
333
|
});
|
|
326
334
|
// Append state as query parameter to loginUrl
|
|
327
335
|
const url = new URL(this.loginUrl, window.location.origin);
|
|
@@ -401,6 +409,9 @@ export class CivicAuth {
|
|
|
401
409
|
displayMode: this.config.displayMode,
|
|
402
410
|
});
|
|
403
411
|
try {
|
|
412
|
+
// Set authentication timestamp when preloading starts
|
|
413
|
+
// This ensures stale detection works even for preloaded iframes
|
|
414
|
+
this.setAuthenticationStartedAtSeconds();
|
|
404
415
|
await this.iframeAuthHandler.preloadIframe(fullAuthUrl);
|
|
405
416
|
const iframeElement = this.iframeAuthHandler.getIframeElement();
|
|
406
417
|
if (iframeElement) {
|
|
@@ -409,6 +420,8 @@ export class CivicAuth {
|
|
|
409
420
|
this.logger.info("โ
Authentication iframe preloaded successfully");
|
|
410
421
|
}
|
|
411
422
|
catch (error) {
|
|
423
|
+
// Clear timestamp if preloading fails
|
|
424
|
+
this.clearAuthenticationStartedAtSeconds();
|
|
412
425
|
const errorMessage = error instanceof Error ? error.message : "Failed to preload iframe";
|
|
413
426
|
this.logger.error("โ Failed to preload authentication iframe", {
|
|
414
427
|
error: errorMessage,
|
|
@@ -458,6 +471,136 @@ export class CivicAuth {
|
|
|
458
471
|
}
|
|
459
472
|
return this.iframeAuthHandler?.getPreloadEnabled() ?? false;
|
|
460
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Check if the authentication session is stale (older than timeout)
|
|
476
|
+
* @returns True if session is stale and needs re-initialization
|
|
477
|
+
*/
|
|
478
|
+
isAuthenticationSessionStale() {
|
|
479
|
+
const timestampSeconds = this.getAuthenticationStartedAtSeconds();
|
|
480
|
+
if (!timestampSeconds) {
|
|
481
|
+
return false; // No timestamp means no stale session
|
|
482
|
+
}
|
|
483
|
+
const currentTimeSeconds = Math.floor(Date.now() / 1000);
|
|
484
|
+
const elapsedSeconds = currentTimeSeconds - timestampSeconds;
|
|
485
|
+
return elapsedSeconds > AUTH_SESSION_TIMEOUT_SECONDS;
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Get the authentication started timestamp from storage
|
|
489
|
+
* @returns The timestamp in seconds or null if not set
|
|
490
|
+
*/
|
|
491
|
+
getAuthenticationStartedAtSeconds() {
|
|
492
|
+
try {
|
|
493
|
+
let value = null;
|
|
494
|
+
// Try localStorage first
|
|
495
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
496
|
+
value = window.localStorage.getItem(AUTH_SESSION_TIMESTAMP_KEY);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
// Fall back to in-memory store
|
|
500
|
+
value = inMemoryStore.get(AUTH_SESSION_TIMESTAMP_KEY) || null;
|
|
501
|
+
}
|
|
502
|
+
if (value) {
|
|
503
|
+
const timestamp = parseInt(value, 10);
|
|
504
|
+
return isNaN(timestamp) ? null : timestamp;
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
// localStorage might throw in private browsing mode
|
|
510
|
+
this.logger.warn("Failed to get authentication timestamp, using in-memory fallback", { error });
|
|
511
|
+
const value = inMemoryStore.get(AUTH_SESSION_TIMESTAMP_KEY);
|
|
512
|
+
if (value) {
|
|
513
|
+
const timestamp = parseInt(value, 10);
|
|
514
|
+
return isNaN(timestamp) ? null : timestamp;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Set the authentication started timestamp in storage
|
|
521
|
+
*/
|
|
522
|
+
setAuthenticationStartedAtSeconds() {
|
|
523
|
+
try {
|
|
524
|
+
const currentTimeSeconds = Math.floor(Date.now() / 1000);
|
|
525
|
+
const value = currentTimeSeconds.toString();
|
|
526
|
+
// Try localStorage first
|
|
527
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
528
|
+
window.localStorage.setItem(AUTH_SESSION_TIMESTAMP_KEY, value);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
// Fall back to in-memory store
|
|
532
|
+
inMemoryStore.set(AUTH_SESSION_TIMESTAMP_KEY, value);
|
|
533
|
+
}
|
|
534
|
+
this.logger.debug("Authentication timestamp set", {
|
|
535
|
+
timestamp: currentTimeSeconds,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
// localStorage might throw in private browsing mode
|
|
540
|
+
this.logger.warn("Failed to set authentication timestamp in localStorage, using in-memory fallback", { error });
|
|
541
|
+
const currentTimeSeconds = Math.floor(Date.now() / 1000);
|
|
542
|
+
inMemoryStore.set(AUTH_SESSION_TIMESTAMP_KEY, currentTimeSeconds.toString());
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Clear the authentication started timestamp from storage
|
|
547
|
+
*/
|
|
548
|
+
clearAuthenticationStartedAtSeconds() {
|
|
549
|
+
try {
|
|
550
|
+
// Try localStorage first
|
|
551
|
+
if (typeof window !== "undefined" && window.localStorage) {
|
|
552
|
+
window.localStorage.removeItem(AUTH_SESSION_TIMESTAMP_KEY);
|
|
553
|
+
}
|
|
554
|
+
// Also clear from in-memory store
|
|
555
|
+
inMemoryStore.delete(AUTH_SESSION_TIMESTAMP_KEY);
|
|
556
|
+
this.logger.debug("Authentication timestamp cleared");
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
// localStorage might throw in private browsing mode
|
|
560
|
+
this.logger.warn("Failed to clear authentication timestamp from localStorage", { error });
|
|
561
|
+
inMemoryStore.delete(AUTH_SESSION_TIMESTAMP_KEY);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Re-initialize the auth client when session is stale
|
|
566
|
+
* Preserves configuration but resets authentication state
|
|
567
|
+
*/
|
|
568
|
+
async reinitializeForStaleSession() {
|
|
569
|
+
this.logger.info("๐ Re-initializing due to stale authentication session");
|
|
570
|
+
// Clear any existing auth promise to ensure fresh start
|
|
571
|
+
if (this.authPromise) {
|
|
572
|
+
this.logger.info("๐งน Clearing existing auth promise for fresh start");
|
|
573
|
+
this.authPromise = undefined;
|
|
574
|
+
this.authPromiseResolve = undefined;
|
|
575
|
+
this.authPromiseReject = undefined;
|
|
576
|
+
}
|
|
577
|
+
// Clean up any existing handlers before re-initialization
|
|
578
|
+
if (this.messageHandler || this.popupHandler || this.iframeAuthHandler) {
|
|
579
|
+
this.logger.info("๐งน Cleaning up existing handlers before re-initialization");
|
|
580
|
+
this.cleanup();
|
|
581
|
+
}
|
|
582
|
+
// Clear the stale authentication timestamp
|
|
583
|
+
this.clearAuthenticationStartedAtSeconds();
|
|
584
|
+
try {
|
|
585
|
+
// Ensure OAuth endpoints are available
|
|
586
|
+
if (!this.endpoints) {
|
|
587
|
+
this.logger.info("๐ Getting OAuth endpoints for re-initialization");
|
|
588
|
+
this.endpoints = await getOauthEndpoints(this.config.oauthServerBaseUrl);
|
|
589
|
+
}
|
|
590
|
+
// Re-initialize all handlers with preserved config
|
|
591
|
+
this.logger.info("๐ Re-initializing handlers with preserved configuration");
|
|
592
|
+
this.initializeHandlers();
|
|
593
|
+
this.logger.info("โ
Re-initialization completed successfully");
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
const errorMessage = error instanceof Error ? error.message : "Failed to re-initialize";
|
|
597
|
+
this.logger.error("โ Failed to re-initialize for stale session", {
|
|
598
|
+
error: errorMessage,
|
|
599
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
600
|
+
});
|
|
601
|
+
throw new CivicAuthError(`Failed to re-initialize authentication: ${errorMessage}`, CivicAuthErrorCode.INIT_FAILED);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
461
604
|
/**
|
|
462
605
|
* Starts the authentication process
|
|
463
606
|
* @returns A promise that resolves with the authentication result
|
|
@@ -469,6 +612,12 @@ export class CivicAuth {
|
|
|
469
612
|
userAgent: navigator.userAgent,
|
|
470
613
|
currentUrl: window.location.href,
|
|
471
614
|
});
|
|
615
|
+
// Check if authentication session is stale and needs re-initialization
|
|
616
|
+
const isStale = this.isAuthenticationSessionStale();
|
|
617
|
+
if (isStale) {
|
|
618
|
+
this.logger.info("โ ๏ธ Authentication session is stale, re-initializing...");
|
|
619
|
+
await this.reinitializeForStaleSession();
|
|
620
|
+
}
|
|
472
621
|
// Send SDK analytics when authentication starts
|
|
473
622
|
// Fire and forget - don't block authentication if analytics fails
|
|
474
623
|
collectAndSendSDKAnalytics(this.config.clientId, this.config.oauthServerBaseUrl, this.config.framework || "vanillajs");
|
|
@@ -486,7 +635,9 @@ export class CivicAuth {
|
|
|
486
635
|
this.logger.info("โณ Authentication already in progress, returning existing promise");
|
|
487
636
|
return this.authPromise;
|
|
488
637
|
}
|
|
489
|
-
|
|
638
|
+
// Set authentication started timestamp for fresh authentication
|
|
639
|
+
this.setAuthenticationStartedAtSeconds();
|
|
640
|
+
const fullAuthUrl = await this.buildAuthUrl(isStale);
|
|
490
641
|
this.logger.info("๐ Built authentication URL", {
|
|
491
642
|
url: fullAuthUrl,
|
|
492
643
|
displayMode: this.config.displayMode,
|
|
@@ -715,6 +866,8 @@ export class CivicAuth {
|
|
|
715
866
|
*/
|
|
716
867
|
handleAuthSuccess(result) {
|
|
717
868
|
this.logger.info("โ
Authentication successful");
|
|
869
|
+
// Clear authentication timestamp on successful completion
|
|
870
|
+
this.clearAuthenticationStartedAtSeconds();
|
|
718
871
|
this.authPromiseResolve?.(result);
|
|
719
872
|
// For embedded mode, preserve the iframe completely - don't cleanup
|
|
720
873
|
// The iframe should stay visible showing the successful authentication state
|
|
@@ -735,6 +888,8 @@ export class CivicAuth {
|
|
|
735
888
|
*/
|
|
736
889
|
handleAuthError(error) {
|
|
737
890
|
this.logger.error("โ Authentication failed", { error: error.message });
|
|
891
|
+
// Clear authentication timestamp on error/cancellation
|
|
892
|
+
this.clearAuthenticationStartedAtSeconds();
|
|
738
893
|
this.authPromiseReject?.(error);
|
|
739
894
|
// For embedded mode, preserve the iframe to show error state
|
|
740
895
|
// For other modes, do full cleanup as before
|