@civic/auth 0.9.4 → 0.9.6-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/dist/nextjs/middleware.d.ts.map +1 -1
- package/dist/nextjs/middleware.js +31 -93
- package/dist/nextjs/middleware.js.map +1 -1
- package/dist/nextjs/routeHandler.d.ts.map +1 -1
- package/dist/nextjs/routeHandler.js +16 -6
- package/dist/nextjs/routeHandler.js.map +1 -1
- package/dist/nextjs/utils.d.ts +28 -0
- package/dist/nextjs/utils.d.ts.map +1 -1
- package/dist/nextjs/utils.js +113 -0
- package/dist/nextjs/utils.js.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.d.ts +2 -1
- package/dist/reactjs/providers/CivicAuthProvider.d.ts.map +1 -1
- package/dist/reactjs/providers/CivicAuthProvider.js +3 -1
- package/dist/reactjs/providers/CivicAuthProvider.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/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
# 0.9.
|
|
1
|
+
# 0.9.5
|
|
2
|
+
|
|
3
|
+
- Fixed an issue with token refresh in the NextJS middleware
|
|
4
|
+
|
|
5
|
+
# 0.9.4
|
|
2
6
|
|
|
3
7
|
- fix: Code Params cleanup by @cyberdude in https://github.com/civicteam/civic-auth/pull/1871
|
|
4
8
|
- fix(TECH-2490): Proper Clean up and Replenishment of session by @cyberdude in https://github.com/civicteam/civic-auth/pull/1872
|
|
5
9
|
|
|
6
10
|
**Full Changelog**: https://github.com/civicteam/civic-auth/compare/@civic/auth@0.9.1-beta.6...@civic/auth@0.9.2
|
|
7
11
|
|
|
12
|
+
# 0.9.3
|
|
13
|
+
|
|
14
|
+
- BUILD FAILED because of Civic Verify
|
|
15
|
+
|
|
8
16
|
# 0.9.2
|
|
9
17
|
|
|
10
18
|
- BUILD FAILED because of Civic Verify
|
|
@@ -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;
|
|
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;AAC9C,OAAO,KAAK,EACV,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,KAAK,EAAe,WAAW,EAAE,MAAM,YAAY,CAAC;AAuC3D,KAAK,UAAU,GAAG,CAChB,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAE1C;;;;;;GAMG;AACH,eAAO,MAAM,2BAA2B,2BACd,sBAAsB,WACrC,WAAW,KACnB,OAAO,CAAC,WAAW,CAoBrB,CAAC;AA+EF;;;;;;;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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server.js";
|
|
2
|
-
import picomatch from "picomatch";
|
|
3
2
|
import { resolveAuthConfig } from "../nextjs/config.js";
|
|
4
3
|
import { loggers } from "../lib/logger.js";
|
|
5
4
|
import { ServerAuthenticationResolver } from "../server/ServerAuthenticationResolver.js";
|
|
5
|
+
import { shouldAttemptRefresh, createRefreshResponse, shouldSkipAuthForSystemUrls, handleLoginUrl, shouldSkipAuthForRoutePatterns, handleUnauthenticatedUser, } from "./utils.js";
|
|
6
6
|
const logger = loggers.nextjs.middleware;
|
|
7
7
|
/**
|
|
8
8
|
* CookieStorage implementation for NextJS middleware context that works with NextRequest
|
|
@@ -30,33 +30,6 @@ class NextjsReadOnlyRequestCookieStorage {
|
|
|
30
30
|
throw new Error("Cannot delete cookies directly in middleware");
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
// Matches globs:
|
|
34
|
-
// Examples:
|
|
35
|
-
// /user
|
|
36
|
-
// /user/*
|
|
37
|
-
// /user/**/info
|
|
38
|
-
const matchGlob = (pathname, globPattern) => {
|
|
39
|
-
const matches = picomatch(globPattern);
|
|
40
|
-
return matches(pathname);
|
|
41
|
-
};
|
|
42
|
-
// Matches globs:
|
|
43
|
-
// Examples:
|
|
44
|
-
// /user
|
|
45
|
-
// /user/*
|
|
46
|
-
// /user/**/info
|
|
47
|
-
const matchesGlobs = (pathname, patterns) => patterns.some((pattern) => {
|
|
48
|
-
if (!pattern)
|
|
49
|
-
return false;
|
|
50
|
-
return matchGlob(pathname, pattern);
|
|
51
|
-
});
|
|
52
|
-
const getOriginUrl = (request, authConfig) => {
|
|
53
|
-
// Use configured baseUrl if provided (for reverse proxy scenarios)
|
|
54
|
-
if (authConfig.baseUrl) {
|
|
55
|
-
return authConfig.baseUrl;
|
|
56
|
-
}
|
|
57
|
-
// Fallback to nextUrl.origin (includes port automatically)
|
|
58
|
-
return request.nextUrl.origin;
|
|
59
|
-
};
|
|
60
33
|
/**
|
|
61
34
|
* use a ServerAuthenticationResolver to validate the existing session
|
|
62
35
|
* using NextJS cookie storage
|
|
@@ -86,82 +59,47 @@ export const validateAuthTokensIfPresent = async (authConfigWithDefaults, reques
|
|
|
86
59
|
/**
|
|
87
60
|
* Core authentication middleware logic.
|
|
88
61
|
*
|
|
89
|
-
*
|
|
90
|
-
* 1.
|
|
91
|
-
* 2.
|
|
92
|
-
* 3.
|
|
93
|
-
* - If refresh token exists: redirect to refresh endpoint
|
|
94
|
-
* - If expired tokens exist: redirect to logout callback to clear them
|
|
95
|
-
* - If on login URL: allow access (prevents redirect loops)
|
|
96
|
-
* - Otherwise: redirect to login
|
|
97
|
-
* 4. Allow authenticated users to continue
|
|
62
|
+
* The Authentication Story:
|
|
63
|
+
* 1. Validate tokens to understand current authentication state
|
|
64
|
+
* 2. Attempt token refresh if needed (the OAuth pipeline approach)
|
|
65
|
+
* 3. Apply route-specific authentication rules based on final state
|
|
98
66
|
*/
|
|
99
67
|
const applyAuth = async (authConfig, request) => {
|
|
100
68
|
const authConfigWithDefaults = resolveAuthConfig(authConfig);
|
|
101
|
-
//
|
|
69
|
+
// Step 1: Understand the current authentication state
|
|
102
70
|
const session = await validateAuthTokensIfPresent(authConfigWithDefaults, request);
|
|
103
|
-
logger.debug("
|
|
104
|
-
session,
|
|
71
|
+
logger.debug("Authentication state:", {
|
|
72
|
+
authenticated: session.authenticated,
|
|
105
73
|
pathName: request.nextUrl.pathname,
|
|
106
74
|
loginUrl: authConfigWithDefaults.loginUrl,
|
|
107
75
|
});
|
|
108
|
-
//
|
|
109
|
-
if (
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
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;
|
|
76
|
+
// Step 2: Attempt token refresh if needed (OAuth pipeline)
|
|
77
|
+
if (shouldAttemptRefresh(session)) {
|
|
78
|
+
const refreshResponse = createRefreshResponse(request, authConfigWithDefaults);
|
|
79
|
+
if (refreshResponse) {
|
|
80
|
+
return refreshResponse;
|
|
130
81
|
}
|
|
131
82
|
}
|
|
132
|
-
//
|
|
83
|
+
// Step 3: Apply route-specific authentication rules
|
|
84
|
+
// Skip authentication for system URLs (callback, challenge, logout)
|
|
85
|
+
if (shouldSkipAuthForSystemUrls(request.nextUrl.pathname, authConfigWithDefaults, request.method)) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
// Handle login URL with special logic
|
|
89
|
+
if (request.nextUrl.pathname === authConfigWithDefaults.loginUrl) {
|
|
90
|
+
handleLoginUrl(request.nextUrl.pathname, session, authConfigWithDefaults);
|
|
91
|
+
return undefined; // Always allow access to login URL
|
|
92
|
+
}
|
|
93
|
+
// Skip authentication for routes not matching include/exclude patterns
|
|
94
|
+
if (shouldSkipAuthForRoutePatterns(request.nextUrl.pathname, authConfigWithDefaults)) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
// Step 4: Handle unauthenticated users on protected routes
|
|
133
98
|
if (!session.authenticated) {
|
|
134
|
-
|
|
135
|
-
// Avoid redirect loops by checking if already in a redirect flow
|
|
136
|
-
if (!request.nextUrl.searchParams.has("targetUrl")) {
|
|
137
|
-
// Attempt session rehydration if refresh token exists
|
|
138
|
-
if (session.refreshToken) {
|
|
139
|
-
const refreshUrl = new URL(authConfigWithDefaults.refreshUrl, getOriginUrl(request, authConfigWithDefaults));
|
|
140
|
-
refreshUrl.searchParams.set("targetUrl", targetUrl);
|
|
141
|
-
const redirectUrl = `${refreshUrl.toString()}`;
|
|
142
|
-
logger.debug(`→ Refresh token found - redirecting to "${redirectUrl}"`);
|
|
143
|
-
return NextResponse.redirect(redirectUrl);
|
|
144
|
-
}
|
|
145
|
-
// Clear expired/invalid tokens if they exist without refresh token
|
|
146
|
-
if (session.accessToken || session.idToken) {
|
|
147
|
-
const clearBadTokensUrl = new URL(authConfigWithDefaults.logoutCallbackUrl, getOriginUrl(request, authConfigWithDefaults));
|
|
148
|
-
clearBadTokensUrl.searchParams.set("targetUrl", targetUrl);
|
|
149
|
-
logger.debug(`→ Invalid tokens found without refresh - clearing via "${clearBadTokensUrl}"`);
|
|
150
|
-
return NextResponse.redirect(clearBadTokensUrl);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// Allow access to login page to prevent redirect loops
|
|
154
|
-
if (request.nextUrl.pathname === authConfigWithDefaults.loginUrl) {
|
|
155
|
-
logger.debug(`→ Allowing access to login page for unauthenticated user`);
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
158
|
-
// Final fallback: redirect to login
|
|
159
|
-
const loginUrl = new URL(authConfigWithDefaults.loginUrl, getOriginUrl(request, authConfigWithDefaults));
|
|
160
|
-
const redirectUrl = `${loginUrl.toString()}`;
|
|
161
|
-
logger.debug(`→ No valid tokens found - redirecting to login "${redirectUrl}"`);
|
|
162
|
-
return NextResponse.redirect(redirectUrl);
|
|
99
|
+
return handleUnauthenticatedUser(session, request, authConfigWithDefaults);
|
|
163
100
|
}
|
|
164
|
-
|
|
101
|
+
// Step 5: Happy ending - authentication passed
|
|
102
|
+
logger.debug("→ Authentication successful, allowing access");
|
|
165
103
|
return undefined;
|
|
166
104
|
};
|
|
167
105
|
/**
|
|
@@ -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:\", {\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
|
+
{"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 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\";\nimport {\n shouldAttemptRefresh,\n createRefreshResponse,\n shouldSkipAuthForSystemUrls,\n handleLoginUrl,\n shouldSkipAuthForRoutePatterns,\n handleUnauthenticatedUser,\n} from \"./utils.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/**\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\n// internal - used by all exported functions\n/**\n * Core authentication middleware logic.\n *\n * The Authentication Story:\n * 1. Validate tokens to understand current authentication state\n * 2. Attempt token refresh if needed (the OAuth pipeline approach)\n * 3. Apply route-specific authentication rules based on final state\n */\nconst applyAuth = async (\n authConfig: OptionalAuthConfig,\n request: NextRequest,\n): Promise<NextResponse | undefined> => {\n const authConfigWithDefaults = resolveAuthConfig(authConfig);\n\n // Step 1: Understand the current authentication state\n const session = await validateAuthTokensIfPresent(\n authConfigWithDefaults,\n request,\n );\n logger.debug(\"Authentication state:\", {\n authenticated: session.authenticated,\n pathName: request.nextUrl.pathname,\n loginUrl: authConfigWithDefaults.loginUrl,\n });\n\n // Step 2: Attempt token refresh if needed (OAuth pipeline)\n if (shouldAttemptRefresh(session)) {\n const refreshResponse = createRefreshResponse(\n request,\n authConfigWithDefaults,\n );\n\n if (refreshResponse) {\n return refreshResponse;\n }\n }\n\n // Step 3: Apply route-specific authentication rules\n\n // Skip authentication for system URLs (callback, challenge, logout)\n if (\n shouldSkipAuthForSystemUrls(\n request.nextUrl.pathname,\n authConfigWithDefaults,\n request.method,\n )\n ) {\n return undefined;\n }\n\n // Handle login URL with special logic\n if (request.nextUrl.pathname === authConfigWithDefaults.loginUrl) {\n handleLoginUrl(request.nextUrl.pathname, session, authConfigWithDefaults);\n return undefined; // Always allow access to login URL\n }\n\n // Skip authentication for routes not matching include/exclude patterns\n if (\n shouldSkipAuthForRoutePatterns(\n request.nextUrl.pathname,\n authConfigWithDefaults,\n )\n ) {\n return undefined;\n }\n\n // Step 4: Handle unauthenticated users on protected routes\n if (!session.authenticated) {\n return handleUnauthenticatedUser(session, request, authConfigWithDefaults);\n }\n\n // Step 5: Happy ending - authentication passed\n logger.debug(\"→ Authentication successful, allowing access\");\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":"routeHandler.d.ts","sourceRoot":"","sources":["../../src/nextjs/routeHandler.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAA0B,MAAM,oBAAoB,CAAC;AAW7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"routeHandler.d.ts","sourceRoot":"","sources":["../../src/nextjs/routeHandler.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAA0B,MAAM,oBAAoB,CAAC;AAW7E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAqb9C,wBAAsB,YAAY,CAChC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAwDD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,YAAY,CAAC,CAwBvB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,OAAO,iCAEF,WAAW,KAAG,OAAO,CAAC,YAAY,CAkCjD,CAAC"}
|
|
@@ -111,6 +111,7 @@ async function handleRefresh(request, config) {
|
|
|
111
111
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
112
112
|
const cookieStorage = getCookieStorageWithUserOverrides(config);
|
|
113
113
|
const userSession = new GenericUserSession(cookieStorage);
|
|
114
|
+
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
114
115
|
try {
|
|
115
116
|
const onError = (error) => {
|
|
116
117
|
logger.error("handleRefresh: Token refresh failed:", error);
|
|
@@ -128,20 +129,26 @@ async function handleRefresh(request, config) {
|
|
|
128
129
|
throw new AuthError("Failed to get user info", 401);
|
|
129
130
|
}
|
|
130
131
|
await userSession.set(user);
|
|
131
|
-
|
|
132
|
+
logger.debug("handleRefresh: Token refresh successful");
|
|
132
133
|
if (targetUrl) {
|
|
133
|
-
|
|
134
|
+
// Success: clear the refresh attempt tracking cookie and redirect to target
|
|
135
|
+
const response = NextResponse.redirect(targetUrl);
|
|
136
|
+
response.cookies.delete("_civic_last_refresh");
|
|
137
|
+
return response;
|
|
134
138
|
}
|
|
135
139
|
// For backend flows, tokens might be null since they're managed in HTTP-only cookies
|
|
136
|
-
|
|
140
|
+
const response = NextResponse.json({
|
|
141
|
+
status: "success",
|
|
142
|
+
tokens: tokens || null,
|
|
143
|
+
});
|
|
144
|
+
return response;
|
|
137
145
|
}
|
|
138
146
|
catch (error) {
|
|
139
147
|
logger.error("handleRefresh: Token refresh failed, clearing tokens:", error);
|
|
140
148
|
await clearTokens(cookieStorage);
|
|
141
149
|
await userSession.clear();
|
|
142
|
-
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
143
150
|
if (targetUrl) {
|
|
144
|
-
logger.warn("redirecting to targetUrl"
|
|
151
|
+
logger.warn("handleRefresh: Refresh failed, redirecting to targetUrl");
|
|
145
152
|
return NextResponse.redirect(targetUrl);
|
|
146
153
|
}
|
|
147
154
|
return NextResponse.json({ status: "failed" });
|
|
@@ -335,7 +342,10 @@ export async function handleLogout(request, config) {
|
|
|
335
342
|
// read the state from the query parameters
|
|
336
343
|
const state = request.nextUrl.searchParams.get("state");
|
|
337
344
|
if (!state || !idToken) {
|
|
338
|
-
logger.error("handleLogout: missing state or idToken", {
|
|
345
|
+
logger.error("handleLogout: missing state or idToken", {
|
|
346
|
+
hasState: !!state,
|
|
347
|
+
hasIdToken: !!idToken,
|
|
348
|
+
});
|
|
339
349
|
await clearAuthCookies();
|
|
340
350
|
// if token or state is missing, the logout call to the server will fail,
|
|
341
351
|
// (token has potentially expired already) so go straight to the postLogoutUrl
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routeHandler.js","sourceRoot":"","sources":["../../src/nextjs/routeHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n LOGOUT_SUCCESS_TEXT,\n TOKEN_EXCHANGE_SUCCESS_TEXT,\n TOKEN_EXCHANGE_TRIGGER_TEXT,\n} from \"@/constants.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport {\n displayModeFromState,\n loginSuccessUrlFromState,\n serverTokenExchangeFromState,\n} from \"@/lib/oauth.js\";\nimport type { AuthConfig, AuthConfigWithDefaults } from \"@/nextjs/config.js\";\nimport { resolveAuthConfig } from \"@/nextjs/config.js\";\nimport { clearAuthCookies, NextjsCookieStorage } from \"@/nextjs/cookies.js\";\nimport { getUser } from \"@/nextjs/index.js\";\nimport { resolveCallbackUrl } from \"@/nextjs/utils.js\";\nimport { resolveOAuthAccessCode } from \"@/server/login.js\";\nimport { GenericPublicClientPKCEProducer } from \"@/services/PKCE.js\";\nimport { CodeVerifier, OAuthTokenTypes } from \"@/shared/lib/types.js\";\nimport { GenericUserSession } from \"@/shared/lib/UserSession.js\";\nimport { clearTokens, generateOauthLogoutUrl } from \"@/shared/lib/util.js\";\nimport { revalidatePath } from \"next/cache.js\";\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport { NextServerAuthenticationRefresherImpl } from \"./NextServerAuthenticationRefresherImpl.js\";\n\nconst logger = loggers.nextjs.handlers.auth;\n\nclass AuthError extends Error {\n constructor(\n message: string,\n public readonly status: number = 401,\n ) {\n super(message);\n this.name = \"AuthError\";\n }\n}\n\nconst tryUriDecode = (value: string): string | null => {\n try {\n return decodeURIComponent(value);\n } catch (e) {\n logger.error(\"Error decoding URI component:\", e);\n return value;\n }\n};\n\nconst getDecodedQueryParam = (\n request: NextRequest,\n paramName: string,\n): string | null => {\n const queryParam = request.nextUrl.searchParams.get(paramName);\n if (queryParam) {\n return tryUriDecode(queryParam);\n }\n return null;\n};\n\nconst getCookieOrQueryParam = (\n request: NextRequest,\n cookieName: string,\n queryName: string,\n): string | null => {\n // First check the cookie as it might have the full path with base directory\n const cookieValue = request.cookies.get(cookieName)?.value;\n if (cookieValue) {\n return tryUriDecode(cookieValue);\n }\n\n // Fallback to query parameter\n return getDecodedQueryParam(request, queryName);\n};\n\nconst getAppUrl = (request: NextRequest): string | null =>\n getCookieOrQueryParam(request, CodeVerifier.APP_URL, \"appUrl\");\n\n// The loginSuccessUrl can either be decoded from the state parameter, or passed as a cookie or query parameter\nconst getLoginSuccessUrl = (\n request: NextRequest,\n baseUrl?: string | null,\n): string | null => {\n const loginSuccessUrl =\n loginSuccessUrlFromState(request.nextUrl.searchParams.get(\"state\")) ||\n getDecodedQueryParam(request, \"loginSuccessUrl\");\n if (!loginSuccessUrl) {\n return null;\n }\n return baseUrl ? new URL(loginSuccessUrl, baseUrl).href : loginSuccessUrl;\n};\n\nconst getIdToken = async (config: AuthConfig): Promise<string | null> => {\n const cookieStorage = new NextjsCookieStorage(config.cookies?.tokens ?? {});\n return cookieStorage.get(OAuthTokenTypes.ID_TOKEN);\n};\n\n/**\n * create a code verifier and challenge for PKCE\n * saving the verifier in a cookie for later use\n * @returns {Promise<NextResponse>}\n */\nasync function handleChallenge(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const cookieStorage = new NextjsCookieStorage(config.cookies?.tokens ?? {});\n const pkceProducer = new GenericPublicClientPKCEProducer(cookieStorage);\n\n const challenge = await pkceProducer.getCodeChallenge();\n const appUrl = request.nextUrl.searchParams.get(\"appUrl\");\n if (appUrl) {\n await cookieStorage.set(CodeVerifier.APP_URL, appUrl);\n }\n\n return NextResponse.json({ status: \"success\", challenge });\n}\n\nconst getCookieStorageWithUserOverrides = (config: AuthConfig) => {\n const resolvedConfigs = resolveAuthConfig(config);\n return new NextjsCookieStorage({\n ...resolvedConfigs.cookies.tokens,\n user: resolvedConfigs.cookies.user,\n });\n};\nasync function performTokenExchangeAndSetCookies(\n config: AuthConfig,\n code: string,\n state: string,\n appUrl: string,\n) {\n const resolvedConfigs = resolveAuthConfig(config);\n // TODO This is messy, better would be to fix the config.cookies type to always be <name: settings>\n // rather than nesting the tokens-related ones *and* code-verifier inside \"tokens\"\n // (despite code-verifier not relating directly to tokens)\n const cookieStorage = getCookieStorageWithUserOverrides(config);\n\n const callbackUrl = resolveCallbackUrl(resolvedConfigs, appUrl);\n try {\n await resolveOAuthAccessCode(code, state, cookieStorage, {\n ...resolvedConfigs,\n redirectUrl: callbackUrl,\n });\n } catch (error) {\n logger.error(\"Token exchange failed:\", error);\n throw new AuthError(\"Failed to authenticate user\", 401);\n }\n\n const user = await getUser();\n if (!user) {\n throw new AuthError(\"Failed to get user info\", 401);\n }\n const userSession = new GenericUserSession(cookieStorage);\n await userSession.set(user);\n}\nasync function handleRefresh(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const cookieStorage = getCookieStorageWithUserOverrides(config);\n const userSession = new GenericUserSession(cookieStorage);\n try {\n const onError = (error: Error) => {\n logger.error(\"handleRefresh: Token refresh failed:\", error);\n throw new AuthError(\"Failed to refresh tokens\", 500);\n };\n const refresher = await NextServerAuthenticationRefresherImpl.build(\n {\n clientId: resolvedConfigs.clientId,\n oauthServer: resolvedConfigs.oauthServer,\n redirectUrl: resolvedConfigs.callbackUrl,\n refreshUrl: resolvedConfigs.refreshUrl,\n },\n cookieStorage,\n onError,\n );\n\n const tokens = await refresher.refreshAccessToken();\n const user = await getUser();\n if (!user) {\n throw new AuthError(\"Failed to get user info\", 401);\n }\n await userSession.set(user);\n const targetUrl = request.nextUrl.searchParams.get(\"targetUrl\");\n if (targetUrl) {\n return NextResponse.redirect(targetUrl);\n }\n // For backend flows, tokens might be null since they're managed in HTTP-only cookies\n return NextResponse.json({ status: \"success\", tokens: tokens || null });\n } catch (error) {\n logger.error(\n \"handleRefresh: Token refresh failed, clearing tokens:\",\n error,\n );\n await clearTokens(cookieStorage);\n await userSession.clear();\n const targetUrl = request.nextUrl.searchParams.get(\"targetUrl\");\n if (targetUrl) {\n logger.warn(\"redirecting to targetUrl\", targetUrl);\n return NextResponse.redirect(targetUrl);\n }\n return NextResponse.json({ status: \"failed\" });\n }\n}\n\nconst generateHtmlResponseWithCallback = (\n request: NextRequest,\n callbackUrl: string,\n loginSuccessUrl?: string,\n) => {\n // we need to replace the URL with resolved config in case the server is hosted\n // behind a reverse proxy or load balancer\n const requestUrl = new URL(request.url);\n const fetchUrl = `${callbackUrl}?${requestUrl.searchParams.toString()}&sameDomainCallback=true`;\n const loginSuccessSegment = loginSuccessUrl\n ? `&loginSuccessUrl=${encodeURIComponent(loginSuccessUrl)}`\n : \"\";\n const html = `<html lang=\"en\">\n <body>\n <span style=\"display:none\">\n <script>\n window.onload = function () {\n // Get the complete URL including origin and path\n // This ensures we capture any base path like /directory\n const appUrl = window.location.href.substring(\n 0,\n window.location.href.indexOf(\"/api/auth\")\n );\n fetch('${fetchUrl}&appUrl=' + encodeURIComponent(appUrl) + '${loginSuccessSegment}').then((response) => {\n response.json().then((jsonResponse) => {\n // For login: Redirect back to the callback route, so Case 2 in handleTokenExchangeComplete will be triggered\n // For logout: Redirect to the postLogoutRedirectUrl\n if(jsonResponse.redirectUrl) {\n window.location.href = jsonResponse.redirectUrl;\n }\n });\n });\n };\n </script>\n </span>\n </body>\n</html>\n`;\n const response = new NextResponse(html);\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n return response;\n};\n\ntype TokkenExchangeCompletInput = {\n request: NextRequest;\n config: AuthConfig;\n loginSuccessUrl?: string | null;\n appUrl?: string | null;\n state: string;\n};\n\nconst handleTokenExchangeComplete = async (\n params: TokkenExchangeCompletInput,\n) => {\n const { request, config, appUrl, loginSuccessUrl, state } = params;\n // Case 1: We are being called via fetch to facilitate access to the cookies. Return success json. The iframe has javascript that will reload this route so Case 2 below will be triggered.\n if (isCalledFromBrowserFetch(request)) {\n logger.debug(\n \"CASE 1: sameDomainCallback=true, returning JSON response with redirect URL\",\n { appUrl, loginSuccessUrl, callbackUrl: config.callbackUrl },\n );\n\n const currentUrl = new URL(request.url);\n // When the client-side JS redirects back here, we don't want to hit this branch again because we can't return JSON from a redirect.\n // So we strip off the sameDomainCallback parameter from the URL.\n const newSearchParams = new URLSearchParams(currentUrl.search);\n newSearchParams.delete(\"sameDomainCallback\");\n\n // We strip off the origin so reverse proxies don't break the redirect.\n const redirectUrl = `${currentUrl.pathname}?${newSearchParams.toString()}${currentUrl.hash}`;\n return NextResponse.json({\n status: \"success\",\n // This makes the iframe redirect back to this route, so Case 2 below will be triggered.\n\n redirectUrl,\n });\n }\n\n // Case 2: We are already authenticated and in iframe mode.\n // Case 2a: We have a custom loginSuccessUrl, so we have to trigger a top-level redirect to it. We do this by rendering a page with the TOKEN_EXCHANGE_SUCCESS_TEXT, which is then picked up by the iframe container.\n // Case 2b: We don't have a custom loginSuccessUrl, so we just redirect to the appUrl. If we don't do this, Cypress tests will fail in the 'no custom loginSuccessUrl' case, because in Cypress an iframe redirect is converted to a top-level redirect,\n // which means the iframe container no longer exists and so can't action the redirect.\n const user = await getUser();\n if (!!user && displayModeFromState(state, \"iframe\") === \"iframe\") {\n if (loginSuccessUrl) {\n logger.debug(\n \"CASE 2a: iframe mode with loginSuccessUrl configured. Returning TOKEN_EXCHANGE_SUCCESS_TEXT to trigger redirect to loginSuccessUrl if any\",\n { loginSuccessUrl },\n );\n const response = new NextResponse(\n `<html lang=\"en\"><span style=\"display:none\">${TOKEN_EXCHANGE_SUCCESS_TEXT}</span></html>`,\n );\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n return response;\n } else {\n logger.debug(\n \"CASE 2b: iframe mode with no loginSuccessUrl configured. Doing a normal redirect without relying on the iframe container to redirect.\",\n );\n return NextResponse.redirect(`${appUrl}`);\n }\n }\n\n // CASE 3: We're not in iframe mode. We can just do a stright http redirect to the final destination, which is either the loginSuccessUrl if specified, or the appUrl.\n logger.debug(\"CASE 3: non-iframe mode, redirecting to loginSuccessUrl\");\n return NextResponse.redirect(`${loginSuccessUrl || appUrl}`);\n};\n\nasync function handleCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const code = request.nextUrl.searchParams.get(\"code\");\n const state = request.nextUrl.searchParams.get(\"state\");\n\n if (!code || !state) throw new AuthError(\"Bad parameters\", 400);\n\n // appUrl is passed from the client to the server in the query string\n // this is necessary because the server does not have access to the client's window.location.origin\n // and can not accurately determine the appUrl (specially if the app is behind a reverse proxy)\n const appUrl = getAppUrl(request);\n\n // If the integrator has specified a loginSuccessUrl, we'll send the user there after the login completes (including token exchange)\n // We pass in the basePath from config to use as the baseUrl, because we might not have access to the app_url cookie at this point if this was a third-party redirect.\n const loginSuccessUrl = getLoginSuccessUrl(request, appUrl);\n\n const tokenExchangeCompleteParams = {\n request,\n config,\n appUrl,\n state,\n loginSuccessUrl,\n };\n\n const user = await getUser();\n if (user) {\n // User already authenticated.\n return handleTokenExchangeComplete(tokenExchangeCompleteParams);\n }\n\n // User not authenticated yet.\n\n // If we have a code_verifier cookie and the appUrl, we can do a token exchange.\n // Otherwise, just render an empty page.\n // The initial redirect back from the auth server does not send cookies, because the redirect is from a 3rd-party domain.\n // The client will make an additional call to this route with cookies included, at which point we do the token exchange.\n const codeVerifier = request.cookies.get(CodeVerifier.COOKIE_NAME);\n\n if (!codeVerifier || !appUrl) {\n logger.debug(\"handleCallback no code_verifier found\", {\n state,\n serverTokenExchange: serverTokenExchangeFromState(`${state}`),\n });\n let response = new NextResponse(\n `<html lang=\"en\"><body><span style=\"display:none\">${TOKEN_EXCHANGE_TRIGGER_TEXT}</span></body></html>`,\n );\n\n // in server-side token exchange mode we need to launch a page that will trigger the token exchange\n // from the same domain, allowing it access to the code_verifier cookie\n // we only need to do this in redirect mode, as the iframe already triggers a client-side token exchange\n // if no code-verifier cookie is found\n if (state && serverTokenExchangeFromState(state)) {\n logger.debug(\n \"handleCallback serverTokenExchangeFromState, launching redirect page...\",\n {\n requestUrl: request.url,\n configCallbackUrl: resolvedConfigs.callbackUrl,\n },\n );\n // generate a page that will callback to the same domain, allowing access\n // to the code_verifier cookie and passing the appUrl.\n response = generateHtmlResponseWithCallback(\n request,\n resolvedConfigs.callbackUrl,\n loginSuccessUrl || undefined,\n );\n }\n logger.debug(\n `handleCallback no code_verifier found, returning ${TOKEN_EXCHANGE_TRIGGER_TEXT}`,\n );\n return response;\n }\n\n await performTokenExchangeAndSetCookies(resolvedConfigs, code, state, appUrl);\n return handleTokenExchangeComplete(tokenExchangeCompleteParams);\n}\n\n/**\n * If redirectPath is an absolute path, return it as-is.\n * Otherwise for relative paths, append it to the current domain.\n * @param redirectPath\n * @param currentBasePath\n * @returns\n */\nconst getAbsoluteRedirectPath = (\n redirectPath: string,\n currentBasePath: string,\n) => new URL(redirectPath, currentBasePath).href;\n\nconst getPostLogoutRedirectUrl = (\n request: NextRequest,\n config: AuthConfig,\n): string => {\n const { loginUrl } = resolveAuthConfig(config);\n // if we have a target URL in the request, it's come from civic middleware\n // and we should use it as the redirect target.\n const targetUrl = request.nextUrl.searchParams.get(\"targetUrl\");\n if (targetUrl) {\n // If a targetUrl is provided, use it as the redirect target.\n // This is useful for redirecting to a specific page after logout.\n return targetUrl;\n }\n const redirectTarget = loginUrl ?? \"/\";\n\n // if the optional loginUrl is provided and it is an absolute URL,\n // use it as the redirect target\n const isAbsoluteRedirect = /^(https?:\\/\\/|www\\.).+/i.test(redirectTarget);\n if (isAbsoluteRedirect) {\n return redirectTarget;\n }\n\n // if loginUrl is not defined, the appUrl is passed from the client to the server\n // in the query string or cookies. This is necessary because the server does not\n // have access to the client's window.location and can not accurately determine\n // the appUrl (specially if the app is behind a reverse proxy).\n const appUrl = getAppUrl(request);\n if (appUrl) return getAbsoluteRedirectPath(redirectTarget, appUrl);\n\n // If we can't determine the post-logout redirect URL, fallback to the app root as it's the most likely location of the login page.\n return request.nextUrl.origin;\n};\n\nconst revalidateUrlPath = async (url: string) => {\n try {\n const path = new URL(url).pathname;\n revalidatePath(path);\n } catch (error) {\n logger.warn(\"Failed to revalidate path after logout:\", error);\n }\n};\n\nexport async function handleLogout(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n // Ensure we have the proper app URL including any base path\n const appBaseUrl = getAppUrl(request) || request.url;\n\n // Construct the post-logout URL with the base path included\n const postLogoutUrl = new URL(resolvedConfigs.logoutCallbackUrl, appBaseUrl);\n\n // read the id_token from the cookies\n const idToken = await getIdToken(resolvedConfigs);\n\n // read the state from the query parameters\n const state = request.nextUrl.searchParams.get(\"state\");\n\n if (!state || !idToken) {\n logger.error(\"handleLogout: missing state or idToken\", { state, idToken });\n await clearAuthCookies();\n // if token or state is missing, the logout call to the server will fail,\n // (token has potentially expired already) so go straight to the postLogoutUrl\n // so the user can be signed out.\n return NextResponse.redirect(\n `${postLogoutUrl}${state ? \"?state=\" + state : \"\"}`,\n );\n }\n\n const displayMode = displayModeFromState(state, \"iframe\");\n if (displayMode === \"iframe\") {\n // clear auth cookies immediately before calling the logout endpoint to give faster UX\n await clearAuthCookies();\n await revalidateUrlPath(request.url);\n }\n\n const logoutUrl = await generateOauthLogoutUrl({\n clientId: resolvedConfigs.clientId,\n idToken,\n state,\n redirectUrl: postLogoutUrl.href,\n oauthServer: resolvedConfigs.oauthServer,\n });\n return NextResponse.redirect(`${logoutUrl.href}`);\n}\n\nconst isCalledFromBrowserFetch = (request: NextRequest) =>\n request.url.includes(\"sameDomainCallback=true\");\n\n/**\n * Called after the cookies have been cleared.\n */\ntype LogoutCompleteInputs = {\n request: NextRequest;\n resolvedConfigs: AuthConfigWithDefaults;\n};\nconst handleLogoutComplete = async (params: LogoutCompleteInputs) => {\n const { request, resolvedConfigs } = params;\n const state = request.nextUrl.searchParams.get(\"state\");\n const postLogoutRedirectUrl = getPostLogoutRedirectUrl(\n request,\n resolvedConfigs,\n );\n\n // If this is a FETCH call, we can only return json. Trying to redirect or return HTML will fail.\n if (isCalledFromBrowserFetch(request)) {\n logger.debug(\n \"handleLogoutComplete: sameDomainCallback=true, returning JSON response with redirect URL\",\n { postLogoutRedirectUrl },\n );\n // The client-side JS will do a window.location.href redirect to postLogoutRedirectUrl when this request returns success.\n return NextResponse.json({\n status: \"success\",\n redirectUrl: postLogoutRedirectUrl,\n });\n }\n\n // If this is a redirect inside an iframe and the user is indeed logged out, render some text that makes the parent redirect to the postLogoutRedirectUrl.\n const user = await getUser();\n if (!user && !!state && displayModeFromState(state, \"iframe\") === \"iframe\") {\n // User is logged out while in an iframe redirect (not a FETCH call).\n // Render some text to make the CivicLogoutIframeContainer redirect to the postLogoutRedirectUrl.\n const response = new NextResponse(\n `<html lang=\"en\"><span style=\"display:none\">${LOGOUT_SUCCESS_TEXT}<a href=\"${[postLogoutRedirectUrl]}\" rel=\"civic-auth-post-logout-redirect-url\"></a></span></html>`,\n );\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n logger.debug(\n \"handleLogoutComplete: iframe mode, rendering HTML with logout success text\",\n { postLogoutRedirectUrl },\n );\n return response;\n }\n\n logger.debug(\"handleLogoutComplete: redirecting to postLogoutRedirectUrl\", {\n postLogoutRedirectUrl,\n });\n revalidateUrlPath(postLogoutRedirectUrl);\n return NextResponse.redirect(postLogoutRedirectUrl);\n};\n\nexport async function handleLogoutCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const canAccessCookies = !!(await getIdToken(resolvedConfigs));\n\n // If we have access to cookies, clear them.\n if (canAccessCookies) {\n logger.debug(\n \"handleLogoutCallback can access cookies: clearing auth cookies\",\n );\n await clearAuthCookies();\n return handleLogoutComplete({ request, resolvedConfigs });\n }\n\n logger.debug(\n \"handleLogoutCallback cannot access cookies: generating HTML response with callback\",\n );\n // If we don't have access to cookies, render some javascript to the client that will:\n // 1. make a same-domain fetch call back to this endpoint and receive a '{status: \"success\"}' back.\n // 2. On status: success, set the window.location.href to the post-logout redirect URL (usually the appUrl).\n return generateHtmlResponseWithCallback(\n request,\n // The client-side JS will make a fetch call back to this URL.\n resolvedConfigs.logoutCallbackUrl,\n );\n}\n\n/**\n * Creates an authentication handler for Next.js API routes\n *\n * Usage:\n * ```ts\n * // app/api/auth/[...civicauth]/route.ts\n * import { handler } from '@civic/auth/nextjs'\n * export const GET = handler({\n * // optional config overrides\n * })\n * ```\n */\nexport const handler =\n (authConfig = {}) =>\n async (request: NextRequest): Promise<NextResponse> => {\n const config = resolveAuthConfig(authConfig);\n\n try {\n const pathname = request.nextUrl.pathname;\n const pathSegments = pathname.split(\"/\");\n const lastSegment = pathSegments[pathSegments.length - 1];\n\n switch (lastSegment) {\n case \"challenge\":\n return await handleChallenge(request, config);\n case \"callback\":\n return await handleCallback(request, config);\n case \"refresh\":\n return await handleRefresh(request, config);\n case \"logout\":\n return await handleLogout(request, config);\n case \"logoutcallback\":\n return await handleLogoutCallback(request, config);\n default:\n throw new AuthError(`Invalid auth route: ${pathname}`, 404);\n }\n } catch (error) {\n logger.error(\"Auth handler error:\", error);\n\n const status = error instanceof AuthError ? error.status : 500;\n const message =\n error instanceof Error ? error.message : \"Authentication failed\";\n\n const response = NextResponse.json({ error: message }, { status });\n\n await clearAuthCookies();\n return response;\n }\n };\n"]}
|
|
1
|
+
{"version":3,"file":"routeHandler.js","sourceRoot":"","sources":["../../src/nextjs/routeHandler.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n LOGOUT_SUCCESS_TEXT,\n TOKEN_EXCHANGE_SUCCESS_TEXT,\n TOKEN_EXCHANGE_TRIGGER_TEXT,\n} from \"@/constants.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport {\n displayModeFromState,\n loginSuccessUrlFromState,\n serverTokenExchangeFromState,\n} from \"@/lib/oauth.js\";\nimport type { AuthConfig, AuthConfigWithDefaults } from \"@/nextjs/config.js\";\nimport { resolveAuthConfig } from \"@/nextjs/config.js\";\nimport { clearAuthCookies, NextjsCookieStorage } from \"@/nextjs/cookies.js\";\nimport { getUser } from \"@/nextjs/index.js\";\nimport { resolveCallbackUrl } from \"@/nextjs/utils.js\";\nimport { resolveOAuthAccessCode } from \"@/server/login.js\";\nimport { GenericPublicClientPKCEProducer } from \"@/services/PKCE.js\";\nimport { CodeVerifier, OAuthTokenTypes } from \"@/shared/lib/types.js\";\nimport { GenericUserSession } from \"@/shared/lib/UserSession.js\";\nimport { clearTokens, generateOauthLogoutUrl } from \"@/shared/lib/util.js\";\nimport { revalidatePath } from \"next/cache.js\";\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport { NextServerAuthenticationRefresherImpl } from \"./NextServerAuthenticationRefresherImpl.js\";\n\nconst logger = loggers.nextjs.handlers.auth;\n\nclass AuthError extends Error {\n constructor(\n message: string,\n public readonly status: number = 401,\n ) {\n super(message);\n this.name = \"AuthError\";\n }\n}\n\nconst tryUriDecode = (value: string): string | null => {\n try {\n return decodeURIComponent(value);\n } catch (e) {\n logger.error(\"Error decoding URI component:\", e);\n return value;\n }\n};\n\nconst getDecodedQueryParam = (\n request: NextRequest,\n paramName: string,\n): string | null => {\n const queryParam = request.nextUrl.searchParams.get(paramName);\n if (queryParam) {\n return tryUriDecode(queryParam);\n }\n return null;\n};\n\nconst getCookieOrQueryParam = (\n request: NextRequest,\n cookieName: string,\n queryName: string,\n): string | null => {\n // First check the cookie as it might have the full path with base directory\n const cookieValue = request.cookies.get(cookieName)?.value;\n if (cookieValue) {\n return tryUriDecode(cookieValue);\n }\n\n // Fallback to query parameter\n return getDecodedQueryParam(request, queryName);\n};\n\nconst getAppUrl = (request: NextRequest): string | null =>\n getCookieOrQueryParam(request, CodeVerifier.APP_URL, \"appUrl\");\n\n// The loginSuccessUrl can either be decoded from the state parameter, or passed as a cookie or query parameter\nconst getLoginSuccessUrl = (\n request: NextRequest,\n baseUrl?: string | null,\n): string | null => {\n const loginSuccessUrl =\n loginSuccessUrlFromState(request.nextUrl.searchParams.get(\"state\")) ||\n getDecodedQueryParam(request, \"loginSuccessUrl\");\n if (!loginSuccessUrl) {\n return null;\n }\n return baseUrl ? new URL(loginSuccessUrl, baseUrl).href : loginSuccessUrl;\n};\n\nconst getIdToken = async (config: AuthConfig): Promise<string | null> => {\n const cookieStorage = new NextjsCookieStorage(config.cookies?.tokens ?? {});\n return cookieStorage.get(OAuthTokenTypes.ID_TOKEN);\n};\n\n/**\n * create a code verifier and challenge for PKCE\n * saving the verifier in a cookie for later use\n * @returns {Promise<NextResponse>}\n */\nasync function handleChallenge(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const cookieStorage = new NextjsCookieStorage(config.cookies?.tokens ?? {});\n const pkceProducer = new GenericPublicClientPKCEProducer(cookieStorage);\n\n const challenge = await pkceProducer.getCodeChallenge();\n const appUrl = request.nextUrl.searchParams.get(\"appUrl\");\n if (appUrl) {\n await cookieStorage.set(CodeVerifier.APP_URL, appUrl);\n }\n\n return NextResponse.json({ status: \"success\", challenge });\n}\n\nconst getCookieStorageWithUserOverrides = (config: AuthConfig) => {\n const resolvedConfigs = resolveAuthConfig(config);\n return new NextjsCookieStorage({\n ...resolvedConfigs.cookies.tokens,\n user: resolvedConfigs.cookies.user,\n });\n};\n\nasync function performTokenExchangeAndSetCookies(\n config: AuthConfig,\n code: string,\n state: string,\n appUrl: string,\n) {\n const resolvedConfigs = resolveAuthConfig(config);\n // TODO This is messy, better would be to fix the config.cookies type to always be <name: settings>\n // rather than nesting the tokens-related ones *and* code-verifier inside \"tokens\"\n // (despite code-verifier not relating directly to tokens)\n const cookieStorage = getCookieStorageWithUserOverrides(config);\n\n const callbackUrl = resolveCallbackUrl(resolvedConfigs, appUrl);\n try {\n await resolveOAuthAccessCode(code, state, cookieStorage, {\n ...resolvedConfigs,\n redirectUrl: callbackUrl,\n });\n } catch (error) {\n logger.error(\"Token exchange failed:\", error);\n throw new AuthError(\"Failed to authenticate user\", 401);\n }\n\n const user = await getUser();\n if (!user) {\n throw new AuthError(\"Failed to get user info\", 401);\n }\n const userSession = new GenericUserSession(cookieStorage);\n await userSession.set(user);\n}\n\nasync function handleRefresh(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const cookieStorage = getCookieStorageWithUserOverrides(config);\n const userSession = new GenericUserSession(cookieStorage);\n\n const targetUrl = request.nextUrl.searchParams.get(\"targetUrl\");\n\n try {\n const onError = (error: Error) => {\n logger.error(\"handleRefresh: Token refresh failed:\", error);\n throw new AuthError(\"Failed to refresh tokens\", 500);\n };\n const refresher = await NextServerAuthenticationRefresherImpl.build(\n {\n clientId: resolvedConfigs.clientId,\n oauthServer: resolvedConfigs.oauthServer,\n redirectUrl: resolvedConfigs.callbackUrl,\n refreshUrl: resolvedConfigs.refreshUrl,\n },\n cookieStorage,\n onError,\n );\n\n const tokens = await refresher.refreshAccessToken();\n const user = await getUser();\n if (!user) {\n throw new AuthError(\"Failed to get user info\", 401);\n }\n await userSession.set(user);\n\n logger.debug(\"handleRefresh: Token refresh successful\");\n\n if (targetUrl) {\n // Success: clear the refresh attempt tracking cookie and redirect to target\n const response = NextResponse.redirect(targetUrl);\n response.cookies.delete(\"_civic_last_refresh\");\n return response;\n }\n // For backend flows, tokens might be null since they're managed in HTTP-only cookies\n const response = NextResponse.json({\n status: \"success\",\n tokens: tokens || null,\n });\n return response;\n } catch (error) {\n logger.error(\n \"handleRefresh: Token refresh failed, clearing tokens:\",\n error,\n );\n await clearTokens(cookieStorage);\n await userSession.clear();\n\n if (targetUrl) {\n logger.warn(\"handleRefresh: Refresh failed, redirecting to targetUrl\");\n return NextResponse.redirect(targetUrl);\n }\n\n return NextResponse.json({ status: \"failed\" });\n }\n}\n\nconst generateHtmlResponseWithCallback = (\n request: NextRequest,\n callbackUrl: string,\n loginSuccessUrl?: string,\n) => {\n // we need to replace the URL with resolved config in case the server is hosted\n // behind a reverse proxy or load balancer\n const requestUrl = new URL(request.url);\n const fetchUrl = `${callbackUrl}?${requestUrl.searchParams.toString()}&sameDomainCallback=true`;\n const loginSuccessSegment = loginSuccessUrl\n ? `&loginSuccessUrl=${encodeURIComponent(loginSuccessUrl)}`\n : \"\";\n const html = `<html lang=\"en\">\n <body>\n <span style=\"display:none\">\n <script>\n window.onload = function () {\n // Get the complete URL including origin and path\n // This ensures we capture any base path like /directory\n const appUrl = window.location.href.substring(\n 0,\n window.location.href.indexOf(\"/api/auth\")\n );\n fetch('${fetchUrl}&appUrl=' + encodeURIComponent(appUrl) + '${loginSuccessSegment}').then((response) => {\n response.json().then((jsonResponse) => {\n // For login: Redirect back to the callback route, so Case 2 in handleTokenExchangeComplete will be triggered\n // For logout: Redirect to the postLogoutRedirectUrl\n if(jsonResponse.redirectUrl) {\n window.location.href = jsonResponse.redirectUrl;\n }\n });\n });\n };\n </script>\n </span>\n </body>\n</html>\n`;\n const response = new NextResponse(html);\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n return response;\n};\n\ntype TokkenExchangeCompletInput = {\n request: NextRequest;\n config: AuthConfig;\n loginSuccessUrl?: string | null;\n appUrl?: string | null;\n state: string;\n};\n\nconst handleTokenExchangeComplete = async (\n params: TokkenExchangeCompletInput,\n) => {\n const { request, config, appUrl, loginSuccessUrl, state } = params;\n // Case 1: We are being called via fetch to facilitate access to the cookies. Return success json. The iframe has javascript that will reload this route so Case 2 below will be triggered.\n if (isCalledFromBrowserFetch(request)) {\n logger.debug(\n \"CASE 1: sameDomainCallback=true, returning JSON response with redirect URL\",\n { appUrl, loginSuccessUrl, callbackUrl: config.callbackUrl },\n );\n\n const currentUrl = new URL(request.url);\n // When the client-side JS redirects back here, we don't want to hit this branch again because we can't return JSON from a redirect.\n // So we strip off the sameDomainCallback parameter from the URL.\n const newSearchParams = new URLSearchParams(currentUrl.search);\n newSearchParams.delete(\"sameDomainCallback\");\n\n // We strip off the origin so reverse proxies don't break the redirect.\n const redirectUrl = `${currentUrl.pathname}?${newSearchParams.toString()}${currentUrl.hash}`;\n return NextResponse.json({\n status: \"success\",\n // This makes the iframe redirect back to this route, so Case 2 below will be triggered.\n\n redirectUrl,\n });\n }\n\n // Case 2: We are already authenticated and in iframe mode.\n // Case 2a: We have a custom loginSuccessUrl, so we have to trigger a top-level redirect to it. We do this by rendering a page with the TOKEN_EXCHANGE_SUCCESS_TEXT, which is then picked up by the iframe container.\n // Case 2b: We don't have a custom loginSuccessUrl, so we just redirect to the appUrl. If we don't do this, Cypress tests will fail in the 'no custom loginSuccessUrl' case, because in Cypress an iframe redirect is converted to a top-level redirect,\n // which means the iframe container no longer exists and so can't action the redirect.\n const user = await getUser();\n if (!!user && displayModeFromState(state, \"iframe\") === \"iframe\") {\n if (loginSuccessUrl) {\n logger.debug(\n \"CASE 2a: iframe mode with loginSuccessUrl configured. Returning TOKEN_EXCHANGE_SUCCESS_TEXT to trigger redirect to loginSuccessUrl if any\",\n { loginSuccessUrl },\n );\n const response = new NextResponse(\n `<html lang=\"en\"><span style=\"display:none\">${TOKEN_EXCHANGE_SUCCESS_TEXT}</span></html>`,\n );\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n return response;\n } else {\n logger.debug(\n \"CASE 2b: iframe mode with no loginSuccessUrl configured. Doing a normal redirect without relying on the iframe container to redirect.\",\n );\n return NextResponse.redirect(`${appUrl}`);\n }\n }\n\n // CASE 3: We're not in iframe mode. We can just do a stright http redirect to the final destination, which is either the loginSuccessUrl if specified, or the appUrl.\n logger.debug(\"CASE 3: non-iframe mode, redirecting to loginSuccessUrl\");\n return NextResponse.redirect(`${loginSuccessUrl || appUrl}`);\n};\n\nasync function handleCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const code = request.nextUrl.searchParams.get(\"code\");\n const state = request.nextUrl.searchParams.get(\"state\");\n\n if (!code || !state) throw new AuthError(\"Bad parameters\", 400);\n\n // appUrl is passed from the client to the server in the query string\n // this is necessary because the server does not have access to the client's window.location.origin\n // and can not accurately determine the appUrl (specially if the app is behind a reverse proxy)\n const appUrl = getAppUrl(request);\n\n // If the integrator has specified a loginSuccessUrl, we'll send the user there after the login completes (including token exchange)\n // We pass in the basePath from config to use as the baseUrl, because we might not have access to the app_url cookie at this point if this was a third-party redirect.\n const loginSuccessUrl = getLoginSuccessUrl(request, appUrl);\n\n const tokenExchangeCompleteParams = {\n request,\n config,\n appUrl,\n state,\n loginSuccessUrl,\n };\n\n const user = await getUser();\n if (user) {\n // User already authenticated.\n return handleTokenExchangeComplete(tokenExchangeCompleteParams);\n }\n\n // User not authenticated yet.\n\n // If we have a code_verifier cookie and the appUrl, we can do a token exchange.\n // Otherwise, just render an empty page.\n // The initial redirect back from the auth server does not send cookies, because the redirect is from a 3rd-party domain.\n // The client will make an additional call to this route with cookies included, at which point we do the token exchange.\n const codeVerifier = request.cookies.get(CodeVerifier.COOKIE_NAME);\n\n if (!codeVerifier || !appUrl) {\n logger.debug(\"handleCallback no code_verifier found\", {\n state,\n serverTokenExchange: serverTokenExchangeFromState(`${state}`),\n });\n let response = new NextResponse(\n `<html lang=\"en\"><body><span style=\"display:none\">${TOKEN_EXCHANGE_TRIGGER_TEXT}</span></body></html>`,\n );\n\n // in server-side token exchange mode we need to launch a page that will trigger the token exchange\n // from the same domain, allowing it access to the code_verifier cookie\n // we only need to do this in redirect mode, as the iframe already triggers a client-side token exchange\n // if no code-verifier cookie is found\n if (state && serverTokenExchangeFromState(state)) {\n logger.debug(\n \"handleCallback serverTokenExchangeFromState, launching redirect page...\",\n {\n requestUrl: request.url,\n configCallbackUrl: resolvedConfigs.callbackUrl,\n },\n );\n // generate a page that will callback to the same domain, allowing access\n // to the code_verifier cookie and passing the appUrl.\n response = generateHtmlResponseWithCallback(\n request,\n resolvedConfigs.callbackUrl,\n loginSuccessUrl || undefined,\n );\n }\n logger.debug(\n `handleCallback no code_verifier found, returning ${TOKEN_EXCHANGE_TRIGGER_TEXT}`,\n );\n return response;\n }\n\n await performTokenExchangeAndSetCookies(resolvedConfigs, code, state, appUrl);\n return handleTokenExchangeComplete(tokenExchangeCompleteParams);\n}\n\n/**\n * If redirectPath is an absolute path, return it as-is.\n * Otherwise for relative paths, append it to the current domain.\n * @param redirectPath\n * @param currentBasePath\n * @returns\n */\nconst getAbsoluteRedirectPath = (\n redirectPath: string,\n currentBasePath: string,\n) => new URL(redirectPath, currentBasePath).href;\n\nconst getPostLogoutRedirectUrl = (\n request: NextRequest,\n config: AuthConfig,\n): string => {\n const { loginUrl } = resolveAuthConfig(config);\n // if we have a target URL in the request, it's come from civic middleware\n // and we should use it as the redirect target.\n const targetUrl = request.nextUrl.searchParams.get(\"targetUrl\");\n if (targetUrl) {\n // If a targetUrl is provided, use it as the redirect target.\n // This is useful for redirecting to a specific page after logout.\n return targetUrl;\n }\n const redirectTarget = loginUrl ?? \"/\";\n\n // if the optional loginUrl is provided and it is an absolute URL,\n // use it as the redirect target\n const isAbsoluteRedirect = /^(https?:\\/\\/|www\\.).+/i.test(redirectTarget);\n if (isAbsoluteRedirect) {\n return redirectTarget;\n }\n\n // if loginUrl is not defined, the appUrl is passed from the client to the server\n // in the query string or cookies. This is necessary because the server does not\n // have access to the client's window.location and can not accurately determine\n // the appUrl (specially if the app is behind a reverse proxy).\n const appUrl = getAppUrl(request);\n if (appUrl) return getAbsoluteRedirectPath(redirectTarget, appUrl);\n\n // If we can't determine the post-logout redirect URL, fallback to the app root as it's the most likely location of the login page.\n return request.nextUrl.origin;\n};\n\nconst revalidateUrlPath = async (url: string) => {\n try {\n const path = new URL(url).pathname;\n revalidatePath(path);\n } catch (error) {\n logger.warn(\"Failed to revalidate path after logout:\", error);\n }\n};\n\nexport async function handleLogout(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n\n // Ensure we have the proper app URL including any base path\n const appBaseUrl = getAppUrl(request) || request.url;\n\n // Construct the post-logout URL with the base path included\n const postLogoutUrl = new URL(resolvedConfigs.logoutCallbackUrl, appBaseUrl);\n\n // read the id_token from the cookies\n const idToken = await getIdToken(resolvedConfigs);\n\n // read the state from the query parameters\n const state = request.nextUrl.searchParams.get(\"state\");\n\n if (!state || !idToken) {\n logger.error(\"handleLogout: missing state or idToken\", {\n hasState: !!state,\n hasIdToken: !!idToken,\n });\n await clearAuthCookies();\n // if token or state is missing, the logout call to the server will fail,\n // (token has potentially expired already) so go straight to the postLogoutUrl\n // so the user can be signed out.\n return NextResponse.redirect(\n `${postLogoutUrl}${state ? \"?state=\" + state : \"\"}`,\n );\n }\n\n const displayMode = displayModeFromState(state, \"iframe\");\n if (displayMode === \"iframe\") {\n // clear auth cookies immediately before calling the logout endpoint to give faster UX\n await clearAuthCookies();\n await revalidateUrlPath(request.url);\n }\n\n const logoutUrl = await generateOauthLogoutUrl({\n clientId: resolvedConfigs.clientId,\n idToken,\n state,\n redirectUrl: postLogoutUrl.href,\n oauthServer: resolvedConfigs.oauthServer,\n });\n return NextResponse.redirect(`${logoutUrl.href}`);\n}\n\nconst isCalledFromBrowserFetch = (request: NextRequest) =>\n request.url.includes(\"sameDomainCallback=true\");\n\n/**\n * Called after the cookies have been cleared.\n */\ntype LogoutCompleteInputs = {\n request: NextRequest;\n resolvedConfigs: AuthConfigWithDefaults;\n};\nconst handleLogoutComplete = async (params: LogoutCompleteInputs) => {\n const { request, resolvedConfigs } = params;\n const state = request.nextUrl.searchParams.get(\"state\");\n const postLogoutRedirectUrl = getPostLogoutRedirectUrl(\n request,\n resolvedConfigs,\n );\n\n // If this is a FETCH call, we can only return json. Trying to redirect or return HTML will fail.\n if (isCalledFromBrowserFetch(request)) {\n logger.debug(\n \"handleLogoutComplete: sameDomainCallback=true, returning JSON response with redirect URL\",\n { postLogoutRedirectUrl },\n );\n // The client-side JS will do a window.location.href redirect to postLogoutRedirectUrl when this request returns success.\n return NextResponse.json({\n status: \"success\",\n redirectUrl: postLogoutRedirectUrl,\n });\n }\n\n // If this is a redirect inside an iframe and the user is indeed logged out, render some text that makes the parent redirect to the postLogoutRedirectUrl.\n const user = await getUser();\n if (!user && !!state && displayModeFromState(state, \"iframe\") === \"iframe\") {\n // User is logged out while in an iframe redirect (not a FETCH call).\n // Render some text to make the CivicLogoutIframeContainer redirect to the postLogoutRedirectUrl.\n const response = new NextResponse(\n `<html lang=\"en\"><span style=\"display:none\">${LOGOUT_SUCCESS_TEXT}<a href=\"${[postLogoutRedirectUrl]}\" rel=\"civic-auth-post-logout-redirect-url\"></a></span></html>`,\n );\n response.headers.set(\"Content-Type\", \"text/html; charset=utf-8\");\n logger.debug(\n \"handleLogoutComplete: iframe mode, rendering HTML with logout success text\",\n { postLogoutRedirectUrl },\n );\n return response;\n }\n\n logger.debug(\"handleLogoutComplete: redirecting to postLogoutRedirectUrl\", {\n postLogoutRedirectUrl,\n });\n revalidateUrlPath(postLogoutRedirectUrl);\n return NextResponse.redirect(postLogoutRedirectUrl);\n};\n\nexport async function handleLogoutCallback(\n request: NextRequest,\n config: AuthConfig,\n): Promise<NextResponse> {\n const resolvedConfigs = resolveAuthConfig(config);\n const canAccessCookies = !!(await getIdToken(resolvedConfigs));\n\n // If we have access to cookies, clear them.\n if (canAccessCookies) {\n logger.debug(\n \"handleLogoutCallback can access cookies: clearing auth cookies\",\n );\n await clearAuthCookies();\n return handleLogoutComplete({ request, resolvedConfigs });\n }\n\n logger.debug(\n \"handleLogoutCallback cannot access cookies: generating HTML response with callback\",\n );\n // If we don't have access to cookies, render some javascript to the client that will:\n // 1. make a same-domain fetch call back to this endpoint and receive a '{status: \"success\"}' back.\n // 2. On status: success, set the window.location.href to the post-logout redirect URL (usually the appUrl).\n return generateHtmlResponseWithCallback(\n request,\n // The client-side JS will make a fetch call back to this URL.\n resolvedConfigs.logoutCallbackUrl,\n );\n}\n\n/**\n * Creates an authentication handler for Next.js API routes\n *\n * Usage:\n * ```ts\n * // app/api/auth/[...civicauth]/route.ts\n * import { handler } from '@civic/auth/nextjs'\n * export const GET = handler({\n * // optional config overrides\n * })\n * ```\n */\nexport const handler =\n (authConfig = {}) =>\n async (request: NextRequest): Promise<NextResponse> => {\n const config = resolveAuthConfig(authConfig);\n\n try {\n const pathname = request.nextUrl.pathname;\n const pathSegments = pathname.split(\"/\");\n const lastSegment = pathSegments[pathSegments.length - 1];\n\n switch (lastSegment) {\n case \"challenge\":\n return await handleChallenge(request, config);\n case \"callback\":\n return await handleCallback(request, config);\n case \"refresh\":\n return await handleRefresh(request, config);\n case \"logout\":\n return await handleLogout(request, config);\n case \"logoutcallback\":\n return await handleLogoutCallback(request, config);\n default:\n throw new AuthError(`Invalid auth route: ${pathname}`, 404);\n }\n } catch (error) {\n logger.error(\"Auth handler error:\", error);\n\n const status = error instanceof AuthError ? error.status : 500;\n const message =\n error instanceof Error ? error.message : \"Authentication failed\";\n\n const response = NextResponse.json({ error: message }, { status });\n\n await clearAuthCookies();\n return response;\n }\n };\n"]}
|
package/dist/nextjs/utils.d.ts
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import type { AuthConfigWithDefaults } from "../nextjs/config.js";
|
|
2
|
+
import type { NextRequest } from "next/server.js";
|
|
3
|
+
import { NextResponse } from "next/server.js";
|
|
4
|
+
import type { SessionData } from "../types.js";
|
|
2
5
|
export declare const resolveCallbackUrl: (config: AuthConfigWithDefaults, baseUrl?: string) => string;
|
|
3
6
|
export declare function sanitizeBasePath(path: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Determines if we should attempt token refresh based on session state
|
|
9
|
+
*/
|
|
10
|
+
export declare const shouldAttemptRefresh: (session: SessionData) => boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Creates a redirect response to the refresh endpoint
|
|
13
|
+
*/
|
|
14
|
+
export declare const createRefreshResponse: (request: NextRequest, authConfig: AuthConfigWithDefaults) => NextResponse | null;
|
|
15
|
+
/**
|
|
16
|
+
* Checks if the current path is a system URL that should skip auth
|
|
17
|
+
*/
|
|
18
|
+
export declare const shouldSkipAuthForSystemUrls: (pathname: string, authConfig: AuthConfigWithDefaults, method: string) => boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Handles authentication logic specifically for the login URL
|
|
21
|
+
* Provides logging for login URL access patterns
|
|
22
|
+
*/
|
|
23
|
+
export declare const handleLoginUrl: (pathname: string, session: SessionData, authConfig: AuthConfigWithDefaults) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Checks if the current path should skip auth based on include/exclude patterns
|
|
26
|
+
*/
|
|
27
|
+
export declare const shouldSkipAuthForRoutePatterns: (pathname: string, authConfig: AuthConfigWithDefaults) => boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Handles final authentication logic for unauthenticated users on protected routes
|
|
30
|
+
*/
|
|
31
|
+
export declare const handleUnauthenticatedUser: (session: SessionData, request: NextRequest, authConfig: AuthConfigWithDefaults) => NextResponse;
|
|
4
32
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/nextjs/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/nextjs/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM9C,eAAO,MAAM,kBAAkB,WACrB,sBAAsB,YACpB,MAAM,KACf,MAGF,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQrD;AA+BD;;GAEG;AACH,eAAO,MAAM,oBAAoB,YAAa,WAAW,KAAG,OAE3D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,YACvB,WAAW,cACR,sBAAsB,KACjC,YAAY,GAAG,IAmBjB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,2BAA2B,aAC5B,MAAM,cACJ,sBAAsB,UAC1B,MAAM,KACb,OAkBF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,cAAc,aACf,MAAM,WACP,WAAW,cACR,sBAAsB,KACjC,IAWF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,8BAA8B,aAC/B,MAAM,cACJ,sBAAsB,KACjC,OAiBF,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,yBAAyB,YAC3B,WAAW,WACX,WAAW,cACR,sBAAsB,KACjC,YA2BF,CAAC"}
|
package/dist/nextjs/utils.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { NextResponse } from "next/server.js";
|
|
2
|
+
import { loggers } from "../lib/logger.js";
|
|
3
|
+
import picomatch from "picomatch";
|
|
4
|
+
const logger = loggers.nextjs.middleware;
|
|
1
5
|
export const resolveCallbackUrl = (config, baseUrl) => {
|
|
2
6
|
const callbackUrl = new URL(config?.callbackUrl, baseUrl).toString();
|
|
3
7
|
return callbackUrl.toString();
|
|
@@ -10,4 +14,113 @@ export function sanitizeBasePath(path) {
|
|
|
10
14
|
// Remove all trailing slashes (not just one)
|
|
11
15
|
return withLeadingSlash.replace(/\/+$/, "");
|
|
12
16
|
}
|
|
17
|
+
const getOriginUrl = (request, authConfig) => {
|
|
18
|
+
// Use configured baseUrl if provided (for reverse proxy scenarios)
|
|
19
|
+
if (authConfig.baseUrl) {
|
|
20
|
+
return authConfig.baseUrl;
|
|
21
|
+
}
|
|
22
|
+
// Fallback to nextUrl.origin (includes port automatically)
|
|
23
|
+
return request.nextUrl.origin;
|
|
24
|
+
};
|
|
25
|
+
// Matches globs:
|
|
26
|
+
// Examples:
|
|
27
|
+
// /user
|
|
28
|
+
// /user/*
|
|
29
|
+
// /user/**/info
|
|
30
|
+
const matchGlob = (pathname, globPattern) => {
|
|
31
|
+
const matches = picomatch(globPattern);
|
|
32
|
+
return matches(pathname);
|
|
33
|
+
};
|
|
34
|
+
const matchesGlobs = (pathname, patterns) => patterns.some((pattern) => {
|
|
35
|
+
if (!pattern)
|
|
36
|
+
return false;
|
|
37
|
+
return matchGlob(pathname, pattern);
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Determines if we should attempt token refresh based on session state
|
|
41
|
+
*/
|
|
42
|
+
export const shouldAttemptRefresh = (session) => {
|
|
43
|
+
return !session.authenticated && !!session.refreshToken;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Creates a redirect response to the refresh endpoint
|
|
47
|
+
*/
|
|
48
|
+
export const createRefreshResponse = (request, authConfig) => {
|
|
49
|
+
const targetUrl = getOriginUrl(request, authConfig) + request.nextUrl.pathname;
|
|
50
|
+
// Check if we're already in a redirect flow
|
|
51
|
+
if (request.nextUrl.searchParams.has("targetUrl")) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
// Create refresh redirect
|
|
55
|
+
const refreshUrl = new URL(authConfig.refreshUrl, getOriginUrl(request, authConfig));
|
|
56
|
+
refreshUrl.searchParams.set("targetUrl", targetUrl);
|
|
57
|
+
logger.debug("→ Refresh token found - redirecting to refresh endpoint");
|
|
58
|
+
return NextResponse.redirect(refreshUrl.toString());
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Checks if the current path is a system URL that should skip auth
|
|
62
|
+
*/
|
|
63
|
+
export const shouldSkipAuthForSystemUrls = (pathname, authConfig, method) => {
|
|
64
|
+
const systemUrls = [
|
|
65
|
+
authConfig.callbackUrl,
|
|
66
|
+
authConfig.challengeUrl,
|
|
67
|
+
authConfig.logoutCallbackUrl,
|
|
68
|
+
authConfig.logoutUrl,
|
|
69
|
+
];
|
|
70
|
+
const isSystemUrl = systemUrls.includes(pathname) && method === "GET";
|
|
71
|
+
if (isSystemUrl) {
|
|
72
|
+
logger.debug("→ Skipping auth check - this a URL defined in authConfig", pathname);
|
|
73
|
+
}
|
|
74
|
+
return isSystemUrl;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Handles authentication logic specifically for the login URL
|
|
78
|
+
* Provides logging for login URL access patterns
|
|
79
|
+
*/
|
|
80
|
+
export const handleLoginUrl = (pathname, session, authConfig) => {
|
|
81
|
+
if (pathname !== authConfig.loginUrl) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// We are on the login URL - log the access pattern
|
|
85
|
+
if (session.authenticated) {
|
|
86
|
+
logger.debug(`→ Authenticated user accessing login page`);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
logger.debug(`→ Unauthenticated user accessing login page`);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Checks if the current path should skip auth based on include/exclude patterns
|
|
94
|
+
*/
|
|
95
|
+
export const shouldSkipAuthForRoutePatterns = (pathname, authConfig) => {
|
|
96
|
+
// Check include patterns
|
|
97
|
+
if (!matchesGlobs(pathname, authConfig.include)) {
|
|
98
|
+
logger.debug("→ Skipping auth check - path not in include patterns", pathname);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
// Check exclude patterns
|
|
102
|
+
if (matchesGlobs(pathname, authConfig.exclude)) {
|
|
103
|
+
logger.debug("→ Skipping auth check - path in exclude patterns", pathname);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Handles final authentication logic for unauthenticated users on protected routes
|
|
110
|
+
*/
|
|
111
|
+
export const handleUnauthenticatedUser = (session, request, authConfig) => {
|
|
112
|
+
const targetUrl = getOriginUrl(request, authConfig) + request.nextUrl.pathname;
|
|
113
|
+
// Clear expired/invalid tokens if they exist without refresh token
|
|
114
|
+
if (session.accessToken || session.idToken) {
|
|
115
|
+
const clearBadTokensUrl = new URL(authConfig.logoutCallbackUrl, getOriginUrl(request, authConfig));
|
|
116
|
+
clearBadTokensUrl.searchParams.set("targetUrl", targetUrl);
|
|
117
|
+
logger.debug(`→ Invalid tokens found without refresh - clearing via "${clearBadTokensUrl}"`);
|
|
118
|
+
return NextResponse.redirect(clearBadTokensUrl.toString());
|
|
119
|
+
}
|
|
120
|
+
// Final fallback: redirect to login
|
|
121
|
+
const loginUrl = new URL(authConfig.loginUrl, getOriginUrl(request, authConfig));
|
|
122
|
+
const redirectUrl = `${loginUrl.toString()}`;
|
|
123
|
+
logger.debug(`→ No valid tokens found - redirecting to login "${redirectUrl}"`);
|
|
124
|
+
return NextResponse.redirect(redirectUrl);
|
|
125
|
+
};
|
|
13
126
|
//# sourceMappingURL=utils.js.map
|
package/dist/nextjs/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/nextjs/utils.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,MAA8B,EAC9B,OAAgB,EACR,EAAE;IACV,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrE,OAAO,WAAW,CAAC,QAAQ,EAAE,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC;IAErC,gCAAgC;IAChC,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IAElE,6CAA6C;IAC7C,OAAO,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC","sourcesContent":["import type { AuthConfigWithDefaults } from \"@/nextjs/config.js\";\n\nexport const resolveCallbackUrl = (\n config: AuthConfigWithDefaults,\n baseUrl?: string,\n): string => {\n const callbackUrl = new URL(config?.callbackUrl, baseUrl).toString();\n return callbackUrl.toString();\n};\n\nexport function sanitizeBasePath(path: string): string {\n if (!path || path === \"/\") return \"\";\n\n // Ensure it starts with a slash\n const withLeadingSlash = path.startsWith(\"/\") ? path : `/${path}`;\n\n // Remove all trailing slashes (not just one)\n return withLeadingSlash.replace(/\\/+$/, \"\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/nextjs/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AuthConfigWithDefaults } from \"@/nextjs/config.js\";\nimport type { NextRequest } from \"next/server.js\";\nimport { NextResponse } from \"next/server.js\";\nimport type { SessionData } from \"@/types.js\";\nimport { loggers } from \"@/lib/logger.js\";\nimport picomatch from \"picomatch\";\n\nconst logger = loggers.nextjs.middleware;\n\nexport const resolveCallbackUrl = (\n config: AuthConfigWithDefaults,\n baseUrl?: string,\n): string => {\n const callbackUrl = new URL(config?.callbackUrl, baseUrl).toString();\n return callbackUrl.toString();\n};\n\nexport function sanitizeBasePath(path: string): string {\n if (!path || path === \"/\") return \"\";\n\n // Ensure it starts with a slash\n const withLeadingSlash = path.startsWith(\"/\") ? path : `/${path}`;\n\n // Remove all trailing slashes (not just one)\n return withLeadingSlash.replace(/\\/+$/, \"\");\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// 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\nconst matchesGlobs = (pathname: string, patterns: string[]) =>\n patterns.some((pattern) => {\n if (!pattern) return false;\n return matchGlob(pathname, pattern);\n });\n\n/**\n * Determines if we should attempt token refresh based on session state\n */\nexport const shouldAttemptRefresh = (session: SessionData): boolean => {\n return !session.authenticated && !!session.refreshToken;\n};\n\n/**\n * Creates a redirect response to the refresh endpoint\n */\nexport const createRefreshResponse = (\n request: NextRequest,\n authConfig: AuthConfigWithDefaults,\n): NextResponse | null => {\n const targetUrl =\n getOriginUrl(request, authConfig) + request.nextUrl.pathname;\n\n // Check if we're already in a redirect flow\n if (request.nextUrl.searchParams.has(\"targetUrl\")) {\n return null;\n }\n\n // Create refresh redirect\n const refreshUrl = new URL(\n authConfig.refreshUrl,\n getOriginUrl(request, authConfig),\n );\n refreshUrl.searchParams.set(\"targetUrl\", targetUrl);\n\n logger.debug(\"→ Refresh token found - redirecting to refresh endpoint\");\n\n return NextResponse.redirect(refreshUrl.toString());\n};\n\n/**\n * Checks if the current path is a system URL that should skip auth\n */\nexport const shouldSkipAuthForSystemUrls = (\n pathname: string,\n authConfig: AuthConfigWithDefaults,\n method: string,\n): boolean => {\n const systemUrls = [\n authConfig.callbackUrl,\n authConfig.challengeUrl,\n authConfig.logoutCallbackUrl,\n authConfig.logoutUrl,\n ];\n\n const isSystemUrl = systemUrls.includes(pathname) && method === \"GET\";\n\n if (isSystemUrl) {\n logger.debug(\n \"→ Skipping auth check - this a URL defined in authConfig\",\n pathname,\n );\n }\n\n return isSystemUrl;\n};\n\n/**\n * Handles authentication logic specifically for the login URL\n * Provides logging for login URL access patterns\n */\nexport const handleLoginUrl = (\n pathname: string,\n session: SessionData,\n authConfig: AuthConfigWithDefaults,\n): void => {\n if (pathname !== authConfig.loginUrl) {\n return;\n }\n\n // We are on the login URL - log the access pattern\n if (session.authenticated) {\n logger.debug(`→ Authenticated user accessing login page`);\n } else {\n logger.debug(`→ Unauthenticated user accessing login page`);\n }\n};\n\n/**\n * Checks if the current path should skip auth based on include/exclude patterns\n */\nexport const shouldSkipAuthForRoutePatterns = (\n pathname: string,\n authConfig: AuthConfigWithDefaults,\n): boolean => {\n // Check include patterns\n if (!matchesGlobs(pathname, authConfig.include)) {\n logger.debug(\n \"→ Skipping auth check - path not in include patterns\",\n pathname,\n );\n return true;\n }\n\n // Check exclude patterns\n if (matchesGlobs(pathname, authConfig.exclude)) {\n logger.debug(\"→ Skipping auth check - path in exclude patterns\", pathname);\n return true;\n }\n\n return false;\n};\n\n/**\n * Handles final authentication logic for unauthenticated users on protected routes\n */\nexport const handleUnauthenticatedUser = (\n session: SessionData,\n request: NextRequest,\n authConfig: AuthConfigWithDefaults,\n): NextResponse => {\n const targetUrl =\n getOriginUrl(request, authConfig) + request.nextUrl.pathname;\n\n // Clear expired/invalid tokens if they exist without refresh token\n if (session.accessToken || session.idToken) {\n const clearBadTokensUrl = new URL(\n authConfig.logoutCallbackUrl,\n getOriginUrl(request, authConfig),\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.toString());\n }\n\n // Final fallback: redirect to login\n const loginUrl = new URL(\n authConfig.loginUrl,\n getOriginUrl(request, authConfig),\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"]}
|
|
@@ -3,7 +3,8 @@ import type { DisplayMode } from "../../types.js";
|
|
|
3
3
|
import type { LoggingConfig } from "../../vanillajs/auth/types/AuthTypes.js";
|
|
4
4
|
export interface CivicAuthProviderProps {
|
|
5
5
|
children: React.ReactNode;
|
|
6
|
-
clientId
|
|
6
|
+
clientId?: string;
|
|
7
|
+
loginUrl?: string;
|
|
7
8
|
redirectUrl?: string;
|
|
8
9
|
config?: {
|
|
9
10
|
oauthServer?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CivicAuthProvider.d.ts","sourceRoot":"","sources":["../../../src/reactjs/providers/CivicAuthProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"CivicAuthProvider.d.ts","sourceRoot":"","sources":["../../../src/reactjs/providers/CivicAuthProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAoB,MAAM,OAAO,CAAC;AAKzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAEzE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,QAAA,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAwEvD,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { Fragment as _Fragment, jsx as _jsx } from "@emotion/react/jsx-runtime";
|
|
3
3
|
import React, { useEffect } from "react";
|
|
4
4
|
import { GlobalAuthManager, } from "../core/GlobalAuthManager.js";
|
|
5
|
-
const CivicAuthProvider = ({ children, clientId, redirectUrl, config, scopes, displayMode = "iframe", iframeMode = "modal", onSignIn, onSignOut, nonce, logoutRedirectUrl, authProcessTimeout, preloadIframe, logging, autoRedirect = true, }) => {
|
|
5
|
+
const CivicAuthProvider = ({ children, clientId, loginUrl, redirectUrl, config, scopes, displayMode = "iframe", iframeMode = "modal", onSignIn, onSignOut, nonce, logoutRedirectUrl, authProcessTimeout, preloadIframe, logging, autoRedirect = true, }) => {
|
|
6
6
|
// Initialize GlobalAuthManager with the provider config
|
|
7
7
|
useEffect(() => {
|
|
8
8
|
const globalConfig = {
|
|
9
9
|
clientId,
|
|
10
|
+
loginUrl,
|
|
10
11
|
redirectUrl,
|
|
11
12
|
config: {
|
|
12
13
|
oauthServer: config?.oauthServer,
|
|
@@ -36,6 +37,7 @@ const CivicAuthProvider = ({ children, clientId, redirectUrl, config, scopes, di
|
|
|
36
37
|
initializeAuth();
|
|
37
38
|
}, [
|
|
38
39
|
clientId,
|
|
40
|
+
loginUrl,
|
|
39
41
|
redirectUrl,
|
|
40
42
|
config?.oauthServer,
|
|
41
43
|
scopes,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CivicAuthProvider.js","sourceRoot":"","sources":["../../../src/reactjs/providers/CivicAuthProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EACL,iBAAiB,GAElB,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"CivicAuthProvider.js","sourceRoot":"","sources":["../../../src/reactjs/providers/CivicAuthProvider.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EACL,iBAAiB,GAElB,MAAM,8BAA8B,CAAC;AAyBtC,MAAM,iBAAiB,GAAqC,CAAC,EAC3D,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,MAAM,EACN,MAAM,EACN,WAAW,GAAG,QAAQ,EACtB,UAAU,GAAG,OAAO,EACpB,QAAQ,EACR,SAAS,EACT,KAAK,EACL,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,OAAO,EACP,YAAY,GAAG,IAAI,GACpB,EAAE,EAAE;IACH,wDAAwD;IACxD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,YAAY,GAAqB;YACrC,QAAQ;YACR,QAAQ;YACR,WAAW;YACX,MAAM,EAAE;gBACN,WAAW,EAAE,MAAM,EAAE,WAAW;aACjC;YACD,MAAM;YACN,WAAW;YACX,UAAU;YACV,KAAK;YACL,iBAAiB;YACjB,kBAAkB;YAClB,aAAa;YACb,QAAQ;YACR,SAAS;YACT,OAAO;YACP,YAAY;YACZ,SAAS,EAAE,SAAS;SACrB,CAAC;QAEF,MAAM,OAAO,GAAG,iBAAiB,CAAC,WAAW,EAAE,CAAC;QAEhD,MAAM,cAAc,GAAG,KAAK,IAAI,EAAE;YAChC,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,iDAAiD,EAAE,KAAK,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC,CAAC;QAEF,cAAc,EAAE,CAAC;IACnB,CAAC,EAAE;QACD,QAAQ;QACR,QAAQ;QACR,WAAW;QACX,MAAM,EAAE,WAAW;QACnB,MAAM;QACN,WAAW;QACX,UAAU;QACV,KAAK;QACL,iBAAiB;QACjB,kBAAkB;QAClB,aAAa;QACb,QAAQ;QACR,SAAS;QACT,OAAO;QACP,YAAY;KACb,CAAC,CAAC;IAEH,oDAAoD;IACpD,OAAO,4BAAG,QAAQ,GAAI,CAAC;AACzB,CAAC,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["\"use client\";\nimport React, { useEffect } from \"react\";\nimport {\n GlobalAuthManager,\n type GlobalAuthConfig,\n} from \"../core/GlobalAuthManager.js\";\nimport type { DisplayMode } from \"@/types.js\";\nimport type { LoggingConfig } from \"@/vanillajs/auth/types/AuthTypes.js\";\n\nexport interface CivicAuthProviderProps {\n children: React.ReactNode;\n clientId?: string;\n loginUrl?: string;\n redirectUrl?: string;\n config?: {\n oauthServer?: string;\n };\n scopes?: string[];\n displayMode?: DisplayMode;\n iframeMode?: \"modal\" | \"embedded\";\n onSignIn?: (error?: Error) => void;\n onSignOut?: () => void;\n nonce?: string;\n logoutRedirectUrl?: string;\n authProcessTimeout?: number;\n preloadIframe?: boolean;\n logging?: LoggingConfig;\n autoRedirect?: boolean;\n}\n\nconst CivicAuthProvider: React.FC<CivicAuthProviderProps> = ({\n children,\n clientId,\n loginUrl,\n redirectUrl,\n config,\n scopes,\n displayMode = \"iframe\",\n iframeMode = \"modal\",\n onSignIn,\n onSignOut,\n nonce,\n logoutRedirectUrl,\n authProcessTimeout,\n preloadIframe,\n logging,\n autoRedirect = true,\n}) => {\n // Initialize GlobalAuthManager with the provider config\n useEffect(() => {\n const globalConfig: GlobalAuthConfig = {\n clientId,\n loginUrl,\n redirectUrl,\n config: {\n oauthServer: config?.oauthServer,\n },\n scopes,\n displayMode,\n iframeMode,\n nonce,\n logoutRedirectUrl,\n authProcessTimeout,\n preloadIframe,\n onSignIn,\n onSignOut,\n logging,\n autoRedirect,\n framework: \"reactjs\",\n };\n\n const manager = GlobalAuthManager.getInstance();\n\n const initializeAuth = async () => {\n try {\n await manager.initialize(globalConfig);\n } catch (error) {\n console.error(\"Failed to initialize auth in CivicAuthProvider:\", error);\n }\n };\n\n initializeAuth();\n }, [\n clientId,\n loginUrl,\n redirectUrl,\n config?.oauthServer,\n scopes,\n displayMode,\n iframeMode,\n nonce,\n logoutRedirectUrl,\n authProcessTimeout,\n preloadIframe,\n onSignIn,\n onSignOut,\n logging,\n autoRedirect,\n ]);\n\n // Just render children - no context provider needed\n return <>{children}</>;\n};\n\nexport { CivicAuthProvider };\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.6-beta.1";
|
|
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.6-beta.1\";\n"]}
|