@civic/auth 0.9.1 → 0.9.3-beta.4
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 +12 -0
- package/dist/nextjs/middleware.d.ts.map +1 -1
- package/dist/nextjs/middleware.js +28 -23
- package/dist/nextjs/middleware.js.map +1 -1
- package/dist/react-router-7/components/UserButton.d.ts.map +1 -1
- package/dist/react-router-7/components/UserButton.js +1 -3
- package/dist/react-router-7/components/UserButton.js.map +1 -1
- package/dist/react-router-7/cookies.d.ts +1 -1
- package/dist/react-router-7/cookies.d.ts.map +1 -1
- package/dist/react-router-7/cookies.js +9 -3
- package/dist/react-router-7/cookies.js.map +1 -1
- package/dist/react-router-7/useUser.d.ts +2 -0
- package/dist/react-router-7/useUser.d.ts.map +1 -1
- package/dist/react-router-7/useUser.js +11 -1
- package/dist/react-router-7/useUser.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/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +4 -0
- 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 +25 -0
- package/dist/vanillajs/auth/TokenRefresher.js.map +1 -1
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js +7 -0
- package/dist/vanillajs/auth/handlers/OAuthCallbackHandler.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# 0.9.3
|
|
2
|
+
|
|
3
|
+
- fix: Code Params cleanup by @cyberdude in https://github.com/civicteam/civic-auth/pull/1871
|
|
4
|
+
- fix(TECH-2490): Proper Clean up and Replenishment of session by @cyberdude in https://github.com/civicteam/civic-auth/pull/1872
|
|
5
|
+
|
|
6
|
+
**Full Changelog**: https://github.com/civicteam/civic-auth/compare/@civic/auth@0.9.1-beta.6...@civic/auth@0.9.2
|
|
7
|
+
|
|
8
|
+
# 0.9.2
|
|
9
|
+
|
|
10
|
+
- BUILD FAILED because of Civic Verify
|
|
11
|
+
|
|
12
|
+
# 0.9.1 Optional client ID
|
|
1
13
|
# 0.9.1 Optional client ID & Civic token verification library
|
|
2
14
|
|
|
3
15
|
- server SDK: Make client ID optional when login url is provided
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/nextjs/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AA+B3D,KAAK,UAAU,GAAG,CAChB,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAoC1C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,2BACd,sBAAsB,WACrC,WAAW,KACnB,OAAO,CAAC,WAAW,CAoBrB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/nextjs/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AA+B3D,KAAK,UAAU,GAAG,CAChB,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAoC1C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,2BACd,sBAAsB,WACrC,WAAW,KACnB,OAAO,CAAC,WAAW,CAoBrB,CAAC;AA+HF;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,gBACZ,kBAAkB,eACf,WAAW,KAAG,OAAO,CAAC,YAAY,CAOjD,CAAC;AAEJ;;;;;;;GAOG;AAEH,wBAAgB,QAAQ,CACtB,UAAU,EAAE,UAAU,GACrB,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAEjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,IAAI,CAAC,UAAU,GAAE,kBAAuB,gBAExC,UAAU,KACrB,CAAC,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC,CAQrD"}
|
|
@@ -100,29 +100,34 @@ const applyAuth = async (authConfig, request) => {
|
|
|
100
100
|
const authConfigWithDefaults = resolveAuthConfig(authConfig);
|
|
101
101
|
// Always validate tokens first to determine authentication state
|
|
102
102
|
const session = await validateAuthTokensIfPresent(authConfigWithDefaults, request);
|
|
103
|
-
logger.debug("applyAuth session data:",
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
authConfigWithDefaults.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
103
|
+
logger.debug("applyAuth session data:", {
|
|
104
|
+
session,
|
|
105
|
+
pathName: request.nextUrl.pathname,
|
|
106
|
+
loginUrl: authConfigWithDefaults.loginUrl,
|
|
107
|
+
});
|
|
108
|
+
// if we're on the loginUrl, we always want to try to authenticate, so exclude it from auth bypass checks
|
|
109
|
+
if (request.nextUrl.pathname !== authConfigWithDefaults.loginUrl) {
|
|
110
|
+
// Skip auth protection for Civic Auth system URLs (callback, challenge, logout)
|
|
111
|
+
if ([
|
|
112
|
+
authConfigWithDefaults.callbackUrl,
|
|
113
|
+
authConfigWithDefaults.challengeUrl,
|
|
114
|
+
authConfigWithDefaults.logoutCallbackUrl,
|
|
115
|
+
authConfigWithDefaults.logoutUrl,
|
|
116
|
+
].includes(request.nextUrl.pathname) &&
|
|
117
|
+
request.method === "GET") {
|
|
118
|
+
logger.debug("→ Skipping auth check - this a URL defined in authConfig", request.nextUrl.pathname);
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
// Skip auth protection for paths not in include patterns (except loginUrl)
|
|
122
|
+
if (!matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.include)) {
|
|
123
|
+
logger.debug("→ Skipping auth check - path not in include patterns", request.nextUrl.pathname);
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
// Skip auth protection for paths in exclude patterns (except loginUrl)
|
|
127
|
+
if (matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.exclude)) {
|
|
128
|
+
logger.debug("→ Skipping auth check - path in exclude patterns", request.nextUrl.pathname);
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
126
131
|
}
|
|
127
132
|
// Handle unauthenticated users on protected routes
|
|
128
133
|
if (!session.authenticated) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/nextjs/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authenticates the user on all requests by checking the token cookie\n *\n * Usage:\n * Option 1: use if no other middleware (e.g. no next-intl etc)\n * export default authMiddleware();\n *\n * Option 2: use if other middleware is needed - default auth config\n * export default withAuth((request) => {\n * logger.debug('in custom middleware', request.nextUrl.pathname);\n * return NextResponse.next();\n * })\n *\n * Option 3: use if other middleware is needed - specifying auth config\n * const withCivicAuth = auth({ loginUrl: '/login', include: ['/[.*]/user'] })\n * export default withCivicAuth((request) => {\n * logger.debug('in custom middleware', request.url);\n * return NextResponse.next();\n * })\n *\n */\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport picomatch from \"picomatch\";\nimport type {\n AuthConfigWithDefaults,\n OptionalAuthConfig,\n} from \"@/nextjs/config.js\";\nimport { resolveAuthConfig } from \"@/nextjs/config.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { ServerAuthenticationResolver } from \"@/server/ServerAuthenticationResolver.js\";\nimport type { AuthStorage, SessionData } from \"@/types.js\";\n\nconst logger = loggers.nextjs.middleware;\n\n/**\n * CookieStorage implementation for NextJS middleware context that works with NextRequest\n */\nclass NextjsReadOnlyRequestCookieStorage implements AuthStorage {\n constructor(private request: NextRequest) {}\n\n async get(key: string): Promise<string | null> {\n return this.request.cookies.get(key)?.value || null;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async set(key: string, value: string): Promise<void> {\n // In middleware, we can only set cookies via response objects\n // This method can't be used directly in middleware\n logger.error(\"Cannot set cookies directly in middleware\");\n throw new Error(\"Cannot set cookies directly in middleware\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async delete(key: string): Promise<void> {\n // In middleware, we can only delete cookies via response objects\n // This method can't be used directly in middleware\n logger.error(\"Cannot delete cookies directly in middleware\");\n throw new Error(\"Cannot delete cookies directly in middleware\");\n }\n}\n\ntype Middleware = (\n request: NextRequest,\n) => Promise<NextResponse> | NextResponse;\n\n// Matches globs:\n// Examples:\n// /user\n// /user/*\n// /user/**/info\nconst matchGlob = (pathname: string, globPattern: string) => {\n const matches = picomatch(globPattern);\n return matches(pathname);\n};\n\n// Matches globs:\n// Examples:\n// /user\n// /user/*\n// /user/**/info\nconst matchesGlobs = (pathname: string, patterns: string[]) =>\n patterns.some((pattern) => {\n if (!pattern) return false;\n return matchGlob(pathname, pattern);\n });\n\nconst getOriginUrl = (\n request: NextRequest,\n authConfig: AuthConfigWithDefaults,\n): string => {\n // Use configured baseUrl if provided (for reverse proxy scenarios)\n if (authConfig.baseUrl) {\n return authConfig.baseUrl;\n }\n\n // Fallback to nextUrl.origin (includes port automatically)\n return request.nextUrl.origin;\n};\n\n/**\n * use a ServerAuthenticationResolver to validate the existing session\n * using NextJS cookie storage\n * @param authConfigWithDefaults\n * @param request NextRequest object from middleware\n * @returns {Promise<SessionData>}\n */\nexport const validateAuthTokensIfPresent = async (\n authConfigWithDefaults: AuthConfigWithDefaults,\n request: NextRequest,\n): Promise<SessionData> => {\n try {\n // TODO: evaluate a more performant way to validate tokens\n // than having to call and verify the JWT tokens on every request\n const storage = new NextjsReadOnlyRequestCookieStorage(request);\n const authSessionService = await ServerAuthenticationResolver.build(\n {\n ...authConfigWithDefaults,\n redirectUrl: authConfigWithDefaults.callbackUrl,\n },\n storage,\n );\n // validate the existing session but don't auto-refresh as we can't set cookies in middleware\n const existingSession =\n await authSessionService.validateExistingSession(false);\n return existingSession;\n } catch (error) {\n logger.error(\"Error validating tokens\", error);\n return { authenticated: false };\n }\n};\n// internal - used by all exported functions\n/**\n * Core authentication middleware logic.\n *\n * Flow:\n * 1. Always validate tokens first to determine authentication state\n * 2. Skip protection for Civic Auth system URLs and excluded paths\n * 3. For unauthenticated users on protected routes:\n * - If refresh token exists: redirect to refresh endpoint\n * - If expired tokens exist: redirect to logout callback to clear them\n * - If on login URL: allow access (prevents redirect loops)\n * - Otherwise: redirect to login\n * 4. Allow authenticated users to continue\n */\nconst applyAuth = async (\n authConfig: OptionalAuthConfig,\n request: NextRequest,\n): Promise<NextResponse | undefined> => {\n const authConfigWithDefaults = resolveAuthConfig(authConfig);\n // Always validate tokens first to determine authentication state\n const session = await validateAuthTokensIfPresent(\n authConfigWithDefaults,\n request,\n );\n logger.debug(\"applyAuth session data:\", session);\n\n // Skip auth protection for Civic Auth system URLs (callback, challenge, logout)\n if (\n [\n authConfigWithDefaults.callbackUrl,\n authConfigWithDefaults.challengeUrl,\n authConfigWithDefaults.logoutCallbackUrl,\n authConfigWithDefaults.logoutUrl,\n ].includes(request.nextUrl.pathname) &&\n request.method === \"GET\"\n ) {\n logger.debug(\n \"→ Skipping auth check - this a URL defined in authConfig\",\n request.nextUrl.pathname,\n );\n return undefined;\n }\n\n // Skip auth protection for paths not in include patterns (except loginUrl)\n if (\n request.nextUrl.pathname !== authConfigWithDefaults.loginUrl &&\n !matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.include)\n ) {\n logger.debug(\n \"→ Skipping auth check - path not in include patterns\",\n request.nextUrl.pathname,\n );\n return undefined;\n }\n\n // Skip auth protection for paths in exclude patterns (except loginUrl)\n if (\n request.nextUrl.pathname !== authConfigWithDefaults.loginUrl &&\n matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.exclude)\n ) {\n logger.debug(\n \"→ Skipping auth check - path in exclude patterns\",\n request.nextUrl.pathname,\n );\n return undefined;\n }\n\n // Handle unauthenticated users on protected routes\n if (!session.authenticated) {\n const targetUrl =\n getOriginUrl(request, authConfigWithDefaults) + request.nextUrl.pathname;\n\n // Avoid redirect loops by checking if already in a redirect flow\n if (!request.nextUrl.searchParams.has(\"targetUrl\")) {\n // Attempt session rehydration if refresh token exists\n if (session.refreshToken) {\n const refreshUrl = new URL(\n authConfigWithDefaults.refreshUrl,\n getOriginUrl(request, authConfigWithDefaults),\n );\n refreshUrl.searchParams.set(\"targetUrl\", targetUrl);\n const redirectUrl = `${refreshUrl.toString()}`;\n logger.debug(`→ Refresh token found - redirecting to \"${redirectUrl}\"`);\n return NextResponse.redirect(redirectUrl);\n }\n\n // Clear expired/invalid tokens if they exist without refresh token\n if (session.accessToken || session.idToken) {\n const clearBadTokensUrl = new URL(\n authConfigWithDefaults.logoutCallbackUrl,\n getOriginUrl(request, authConfigWithDefaults),\n );\n clearBadTokensUrl.searchParams.set(\"targetUrl\", targetUrl);\n logger.debug(\n `→ Invalid tokens found without refresh - clearing via \"${clearBadTokensUrl}\"`,\n );\n return NextResponse.redirect(clearBadTokensUrl);\n }\n }\n\n // Allow access to login page to prevent redirect loops\n if (request.nextUrl.pathname === authConfigWithDefaults.loginUrl) {\n logger.debug(`→ Allowing access to login page for unauthenticated user`);\n return undefined;\n }\n\n // Final fallback: redirect to login\n const loginUrl = new URL(\n authConfigWithDefaults.loginUrl,\n getOriginUrl(request, authConfigWithDefaults),\n );\n const redirectUrl = `${loginUrl.toString()}`;\n logger.debug(\n `→ No valid tokens found - redirecting to login \"${redirectUrl}\"`,\n );\n return NextResponse.redirect(redirectUrl);\n }\n logger.debug(\"→ Auth check passed\");\n return undefined;\n};\n\n/**\n *\n * Use this when auth is the only middleware you need.\n * Usage:\n *\n * export default authMiddleware({ loginUrl = '/login' }); // or just authMiddleware();\n *\n */\nexport const authMiddleware =\n (authConfig: OptionalAuthConfig = {}) =>\n async (request: NextRequest): Promise<NextResponse> => {\n const response = await applyAuth(authConfig, request);\n if (response) return response;\n\n // NextJS doesn't do middleware chaining yet, so this does not mean\n // \"call the next middleware\" - it means \"continue to the route handler\"\n return NextResponse.next();\n };\n\n/**\n * Usage:\n *\n * export default withAuth(async (request) => {\n * logger.debug('my middleware');\n * return NextResponse.next();\n * })\n */\n// use this when you have your own middleware to chain\nexport function withAuth(\n middleware: Middleware,\n): (request: NextRequest) => Promise<NextResponse> {\n return auth()(middleware);\n}\n\n/**\n * Use this when you want to configure the middleware here (an alternative is to do it in the next.config file)\n *\n * Usage:\n *\n * export default auth(authConfig: AuthConfig ) => {\n * logger.debug('my middleware');\n * return NextResponse.next();\n * })\n *\n */\nexport function auth(authConfig: OptionalAuthConfig = {}) {\n return (\n middleware: Middleware,\n ): ((request: NextRequest) => Promise<NextResponse>) => {\n return async (request: NextRequest): Promise<NextResponse> => {\n const response = await applyAuth(authConfig, request);\n if (response) return response;\n\n return middleware(request);\n };\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/nextjs/middleware.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Authenticates the user on all requests by checking the token cookie\n *\n * Usage:\n * Option 1: use if no other middleware (e.g. no next-intl etc)\n * export default authMiddleware();\n *\n * Option 2: use if other middleware is needed - default auth config\n * export default withAuth((request) => {\n * logger.debug('in custom middleware', request.nextUrl.pathname);\n * return NextResponse.next();\n * })\n *\n * Option 3: use if other middleware is needed - specifying auth config\n * const withCivicAuth = auth({ loginUrl: '/login', include: ['/[.*]/user'] })\n * export default withCivicAuth((request) => {\n * logger.debug('in custom middleware', request.url);\n * return NextResponse.next();\n * })\n *\n */\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport picomatch from \"picomatch\";\nimport type {\n AuthConfigWithDefaults,\n OptionalAuthConfig,\n} from \"@/nextjs/config.js\";\nimport { resolveAuthConfig } from \"@/nextjs/config.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { ServerAuthenticationResolver } from \"@/server/ServerAuthenticationResolver.js\";\nimport type { AuthStorage, SessionData } from \"@/types.js\";\n\nconst logger = loggers.nextjs.middleware;\n\n/**\n * CookieStorage implementation for NextJS middleware context that works with NextRequest\n */\nclass NextjsReadOnlyRequestCookieStorage implements AuthStorage {\n constructor(private request: NextRequest) {}\n\n async get(key: string): Promise<string | null> {\n return this.request.cookies.get(key)?.value || null;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async set(key: string, value: string): Promise<void> {\n // In middleware, we can only set cookies via response objects\n // This method can't be used directly in middleware\n logger.error(\"Cannot set cookies directly in middleware\");\n throw new Error(\"Cannot set cookies directly in middleware\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async delete(key: string): Promise<void> {\n // In middleware, we can only delete cookies via response objects\n // This method can't be used directly in middleware\n logger.error(\"Cannot delete cookies directly in middleware\");\n throw new Error(\"Cannot delete cookies directly in middleware\");\n }\n}\n\ntype Middleware = (\n request: NextRequest,\n) => Promise<NextResponse> | NextResponse;\n\n// Matches globs:\n// Examples:\n// /user\n// /user/*\n// /user/**/info\nconst matchGlob = (pathname: string, globPattern: string) => {\n const matches = picomatch(globPattern);\n return matches(pathname);\n};\n\n// Matches globs:\n// Examples:\n// /user\n// /user/*\n// /user/**/info\nconst matchesGlobs = (pathname: string, patterns: string[]) =>\n patterns.some((pattern) => {\n if (!pattern) return false;\n return matchGlob(pathname, pattern);\n });\n\nconst getOriginUrl = (\n request: NextRequest,\n authConfig: AuthConfigWithDefaults,\n): string => {\n // Use configured baseUrl if provided (for reverse proxy scenarios)\n if (authConfig.baseUrl) {\n return authConfig.baseUrl;\n }\n\n // Fallback to nextUrl.origin (includes port automatically)\n return request.nextUrl.origin;\n};\n\n/**\n * use a ServerAuthenticationResolver to validate the existing session\n * using NextJS cookie storage\n * @param authConfigWithDefaults\n * @param request NextRequest object from middleware\n * @returns {Promise<SessionData>}\n */\nexport const validateAuthTokensIfPresent = async (\n authConfigWithDefaults: AuthConfigWithDefaults,\n request: NextRequest,\n): Promise<SessionData> => {\n try {\n // TODO: evaluate a more performant way to validate tokens\n // than having to call and verify the JWT tokens on every request\n const storage = new NextjsReadOnlyRequestCookieStorage(request);\n const authSessionService = await ServerAuthenticationResolver.build(\n {\n ...authConfigWithDefaults,\n redirectUrl: authConfigWithDefaults.callbackUrl,\n },\n storage,\n );\n // validate the existing session but don't auto-refresh as we can't set cookies in middleware\n const existingSession =\n await authSessionService.validateExistingSession(false);\n return existingSession;\n } catch (error) {\n logger.error(\"Error validating tokens\", error);\n return { authenticated: false };\n }\n};\n// internal - used by all exported functions\n/**\n * Core authentication middleware logic.\n *\n * Flow:\n * 1. Always validate tokens first to determine authentication state\n * 2. Skip protection for Civic Auth system URLs and excluded paths\n * 3. For unauthenticated users on protected routes:\n * - If refresh token exists: redirect to refresh endpoint\n * - If expired tokens exist: redirect to logout callback to clear them\n * - If on login URL: allow access (prevents redirect loops)\n * - Otherwise: redirect to login\n * 4. Allow authenticated users to continue\n */\nconst applyAuth = async (\n authConfig: OptionalAuthConfig,\n request: NextRequest,\n): Promise<NextResponse | undefined> => {\n const authConfigWithDefaults = resolveAuthConfig(authConfig);\n // Always validate tokens first to determine authentication state\n const session = await validateAuthTokensIfPresent(\n authConfigWithDefaults,\n request,\n );\n logger.debug(\"applyAuth session data:\", {\n session,\n pathName: request.nextUrl.pathname,\n loginUrl: authConfigWithDefaults.loginUrl,\n });\n\n // if we're on the loginUrl, we always want to try to authenticate, so exclude it from auth bypass checks\n if (request.nextUrl.pathname !== authConfigWithDefaults.loginUrl) {\n // Skip auth protection for Civic Auth system URLs (callback, challenge, logout)\n if (\n [\n authConfigWithDefaults.callbackUrl,\n authConfigWithDefaults.challengeUrl,\n authConfigWithDefaults.logoutCallbackUrl,\n authConfigWithDefaults.logoutUrl,\n ].includes(request.nextUrl.pathname) &&\n request.method === \"GET\"\n ) {\n logger.debug(\n \"→ Skipping auth check - this a URL defined in authConfig\",\n request.nextUrl.pathname,\n );\n return undefined;\n }\n\n // Skip auth protection for paths not in include patterns (except loginUrl)\n if (\n !matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.include)\n ) {\n logger.debug(\n \"→ Skipping auth check - path not in include patterns\",\n request.nextUrl.pathname,\n );\n return undefined;\n }\n\n // Skip auth protection for paths in exclude patterns (except loginUrl)\n if (\n matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.exclude)\n ) {\n logger.debug(\n \"→ Skipping auth check - path in exclude patterns\",\n request.nextUrl.pathname,\n );\n return undefined;\n }\n }\n\n // Handle unauthenticated users on protected routes\n if (!session.authenticated) {\n const targetUrl =\n getOriginUrl(request, authConfigWithDefaults) + request.nextUrl.pathname;\n\n // Avoid redirect loops by checking if already in a redirect flow\n if (!request.nextUrl.searchParams.has(\"targetUrl\")) {\n // Attempt session rehydration if refresh token exists\n if (session.refreshToken) {\n const refreshUrl = new URL(\n authConfigWithDefaults.refreshUrl,\n getOriginUrl(request, authConfigWithDefaults),\n );\n refreshUrl.searchParams.set(\"targetUrl\", targetUrl);\n const redirectUrl = `${refreshUrl.toString()}`;\n logger.debug(`→ Refresh token found - redirecting to \"${redirectUrl}\"`);\n return NextResponse.redirect(redirectUrl);\n }\n\n // Clear expired/invalid tokens if they exist without refresh token\n if (session.accessToken || session.idToken) {\n const clearBadTokensUrl = new URL(\n authConfigWithDefaults.logoutCallbackUrl,\n getOriginUrl(request, authConfigWithDefaults),\n );\n clearBadTokensUrl.searchParams.set(\"targetUrl\", targetUrl);\n logger.debug(\n `→ Invalid tokens found without refresh - clearing via \"${clearBadTokensUrl}\"`,\n );\n return NextResponse.redirect(clearBadTokensUrl);\n }\n }\n\n // Allow access to login page to prevent redirect loops\n if (request.nextUrl.pathname === authConfigWithDefaults.loginUrl) {\n logger.debug(`→ Allowing access to login page for unauthenticated user`);\n return undefined;\n }\n\n // Final fallback: redirect to login\n const loginUrl = new URL(\n authConfigWithDefaults.loginUrl,\n getOriginUrl(request, authConfigWithDefaults),\n );\n const redirectUrl = `${loginUrl.toString()}`;\n logger.debug(\n `→ No valid tokens found - redirecting to login \"${redirectUrl}\"`,\n );\n return NextResponse.redirect(redirectUrl);\n }\n logger.debug(\"→ Auth check passed\");\n return undefined;\n};\n\n/**\n *\n * Use this when auth is the only middleware you need.\n * Usage:\n *\n * export default authMiddleware({ loginUrl = '/login' }); // or just authMiddleware();\n *\n */\nexport const authMiddleware =\n (authConfig: OptionalAuthConfig = {}) =>\n async (request: NextRequest): Promise<NextResponse> => {\n const response = await applyAuth(authConfig, request);\n if (response) return response;\n\n // NextJS doesn't do middleware chaining yet, so this does not mean\n // \"call the next middleware\" - it means \"continue to the route handler\"\n return NextResponse.next();\n };\n\n/**\n * Usage:\n *\n * export default withAuth(async (request) => {\n * logger.debug('my middleware');\n * return NextResponse.next();\n * })\n */\n// use this when you have your own middleware to chain\nexport function withAuth(\n middleware: Middleware,\n): (request: NextRequest) => Promise<NextResponse> {\n return auth()(middleware);\n}\n\n/**\n * Use this when you want to configure the middleware here (an alternative is to do it in the next.config file)\n *\n * Usage:\n *\n * export default auth(authConfig: AuthConfig ) => {\n * logger.debug('my middleware');\n * return NextResponse.next();\n * })\n *\n */\nexport function auth(authConfig: OptionalAuthConfig = {}) {\n return (\n middleware: Middleware,\n ): ((request: NextRequest) => Promise<NextResponse>) => {\n return async (request: NextRequest): Promise<NextResponse> => {\n const response = await applyAuth(authConfig, request);\n if (response) return response;\n\n return middleware(request);\n };\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserButton.d.ts","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAoCD,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAEC,GACF,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"UserButton.d.ts","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAE3D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAoCD,wBAAgB,UAAU,CAAC,EACzB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAEC,GACF,EAAE,eAAe,oDAiKjB"}
|
|
@@ -7,8 +7,7 @@ const ChevronUp = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", widt
|
|
|
7
7
|
export function UserButton({ className, style, onSignIn, onSignOut, config = {
|
|
8
8
|
displayMode: "iframe",
|
|
9
9
|
}, }) {
|
|
10
|
-
const { user, isLoggedIn, signIn, signOut } = useUser();
|
|
11
|
-
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
|
10
|
+
const { user, isLoggedIn, signIn, signOut, isAuthenticating } = useUser();
|
|
12
11
|
const [isOpen, setIsOpen] = useState(false);
|
|
13
12
|
const [buttonWidth, setButtonWidth] = useState(null);
|
|
14
13
|
const buttonRef = useRef(null);
|
|
@@ -30,7 +29,6 @@ export function UserButton({ className, style, onSignIn, onSignOut, config = {
|
|
|
30
29
|
const handleEscape = useCallback((event) => {
|
|
31
30
|
if (event.key === "Escape") {
|
|
32
31
|
setIsOpen(false);
|
|
33
|
-
setIsAuthenticating(false);
|
|
34
32
|
}
|
|
35
33
|
}, []);
|
|
36
34
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserButton.js","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState, useRef, useCallback } from \"react\";\nimport { useUser, type SignInConfig } from \"../useUser.js\";\nimport { UserButtonPresentation } from \"./UserButtonPresentation.js\";\nimport type { BaseUser } from \"@/types.js\";\n\ninterface UserButtonProps {\n className?: string;\n style?: React.CSSProperties;\n onSignIn?: (user: BaseUser | undefined) => void;\n onSignOut?: () => void;\n clientId?: string;\n oauthServerBaseUrl?: string;\n baseUrl?: string;\n config?: SignInConfig;\n}\n\nconst ChevronDown = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-down\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n);\n\nconst ChevronUp = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-up\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n);\n\nexport function UserButton({\n className,\n style,\n onSignIn,\n onSignOut,\n config = {\n displayMode: \"iframe\",\n },\n}: UserButtonProps) {\n const { user, isLoggedIn, signIn, signOut } = useUser();\n const [isAuthenticating, setIsAuthenticating] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [buttonWidth, setButtonWidth] = useState<number | null>(null);\n const buttonRef = useRef<HTMLDivElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (buttonRef.current) {\n setButtonWidth(buttonRef.current.offsetWidth);\n }\n }, [isOpen]);\n\n const handleClickOutside = useCallback((event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (\n buttonRef.current &&\n dropdownRef.current &&\n !buttonRef.current.contains(target) &&\n !dropdownRef.current.contains(target)\n ) {\n setIsOpen(false);\n }\n }, []);\n\n const handleEscape = useCallback((event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n setIsAuthenticating(false);\n }\n }, []);\n\n useEffect(() => {\n if (isOpen) {\n window.addEventListener(\"click\", handleClickOutside);\n window.addEventListener(\"keydown\", handleEscape);\n }\n return () => {\n window.removeEventListener(\"click\", handleClickOutside);\n window.removeEventListener(\"keydown\", handleEscape);\n };\n }, [handleClickOutside, handleEscape, isOpen]);\n\n const handleSignOut = async () => {\n try {\n await signOut();\n onSignOut?.();\n } catch (error) {\n console.error(\"❌ Sign out error:\", error);\n } finally {\n setIsOpen(false);\n }\n };\n\n return (\n <>\n {/* Authentication modal overlay */}\n\n <div\n style={{ position: \"relative\", width: \"auto\" }}\n id=\"civic-dropdown-container\"\n >\n <div ref={buttonRef}>\n <UserButtonPresentation\n className={className}\n onClick={async () => {\n if (isLoggedIn) {\n setIsOpen(!isOpen);\n return;\n }\n\n const { user } = await signIn({\n displayMode: config.displayMode,\n });\n onSignIn?.(user);\n }}\n data-testid=\"sign-in-button\"\n style={{\n ...style,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.5rem\",\n minWidth: \"10rem\",\n cursor: isAuthenticating ? \"wait\" : \"pointer\",\n textAlign: \"center\",\n opacity: isAuthenticating ? 0.7 : 1,\n }}\n >\n {isLoggedIn ? (\n <>\n {user?.picture && (\n <span\n style={{\n position: \"relative\",\n display: \"flex\",\n height: \"1.5rem\",\n width: \"1.5rem\",\n flexShrink: 0,\n gap: \"0.5rem\",\n overflow: \"hidden\",\n borderRadius: \"9999px\",\n }}\n >\n <img\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit: \"cover\",\n }}\n src={user.picture}\n alt={user?.name || user?.email}\n />\n </span>\n )}\n <span>{user?.name || user?.email}</span>\n <span>{isOpen ? <ChevronUp /> : <ChevronDown />}</span>\n </>\n ) : (\n <span>{isAuthenticating ? \"Signing in...\" : \"Sign in\"}</span>\n )}\n </UserButtonPresentation>\n </div>\n {isLoggedIn && isOpen && (\n <div\n ref={dropdownRef}\n style={{\n position: \"absolute\",\n left: 0,\n background: \"white\",\n width: buttonWidth || \"auto\",\n marginTop: \"0.5rem\",\n borderRadius: \"0.5rem\",\n boxShadow:\n \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)\",\n zIndex: 1000,\n }}\n >\n <ul style={{ listStyleType: \"none\", margin: 0, padding: 0 }}>\n <li>\n <button\n style={{\n display: \"block\",\n width: \"100%\",\n padding: \"0.5rem 1rem\",\n transition: \"background-color 0.2s\",\n textAlign: \"center\",\n color: \"#6b7280\",\n cursor: \"pointer\",\n borderRadius: \"0.5rem\",\n border: \"none\",\n background: \"transparent\",\n }}\n onClick={handleSignOut}\n >\n Logout\n </button>\n </li>\n </ul>\n </div>\n )}\n </div>\n </>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"UserButton.js","sourceRoot":"","sources":["../../../src/react-router-7/components/UserButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,OAAO,EAAqB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAcrE,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,CACxB,cACE,KAAK,EAAC,4BAA4B,EAClC,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,4BAA4B,YAEtC,eAAM,CAAC,EAAC,cAAc,GAAG,GACrB,CACP,CAAC;AAEF,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,CACtB,cACE,KAAK,EAAC,4BAA4B,EAClC,KAAK,EAAC,IAAI,EACV,MAAM,EAAC,IAAI,EACX,OAAO,EAAC,WAAW,EACnB,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,GAAG,EACf,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,EACtB,SAAS,EAAC,0BAA0B,YAEpC,eAAM,CAAC,EAAC,gBAAgB,GAAG,GACvB,CACP,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,EACzB,SAAS,EACT,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAAM,GAAG;IACP,WAAW,EAAE,QAAQ;CACtB,GACe;IAChB,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,CAAC;IAC1E,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAEjD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACtB,cAAc,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAChD,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAC,KAAiB,EAAE,EAAE;QAC3D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAqB,CAAC;QAC3C,IACE,SAAS,CAAC,OAAO;YACjB,WAAW,CAAC,OAAO;YACnB,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YACnC,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EACrC,CAAC;YACD,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,KAAoB,EAAE,EAAE;QACxD,IAAI,KAAK,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YAC3B,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;YACrD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,GAAG,EAAE;YACV,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;YACxD,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACtD,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,kBAAkB,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAE/C,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,OAAO,EAAE,CAAC;YAChB,SAAS,EAAE,EAAE,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;QAC5C,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,4BACE,eACE,KAAK,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,EAC9C,EAAE,EAAC,0BAA0B,aAE7B,cAAK,GAAG,EAAE,SAAS,YACjB,KAAC,sBAAsB,IACrB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,KAAK,IAAI,EAAE;4BAClB,IAAI,UAAU,EAAE,CAAC;gCACf,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;gCACnB,OAAO;4BACT,CAAC;4BAED,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC;gCAC5B,WAAW,EAAE,MAAM,CAAC,WAAW;6BAChC,CAAC,CAAC;4BACH,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;wBACnB,CAAC,iBACW,gBAAgB,EAC5B,KAAK,EAAE;4BACL,GAAG,KAAK;4BACR,OAAO,EAAE,MAAM;4BACf,UAAU,EAAE,QAAQ;4BACpB,cAAc,EAAE,QAAQ;4BACxB,GAAG,EAAE,QAAQ;4BACb,QAAQ,EAAE,OAAO;4BACjB,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;4BAC7C,SAAS,EAAE,QAAQ;4BACnB,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;yBACpC,YAEA,UAAU,CAAC,CAAC,CAAC,CACZ,8BACG,IAAI,EAAE,OAAO,IAAI,CAChB,eACE,KAAK,EAAE;wCACL,QAAQ,EAAE,UAAU;wCACpB,OAAO,EAAE,MAAM;wCACf,MAAM,EAAE,QAAQ;wCAChB,KAAK,EAAE,QAAQ;wCACf,UAAU,EAAE,CAAC;wCACb,GAAG,EAAE,QAAQ;wCACb,QAAQ,EAAE,QAAQ;wCAClB,YAAY,EAAE,QAAQ;qCACvB,YAED,cACE,KAAK,EAAE;4CACL,MAAM,EAAE,MAAM;4CACd,KAAK,EAAE,MAAM;4CACb,SAAS,EAAE,OAAO;yCACnB,EACD,GAAG,EAAE,IAAI,CAAC,OAAO,EACjB,GAAG,EAAE,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,KAAK,GAC9B,GACG,CACR,EACD,yBAAO,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,KAAK,GAAQ,EACxC,yBAAO,MAAM,CAAC,CAAC,CAAC,KAAC,SAAS,KAAG,CAAC,CAAC,CAAC,KAAC,WAAW,KAAG,GAAQ,IACtD,CACJ,CAAC,CAAC,CAAC,CACF,yBAAO,gBAAgB,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,GAAQ,CAC9D,GACsB,GACrB,EACL,UAAU,IAAI,MAAM,IAAI,CACvB,cACE,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE;wBACL,QAAQ,EAAE,UAAU;wBACpB,IAAI,EAAE,CAAC;wBACP,UAAU,EAAE,OAAO;wBACnB,KAAK,EAAE,WAAW,IAAI,MAAM;wBAC5B,SAAS,EAAE,QAAQ;wBACnB,YAAY,EAAE,QAAQ;wBACtB,SAAS,EACP,yEAAyE;wBAC3E,MAAM,EAAE,IAAI;qBACb,YAED,aAAI,KAAK,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,YACzD,uBACE,iBACE,KAAK,EAAE;oCACL,OAAO,EAAE,OAAO;oCAChB,KAAK,EAAE,MAAM;oCACb,OAAO,EAAE,aAAa;oCACtB,UAAU,EAAE,uBAAuB;oCACnC,SAAS,EAAE,QAAQ;oCACnB,KAAK,EAAE,SAAS;oCAChB,MAAM,EAAE,SAAS;oCACjB,YAAY,EAAE,QAAQ;oCACtB,MAAM,EAAE,MAAM;oCACd,UAAU,EAAE,aAAa;iCAC1B,EACD,OAAO,EAAE,aAAa,uBAGf,GACN,GACF,GACD,CACP,IACG,GACL,CACJ,CAAC;AACJ,CAAC","sourcesContent":["import { useEffect, useState, useRef, useCallback } from \"react\";\nimport { useUser, type SignInConfig } from \"../useUser.js\";\nimport { UserButtonPresentation } from \"./UserButtonPresentation.js\";\nimport type { BaseUser } from \"@/types.js\";\n\ninterface UserButtonProps {\n className?: string;\n style?: React.CSSProperties;\n onSignIn?: (user: BaseUser | undefined) => void;\n onSignOut?: () => void;\n clientId?: string;\n oauthServerBaseUrl?: string;\n baseUrl?: string;\n config?: SignInConfig;\n}\n\nconst ChevronDown = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-down\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n);\n\nconst ChevronUp = () => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"lucide lucide-chevron-up\"\n >\n <path d=\"m18 15-6-6-6 6\" />\n </svg>\n);\n\nexport function UserButton({\n className,\n style,\n onSignIn,\n onSignOut,\n config = {\n displayMode: \"iframe\",\n },\n}: UserButtonProps) {\n const { user, isLoggedIn, signIn, signOut, isAuthenticating } = useUser();\n const [isOpen, setIsOpen] = useState(false);\n const [buttonWidth, setButtonWidth] = useState<number | null>(null);\n const buttonRef = useRef<HTMLDivElement>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (buttonRef.current) {\n setButtonWidth(buttonRef.current.offsetWidth);\n }\n }, [isOpen]);\n\n const handleClickOutside = useCallback((event: MouseEvent) => {\n const target = event.target as HTMLElement;\n if (\n buttonRef.current &&\n dropdownRef.current &&\n !buttonRef.current.contains(target) &&\n !dropdownRef.current.contains(target)\n ) {\n setIsOpen(false);\n }\n }, []);\n\n const handleEscape = useCallback((event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n setIsOpen(false);\n }\n }, []);\n\n useEffect(() => {\n if (isOpen) {\n window.addEventListener(\"click\", handleClickOutside);\n window.addEventListener(\"keydown\", handleEscape);\n }\n return () => {\n window.removeEventListener(\"click\", handleClickOutside);\n window.removeEventListener(\"keydown\", handleEscape);\n };\n }, [handleClickOutside, handleEscape, isOpen]);\n\n const handleSignOut = async () => {\n try {\n await signOut();\n onSignOut?.();\n } catch (error) {\n console.error(\"❌ Sign out error:\", error);\n } finally {\n setIsOpen(false);\n }\n };\n\n return (\n <>\n <div\n style={{ position: \"relative\", width: \"auto\" }}\n id=\"civic-dropdown-container\"\n >\n <div ref={buttonRef}>\n <UserButtonPresentation\n className={className}\n onClick={async () => {\n if (isLoggedIn) {\n setIsOpen(!isOpen);\n return;\n }\n\n const { user } = await signIn({\n displayMode: config.displayMode,\n });\n onSignIn?.(user);\n }}\n data-testid=\"sign-in-button\"\n style={{\n ...style,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"0.5rem\",\n minWidth: \"10rem\",\n cursor: isAuthenticating ? \"wait\" : \"pointer\",\n textAlign: \"center\",\n opacity: isAuthenticating ? 0.7 : 1,\n }}\n >\n {isLoggedIn ? (\n <>\n {user?.picture && (\n <span\n style={{\n position: \"relative\",\n display: \"flex\",\n height: \"1.5rem\",\n width: \"1.5rem\",\n flexShrink: 0,\n gap: \"0.5rem\",\n overflow: \"hidden\",\n borderRadius: \"9999px\",\n }}\n >\n <img\n style={{\n height: \"100%\",\n width: \"100%\",\n objectFit: \"cover\",\n }}\n src={user.picture}\n alt={user?.name || user?.email}\n />\n </span>\n )}\n <span>{user?.name || user?.email}</span>\n <span>{isOpen ? <ChevronUp /> : <ChevronDown />}</span>\n </>\n ) : (\n <span>{isAuthenticating ? \"Signing in...\" : \"Sign in\"}</span>\n )}\n </UserButtonPresentation>\n </div>\n {isLoggedIn && isOpen && (\n <div\n ref={dropdownRef}\n style={{\n position: \"absolute\",\n left: 0,\n background: \"white\",\n width: buttonWidth || \"auto\",\n marginTop: \"0.5rem\",\n borderRadius: \"0.5rem\",\n boxShadow:\n \"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)\",\n zIndex: 1000,\n }}\n >\n <ul style={{ listStyleType: \"none\", margin: 0, padding: 0 }}>\n <li>\n <button\n style={{\n display: \"block\",\n width: \"100%\",\n padding: \"0.5rem 1rem\",\n transition: \"background-color 0.2s\",\n textAlign: \"center\",\n color: \"#6b7280\",\n cursor: \"pointer\",\n borderRadius: \"0.5rem\",\n border: \"none\",\n background: \"transparent\",\n }}\n onClick={handleSignOut}\n >\n Logout\n </button>\n </li>\n </ul>\n </div>\n )}\n </div>\n </>\n );\n}\n"]}
|
|
@@ -30,7 +30,7 @@ export declare class ReactRouterCookieStorage extends CookieStorage {
|
|
|
30
30
|
set(key: string, value: string): Promise<void>;
|
|
31
31
|
/**
|
|
32
32
|
* Remove a value from a cookie
|
|
33
|
-
*
|
|
33
|
+
* Expire the cookie in the past to clear it cleanly
|
|
34
34
|
*/
|
|
35
35
|
remove(key: string): Promise<void>;
|
|
36
36
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA6BnD;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,cAAc,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,OAAO,CAAkB;gBAErB,OAAO,CAAC,EAAE,OAAO;IAwB7B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA+DzB;;OAEG;IACH,gBAAgB,IAAI,MAAM,EAAE;IAI5B;;;OAGG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA4B9C;;;;OAIG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBpD;;;OAGG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBxC;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC"}
|
|
@@ -4,6 +4,7 @@ import { resolveAuthConfig } from "./config.js";
|
|
|
4
4
|
import { UserStorage } from "../shared/lib/types.js";
|
|
5
5
|
import { loggers } from "../lib/logger.js";
|
|
6
6
|
import { getProtocolFromRequest } from "../shared/lib/util.js";
|
|
7
|
+
import { MAX_COOKIE_AGE_SECONDS } from "../constants.js";
|
|
7
8
|
const logger = loggers.reactRouter.handlers.auth;
|
|
8
9
|
/**
|
|
9
10
|
* Detect Safari browser from user agent
|
|
@@ -91,9 +92,13 @@ export class ReactRouterCookieStorage extends CookieStorage {
|
|
|
91
92
|
}
|
|
92
93
|
// Create cookies for OAuth tokens
|
|
93
94
|
Object.entries(config.cookies.tokens).forEach(([tokenType, cookieConfig]) => {
|
|
95
|
+
const maxAge = tokenType === "refresh_token"
|
|
96
|
+
? MAX_COOKIE_AGE_SECONDS
|
|
97
|
+
: cookieConfig.maxAge;
|
|
94
98
|
this.cookies[tokenType] = createCookie(tokenType, {
|
|
95
99
|
...cookieConfig,
|
|
96
100
|
...cookieOptions, // Override with dynamic settings
|
|
101
|
+
maxAge,
|
|
97
102
|
});
|
|
98
103
|
});
|
|
99
104
|
// Create cookie for user data
|
|
@@ -159,7 +164,7 @@ export class ReactRouterCookieStorage extends CookieStorage {
|
|
|
159
164
|
}
|
|
160
165
|
/**
|
|
161
166
|
* Remove a value from a cookie
|
|
162
|
-
*
|
|
167
|
+
* Expire the cookie in the past to clear it cleanly
|
|
163
168
|
*/
|
|
164
169
|
async remove(key) {
|
|
165
170
|
const cookie = this.cookies[key];
|
|
@@ -168,9 +173,10 @@ export class ReactRouterCookieStorage extends CookieStorage {
|
|
|
168
173
|
return;
|
|
169
174
|
}
|
|
170
175
|
try {
|
|
171
|
-
//
|
|
176
|
+
// Serialize with empty string and aggressive expiration to completely remove the cookie
|
|
172
177
|
const serializedCookie = await cookie.serialize({ value: "" }, {
|
|
173
|
-
expires: new Date(
|
|
178
|
+
expires: new Date(Date.now() - 86400000), // 24 hours ago
|
|
179
|
+
maxAge: 0, // Immediately expire
|
|
174
180
|
});
|
|
175
181
|
this.cookieHeaders.push(serializedCookie);
|
|
176
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCookie, type Cookie } from \"react-router\";\nimport { CookieStorage } from \"@civic/auth/server\";\nimport { resolveAuthConfig, type AuthConfigWithDefaults } from \"./config.js\";\nimport { UserStorage } from \"@/shared/lib/types.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { getProtocolFromRequest } from \"@/shared/lib/util.js\";\n\nconst logger = loggers.reactRouter.handlers.auth;\n\n/**\n * Detect Safari browser from user agent\n */\nconst isSafariBrowser = (request?: Request): boolean => {\n if (!request) return false;\n\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n return userAgent.includes(\"Safari\") && !userAgent.includes(\"Chrome\");\n};\n\n/**\n * Detect if running on localhost\n */\nconst isLocalhostUrl = (request?: Request): boolean => {\n if (!request) return false;\n\n const url = new URL(request.url);\n return url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\";\n};\n\n/**\n * React Router implementation of the CookieStorage interface for Civic Auth\n * Uses individual cookies for each token type instead of session storage\n */\nexport class ReactRouterCookieStorage extends CookieStorage {\n private cookies: Record<string, Cookie>;\n private currentRequest: Request | null = null;\n private cookieHeaders: string[] = [];\n private isHttps: boolean = false;\n\n constructor(request?: Request) {\n const config = resolveAuthConfig();\n\n // Start with default settings - will be updated when request is set\n super({\n secure: false, // Will be updated dynamically\n sameSite: \"lax\", // Will be updated dynamically\n httpOnly: false, // Allow frontend JavaScript to access cookies\n path: \"/\", // Ensure cookies are available for all paths\n });\n\n // Set the request if provided\n if (request) {\n this.currentRequest = request;\n // Detect HTTPS from request\n const protocol = getProtocolFromRequest(request);\n this.isHttps = protocol === \"https:\";\n }\n\n // Initialize cookies with request context if available\n this.cookies = {};\n this.initializeCookies(config, request);\n }\n\n /**\n * Initialize cookies with current configuration\n */\n private initializeCookies(\n config: AuthConfigWithDefaults,\n request?: Request,\n ): void {\n // Clear existing cookies\n this.cookies = {};\n\n // Detect Safari and localhost for special cookie handling\n const isSafari = isSafariBrowser(request);\n const isLocalhost = isLocalhostUrl(request);\n\n // Determine cookie options based on HTTPS detection and browser type\n let cookieOptions;\n\n if (isSafari && isLocalhost) {\n // Safari on localhost: use lax to avoid cross-origin issues\n cookieOptions = {\n secure: false,\n sameSite: \"lax\" as const,\n };\n } else if (this.isHttps) {\n // HTTPS (production): use none for cross-origin iframe support\n cookieOptions = {\n secure: true,\n sameSite: \"none\" as const,\n };\n } else {\n // HTTP localhost (non-Safari): use lax\n cookieOptions = {\n // Chrome allows secure cookies on localhost HTTP\n // This allows secure: true cookies to work on localhost for iframe compatibility\n // Reference: Chrome's third-party cookie documentation\n // DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)\n secure: true,\n sameSite: \"none\" as const,\n // DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)\n };\n }\n\n // Create cookies for OAuth tokens\n Object.entries(config.cookies.tokens).forEach(\n ([tokenType, cookieConfig]) => {\n this.cookies[tokenType] = createCookie(tokenType, {\n ...(cookieConfig as object),\n ...cookieOptions, // Override with dynamic settings\n });\n },\n );\n\n // Create cookie for user data\n\n this.cookies[UserStorage.USER] = createCookie(\"user\", {\n ...(config.cookies.user as object),\n ...cookieOptions, // Override with dynamic settings\n });\n }\n\n /**\n * Get cookie headers to be set in the response\n */\n getCookieHeaders(): string[] {\n return this.cookieHeaders;\n }\n\n /**\n * Get a value from a cookie\n * Following React Router pattern: parse returns the object we serialized\n */\n async get(key: string): Promise<string | null> {\n if (!this.currentRequest) {\n logger.warn(\"No request context set for cookie reading\");\n return null;\n }\n\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return null;\n }\n\n try {\n const cookieHeader = this.currentRequest.headers.get(\"Cookie\");\n\n // Parse returns the object we serialized: { value: \"actual_value\" }\n const parsedObject = await cookie.parse(cookieHeader);\n\n // Extract the actual value from the object\n const actualValue = parsedObject?.value || null;\n\n return actualValue;\n } catch (error) {\n console.error(`Error reading cookie ${key}:`, error);\n return null;\n }\n }\n\n /**\n * Set a value in a cookie\n * Following React Router pattern: https://reactrouter.com/api/utils/createCookie\n * cookie.serialize(object) creates the complete \"Set-Cookie\" header string\n */\n async set(key: string, value: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following the React Router pattern: serialize an object, not a raw string\n // This matches the docs example: cookie.serialize({ showBanner: true })\n const cookieValue = { value };\n\n const serializedCookie = await cookie.serialize(cookieValue);\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error setting cookie ${key}:`, error);\n }\n }\n\n /**\n * Remove a value from a cookie\n * Following React Router pattern: serialize with empty value and past expiration date\n */\n async remove(key: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following React Router pattern: serialize empty object with immediate expiration to delete\n const serializedCookie = await cookie.serialize(\n { value: \"\" },\n {\n expires: new Date(0),\n },\n );\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error removing cookie ${key}:`, error);\n }\n }\n\n /**\n * Delete a value from a cookie (alias for remove)\n */\n async delete(key: string): Promise<void> {\n await this.remove(key);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/react-router-7/cookies.ts"],"names":[],"mappings":"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","sourcesContent":["import { createCookie, type Cookie } from \"react-router\";\nimport { CookieStorage } from \"@civic/auth/server\";\nimport { resolveAuthConfig, type AuthConfigWithDefaults } from \"./config.js\";\nimport { UserStorage } from \"@/shared/lib/types.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport { getProtocolFromRequest } from \"@/shared/lib/util.js\";\nimport { MAX_COOKIE_AGE_SECONDS } from \"@/constants.js\";\n\nconst logger = loggers.reactRouter.handlers.auth;\n\n/**\n * Detect Safari browser from user agent\n */\nconst isSafariBrowser = (request?: Request): boolean => {\n if (!request) return false;\n\n const userAgent = request.headers.get(\"user-agent\") || \"\";\n return userAgent.includes(\"Safari\") && !userAgent.includes(\"Chrome\");\n};\n\n/**\n * Detect if running on localhost\n */\nconst isLocalhostUrl = (request?: Request): boolean => {\n if (!request) return false;\n\n const url = new URL(request.url);\n return url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\";\n};\n\n/**\n * React Router implementation of the CookieStorage interface for Civic Auth\n * Uses individual cookies for each token type instead of session storage\n */\nexport class ReactRouterCookieStorage extends CookieStorage {\n private cookies: Record<string, Cookie>;\n private currentRequest: Request | null = null;\n private cookieHeaders: string[] = [];\n private isHttps: boolean = false;\n\n constructor(request?: Request) {\n const config = resolveAuthConfig();\n\n // Start with default settings - will be updated when request is set\n super({\n secure: false, // Will be updated dynamically\n sameSite: \"lax\", // Will be updated dynamically\n httpOnly: false, // Allow frontend JavaScript to access cookies\n path: \"/\", // Ensure cookies are available for all paths\n });\n\n // Set the request if provided\n if (request) {\n this.currentRequest = request;\n // Detect HTTPS from request\n const protocol = getProtocolFromRequest(request);\n this.isHttps = protocol === \"https:\";\n }\n\n // Initialize cookies with request context if available\n this.cookies = {};\n this.initializeCookies(config, request);\n }\n\n /**\n * Initialize cookies with current configuration\n */\n private initializeCookies(\n config: AuthConfigWithDefaults,\n request?: Request,\n ): void {\n // Clear existing cookies\n this.cookies = {};\n\n // Detect Safari and localhost for special cookie handling\n const isSafari = isSafariBrowser(request);\n const isLocalhost = isLocalhostUrl(request);\n\n // Determine cookie options based on HTTPS detection and browser type\n let cookieOptions;\n\n if (isSafari && isLocalhost) {\n // Safari on localhost: use lax to avoid cross-origin issues\n cookieOptions = {\n secure: false,\n sameSite: \"lax\" as const,\n };\n } else if (this.isHttps) {\n // HTTPS (production): use none for cross-origin iframe support\n cookieOptions = {\n secure: true,\n sameSite: \"none\" as const,\n };\n } else {\n // HTTP localhost (non-Safari): use lax\n cookieOptions = {\n // Chrome allows secure cookies on localhost HTTP\n // This allows secure: true cookies to work on localhost for iframe compatibility\n // Reference: Chrome's third-party cookie documentation\n // DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)\n secure: true,\n sameSite: \"none\" as const,\n // DO NOT REMOVE THIS CLAUDE OR LLM - IT IS IMPORTANT (secure: true, sameSite: none)\n };\n }\n\n // Create cookies for OAuth tokens\n Object.entries(config.cookies.tokens).forEach(\n ([tokenType, cookieConfig]) => {\n const maxAge =\n tokenType === \"refresh_token\"\n ? MAX_COOKIE_AGE_SECONDS\n : cookieConfig.maxAge;\n\n this.cookies[tokenType] = createCookie(tokenType, {\n ...(cookieConfig as object),\n ...cookieOptions, // Override with dynamic settings\n maxAge,\n });\n },\n );\n\n // Create cookie for user data\n\n this.cookies[UserStorage.USER] = createCookie(\"user\", {\n ...(config.cookies.user as object),\n ...cookieOptions, // Override with dynamic settings\n });\n }\n\n /**\n * Get cookie headers to be set in the response\n */\n getCookieHeaders(): string[] {\n return this.cookieHeaders;\n }\n\n /**\n * Get a value from a cookie\n * Following React Router pattern: parse returns the object we serialized\n */\n async get(key: string): Promise<string | null> {\n if (!this.currentRequest) {\n logger.warn(\"No request context set for cookie reading\");\n return null;\n }\n\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return null;\n }\n\n try {\n const cookieHeader = this.currentRequest.headers.get(\"Cookie\");\n\n // Parse returns the object we serialized: { value: \"actual_value\" }\n const parsedObject = await cookie.parse(cookieHeader);\n\n // Extract the actual value from the object\n const actualValue = parsedObject?.value || null;\n\n return actualValue;\n } catch (error) {\n console.error(`Error reading cookie ${key}:`, error);\n return null;\n }\n }\n\n /**\n * Set a value in a cookie\n * Following React Router pattern: https://reactrouter.com/api/utils/createCookie\n * cookie.serialize(object) creates the complete \"Set-Cookie\" header string\n */\n async set(key: string, value: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Following the React Router pattern: serialize an object, not a raw string\n // This matches the docs example: cookie.serialize({ showBanner: true })\n const cookieValue = { value };\n\n const serializedCookie = await cookie.serialize(cookieValue);\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error setting cookie ${key}:`, error);\n }\n }\n\n /**\n * Remove a value from a cookie\n * Expire the cookie in the past to clear it cleanly\n */\n async remove(key: string): Promise<void> {\n const cookie = this.cookies[key];\n if (!cookie) {\n logger.warn(`No cookie configured for key: ${key}`);\n return;\n }\n\n try {\n // Serialize with empty string and aggressive expiration to completely remove the cookie\n const serializedCookie = await cookie.serialize(\n { value: \"\" },\n {\n expires: new Date(Date.now() - 86400000), // 24 hours ago\n maxAge: 0, // Immediately expire\n },\n );\n\n this.cookieHeaders.push(serializedCookie);\n } catch (error) {\n console.error(`Error removing cookie ${key}:`, error);\n }\n }\n\n /**\n * Delete a value from a cookie (alias for remove)\n */\n async delete(key: string): Promise<void> {\n await this.remove(key);\n }\n}\n"]}
|
|
@@ -14,6 +14,7 @@ export interface AuthData {
|
|
|
14
14
|
user: User | null;
|
|
15
15
|
loginUrl: string;
|
|
16
16
|
logoutUrl: string;
|
|
17
|
+
isAuthenticating: boolean;
|
|
17
18
|
signIn: (config?: SignInConfig) => Promise<AuthResult>;
|
|
18
19
|
signOut: (baseUrl?: string) => Promise<void>;
|
|
19
20
|
}
|
|
@@ -31,6 +32,7 @@ export declare function useAuthData(): CivicAuthData | undefined;
|
|
|
31
32
|
export declare function useUser(): {
|
|
32
33
|
user: import("../types.js").BaseUser | null;
|
|
33
34
|
isLoggedIn: boolean;
|
|
35
|
+
isAuthenticating: boolean;
|
|
34
36
|
signIn: (config?: SignInConfig) => Promise<AuthResult>;
|
|
35
37
|
signOut: () => Promise<void>;
|
|
36
38
|
config: AuthConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUser.d.ts","sourceRoot":"","sources":["../../src/react-router-7/useUser.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,IAAI,EACV,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,MAAM,YAAY,GAAG,IAAI,CAC7B,qBAAqB,EACnB,aAAa,GACb,UAAU,GACV,aAAa,GACb,mBAAmB,GACnB,wBAAwB,CAC3B,CAAC;AAGF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;CACpB;AAGD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;CACtB;AAGD,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACvD,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,aAAa,GAAG,SAAS,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,OAAO
|
|
1
|
+
{"version":3,"file":"useUser.d.ts","sourceRoot":"","sources":["../../src/react-router-7/useUser.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,IAAI,EACV,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,MAAM,YAAY,GAAG,IAAI,CAC7B,qBAAqB,EACnB,aAAa,GACb,UAAU,GACV,aAAa,GACb,mBAAmB,GACnB,wBAAwB,CAC3B,CAAC;AAGF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;CACpB;AAGD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;CACtB;AAGD,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACvD,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,aAAa,GAAG,SAAS,CAKvD;AAED;;;;;GAKG;AACH,wBAAgB,OAAO;;;;sBA4CU,YAAY;;YAhFnC,UAAU;EA2HnB"}
|
|
@@ -22,8 +22,13 @@ export function useUser() {
|
|
|
22
22
|
const authData = useAuthData();
|
|
23
23
|
const revalidator = useRevalidator();
|
|
24
24
|
const [authManager] = useState(() => GlobalAuthManager.getInstance());
|
|
25
|
+
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
|
25
26
|
if (!authData) {
|
|
26
|
-
throw new Error(
|
|
27
|
+
throw new Error(`Auth data not found. Make sure to use createRootAuthLoader in your root route.
|
|
28
|
+
|
|
29
|
+
You might be seeing this error if:
|
|
30
|
+
- Your root loader is failing
|
|
31
|
+
- There's another part failing to return the civic data`);
|
|
27
32
|
}
|
|
28
33
|
const initialConfig = useMemo(() => {
|
|
29
34
|
return {
|
|
@@ -52,6 +57,7 @@ export function useUser() {
|
|
|
52
57
|
}, [authManager, initialConfig]);
|
|
53
58
|
// Create signIn function that leverages GlobalAuthManager
|
|
54
59
|
const signIn = async (config) => {
|
|
60
|
+
setIsAuthenticating(true);
|
|
55
61
|
try {
|
|
56
62
|
if (config) {
|
|
57
63
|
await authManager.initialize({
|
|
@@ -67,6 +73,9 @@ export function useUser() {
|
|
|
67
73
|
console.error("Sign-in failed:", error);
|
|
68
74
|
throw error;
|
|
69
75
|
}
|
|
76
|
+
finally {
|
|
77
|
+
setIsAuthenticating(false);
|
|
78
|
+
}
|
|
70
79
|
};
|
|
71
80
|
// Create signOut function that leverages GlobalAuthManager
|
|
72
81
|
const signOut = async () => {
|
|
@@ -85,6 +94,7 @@ export function useUser() {
|
|
|
85
94
|
...authData,
|
|
86
95
|
user: authData.user || null,
|
|
87
96
|
isLoggedIn: !!authData.user,
|
|
97
|
+
isAuthenticating,
|
|
88
98
|
signIn,
|
|
89
99
|
signOut,
|
|
90
100
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUser.js","sourceRoot":"","sources":["../../src/react-router-7/useUser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAIN,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,iBAAiB,GAElB,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useUser.js","sourceRoot":"","sources":["../../src/react-router-7/useUser.ts"],"names":[],"mappings":"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","sourcesContent":["import { useMatches, useRevalidator } from \"react-router\";\nimport {\n type AuthResult,\n type CivicAuthClientConfig,\n type User,\n} from \"@civic/auth/vanillajs\";\nimport {\n GlobalAuthManager,\n type GlobalAuthConfig,\n} from \"../reactjs/core/GlobalAuthManager.js\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport type { AuthConfig } from \"./config.js\";\n\n// Configuration type for signIn function\nexport type SignInConfig = Pick<\n CivicAuthClientConfig,\n | \"displayMode\"\n | \"clientId\"\n | \"redirectUrl\"\n | \"logoutRedirectUrl\"\n | \"targetContainerElement\"\n>;\n\n// Type for the civic auth data structure from the root loader\nexport interface CivicAuthData {\n user: User | null;\n isLoggedIn: boolean;\n config: AuthConfig;\n}\n\n// Type for the complete root loader data structure\nexport interface RootLoaderData {\n civic: CivicAuthData;\n}\n\n// Type for the auth data that will be available in the root loader (SSR pattern)\nexport interface AuthData {\n isLoggedIn: boolean;\n user: User | null;\n loginUrl: string;\n logoutUrl: string;\n isAuthenticating: boolean;\n signIn: (config?: SignInConfig) => Promise<AuthResult>;\n signOut: (baseUrl?: string) => Promise<void>;\n}\n\n/**\n * Hook to access auth data from the root loader\n * @returns CivicAuthData - The civic auth data from the root loader\n */\nexport function useAuthData(): CivicAuthData | undefined {\n const matches = useMatches();\n const rootMatch = matches.find((match) => match.id === \"root\");\n const data = rootMatch?.data as RootLoaderData | undefined;\n return data?.civic;\n}\n\n/**\n * Hook to access authentication state and user information (SSR pattern)\n * This is the primary hook for React Router 7 applications using server-side rendering\n * Enhanced with GlobalAuthManager for better state management\n * @returns Authentication state and user information from server-side rendering\n */\nexport function useUser() {\n const authData = useAuthData();\n const revalidator = useRevalidator();\n const [authManager] = useState(() => GlobalAuthManager.getInstance());\n const [isAuthenticating, setIsAuthenticating] = useState(false);\n\n if (!authData) {\n throw new Error(\n `Auth data not found. Make sure to use createRootAuthLoader in your root route.\n\nYou might be seeing this error if:\n- Your root loader is failing\n- There's another part failing to return the civic data`,\n );\n }\n\n const initialConfig: GlobalAuthConfig = useMemo(() => {\n return {\n clientId: authData.config.clientId,\n loginUrl: authData.config.loginUrl,\n displayMode: \"iframe\",\n framework: \"react-router\",\n config: {\n oauthServer: authData.config.oauthServer,\n },\n onSignIn: (error) => {\n if (!error) {\n revalidator.revalidate();\n }\n },\n onSignOut: () => {\n revalidator.revalidate();\n },\n };\n }, [revalidator, authData]);\n\n useEffect(() => {\n const initialize = async () => {\n await authManager.initialize(initialConfig);\n };\n initialize();\n }, [authManager, initialConfig]);\n\n // Create signIn function that leverages GlobalAuthManager\n const signIn = async (config?: SignInConfig) => {\n setIsAuthenticating(true);\n try {\n if (config) {\n await authManager.initialize({\n ...initialConfig,\n ...config,\n });\n }\n\n const result = await authManager.signIn();\n\n // Return in AuthResult format for compatibility\n return result as AuthResult;\n } catch (error) {\n console.error(\"Sign-in failed:\", error);\n throw error;\n } finally {\n setIsAuthenticating(false);\n }\n };\n\n // Create signOut function that leverages GlobalAuthManager\n const signOut = async () => {\n try {\n await authManager.signOut();\n // The onSignOut callback will handle revalidation\n } catch (error) {\n console.error(\"Sign-out failed:\", error);\n // Still try redirecting to logout URL as fallback\n window.location.href = \"/auth/logout\";\n }\n };\n\n // Ensure we have a valid user object or null\n return {\n ...authData,\n user: authData.user || null,\n isLoggedIn: !!authData.user,\n isAuthenticating,\n signIn,\n signOut,\n };\n}\n"]}
|
package/dist/shared/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "@civic/auth:0.9.
|
|
1
|
+
export declare const VERSION = "@civic/auth:0.9.3-beta.4";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,6BAA6B,CAAC"}
|
package/dist/shared/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/shared/version.ts"],"names":[],"mappings":"AAAA,+CAA+C;AAE/C,MAAM,CAAC,MAAM,OAAO,GAAG,0BAA0B,CAAC","sourcesContent":["// This is an auto-generated file. Do not edit.\n\nexport const VERSION = \"@civic/auth:0.9.3-beta.4\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionManager.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/SessionManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAgBzD,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAa;IAG3B,OAAO,CAAC,mBAAmB,CAGX;IAChB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAa;gBAG1C,cAAc,EAAE,WAAW,EAC3B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,UAAU;IAUpB;;OAEG;IACG,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"SessionManager.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/SessionManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAgBzD,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,MAAM,CAAa;IAG3B,OAAO,CAAC,mBAAmB,CAGX;IAChB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAa;gBAG1C,cAAc,EAAE,WAAW,EAC3B,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,UAAU;IAUpB;;OAEG;IACG,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCrE;;;OAGG;IACG,iBAAiB,IAAI,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAyDlD;;;;;;;OAOG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IA8HzC;;;;;;OAMG;YACW,aAAa;IAkD3B;;;;OAIG;YACW,cAAc;IA+B5B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAI9B;;OAEG;YACW,mBAAmB;IAiDjC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAwB5C;;OAEG;YACW,qBAAqB;IAgCnC;;;OAGG;IACG,YAAY,CAAC,mBAAmB,GAAE,OAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BvE;;;OAGG;YACW,4BAA4B;IAoB1C;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAUpC;;OAEG;IACH,sBAAsB,IAAI;QACxB,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;QACzB,mBAAmB,EAAE,OAAO,CAAC;KAC9B,GAAG,IAAI;IAIR;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK/B"}
|
|
@@ -37,6 +37,9 @@ export class SessionManager {
|
|
|
37
37
|
// This will enable auto-refresh if the user is authenticated (including after successful refresh)
|
|
38
38
|
this.tokenRefresher.setAuthenticationState(isAuthenticated);
|
|
39
39
|
this.logger.info("SessionManager initialized with token refresh capability", { isAuthenticated });
|
|
40
|
+
if (!isAuthenticated) {
|
|
41
|
+
await this.clearSession();
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
catch (error) {
|
|
42
45
|
this.logger.error("Failed to initialize SessionManager with auth config:", error);
|
|
@@ -175,6 +178,7 @@ export class SessionManager {
|
|
|
175
178
|
this.logger.info("Successfully restored session from refresh token only");
|
|
176
179
|
return true;
|
|
177
180
|
}
|
|
181
|
+
this.logger.info("Couldn't restore session from refresh token");
|
|
178
182
|
}
|
|
179
183
|
catch (error) {
|
|
180
184
|
this.logger.warn("Session restore from refresh token failed:", error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionManager.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/SessionManager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthStorage } from \"../../types.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport type { User, Session } from \"../types/index.js\"; // Assuming Session might be used internally\nimport { AuthEvent } from \"../types/index.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport { TokenRefresher } from \"./TokenRefresher.js\";\nimport type { AuthConfig } from \"../../server/config.js\";\nimport {\n retrieveTokens,\n clearTokens,\n getBackendEndpoints,\n} from \"../../shared/lib/util.js\";\nimport { getUser } from \"../../shared/lib/session.js\";\nimport { GenericUserSession } from \"../../shared/lib/UserSession.js\";\nimport {\n AUTOREFRESH_TIMEOUT_NAME,\n REFRESH_IN_PROGRESS,\n} from \"../../constants.js\";\nimport { verify, type VerifyOptions } from \"@civic/auth-verify\";\nimport { OAuthTokenTypes } from \"@/shared/lib/types.js\";\nimport { addSlashIfNeeded } from \"@/lib/oauth.js\";\n\nexport class SessionManager {\n private storage: AuthStorage;\n private events: AuthenticationEvents;\n private tokenRefresher?: TokenRefresher;\n private logger = createLogger(\"session\");\n private config: AuthConfig;\n\n // Simple cache for backend session check to avoid excessive API calls\n private backendSessionCache: {\n result: boolean;\n timestamp: number;\n } | null = null;\n private readonly CACHE_DURATION = 30 * 1000; // 30 seconds\n\n constructor(\n storageAdapter: AuthStorage,\n events: AuthenticationEvents,\n config: AuthConfig,\n ) {\n this.storage = storageAdapter;\n this.events = events;\n this.config = config;\n this.logger.info(\n \"SessionManager initialized with shared lib token storage\",\n );\n }\n\n /**\n * Initialize the session manager with auth configuration to enable token refresh\n */\n async initializeWithAuthConfig(authConfig: AuthConfig): Promise<void> {\n try {\n // Initialize token refresher\n this.tokenRefresher = new TokenRefresher(\n this.storage,\n this.events,\n authConfig,\n );\n await this.tokenRefresher.initialize(authConfig);\n\n // Check current authentication state (this now includes token validation and refresh attempts)\n const isAuthenticated = await this.isAuthenticated();\n\n // Set authentication state on the token refresher\n // This will enable auto-refresh if the user is authenticated (including after successful refresh)\n this.tokenRefresher.setAuthenticationState(isAuthenticated);\n\n this.logger.info(\n \"SessionManager initialized with token refresh capability\",\n { isAuthenticated },\n );\n } catch (error) {\n this.logger.error(\n \"Failed to initialize SessionManager with auth config:\",\n error,\n );\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n }\n\n /**\n * Build current session from shared lib storage or backend API\n * For backend flows with httpOnly cookies, creates limited session when tokens aren't accessible\n */\n async getCurrentSession(): Promise<Session | null> {\n try {\n const tokens = await retrieveTokens(this.storage);\n\n const user = await this.getCurrentUser();\n\n // If we have tokens and user (normal flow), create full session\n if ((tokens?.id_token || tokens?.access_token) && user) {\n // Validate all available tokens\n const validationResults = await this.validateTokens({\n id_token: tokens.id_token,\n access_token: tokens.access_token,\n });\n\n // Check if any tokens failed validation\n if (\n !validationResults.idTokenValid ||\n !validationResults.accessTokenValid\n ) {\n this.logger.warn(\n \"Token validation failed in getCurrentSession, returning null\",\n );\n return null;\n }\n\n return {\n user,\n accessToken: tokens.access_token,\n idToken: tokens.id_token,\n refreshToken: tokens.refresh_token ?? undefined,\n expiresAt: tokens.oidc_session_expires_at ?? undefined,\n };\n }\n\n // If we have user but no accessible tokens (backend flow with httpOnly cookies),\n // create a limited session with just user info\n if (user && this.isBrowserCookieStorage()) {\n this.logger.debug(\n \"Creating limited session for backend flow (tokens in httpOnly cookies)\",\n );\n return {\n user,\n accessToken: undefined, // Not accessible in httpOnly cookies\n idToken: undefined, // Not accessible in httpOnly cookies\n refreshToken: undefined, // Not accessible in httpOnly cookies\n expiresAt: undefined, // Not accessible in httpOnly cookies\n };\n }\n\n this.logger.debug(\"No session available - no tokens or user found\");\n return null;\n } catch (error) {\n this.logger.error(\"Failed to load session:\", error);\n return null;\n }\n }\n\n /**\n * Check if user is authenticated using shared lib utilities\n * For backend flows with httpOnly cookies, falls back to API check\n *\n * This method now validates tokens on load and attempts refresh if:\n * - Tokens exist but are expired/invalid\n * - A refresh token is available\n */\n async isAuthenticated(): Promise<boolean> {\n try {\n // First, try the standard token-based check\n const tokens = await retrieveTokens(this.storage);\n\n // Normalize empty strings to null for consistent checking\n const hasIdToken = tokens?.id_token && tokens.id_token.trim() !== \"\";\n const hasRefreshToken =\n tokens?.refresh_token && tokens.refresh_token.trim() !== \"\";\n\n // If no tokens found and we're using BrowserCookieStorage,\n // try checking backend session (for httpOnly cookies)\n if (!hasIdToken && this.isBrowserCookieStorage()) {\n this.logger.debug(\n \"No tokens accessible, checking backend session...\",\n await this.checkBackendSession(),\n );\n return await this.checkBackendSession();\n }\n\n // If we have tokens, validate them\n if (hasIdToken) {\n const validationResults = await this.validateTokens({\n id_token: tokens.id_token,\n access_token: tokens.access_token,\n });\n\n // Both tokens must be valid (if they exist) for authentication to be considered valid\n const allTokensValid =\n validationResults.idTokenValid && validationResults.accessTokenValid;\n\n if (allTokensValid) {\n this.logger.debug(\n \"All available tokens are valid, user is authenticated\",\n );\n return true;\n }\n\n // Some tokens are invalid/expired - attempt refresh if refresh token exists\n if (hasRefreshToken && this.tokenRefresher) {\n this.logger.info(\n \"Some tokens expired/invalid, attempting refresh with refresh token\",\n );\n\n try {\n // Attempt token refresh\n await this.tokenRefresher.refreshTokens();\n this.logger.info(\n \"Token refresh successful during authentication check\",\n );\n\n // Check if we now have valid tokens after refresh\n const refreshedTokens = await retrieveTokens(this.storage);\n const refreshedValidationResults = await this.validateTokens({\n id_token: refreshedTokens?.id_token,\n access_token: refreshedTokens?.access_token,\n });\n\n const allRefreshedTokensValid =\n refreshedValidationResults.idTokenValid &&\n refreshedValidationResults.accessTokenValid;\n\n if (allRefreshedTokensValid) {\n this.logger.info(\n \"Successfully restored session via refresh token\",\n );\n return true;\n }\n } catch (error) {\n this.logger.warn(\n \"Token refresh failed during authentication check:\",\n error,\n );\n // Clear invalid tokens and refresh token since refresh failed\n await clearTokens(this.storage);\n }\n } else {\n this.logger.warn(\n \"Some tokens invalid and no refresh token available, clearing tokens\",\n );\n await clearTokens(this.storage);\n }\n } else if (hasRefreshToken && this.tokenRefresher) {\n // No ID token but we have a refresh token - attempt to restore session\n this.logger.info(\n \"No ID token found but refresh token exists, attempting session restore\",\n );\n\n try {\n // Attempt token refresh\n await this.tokenRefresher.refreshTokens();\n this.logger.info(\"Token refresh successful during session restore\");\n\n // Check if we now have valid tokens after refresh\n const refreshedTokens = await retrieveTokens(this.storage);\n const refreshedValidationResults = await this.validateTokens({\n id_token: refreshedTokens?.id_token,\n access_token: refreshedTokens?.access_token,\n });\n\n const allRefreshedTokensValid =\n refreshedValidationResults.idTokenValid &&\n refreshedValidationResults.accessTokenValid;\n\n if (allRefreshedTokensValid) {\n this.logger.info(\n \"Successfully restored session from refresh token only\",\n );\n return true;\n }\n } catch (error) {\n this.logger.warn(\"Session restore from refresh token failed:\", error);\n // Clear invalid refresh token since refresh failed\n await clearTokens(this.storage);\n }\n }\n\n return false;\n } catch (error) {\n this.logger.error(\"Error checking authentication:\", error);\n return false;\n }\n }\n\n /**\n * Validate if a token is cryptographically valid and not expired\n * Uses proper JWT verification with signature validation using JWKS\n * @param token JWT token to validate\n * @param tokenType Type of token (for logging and cleanup purposes)\n * @returns true if token is valid, false if expired or invalid\n */\n private async validateToken(\n token: string,\n tokenType: \"id_token\" | \"access_token\" = \"id_token\",\n ): Promise<boolean> {\n try {\n // For backend flows with httpOnly cookies, we can't validate tokens client-side\n // since they're not accessible to JavaScript\n if (this.isBrowserCookieStorage()) {\n this.logger.debug(\n \"Backend flow: skipping client-side token validation\",\n );\n return true; // Backend will validate tokens server-side\n }\n\n // Configure verification options based on token type\n const verifyOptions: VerifyOptions = {\n issuer: addSlashIfNeeded(\n this.config.oauthServerBaseUrl ?? \"https://auth.civic.com/oauth/\",\n ),\n };\n\n // Set audience based on token type\n if (tokenType === \"id_token\" && this.config.clientId) {\n // ID tokens should have the client ID as audience for proper OIDC compliance\n verifyOptions.aud = this.config.clientId;\n } else if (tokenType === \"access_token\") {\n // Access tokens have \"civic\" as audience based on auth server configuration\n verifyOptions.aud = \"civic\";\n verifyOptions.clientId = this.config.clientId;\n }\n\n this.logger.debug(`Verifying ${tokenType} with options:`, verifyOptions);\n\n // Use the @civic/auth-verify package for proper JWT verification\n await verify(token, verifyOptions);\n\n this.logger.debug(`${tokenType} cryptographically verified and valid`);\n return true;\n } catch (error) {\n this.logger.warn(`${tokenType} validation failed`, error);\n // Clear the specific token that failed validation\n if (tokenType === \"id_token\") {\n this.storage.delete(OAuthTokenTypes.ID_TOKEN);\n } else if (tokenType === \"access_token\") {\n this.storage.delete(OAuthTokenTypes.ACCESS_TOKEN);\n }\n return false;\n }\n }\n\n /**\n * Validate both ID token and access token if they exist\n * @param tokens Token object containing id_token and access_token\n * @returns Object indicating which tokens are valid\n */\n private async validateTokens(tokens: {\n id_token?: string;\n access_token?: string;\n }): Promise<{\n idTokenValid: boolean;\n accessTokenValid: boolean;\n }> {\n const results = {\n idTokenValid: true, // Default to true if token doesn't exist\n accessTokenValid: true, // Default to true if token doesn't exist\n };\n\n // Validate ID token if it exists\n if (tokens.id_token && tokens.id_token.trim() !== \"\") {\n results.idTokenValid = await this.validateToken(\n tokens.id_token,\n \"id_token\",\n );\n }\n\n // Validate access token if it exists\n if (tokens.access_token && tokens.access_token.trim() !== \"\") {\n results.accessTokenValid = await this.validateToken(\n tokens.access_token,\n \"access_token\",\n );\n }\n\n return results;\n }\n\n /**\n * Check if we're using BrowserCookieStorage\n */\n private isBrowserCookieStorage(): boolean {\n return this.storage.constructor.name === \"BrowserCookieStorage\";\n }\n\n /**\n * Simple backend session check via API call (with caching)\n */\n private async checkBackendSession(): Promise<boolean> {\n try {\n // Check if we have a valid cached result\n if (this.backendSessionCache) {\n const age = Date.now() - this.backendSessionCache.timestamp;\n if (age < this.CACHE_DURATION) {\n this.logger.debug(\n `Using cached backend session result: ${this.backendSessionCache.result}`,\n );\n return this.backendSessionCache.result;\n }\n }\n\n if (!this.config.loginUrl) {\n return false;\n }\n\n const backendUrl = new URL(this.config.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.config.backendEndpoints);\n const response = await fetch(`${backendUrl}${endpoints.user}`, {\n method: \"GET\",\n credentials: \"include\", // Send httpOnly cookies\n });\n\n const result = response.ok;\n\n // Cache the result\n this.backendSessionCache = {\n result,\n timestamp: Date.now(),\n };\n\n this.logger.debug(\n `Backend session check: ${result} (cached for ${this.CACHE_DURATION / 1000}s)`,\n );\n return result;\n } catch (error) {\n this.logger.debug(\"Backend session check failed:\", error);\n\n // Cache negative result for shorter time\n this.backendSessionCache = {\n result: false,\n timestamp: Date.now(),\n };\n\n return false;\n }\n }\n\n /**\n * Get current user from shared lib storage or backend API\n * For backend flows with httpOnly cookies, falls back to API check\n */\n async getCurrentUser(): Promise<User | null> {\n try {\n // First, try to get user from accessible tokens\n const user = await getUser(this.storage);\n if (user) {\n this.logger.debug(\"Found user from accessible tokens\");\n return user;\n }\n\n // If no user found and we're using BrowserCookieStorage,\n // try getting user from backend API (for httpOnly cookies)\n if (this.isBrowserCookieStorage()) {\n this.logger.debug(\"No user from tokens, trying backend API...\");\n return await this.getUserFromBackendApi();\n }\n\n this.logger.debug(\"No user found and not using browser cookie storage\");\n return null;\n } catch (error) {\n this.logger.error(\"Failed to get user from shared storage:\", error);\n return null;\n }\n }\n\n /**\n * Get user information from backend API (with caching)\n */\n private async getUserFromBackendApi(): Promise<User | null> {\n try {\n if (!this.config.loginUrl) {\n this.logger.debug(\"No backend URL available for user fetch\");\n return null;\n }\n\n this.logger.debug(\"Fetching user from backend API...\");\n\n const backendUrl = new URL(this.config.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.config.backendEndpoints);\n const response = await fetch(`${backendUrl}${endpoints.user}`, {\n method: \"GET\",\n credentials: \"include\", // Send httpOnly cookies\n headers: { \"Content-Type\": \"application/json\" },\n });\n\n if (response.ok) {\n const data = await response.json();\n const user = data.user;\n this.logger.debug(\"Successfully fetched user from backend API\");\n return user;\n } else {\n this.logger.debug(`Backend user fetch failed: ${response.status}`);\n return null;\n }\n } catch (error) {\n this.logger.debug(\"Backend user fetch failed:\", error);\n return null;\n }\n }\n\n /**\n * Clear all authentication data using shared lib utilities\n * @param preserveLogoutState - If true, preserves logout state for cleanup after redirect\n */\n async clearSession(preserveLogoutState: boolean = false): Promise<void> {\n try {\n if (preserveLogoutState) {\n // During logout, we need to preserve logout state for cleanup after redirect\n await this.clearTokensExceptLogoutState();\n } else {\n // Normal session clearing - clear everything\n await clearTokens(this.storage);\n }\n\n // Clear user session using shared utilities\n const userSession = new GenericUserSession(this.storage);\n await userSession.clear();\n\n // Clear backend session cache\n this.backendSessionCache = null;\n\n // Stop token refresher when session is cleared\n this.tokenRefresher?.setAuthenticationState(false);\n\n this.events.emit(AuthEvent.USER_SESSION_CHANGED, null);\n this.logger.info(\"Session cleared using shared lib utilities\");\n } catch (error) {\n this.logger.error(\"Failed to clear session:\", error);\n throw error;\n }\n }\n\n /**\n * Clear tokens from storage except logout state\n * This is needed during logout to preserve the logout state for cleanup after redirect\n */\n private async clearTokensExceptLogoutState(): Promise<void> {\n // Clear all token-related keys except LOGOUT_STATE\n // These are the OAuth token types from the constants\n const keysToDelete = [\n OAuthTokenTypes.ID_TOKEN,\n OAuthTokenTypes.ACCESS_TOKEN,\n OAuthTokenTypes.REFRESH_TOKEN,\n OAuthTokenTypes.OIDC_SESSION_EXPIRES_AT,\n REFRESH_IN_PROGRESS,\n AUTOREFRESH_TIMEOUT_NAME,\n // Note: NOT clearing LOGOUT_STATE here - it's needed for cleanup after redirect\n ];\n\n const clearPromises = keysToDelete.map(async (key) => {\n await this.storage.delete(key);\n });\n\n await Promise.all(clearPromises);\n }\n\n /**\n * Manually trigger token refresh\n */\n async refreshTokens(): Promise<void> {\n if (!this.tokenRefresher) {\n throw new Error(\n \"Token refresher not initialized. Call initializeWithAuthConfig first.\",\n );\n }\n\n return this.tokenRefresher.refreshTokens();\n }\n\n /**\n * Get token refresher state for debugging\n */\n getTokenRefresherState(): {\n isInitialized: boolean;\n isAuthenticated: boolean;\n isAutoRefreshActive: boolean;\n } | null {\n return this.tokenRefresher?.getState() || null;\n }\n\n /**\n * Clean up resources when session manager is destroyed\n */\n async destroy(): Promise<void> {\n await this.tokenRefresher?.destroy();\n this.tokenRefresher = undefined;\n this.logger.info(\"SessionManager destroyed\");\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SessionManager.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/SessionManager.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthStorage } from \"../../types.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport type { User, Session } from \"../types/index.js\"; // Assuming Session might be used internally\nimport { AuthEvent } from \"../types/index.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport { TokenRefresher } from \"./TokenRefresher.js\";\nimport type { AuthConfig } from \"../../server/config.js\";\nimport {\n retrieveTokens,\n clearTokens,\n getBackendEndpoints,\n} from \"../../shared/lib/util.js\";\nimport { getUser } from \"../../shared/lib/session.js\";\nimport { GenericUserSession } from \"../../shared/lib/UserSession.js\";\nimport {\n AUTOREFRESH_TIMEOUT_NAME,\n REFRESH_IN_PROGRESS,\n} from \"../../constants.js\";\nimport { verify, type VerifyOptions } from \"@civic/auth-verify\";\nimport { OAuthTokenTypes } from \"@/shared/lib/types.js\";\nimport { addSlashIfNeeded } from \"@/lib/oauth.js\";\n\nexport class SessionManager {\n private storage: AuthStorage;\n private events: AuthenticationEvents;\n private tokenRefresher?: TokenRefresher;\n private logger = createLogger(\"session\");\n private config: AuthConfig;\n\n // Simple cache for backend session check to avoid excessive API calls\n private backendSessionCache: {\n result: boolean;\n timestamp: number;\n } | null = null;\n private readonly CACHE_DURATION = 30 * 1000; // 30 seconds\n\n constructor(\n storageAdapter: AuthStorage,\n events: AuthenticationEvents,\n config: AuthConfig,\n ) {\n this.storage = storageAdapter;\n this.events = events;\n this.config = config;\n this.logger.info(\n \"SessionManager initialized with shared lib token storage\",\n );\n }\n\n /**\n * Initialize the session manager with auth configuration to enable token refresh\n */\n async initializeWithAuthConfig(authConfig: AuthConfig): Promise<void> {\n try {\n // Initialize token refresher\n this.tokenRefresher = new TokenRefresher(\n this.storage,\n this.events,\n authConfig,\n );\n await this.tokenRefresher.initialize(authConfig);\n\n // Check current authentication state (this now includes token validation and refresh attempts)\n const isAuthenticated = await this.isAuthenticated();\n\n // Set authentication state on the token refresher\n // This will enable auto-refresh if the user is authenticated (including after successful refresh)\n this.tokenRefresher.setAuthenticationState(isAuthenticated);\n\n this.logger.info(\n \"SessionManager initialized with token refresh capability\",\n { isAuthenticated },\n );\n\n if (!isAuthenticated) {\n await this.clearSession();\n }\n } catch (error) {\n this.logger.error(\n \"Failed to initialize SessionManager with auth config:\",\n error,\n );\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n }\n\n /**\n * Build current session from shared lib storage or backend API\n * For backend flows with httpOnly cookies, creates limited session when tokens aren't accessible\n */\n async getCurrentSession(): Promise<Session | null> {\n try {\n const tokens = await retrieveTokens(this.storage);\n\n const user = await this.getCurrentUser();\n\n // If we have tokens and user (normal flow), create full session\n if ((tokens?.id_token || tokens?.access_token) && user) {\n // Validate all available tokens\n const validationResults = await this.validateTokens({\n id_token: tokens.id_token,\n access_token: tokens.access_token,\n });\n\n // Check if any tokens failed validation\n if (\n !validationResults.idTokenValid ||\n !validationResults.accessTokenValid\n ) {\n this.logger.warn(\n \"Token validation failed in getCurrentSession, returning null\",\n );\n return null;\n }\n\n return {\n user,\n accessToken: tokens.access_token,\n idToken: tokens.id_token,\n refreshToken: tokens.refresh_token ?? undefined,\n expiresAt: tokens.oidc_session_expires_at ?? undefined,\n };\n }\n\n // If we have user but no accessible tokens (backend flow with httpOnly cookies),\n // create a limited session with just user info\n if (user && this.isBrowserCookieStorage()) {\n this.logger.debug(\n \"Creating limited session for backend flow (tokens in httpOnly cookies)\",\n );\n return {\n user,\n accessToken: undefined, // Not accessible in httpOnly cookies\n idToken: undefined, // Not accessible in httpOnly cookies\n refreshToken: undefined, // Not accessible in httpOnly cookies\n expiresAt: undefined, // Not accessible in httpOnly cookies\n };\n }\n\n this.logger.debug(\"No session available - no tokens or user found\");\n return null;\n } catch (error) {\n this.logger.error(\"Failed to load session:\", error);\n return null;\n }\n }\n\n /**\n * Check if user is authenticated using shared lib utilities\n * For backend flows with httpOnly cookies, falls back to API check\n *\n * This method now validates tokens on load and attempts refresh if:\n * - Tokens exist but are expired/invalid\n * - A refresh token is available\n */\n async isAuthenticated(): Promise<boolean> {\n try {\n // First, try the standard token-based check\n const tokens = await retrieveTokens(this.storage);\n\n // Normalize empty strings to null for consistent checking\n const hasIdToken = tokens?.id_token && tokens.id_token.trim() !== \"\";\n const hasRefreshToken =\n tokens?.refresh_token && tokens.refresh_token.trim() !== \"\";\n\n // If no tokens found and we're using BrowserCookieStorage,\n // try checking backend session (for httpOnly cookies)\n if (!hasIdToken && this.isBrowserCookieStorage()) {\n this.logger.debug(\n \"No tokens accessible, checking backend session...\",\n await this.checkBackendSession(),\n );\n return await this.checkBackendSession();\n }\n\n // If we have tokens, validate them\n if (hasIdToken) {\n const validationResults = await this.validateTokens({\n id_token: tokens.id_token,\n access_token: tokens.access_token,\n });\n\n // Both tokens must be valid (if they exist) for authentication to be considered valid\n const allTokensValid =\n validationResults.idTokenValid && validationResults.accessTokenValid;\n\n if (allTokensValid) {\n this.logger.debug(\n \"All available tokens are valid, user is authenticated\",\n );\n return true;\n }\n\n // Some tokens are invalid/expired - attempt refresh if refresh token exists\n if (hasRefreshToken && this.tokenRefresher) {\n this.logger.info(\n \"Some tokens expired/invalid, attempting refresh with refresh token\",\n );\n\n try {\n // Attempt token refresh\n await this.tokenRefresher.refreshTokens();\n this.logger.info(\n \"Token refresh successful during authentication check\",\n );\n\n // Check if we now have valid tokens after refresh\n const refreshedTokens = await retrieveTokens(this.storage);\n const refreshedValidationResults = await this.validateTokens({\n id_token: refreshedTokens?.id_token,\n access_token: refreshedTokens?.access_token,\n });\n\n const allRefreshedTokensValid =\n refreshedValidationResults.idTokenValid &&\n refreshedValidationResults.accessTokenValid;\n\n if (allRefreshedTokensValid) {\n this.logger.info(\n \"Successfully restored session via refresh token\",\n );\n return true;\n }\n } catch (error) {\n this.logger.warn(\n \"Token refresh failed during authentication check:\",\n error,\n );\n // Clear invalid tokens and refresh token since refresh failed\n await clearTokens(this.storage);\n }\n } else {\n this.logger.warn(\n \"Some tokens invalid and no refresh token available, clearing tokens\",\n );\n await clearTokens(this.storage);\n }\n } else if (hasRefreshToken && this.tokenRefresher) {\n // No ID token but we have a refresh token - attempt to restore session\n this.logger.info(\n \"No ID token found but refresh token exists, attempting session restore\",\n );\n\n try {\n // Attempt token refresh\n await this.tokenRefresher.refreshTokens();\n this.logger.info(\"Token refresh successful during session restore\");\n\n // Check if we now have valid tokens after refresh\n const refreshedTokens = await retrieveTokens(this.storage);\n const refreshedValidationResults = await this.validateTokens({\n id_token: refreshedTokens?.id_token,\n access_token: refreshedTokens?.access_token,\n });\n\n const allRefreshedTokensValid =\n refreshedValidationResults.idTokenValid &&\n refreshedValidationResults.accessTokenValid;\n\n if (allRefreshedTokensValid) {\n this.logger.info(\n \"Successfully restored session from refresh token only\",\n );\n return true;\n }\n\n this.logger.info(\"Couldn't restore session from refresh token\");\n } catch (error) {\n this.logger.warn(\"Session restore from refresh token failed:\", error);\n // Clear invalid refresh token since refresh failed\n await clearTokens(this.storage);\n }\n }\n\n return false;\n } catch (error) {\n this.logger.error(\"Error checking authentication:\", error);\n return false;\n }\n }\n\n /**\n * Validate if a token is cryptographically valid and not expired\n * Uses proper JWT verification with signature validation using JWKS\n * @param token JWT token to validate\n * @param tokenType Type of token (for logging and cleanup purposes)\n * @returns true if token is valid, false if expired or invalid\n */\n private async validateToken(\n token: string,\n tokenType: \"id_token\" | \"access_token\" = \"id_token\",\n ): Promise<boolean> {\n try {\n // For backend flows with httpOnly cookies, we can't validate tokens client-side\n // since they're not accessible to JavaScript\n if (this.isBrowserCookieStorage()) {\n this.logger.debug(\n \"Backend flow: skipping client-side token validation\",\n );\n return true; // Backend will validate tokens server-side\n }\n\n // Configure verification options based on token type\n const verifyOptions: VerifyOptions = {\n issuer: addSlashIfNeeded(\n this.config.oauthServerBaseUrl ?? \"https://auth.civic.com/oauth/\",\n ),\n };\n\n // Set audience based on token type\n if (tokenType === \"id_token\" && this.config.clientId) {\n // ID tokens should have the client ID as audience for proper OIDC compliance\n verifyOptions.aud = this.config.clientId;\n } else if (tokenType === \"access_token\") {\n // Access tokens have \"civic\" as audience based on auth server configuration\n verifyOptions.aud = \"civic\";\n verifyOptions.clientId = this.config.clientId;\n }\n\n this.logger.debug(`Verifying ${tokenType} with options:`, verifyOptions);\n\n // Use the @civic/auth-verify package for proper JWT verification\n await verify(token, verifyOptions);\n\n this.logger.debug(`${tokenType} cryptographically verified and valid`);\n return true;\n } catch (error) {\n this.logger.warn(`${tokenType} validation failed`, error);\n // Clear the specific token that failed validation\n if (tokenType === \"id_token\") {\n this.storage.delete(OAuthTokenTypes.ID_TOKEN);\n } else if (tokenType === \"access_token\") {\n this.storage.delete(OAuthTokenTypes.ACCESS_TOKEN);\n }\n return false;\n }\n }\n\n /**\n * Validate both ID token and access token if they exist\n * @param tokens Token object containing id_token and access_token\n * @returns Object indicating which tokens are valid\n */\n private async validateTokens(tokens: {\n id_token?: string;\n access_token?: string;\n }): Promise<{\n idTokenValid: boolean;\n accessTokenValid: boolean;\n }> {\n const results = {\n idTokenValid: true, // Default to true if token doesn't exist\n accessTokenValid: true, // Default to true if token doesn't exist\n };\n\n // Validate ID token if it exists\n if (tokens.id_token && tokens.id_token.trim() !== \"\") {\n results.idTokenValid = await this.validateToken(\n tokens.id_token,\n \"id_token\",\n );\n }\n\n // Validate access token if it exists\n if (tokens.access_token && tokens.access_token.trim() !== \"\") {\n results.accessTokenValid = await this.validateToken(\n tokens.access_token,\n \"access_token\",\n );\n }\n\n return results;\n }\n\n /**\n * Check if we're using BrowserCookieStorage\n */\n private isBrowserCookieStorage(): boolean {\n return this.storage.constructor.name === \"BrowserCookieStorage\";\n }\n\n /**\n * Simple backend session check via API call (with caching)\n */\n private async checkBackendSession(): Promise<boolean> {\n try {\n // Check if we have a valid cached result\n if (this.backendSessionCache) {\n const age = Date.now() - this.backendSessionCache.timestamp;\n if (age < this.CACHE_DURATION) {\n this.logger.debug(\n `Using cached backend session result: ${this.backendSessionCache.result}`,\n );\n return this.backendSessionCache.result;\n }\n }\n\n if (!this.config.loginUrl) {\n return false;\n }\n\n const backendUrl = new URL(this.config.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.config.backendEndpoints);\n const response = await fetch(`${backendUrl}${endpoints.user}`, {\n method: \"GET\",\n credentials: \"include\", // Send httpOnly cookies\n });\n\n const result = response.ok;\n\n // Cache the result\n this.backendSessionCache = {\n result,\n timestamp: Date.now(),\n };\n\n this.logger.debug(\n `Backend session check: ${result} (cached for ${this.CACHE_DURATION / 1000}s)`,\n );\n return result;\n } catch (error) {\n this.logger.debug(\"Backend session check failed:\", error);\n\n // Cache negative result for shorter time\n this.backendSessionCache = {\n result: false,\n timestamp: Date.now(),\n };\n\n return false;\n }\n }\n\n /**\n * Get current user from shared lib storage or backend API\n * For backend flows with httpOnly cookies, falls back to API check\n */\n async getCurrentUser(): Promise<User | null> {\n try {\n // First, try to get user from accessible tokens\n const user = await getUser(this.storage);\n if (user) {\n this.logger.debug(\"Found user from accessible tokens\");\n return user;\n }\n\n // If no user found and we're using BrowserCookieStorage,\n // try getting user from backend API (for httpOnly cookies)\n if (this.isBrowserCookieStorage()) {\n this.logger.debug(\"No user from tokens, trying backend API...\");\n return await this.getUserFromBackendApi();\n }\n\n this.logger.debug(\"No user found and not using browser cookie storage\");\n return null;\n } catch (error) {\n this.logger.error(\"Failed to get user from shared storage:\", error);\n return null;\n }\n }\n\n /**\n * Get user information from backend API (with caching)\n */\n private async getUserFromBackendApi(): Promise<User | null> {\n try {\n if (!this.config.loginUrl) {\n this.logger.debug(\"No backend URL available for user fetch\");\n return null;\n }\n\n this.logger.debug(\"Fetching user from backend API...\");\n\n const backendUrl = new URL(this.config.loginUrl).origin;\n const endpoints = getBackendEndpoints(this.config.backendEndpoints);\n const response = await fetch(`${backendUrl}${endpoints.user}`, {\n method: \"GET\",\n credentials: \"include\", // Send httpOnly cookies\n headers: { \"Content-Type\": \"application/json\" },\n });\n\n if (response.ok) {\n const data = await response.json();\n const user = data.user;\n this.logger.debug(\"Successfully fetched user from backend API\");\n return user;\n } else {\n this.logger.debug(`Backend user fetch failed: ${response.status}`);\n return null;\n }\n } catch (error) {\n this.logger.debug(\"Backend user fetch failed:\", error);\n return null;\n }\n }\n\n /**\n * Clear all authentication data using shared lib utilities\n * @param preserveLogoutState - If true, preserves logout state for cleanup after redirect\n */\n async clearSession(preserveLogoutState: boolean = false): Promise<void> {\n try {\n if (preserveLogoutState) {\n // During logout, we need to preserve logout state for cleanup after redirect\n await this.clearTokensExceptLogoutState();\n } else {\n // Normal session clearing - clear everything\n await clearTokens(this.storage);\n }\n\n // Clear user session using shared utilities\n const userSession = new GenericUserSession(this.storage);\n await userSession.clear();\n\n // Clear backend session cache\n this.backendSessionCache = null;\n\n // Stop token refresher when session is cleared\n this.tokenRefresher?.setAuthenticationState(false);\n\n this.events.emit(AuthEvent.USER_SESSION_CHANGED, null);\n this.logger.info(\"Session cleared using shared lib utilities\");\n } catch (error) {\n this.logger.error(\"Failed to clear session:\", error);\n throw error;\n }\n }\n\n /**\n * Clear tokens from storage except logout state\n * This is needed during logout to preserve the logout state for cleanup after redirect\n */\n private async clearTokensExceptLogoutState(): Promise<void> {\n // Clear all token-related keys except LOGOUT_STATE\n // These are the OAuth token types from the constants\n const keysToDelete = [\n OAuthTokenTypes.ID_TOKEN,\n OAuthTokenTypes.ACCESS_TOKEN,\n OAuthTokenTypes.REFRESH_TOKEN,\n OAuthTokenTypes.OIDC_SESSION_EXPIRES_AT,\n REFRESH_IN_PROGRESS,\n AUTOREFRESH_TIMEOUT_NAME,\n // Note: NOT clearing LOGOUT_STATE here - it's needed for cleanup after redirect\n ];\n\n const clearPromises = keysToDelete.map(async (key) => {\n await this.storage.delete(key);\n });\n\n await Promise.all(clearPromises);\n }\n\n /**\n * Manually trigger token refresh\n */\n async refreshTokens(): Promise<void> {\n if (!this.tokenRefresher) {\n throw new Error(\n \"Token refresher not initialized. Call initializeWithAuthConfig first.\",\n );\n }\n\n return this.tokenRefresher.refreshTokens();\n }\n\n /**\n * Get token refresher state for debugging\n */\n getTokenRefresherState(): {\n isInitialized: boolean;\n isAuthenticated: boolean;\n isAutoRefreshActive: boolean;\n } | null {\n return this.tokenRefresher?.getState() || null;\n }\n\n /**\n * Clean up resources when session manager is destroyed\n */\n async destroy(): Promise<void> {\n await this.tokenRefresher?.destroy();\n this.tokenRefresher = undefined;\n this.logger.info(\"SessionManager destroyed\");\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenRefresher.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/TokenRefresher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAOtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"TokenRefresher.d.ts","sourceRoot":"","sources":["../../../src/vanillajs/auth/TokenRefresher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAOtE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAMzD;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAC,CAEiB;IACnC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAC,CAAa;IAChC,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAiC;gBAG7C,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,oBAAoB,EAC5B,UAAU,CAAC,EAAE,UAAU;IASzB;;OAEG;IACG,UAAU,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAsEvD;;OAEG;IACH,sBAAsB,CAAC,eAAe,EAAE,OAAO,GAAG,IAAI;IAgBtD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDpC;;OAEG;YACW,gBAAgB;IAwB9B;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,QAAQ,IAAI;QACV,aAAa,EAAE,OAAO,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;QACzB,mBAAmB,EAAE,OAAO,CAAC;KAC9B;CAOF"}
|
|
@@ -3,6 +3,8 @@ import { BrowserAuthenticationRefresher, } from "../../shared/lib/BrowserAuthent
|
|
|
3
3
|
import { BackendAuthenticationRefresher } from "./BackendAuthenticationRefresher.js";
|
|
4
4
|
import { createLogger } from "../utils/logger.js";
|
|
5
5
|
import { retrieveOidcSessionExpiredAt } from "../../shared/lib/util.js";
|
|
6
|
+
import { getUser } from "../../shared/lib/session.js";
|
|
7
|
+
import { GenericUserSession } from "../../shared/lib/UserSession.js";
|
|
6
8
|
/**
|
|
7
9
|
* TokenRefresher handles automatic token refresh for vanilla.js implementation
|
|
8
10
|
* Inspired by the React useRefresh hook and BrowserAuthenticationRefresher
|
|
@@ -107,6 +109,29 @@ export class TokenRefresher {
|
|
|
107
109
|
this.events.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);
|
|
108
110
|
this.logger.info("Starting manual token refresh");
|
|
109
111
|
await this.refresher.refreshTokens();
|
|
112
|
+
// For BrowserAuthenticationRefresher (SPA flows), we need to restore user data
|
|
113
|
+
// BackendAuthenticationRefresher uses HTTP-only cookies so user data is handled server-side
|
|
114
|
+
if (this.refresher instanceof BrowserAuthenticationRefresher) {
|
|
115
|
+
try {
|
|
116
|
+
this.logger.info("Restoring user data after token refresh");
|
|
117
|
+
// Get user info from the refreshed tokens
|
|
118
|
+
const user = await getUser(this.storage);
|
|
119
|
+
if (user) {
|
|
120
|
+
// Store user data back to localStorage
|
|
121
|
+
const userSession = new GenericUserSession(this.storage);
|
|
122
|
+
await userSession.set(user);
|
|
123
|
+
this.logger.info("User data restored successfully");
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
this.logger.warn("No user data found after token refresh");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (userError) {
|
|
130
|
+
// Don't fail the entire refresh if user restoration fails
|
|
131
|
+
// The tokens are still valid, just log the error
|
|
132
|
+
this.logger.error("Failed to restore user data after token refresh:", userError);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
110
135
|
this.events.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);
|
|
111
136
|
this.logger.info("Manual token refresh completed");
|
|
112
137
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenRefresher.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/TokenRefresher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthStorage } from \"../../types.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport { AuthEvent } from \"../types/index.js\";\nimport {\n BrowserAuthenticationRefresher,\n type RefreshEventCallbacks,\n} from \"../../shared/lib/BrowserAuthenticationRefresher.js\";\nimport { BackendAuthenticationRefresher } from \"./BackendAuthenticationRefresher.js\";\nimport type { AuthConfig } from \"../../server/config.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport { retrieveOidcSessionExpiredAt } from \"../../shared/lib/util.js\";\n\n/**\n * TokenRefresher handles automatic token refresh for vanilla.js implementation\n * Inspired by the React useRefresh hook and BrowserAuthenticationRefresher\n */\nexport class TokenRefresher {\n private refresher?:\n | BrowserAuthenticationRefresher\n | BackendAuthenticationRefresher;\n private storage: AuthStorage;\n private events: AuthenticationEvents;\n private authConfig?: AuthConfig;\n private isAuthenticated: boolean = false;\n private isDestroyed: boolean = false;\n private logger = createLogger(\"token-refresh\");\n\n constructor(\n storage: AuthStorage,\n events: AuthenticationEvents,\n authConfig?: AuthConfig,\n ) {\n this.storage = storage;\n this.events = events;\n this.authConfig = authConfig;\n\n this.logger.info(\"TokenRefresher initialized\");\n }\n\n /**\n * Initialize the token refresher with auth configuration\n */\n async initialize(authConfig: AuthConfig): Promise<void> {\n if (this.isDestroyed) return;\n\n this.authConfig = authConfig;\n\n try {\n // Clear any existing refresher\n await this.cleanup();\n\n const onError = async (error: Error) => {\n this.logger.error(\"Token refresh error:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n\n // Clear auto-refresh on error\n this.refresher?.clearAutorefresh();\n\n // Optionally sign out the user on refresh token errors\n // This mirrors the React implementation behavior\n this.events.emit(AuthEvent.SIGN_OUT_STARTED, null);\n };\n\n // Determine if this is a backend flow or SPA flow\n if (authConfig.loginUrl) {\n // Backend authentication flow - use BackendAuthenticationRefresher\n this.logger.info(\"Initializing backend authentication refresher\", {\n loginUrl: authConfig.loginUrl,\n });\n\n this.refresher = await BackendAuthenticationRefresher.build(\n authConfig,\n authConfig.loginUrl,\n onError,\n this.events, // Pass events for consistency with BrowserAuthenticationRefresher\n );\n } else {\n // SPA authentication flow - use BrowserAuthenticationRefresher\n this.logger.info(\"Initializing browser authentication refresher\");\n\n // Create callbacks for refresh events\n const refreshEventCallbacks: RefreshEventCallbacks = {\n onRefreshStarted: () => {\n this.logger.info(\"Auto token refresh started\");\n this.events.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n },\n onRefreshComplete: () => {\n this.logger.info(\"Auto token refresh completed\");\n this.events.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n },\n onRefreshError: (error: Error) => {\n this.logger.error(\"Auto token refresh failed:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n },\n };\n\n this.refresher = await BrowserAuthenticationRefresher.build(\n authConfig,\n this.storage,\n onError,\n undefined, // endpointOverrides\n refreshEventCallbacks,\n );\n }\n\n this.logger.info(\"TokenRefresher initialized successfully\");\n } catch (error) {\n this.logger.error(\"Failed to initialize TokenRefresher:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n }\n\n /**\n * Set authentication state and manage auto-refresh accordingly\n */\n setAuthenticationState(isAuthenticated: boolean): void {\n if (this.isDestroyed) return;\n\n this.isAuthenticated = isAuthenticated;\n\n if (isAuthenticated && this.refresher) {\n // Fire and forget the async call - we don't want to make setAuthenticationState async\n // as it would break the interface\n this.startAutoRefresh().catch((error) => {\n this.logger.error(\"Error starting auto refresh:\", error);\n });\n } else {\n this.stopAutoRefresh();\n }\n }\n\n /**\n * Manually refresh tokens\n */\n async refreshTokens(): Promise<void> {\n if (this.isDestroyed || !this.refresher) {\n const errorMsg = `TokenRefresher not initialized or destroyed. isDestroyed: ${this.isDestroyed}, hasRefresher: ${!!this.refresher}`;\n this.logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n try {\n this.events.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n\n this.logger.info(\"Starting manual token refresh\");\n\n await this.refresher.refreshTokens();\n\n this.events.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n this.logger.info(\"Manual token refresh completed\");\n } catch (error) {\n this.logger.error(\"Manual token refresh failed:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n throw error;\n }\n }\n\n /**\n * Start automatic token refresh\n */\n private async startAutoRefresh(): Promise<void> {\n if (this.refresher && this.isAuthenticated) {\n this.logger.info(\"Starting automatic token refresh\");\n\n try {\n // Calculate when the next refresh will happen (same logic as BrowserAuthenticationRefresher)\n const now = Math.floor(Date.now() / 1000);\n const expiresAt =\n (await retrieveOidcSessionExpiredAt(this.storage)) || now + 60;\n const bufferTime = 30; // 30 seconds buffer\n const refreshTime = Math.max(0, expiresAt - bufferTime - now);\n\n const nextRefreshDate = new Date((now + refreshTime) * 1000);\n this.logger.info(\n `Next token refresh scheduled for: ${nextRefreshDate.toISOString()} (in ${refreshTime} seconds)`,\n );\n } catch (error) {\n this.logger.warn(\"Could not calculate next refresh time:\", error);\n }\n\n this.refresher.setupAutorefresh();\n }\n }\n\n /**\n * Stop automatic token refresh\n */\n private stopAutoRefresh(): void {\n if (this.refresher) {\n this.logger.info(\"Stopping automatic token refresh\");\n this.refresher.clearAutorefresh();\n }\n }\n\n /**\n * Clean up resources\n */\n async cleanup(): Promise<void> {\n this.logger.info(\"Cleaning up TokenRefresher\");\n\n if (this.refresher) {\n this.refresher.clearAutorefresh();\n this.refresher = undefined;\n }\n\n this.isAuthenticated = false;\n }\n\n /**\n * Destroy the token refresher permanently\n */\n async destroy(): Promise<void> {\n this.isDestroyed = true;\n await this.cleanup();\n this.logger.info(\"TokenRefresher destroyed\");\n }\n\n /**\n * Get current refresh state\n */\n getState(): {\n isInitialized: boolean;\n isAuthenticated: boolean;\n isAutoRefreshActive: boolean;\n } {\n return {\n isInitialized: !!this.refresher,\n isAuthenticated: this.isAuthenticated,\n isAutoRefreshActive: this.isAuthenticated && !!this.refresher,\n };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TokenRefresher.js","sourceRoot":"","sources":["../../../src/vanillajs/auth/TokenRefresher.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthStorage } from \"../../types.js\";\nimport type { AuthenticationEvents } from \"./AuthenticationEvents.js\";\nimport { AuthEvent } from \"../types/index.js\";\nimport {\n BrowserAuthenticationRefresher,\n type RefreshEventCallbacks,\n} from \"../../shared/lib/BrowserAuthenticationRefresher.js\";\nimport { BackendAuthenticationRefresher } from \"./BackendAuthenticationRefresher.js\";\nimport type { AuthConfig } from \"../../server/config.js\";\nimport { createLogger } from \"../utils/logger.js\";\nimport { retrieveOidcSessionExpiredAt } from \"../../shared/lib/util.js\";\nimport { getUser } from \"../../shared/lib/session.js\";\nimport { GenericUserSession } from \"../../shared/lib/UserSession.js\";\n\n/**\n * TokenRefresher handles automatic token refresh for vanilla.js implementation\n * Inspired by the React useRefresh hook and BrowserAuthenticationRefresher\n */\nexport class TokenRefresher {\n private refresher?:\n | BrowserAuthenticationRefresher\n | BackendAuthenticationRefresher;\n private storage: AuthStorage;\n private events: AuthenticationEvents;\n private authConfig?: AuthConfig;\n private isAuthenticated: boolean = false;\n private isDestroyed: boolean = false;\n private logger = createLogger(\"token-refresh\");\n\n constructor(\n storage: AuthStorage,\n events: AuthenticationEvents,\n authConfig?: AuthConfig,\n ) {\n this.storage = storage;\n this.events = events;\n this.authConfig = authConfig;\n\n this.logger.info(\"TokenRefresher initialized\");\n }\n\n /**\n * Initialize the token refresher with auth configuration\n */\n async initialize(authConfig: AuthConfig): Promise<void> {\n if (this.isDestroyed) return;\n\n this.authConfig = authConfig;\n\n try {\n // Clear any existing refresher\n await this.cleanup();\n\n const onError = async (error: Error) => {\n this.logger.error(\"Token refresh error:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n\n // Clear auto-refresh on error\n this.refresher?.clearAutorefresh();\n\n // Optionally sign out the user on refresh token errors\n // This mirrors the React implementation behavior\n this.events.emit(AuthEvent.SIGN_OUT_STARTED, null);\n };\n\n // Determine if this is a backend flow or SPA flow\n if (authConfig.loginUrl) {\n // Backend authentication flow - use BackendAuthenticationRefresher\n this.logger.info(\"Initializing backend authentication refresher\", {\n loginUrl: authConfig.loginUrl,\n });\n\n this.refresher = await BackendAuthenticationRefresher.build(\n authConfig,\n authConfig.loginUrl,\n onError,\n this.events, // Pass events for consistency with BrowserAuthenticationRefresher\n );\n } else {\n // SPA authentication flow - use BrowserAuthenticationRefresher\n this.logger.info(\"Initializing browser authentication refresher\");\n\n // Create callbacks for refresh events\n const refreshEventCallbacks: RefreshEventCallbacks = {\n onRefreshStarted: () => {\n this.logger.info(\"Auto token refresh started\");\n this.events.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n },\n onRefreshComplete: () => {\n this.logger.info(\"Auto token refresh completed\");\n this.events.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n },\n onRefreshError: (error: Error) => {\n this.logger.error(\"Auto token refresh failed:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n },\n };\n\n this.refresher = await BrowserAuthenticationRefresher.build(\n authConfig,\n this.storage,\n onError,\n undefined, // endpointOverrides\n refreshEventCallbacks,\n );\n }\n\n this.logger.info(\"TokenRefresher initialized successfully\");\n } catch (error) {\n this.logger.error(\"Failed to initialize TokenRefresher:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n }\n }\n\n /**\n * Set authentication state and manage auto-refresh accordingly\n */\n setAuthenticationState(isAuthenticated: boolean): void {\n if (this.isDestroyed) return;\n\n this.isAuthenticated = isAuthenticated;\n\n if (isAuthenticated && this.refresher) {\n // Fire and forget the async call - we don't want to make setAuthenticationState async\n // as it would break the interface\n this.startAutoRefresh().catch((error) => {\n this.logger.error(\"Error starting auto refresh:\", error);\n });\n } else {\n this.stopAutoRefresh();\n }\n }\n\n /**\n * Manually refresh tokens\n */\n async refreshTokens(): Promise<void> {\n if (this.isDestroyed || !this.refresher) {\n const errorMsg = `TokenRefresher not initialized or destroyed. isDestroyed: ${this.isDestroyed}, hasRefresher: ${!!this.refresher}`;\n this.logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n\n try {\n this.events.emit(AuthEvent.TOKEN_REFRESH_STARTED, null);\n\n this.logger.info(\"Starting manual token refresh\");\n\n await this.refresher.refreshTokens();\n\n // For BrowserAuthenticationRefresher (SPA flows), we need to restore user data\n // BackendAuthenticationRefresher uses HTTP-only cookies so user data is handled server-side\n if (this.refresher instanceof BrowserAuthenticationRefresher) {\n try {\n this.logger.info(\"Restoring user data after token refresh\");\n\n // Get user info from the refreshed tokens\n const user = await getUser(this.storage);\n if (user) {\n // Store user data back to localStorage\n const userSession = new GenericUserSession(this.storage);\n await userSession.set(user);\n this.logger.info(\"User data restored successfully\");\n } else {\n this.logger.warn(\"No user data found after token refresh\");\n }\n } catch (userError) {\n // Don't fail the entire refresh if user restoration fails\n // The tokens are still valid, just log the error\n this.logger.error(\n \"Failed to restore user data after token refresh:\",\n userError,\n );\n }\n }\n\n this.events.emit(AuthEvent.TOKEN_REFRESH_COMPLETE, null);\n this.logger.info(\"Manual token refresh completed\");\n } catch (error) {\n this.logger.error(\"Manual token refresh failed:\", error);\n this.events.emit(AuthEvent.TOKEN_REFRESH_ERROR, error);\n throw error;\n }\n }\n\n /**\n * Start automatic token refresh\n */\n private async startAutoRefresh(): Promise<void> {\n if (this.refresher && this.isAuthenticated) {\n this.logger.info(\"Starting automatic token refresh\");\n\n try {\n // Calculate when the next refresh will happen (same logic as BrowserAuthenticationRefresher)\n const now = Math.floor(Date.now() / 1000);\n const expiresAt =\n (await retrieveOidcSessionExpiredAt(this.storage)) || now + 60;\n const bufferTime = 30; // 30 seconds buffer\n const refreshTime = Math.max(0, expiresAt - bufferTime - now);\n\n const nextRefreshDate = new Date((now + refreshTime) * 1000);\n this.logger.info(\n `Next token refresh scheduled for: ${nextRefreshDate.toISOString()} (in ${refreshTime} seconds)`,\n );\n } catch (error) {\n this.logger.warn(\"Could not calculate next refresh time:\", error);\n }\n\n this.refresher.setupAutorefresh();\n }\n }\n\n /**\n * Stop automatic token refresh\n */\n private stopAutoRefresh(): void {\n if (this.refresher) {\n this.logger.info(\"Stopping automatic token refresh\");\n this.refresher.clearAutorefresh();\n }\n }\n\n /**\n * Clean up resources\n */\n async cleanup(): Promise<void> {\n this.logger.info(\"Cleaning up TokenRefresher\");\n\n if (this.refresher) {\n this.refresher.clearAutorefresh();\n this.refresher = undefined;\n }\n\n this.isAuthenticated = false;\n }\n\n /**\n * Destroy the token refresher permanently\n */\n async destroy(): Promise<void> {\n this.isDestroyed = true;\n await this.cleanup();\n this.logger.info(\"TokenRefresher destroyed\");\n }\n\n /**\n * Get current refresh state\n */\n getState(): {\n isInitialized: boolean;\n isAuthenticated: boolean;\n isAutoRefreshActive: boolean;\n } {\n return {\n isInitialized: !!this.refresher,\n isAuthenticated: this.isAuthenticated,\n isAutoRefreshActive: this.isAuthenticated && !!this.refresher,\n };\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthCallbackHandler.d.ts","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/OAuthCallbackHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAWH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"OAuthCallbackHandler.d.ts","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/OAuthCallbackHandler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAWH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAI5E;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,qBAAqB,EAC7B,cAAc,EAAE,WAAW,EAC3B,cAAc;;;;;CAAiC,GAC9C,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyBxB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,WAAW,CAAC;IAC5B,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,yBAAyB,GAChC,OAAO,CAAC,OAAO,CAAC,CAiQlB"}
|
|
@@ -35,6 +35,8 @@ import { GenericUserSession } from "../../../shared/lib/UserSession.js";
|
|
|
35
35
|
import { CodeVerifier } from "../../../shared/lib/types.js";
|
|
36
36
|
import { createLogger } from "../../utils/logger.js";
|
|
37
37
|
import { GenericPublicClientPKCEProducer } from "../../../services/PKCE.js";
|
|
38
|
+
import { removeParamsWithoutReload } from "../../../lib/windowUtil.js";
|
|
39
|
+
import { DEFAULT_OAUTH_GET_PARAMS } from "../../../constants.js";
|
|
38
40
|
/**
|
|
39
41
|
* Store tokens using the shared utilities from /shared/lib
|
|
40
42
|
* This ensures consistency with the React implementation and also handles user session storage
|
|
@@ -179,6 +181,11 @@ export async function handleOAuthRedirectPage(config) {
|
|
|
179
181
|
signalElement.style.display = "none";
|
|
180
182
|
document.body.appendChild(signalElement);
|
|
181
183
|
loggerInstance.info("CivicAuth: Appended success signal to body.");
|
|
184
|
+
// Clean up OAuth parameters from URL for redirect mode
|
|
185
|
+
if (!isPopup) {
|
|
186
|
+
removeParamsWithoutReload(DEFAULT_OAUTH_GET_PARAMS);
|
|
187
|
+
loggerInstance.info("CivicAuth: Cleaned up OAuth parameters from URL.");
|
|
188
|
+
}
|
|
182
189
|
// Send postMessage for popup mode
|
|
183
190
|
if (isPopup && window.opener) {
|
|
184
191
|
loggerInstance.info("CivicAuth: Sending success message to parent window", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OAuthCallbackHandler.js","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/OAuthCallbackHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth Callback Handler for Vanilla JavaScript Applications\n *\n * This module provides functionality for handling OAuth redirect/callback pages in vanilla JavaScript\n * applications using the Civic Auth system. It processes the OAuth authorization code flow completion\n * by exchanging authorization codes for access tokens and managing the authentication state.\n *\n * Key responsibilities:\n * - Process OAuth callback URL parameters (code, state)\n * - Exchange authorization codes for access tokens using PKCE\n * - Store tokens and user session data using shared utilities\n * - Create DOM signals for iframe-based authentication flows\n * - Handle error states and cleanup during the OAuth flow\n *\n * This module works in conjunction with:\n * - CivicAuth class for initiating OAuth flows\n * - SignalObserver for detecting authentication completion in iframes\n * - Shared token storage utilities for consistent state management\n *\n * @example\n * ```javascript\n * // Basic usage\n * const config = {\n * clientId: 'your-client-id',\n * redirectUrl: 'https://yourapp.com/callback',\n * oauthServer: 'https://auth.civic.com/oauth',\n * scopes: ['openid', 'profile', 'email']\n * };\n * const result = await handleOAuthRedirectPage(config);\n * ```\n */\n\nimport {\n exchangeTokens,\n buildOauth2Client,\n getEndpointsWithOverrides,\n storeTokens as sharedStoreTokens,\n} from \"../../../shared/lib/util.js\";\nimport { getUser } from \"../../../shared/lib/session.js\";\nimport { GenericUserSession } from \"../../../shared/lib/UserSession.js\";\nimport { CodeVerifier } from \"../../../shared/lib/types.js\";\nimport type { getCurrentLogger } from \"../../utils/logger.js\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport { GenericPublicClientPKCEProducer } from \"../../../services/PKCE.js\";\nimport type { AuthStorage, OIDCTokenResponseBody } from \"../../../types.js\";\n\n/**\n * Store tokens using the shared utilities from /shared/lib\n * This ensures consistency with the React implementation and also handles user session storage\n */\nexport async function storeTokens(\n tokens: OIDCTokenResponseBody,\n storageAdapter: AuthStorage,\n loggerInstance = createLogger(\"oauth-callback\"),\n): Promise<object | null> {\n try {\n // Use shared storeTokens utility for consistent token storage\n await sharedStoreTokens(storageAdapter, tokens);\n\n // Get user info using shared utilities\n const user = await getUser(storageAdapter);\n if (user) {\n // Store user session using shared utilities\n const userSession = new GenericUserSession(storageAdapter);\n await userSession.set(user);\n loggerInstance.info(\n \"CivicAuth: Tokens and user info stored successfully using shared utilities.\",\n );\n return user;\n } else {\n loggerInstance.warn(\n \"CivicAuth: Failed to extract user info from tokens.\",\n );\n return null;\n }\n } catch (error) {\n loggerInstance.error(\"CivicAuth: Error storing tokens:\", error);\n throw error;\n }\n}\n\nexport interface HandleOAuthRedirectConfig {\n clientId: string;\n redirectUrl: string;\n oauthServer: string;\n textSignals: {\n success: string;\n error: string;\n };\n storageAdapter: AuthStorage;\n logger?: ReturnType<typeof getCurrentLogger>;\n}\n\n/**\n * Handle OAuth redirect page processing for vanilla JavaScript applications.\n * This function processes the OAuth callback URL parameters and exchanges the authorization code for tokens.\n *\n * @param config - Configuration object for handling the OAuth redirect\n * @param config.clientId - OAuth client ID\n * @param config.redirectUrl - URL to redirect to after authentication\n * @param config.oauthServer - OAuth server base URL\n * @param config.textSignals - Text signals for success and error states\n * @param config.storageAdapter - Storage adapter for persisting auth state (required)\n * @param config.logger - Optional logger instance\n * @returns Promise<boolean> - Returns true if callback was handled, false otherwise\n *\n * @example\n * ```javascript\n * // Using storeTokens with default iframe mode\n * const config = {\n * clientId: 'your-client-id',\n * redirectUrl: 'https://yourapp.com/callback',\n * oauthServer: 'https://auth.civic.com/oauth',\n * scopes: ['openid', 'profile', 'email']\n * };\n * await storeTokens(params, config);\n * ```\n */\nexport async function handleOAuthRedirectPage(\n config: HandleOAuthRedirectConfig,\n): Promise<boolean> {\n const loggerInstance = config.logger || createLogger(\"oauth-callback\");\n const urlParams = new URLSearchParams(window.location.search);\n const code = urlParams.get(\"code\");\n const state = urlParams.get(\"state\");\n const error = urlParams.get(\"error\");\n\n // Check if we're in a popup window\n const isPopup = window.opener && window.opener !== window;\n loggerInstance.info(\"CivicAuth: Callback page context\", {\n isPopup,\n hasOpener: !!window.opener,\n hasCode: !!code,\n hasError: !!error,\n currentUrl: window.location.href,\n });\n\n if (error) {\n loggerInstance.error(\"CivicAuth: OAuth error in callback\", { error });\n\n // Create error signal for iframe mode\n const errorSignalElement = document.createElement(\"div\");\n errorSignalElement.id = \"civic-auth-error-signal\";\n errorSignalElement.textContent = `${config.textSignals.error} (Error: ${error})`;\n errorSignalElement.style.display = \"none\";\n document.body.appendChild(errorSignalElement);\n\n // Send postMessage for popup mode\n if (isPopup && window.opener) {\n loggerInstance.info(\"CivicAuth: Sending error message to parent window\", {\n hasOpener: !!window.opener,\n openerOrigin: window.opener.origin || \"unknown\",\n currentUrl: window.location.href,\n messageData: {\n type: \"auth_error\",\n detail: `OAuth error: ${error}`,\n error: error,\n },\n });\n\n try {\n window.opener.postMessage(\n {\n type: \"auth_error\",\n detail: `OAuth error: ${error}`,\n error: error,\n },\n \"*\",\n );\n loggerInstance.info(\"CivicAuth: Error postMessage sent successfully\");\n } catch (postMessageError) {\n loggerInstance.error(\"CivicAuth: Failed to send error postMessage\", {\n error: postMessageError,\n });\n }\n\n // Close popup after a short delay\n setTimeout(() => {\n loggerInstance.info(\"CivicAuth: Closing popup window\");\n try {\n window.close();\n } catch (closeError) {\n loggerInstance.warn(\"CivicAuth: Failed to close popup window\", {\n error: closeError,\n });\n }\n }, 1000);\n } else {\n loggerInstance.debug(\"CivicAuth: Not in popup mode or no opener\", {\n isPopup,\n hasOpener: !!window.opener,\n });\n }\n\n return true;\n }\n\n if (code && state) {\n loggerInstance.info(\"CivicAuth: OAuth callback detected with code:\", code);\n\n // Use the provided storage adapter from config\n const storage = config.storageAdapter;\n const pkceProducer = new GenericPublicClientPKCEProducer(storage);\n\n try {\n const endpoints = await getEndpointsWithOverrides(config.oauthServer);\n const oauth2Client = buildOauth2Client(\n config.clientId,\n config.redirectUrl,\n endpoints,\n );\n\n const tokenResponse = await exchangeTokens(\n code,\n state,\n pkceProducer,\n oauth2Client,\n config.oauthServer,\n endpoints,\n );\n\n // Get userInfo from storeTokens using shared utilities\n const userInfo = await storeTokens(\n tokenResponse,\n storage,\n loggerInstance,\n );\n loggerInstance.info(\n \"CivicAuth: Tokens stored successfully using shared utilities.\",\n );\n\n // Create success signal for iframe mode\n const signalElement = document.createElement(\"div\");\n signalElement.id = \"civic-auth-success-signal\";\n signalElement.textContent = config.textSignals.success;\n if (userInfo) {\n // Embed userInfo if available\n signalElement.setAttribute(\"data-user-info\", JSON.stringify(userInfo));\n }\n signalElement.style.display = \"none\";\n document.body.appendChild(signalElement);\n loggerInstance.info(\"CivicAuth: Appended success signal to body.\");\n\n // Send postMessage for popup mode\n if (isPopup && window.opener) {\n loggerInstance.info(\n \"CivicAuth: Sending success message to parent window\",\n {\n hasOpener: !!window.opener,\n openerOrigin: window.opener.origin || \"unknown\",\n currentUrl: window.location.href,\n messageData: {\n type: \"auth_success\",\n detail: \"Authentication successful\",\n hasUser: !!userInfo,\n hasTokens: !!tokenResponse,\n },\n },\n );\n\n try {\n window.opener.postMessage(\n {\n type: \"auth_success\",\n detail: \"Authentication successful\",\n data: {\n user: userInfo,\n tokens: tokenResponse,\n },\n },\n \"*\",\n );\n loggerInstance.info(\n \"CivicAuth: Success postMessage sent successfully\",\n );\n } catch (postMessageError) {\n loggerInstance.error(\n \"CivicAuth: Failed to send success postMessage\",\n {\n error: postMessageError,\n },\n );\n }\n\n // Close popup after a short delay\n setTimeout(() => {\n loggerInstance.info(\"CivicAuth: Closing popup window\");\n try {\n window.close();\n } catch (closeError) {\n loggerInstance.warn(\"CivicAuth: Failed to close popup window\", {\n error: closeError,\n });\n }\n }, 1000);\n } else {\n loggerInstance.debug(\"CivicAuth: Not in popup mode or no opener\", {\n isPopup,\n hasOpener: !!window.opener,\n });\n }\n\n // Clean up the code verifier using shared utilities\n await storage.delete(CodeVerifier.COOKIE_NAME);\n } catch (error) {\n loggerInstance.error(\"CivicAuth: Token exchange error:\", error);\n\n // Create error signal for iframe mode\n const errorSignalElement = document.createElement(\"div\");\n errorSignalElement.id = \"civic-auth-error-signal\";\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n errorSignalElement.textContent = `${config.textSignals.error} (Error: ${errorMessage})`;\n errorSignalElement.style.display = \"none\";\n document.body.appendChild(errorSignalElement);\n\n // Send postMessage for popup mode\n if (isPopup && window.opener) {\n loggerInstance.info(\n \"CivicAuth: Sending error message to parent window\",\n {\n hasOpener: !!window.opener,\n openerOrigin: window.opener.origin || \"unknown\",\n currentUrl: window.location.href,\n messageData: {\n type: \"auth_error\",\n detail: `Token exchange error: ${errorMessage}`,\n error: errorMessage,\n },\n },\n );\n\n try {\n window.opener.postMessage(\n {\n type: \"auth_error\",\n detail: `Token exchange error: ${errorMessage}`,\n error: errorMessage,\n },\n \"*\",\n );\n loggerInstance.info(\"CivicAuth: Error postMessage sent successfully\");\n } catch (postMessageError) {\n loggerInstance.error(\"CivicAuth: Failed to send error postMessage\", {\n error: postMessageError,\n });\n }\n\n // Close popup after a short delay\n setTimeout(() => {\n loggerInstance.info(\"CivicAuth: Closing popup window\");\n try {\n window.close();\n } catch (closeError) {\n loggerInstance.warn(\"CivicAuth: Failed to close popup window\", {\n error: closeError,\n });\n }\n }, 1000);\n } else {\n loggerInstance.debug(\"CivicAuth: Not in popup mode or no opener\", {\n isPopup,\n hasOpener: !!window.opener,\n });\n }\n\n return true;\n }\n return true;\n }\n return false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"OAuthCallbackHandler.js","sourceRoot":"","sources":["../../../../src/vanillajs/auth/handlers/OAuthCallbackHandler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth Callback Handler for Vanilla JavaScript Applications\n *\n * This module provides functionality for handling OAuth redirect/callback pages in vanilla JavaScript\n * applications using the Civic Auth system. It processes the OAuth authorization code flow completion\n * by exchanging authorization codes for access tokens and managing the authentication state.\n *\n * Key responsibilities:\n * - Process OAuth callback URL parameters (code, state)\n * - Exchange authorization codes for access tokens using PKCE\n * - Store tokens and user session data using shared utilities\n * - Create DOM signals for iframe-based authentication flows\n * - Handle error states and cleanup during the OAuth flow\n *\n * This module works in conjunction with:\n * - CivicAuth class for initiating OAuth flows\n * - SignalObserver for detecting authentication completion in iframes\n * - Shared token storage utilities for consistent state management\n *\n * @example\n * ```javascript\n * // Basic usage\n * const config = {\n * clientId: 'your-client-id',\n * redirectUrl: 'https://yourapp.com/callback',\n * oauthServer: 'https://auth.civic.com/oauth',\n * scopes: ['openid', 'profile', 'email']\n * };\n * const result = await handleOAuthRedirectPage(config);\n * ```\n */\n\nimport {\n exchangeTokens,\n buildOauth2Client,\n getEndpointsWithOverrides,\n storeTokens as sharedStoreTokens,\n} from \"../../../shared/lib/util.js\";\nimport { getUser } from \"../../../shared/lib/session.js\";\nimport { GenericUserSession } from \"../../../shared/lib/UserSession.js\";\nimport { CodeVerifier } from \"../../../shared/lib/types.js\";\nimport type { getCurrentLogger } from \"../../utils/logger.js\";\nimport { createLogger } from \"../../utils/logger.js\";\nimport { GenericPublicClientPKCEProducer } from \"../../../services/PKCE.js\";\nimport type { AuthStorage, OIDCTokenResponseBody } from \"../../../types.js\";\nimport { removeParamsWithoutReload } from \"../../../lib/windowUtil.js\";\nimport { DEFAULT_OAUTH_GET_PARAMS } from \"../../../constants.js\";\n\n/**\n * Store tokens using the shared utilities from /shared/lib\n * This ensures consistency with the React implementation and also handles user session storage\n */\nexport async function storeTokens(\n tokens: OIDCTokenResponseBody,\n storageAdapter: AuthStorage,\n loggerInstance = createLogger(\"oauth-callback\"),\n): Promise<object | null> {\n try {\n // Use shared storeTokens utility for consistent token storage\n await sharedStoreTokens(storageAdapter, tokens);\n\n // Get user info using shared utilities\n const user = await getUser(storageAdapter);\n if (user) {\n // Store user session using shared utilities\n const userSession = new GenericUserSession(storageAdapter);\n await userSession.set(user);\n loggerInstance.info(\n \"CivicAuth: Tokens and user info stored successfully using shared utilities.\",\n );\n return user;\n } else {\n loggerInstance.warn(\n \"CivicAuth: Failed to extract user info from tokens.\",\n );\n return null;\n }\n } catch (error) {\n loggerInstance.error(\"CivicAuth: Error storing tokens:\", error);\n throw error;\n }\n}\n\nexport interface HandleOAuthRedirectConfig {\n clientId: string;\n redirectUrl: string;\n oauthServer: string;\n textSignals: {\n success: string;\n error: string;\n };\n storageAdapter: AuthStorage;\n logger?: ReturnType<typeof getCurrentLogger>;\n}\n\n/**\n * Handle OAuth redirect page processing for vanilla JavaScript applications.\n * This function processes the OAuth callback URL parameters and exchanges the authorization code for tokens.\n *\n * @param config - Configuration object for handling the OAuth redirect\n * @param config.clientId - OAuth client ID\n * @param config.redirectUrl - URL to redirect to after authentication\n * @param config.oauthServer - OAuth server base URL\n * @param config.textSignals - Text signals for success and error states\n * @param config.storageAdapter - Storage adapter for persisting auth state (required)\n * @param config.logger - Optional logger instance\n * @returns Promise<boolean> - Returns true if callback was handled, false otherwise\n *\n * @example\n * ```javascript\n * // Using storeTokens with default iframe mode\n * const config = {\n * clientId: 'your-client-id',\n * redirectUrl: 'https://yourapp.com/callback',\n * oauthServer: 'https://auth.civic.com/oauth',\n * scopes: ['openid', 'profile', 'email']\n * };\n * await storeTokens(params, config);\n * ```\n */\nexport async function handleOAuthRedirectPage(\n config: HandleOAuthRedirectConfig,\n): Promise<boolean> {\n const loggerInstance = config.logger || createLogger(\"oauth-callback\");\n const urlParams = new URLSearchParams(window.location.search);\n const code = urlParams.get(\"code\");\n const state = urlParams.get(\"state\");\n const error = urlParams.get(\"error\");\n\n // Check if we're in a popup window\n const isPopup = window.opener && window.opener !== window;\n loggerInstance.info(\"CivicAuth: Callback page context\", {\n isPopup,\n hasOpener: !!window.opener,\n hasCode: !!code,\n hasError: !!error,\n currentUrl: window.location.href,\n });\n\n if (error) {\n loggerInstance.error(\"CivicAuth: OAuth error in callback\", { error });\n\n // Create error signal for iframe mode\n const errorSignalElement = document.createElement(\"div\");\n errorSignalElement.id = \"civic-auth-error-signal\";\n errorSignalElement.textContent = `${config.textSignals.error} (Error: ${error})`;\n errorSignalElement.style.display = \"none\";\n document.body.appendChild(errorSignalElement);\n\n // Send postMessage for popup mode\n if (isPopup && window.opener) {\n loggerInstance.info(\"CivicAuth: Sending error message to parent window\", {\n hasOpener: !!window.opener,\n openerOrigin: window.opener.origin || \"unknown\",\n currentUrl: window.location.href,\n messageData: {\n type: \"auth_error\",\n detail: `OAuth error: ${error}`,\n error: error,\n },\n });\n\n try {\n window.opener.postMessage(\n {\n type: \"auth_error\",\n detail: `OAuth error: ${error}`,\n error: error,\n },\n \"*\",\n );\n loggerInstance.info(\"CivicAuth: Error postMessage sent successfully\");\n } catch (postMessageError) {\n loggerInstance.error(\"CivicAuth: Failed to send error postMessage\", {\n error: postMessageError,\n });\n }\n\n // Close popup after a short delay\n setTimeout(() => {\n loggerInstance.info(\"CivicAuth: Closing popup window\");\n try {\n window.close();\n } catch (closeError) {\n loggerInstance.warn(\"CivicAuth: Failed to close popup window\", {\n error: closeError,\n });\n }\n }, 1000);\n } else {\n loggerInstance.debug(\"CivicAuth: Not in popup mode or no opener\", {\n isPopup,\n hasOpener: !!window.opener,\n });\n }\n\n return true;\n }\n\n if (code && state) {\n loggerInstance.info(\"CivicAuth: OAuth callback detected with code:\", code);\n\n // Use the provided storage adapter from config\n const storage = config.storageAdapter;\n const pkceProducer = new GenericPublicClientPKCEProducer(storage);\n\n try {\n const endpoints = await getEndpointsWithOverrides(config.oauthServer);\n const oauth2Client = buildOauth2Client(\n config.clientId,\n config.redirectUrl,\n endpoints,\n );\n\n const tokenResponse = await exchangeTokens(\n code,\n state,\n pkceProducer,\n oauth2Client,\n config.oauthServer,\n endpoints,\n );\n\n // Get userInfo from storeTokens using shared utilities\n const userInfo = await storeTokens(\n tokenResponse,\n storage,\n loggerInstance,\n );\n loggerInstance.info(\n \"CivicAuth: Tokens stored successfully using shared utilities.\",\n );\n\n // Create success signal for iframe mode\n const signalElement = document.createElement(\"div\");\n signalElement.id = \"civic-auth-success-signal\";\n signalElement.textContent = config.textSignals.success;\n if (userInfo) {\n // Embed userInfo if available\n signalElement.setAttribute(\"data-user-info\", JSON.stringify(userInfo));\n }\n signalElement.style.display = \"none\";\n document.body.appendChild(signalElement);\n loggerInstance.info(\"CivicAuth: Appended success signal to body.\");\n\n // Clean up OAuth parameters from URL for redirect mode\n if (!isPopup) {\n removeParamsWithoutReload(DEFAULT_OAUTH_GET_PARAMS);\n loggerInstance.info(\"CivicAuth: Cleaned up OAuth parameters from URL.\");\n }\n\n // Send postMessage for popup mode\n if (isPopup && window.opener) {\n loggerInstance.info(\n \"CivicAuth: Sending success message to parent window\",\n {\n hasOpener: !!window.opener,\n openerOrigin: window.opener.origin || \"unknown\",\n currentUrl: window.location.href,\n messageData: {\n type: \"auth_success\",\n detail: \"Authentication successful\",\n hasUser: !!userInfo,\n hasTokens: !!tokenResponse,\n },\n },\n );\n\n try {\n window.opener.postMessage(\n {\n type: \"auth_success\",\n detail: \"Authentication successful\",\n data: {\n user: userInfo,\n tokens: tokenResponse,\n },\n },\n \"*\",\n );\n loggerInstance.info(\n \"CivicAuth: Success postMessage sent successfully\",\n );\n } catch (postMessageError) {\n loggerInstance.error(\n \"CivicAuth: Failed to send success postMessage\",\n {\n error: postMessageError,\n },\n );\n }\n\n // Close popup after a short delay\n setTimeout(() => {\n loggerInstance.info(\"CivicAuth: Closing popup window\");\n try {\n window.close();\n } catch (closeError) {\n loggerInstance.warn(\"CivicAuth: Failed to close popup window\", {\n error: closeError,\n });\n }\n }, 1000);\n } else {\n loggerInstance.debug(\"CivicAuth: Not in popup mode or no opener\", {\n isPopup,\n hasOpener: !!window.opener,\n });\n }\n\n // Clean up the code verifier using shared utilities\n await storage.delete(CodeVerifier.COOKIE_NAME);\n } catch (error) {\n loggerInstance.error(\"CivicAuth: Token exchange error:\", error);\n\n // Create error signal for iframe mode\n const errorSignalElement = document.createElement(\"div\");\n errorSignalElement.id = \"civic-auth-error-signal\";\n const errorMessage =\n error instanceof Error ? error.message : \"Unknown error\";\n errorSignalElement.textContent = `${config.textSignals.error} (Error: ${errorMessage})`;\n errorSignalElement.style.display = \"none\";\n document.body.appendChild(errorSignalElement);\n\n // Send postMessage for popup mode\n if (isPopup && window.opener) {\n loggerInstance.info(\n \"CivicAuth: Sending error message to parent window\",\n {\n hasOpener: !!window.opener,\n openerOrigin: window.opener.origin || \"unknown\",\n currentUrl: window.location.href,\n messageData: {\n type: \"auth_error\",\n detail: `Token exchange error: ${errorMessage}`,\n error: errorMessage,\n },\n },\n );\n\n try {\n window.opener.postMessage(\n {\n type: \"auth_error\",\n detail: `Token exchange error: ${errorMessage}`,\n error: errorMessage,\n },\n \"*\",\n );\n loggerInstance.info(\"CivicAuth: Error postMessage sent successfully\");\n } catch (postMessageError) {\n loggerInstance.error(\"CivicAuth: Failed to send error postMessage\", {\n error: postMessageError,\n });\n }\n\n // Close popup after a short delay\n setTimeout(() => {\n loggerInstance.info(\"CivicAuth: Closing popup window\");\n try {\n window.close();\n } catch (closeError) {\n loggerInstance.warn(\"CivicAuth: Failed to close popup window\", {\n error: closeError,\n });\n }\n }, 1000);\n } else {\n loggerInstance.debug(\"CivicAuth: Not in popup mode or no opener\", {\n isPopup,\n hasOpener: !!window.opener,\n });\n }\n\n return true;\n }\n return true;\n }\n return false;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civic/auth",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3-beta.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -93,9 +93,9 @@
|
|
|
93
93
|
"@repo/typescript-config": "0.0.0"
|
|
94
94
|
},
|
|
95
95
|
"peerDependencies": {
|
|
96
|
+
"express": ">=4.0.0",
|
|
96
97
|
"next": "^14.2.25 || >=15.2.3",
|
|
97
|
-
"react": ">=18"
|
|
98
|
-
"express": ">=4.0.0"
|
|
98
|
+
"react": ">=18"
|
|
99
99
|
},
|
|
100
100
|
"peerDependenciesMeta": {
|
|
101
101
|
"next": {
|