@civic/auth 0.9.5 → 0.9.6-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -1
- package/dist/nextjs/actions.d.ts +12 -0
- package/dist/nextjs/actions.d.ts.map +1 -0
- package/dist/nextjs/actions.js +26 -0
- package/dist/nextjs/actions.js.map +1 -0
- package/dist/nextjs/config.d.ts +2 -0
- package/dist/nextjs/config.d.ts.map +1 -1
- package/dist/nextjs/config.js +3 -2
- package/dist/nextjs/config.js.map +1 -1
- package/dist/nextjs/cookies.d.ts.map +1 -1
- package/dist/nextjs/cookies.js +45 -3
- package/dist/nextjs/cookies.js.map +1 -1
- package/dist/nextjs/hooks/useInitialAuthConfig.d.ts +31 -0
- package/dist/nextjs/hooks/useInitialAuthConfig.d.ts.map +1 -0
- package/dist/nextjs/hooks/useInitialAuthConfig.js +109 -0
- package/dist/nextjs/hooks/useInitialAuthConfig.js.map +1 -0
- package/dist/nextjs/index.d.ts +1 -0
- package/dist/nextjs/index.d.ts.map +1 -1
- package/dist/nextjs/index.js +13 -3
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.d.ts +6 -7
- package/dist/nextjs/providers/NextAuthProvider.d.ts.map +1 -1
- package/dist/nextjs/providers/NextAuthProvider.js +19 -138
- package/dist/nextjs/providers/NextAuthProvider.js.map +1 -1
- package/dist/nextjs/providers/NextAuthProviderClient.d.ts +11 -0
- package/dist/nextjs/providers/NextAuthProviderClient.d.ts.map +1 -0
- package/dist/nextjs/providers/NextAuthProviderClient.js +62 -0
- package/dist/nextjs/providers/NextAuthProviderClient.js.map +1 -0
- package/dist/nextjs/providers/ServerUserContext.d.ts +2 -0
- package/dist/nextjs/providers/ServerUserContext.d.ts.map +1 -0
- package/dist/nextjs/providers/ServerUserContext.js +5 -0
- package/dist/nextjs/providers/ServerUserContext.js.map +1 -0
- package/dist/nextjs/routeHandler.d.ts.map +1 -1
- package/dist/nextjs/routeHandler.js +241 -352
- package/dist/nextjs/routeHandler.js.map +1 -1
- package/dist/react-router-7/components/UserButton.js +1 -1
- package/dist/react-router-7/components/UserButton.js.map +1 -1
- package/dist/react-router-7/routeHandler.d.ts.map +1 -1
- package/dist/react-router-7/routeHandler.js +1 -0
- package/dist/react-router-7/routeHandler.js.map +1 -1
- package/dist/react-router-7/useUser.d.ts.map +1 -1
- package/dist/react-router-7/useUser.js +13 -2
- package/dist/react-router-7/useUser.js.map +1 -1
- package/dist/reactjs/components/ButtonContentOrLoader.d.ts.map +1 -1
- package/dist/reactjs/components/ButtonContentOrLoader.js +2 -4
- package/dist/reactjs/components/ButtonContentOrLoader.js.map +1 -1
- package/dist/reactjs/components/CivicAuthIframeContainer.d.ts +2 -0
- package/dist/reactjs/components/CivicAuthIframeContainer.d.ts.map +1 -0
- package/dist/reactjs/components/CivicAuthIframeContainer.js +26 -0
- package/dist/reactjs/components/CivicAuthIframeContainer.js.map +1 -0
- package/dist/reactjs/components/SignInButton.d.ts.map +1 -1
- package/dist/reactjs/components/SignInButton.js +11 -1
- package/dist/reactjs/components/SignInButton.js.map +1 -1
- package/dist/reactjs/components/UserButton.d.ts +9 -2
- package/dist/reactjs/components/UserButton.d.ts.map +1 -1
- package/dist/reactjs/components/UserButton.js +41 -9
- package/dist/reactjs/components/UserButton.js.map +1 -1
- package/dist/reactjs/components/index.d.ts +1 -0
- package/dist/reactjs/components/index.d.ts.map +1 -1
- package/dist/reactjs/components/index.js +1 -0
- package/dist/reactjs/components/index.js.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.d.ts +26 -0
- package/dist/reactjs/core/GlobalAuthManager.d.ts.map +1 -1
- package/dist/reactjs/core/GlobalAuthManager.js +76 -5
- package/dist/reactjs/core/GlobalAuthManager.js.map +1 -1
- package/dist/reactjs/hooks/useUser.d.ts +19 -2
- package/dist/reactjs/hooks/useUser.d.ts.map +1 -1
- package/dist/reactjs/hooks/useUser.js +95 -7
- package/dist/reactjs/hooks/useUser.js.map +1 -1
- package/dist/reactjs/index.d.ts +1 -2
- package/dist/reactjs/index.d.ts.map +1 -1
- package/dist/reactjs/index.js +1 -2
- package/dist/reactjs/index.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/server/ServerAuthenticationResolver.d.ts.map +1 -1
- package/dist/server/ServerAuthenticationResolver.js +18 -0
- package/dist/server/ServerAuthenticationResolver.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/session.d.ts +51 -0
- package/dist/server/session.d.ts.map +1 -1
- package/dist/server/session.js +276 -15
- package/dist/server/session.js.map +1 -1
- package/dist/shared/components/SVGLoading.js +1 -1
- package/dist/shared/components/SVGLoading.js.map +1 -1
- package/dist/shared/components/UserButtonPresentation.d.ts.map +1 -0
- package/dist/shared/components/UserButtonPresentation.js.map +1 -0
- package/dist/shared/hooks/index.d.ts +1 -2
- package/dist/shared/hooks/index.d.ts.map +1 -1
- package/dist/shared/hooks/index.js +1 -2
- package/dist/shared/hooks/index.js.map +1 -1
- package/dist/shared/hooks/useBfcacheHandler.d.ts +23 -0
- package/dist/shared/hooks/useBfcacheHandler.d.ts.map +1 -0
- package/dist/shared/hooks/useBfcacheHandler.js +65 -0
- package/dist/shared/hooks/useBfcacheHandler.js.map +1 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/lib/util.d.ts +32 -0
- package/dist/shared/lib/util.d.ts.map +1 -1
- package/dist/shared/lib/util.js +79 -0
- package/dist/shared/lib/util.js.map +1 -1
- package/dist/shared/providers/AuthStatusContext.d.ts.map +1 -1
- package/dist/shared/providers/AuthStatusContext.js +2 -1
- package/dist/shared/providers/AuthStatusContext.js.map +1 -1
- package/dist/shared/providers/CivicAuthConfigContext.d.ts +2 -1
- package/dist/shared/providers/CivicAuthConfigContext.d.ts.map +1 -1
- package/dist/shared/providers/CivicAuthConfigContext.js +5 -2
- package/dist/shared/providers/CivicAuthConfigContext.js.map +1 -1
- package/dist/shared/providers/types.d.ts +1 -0
- package/dist/shared/providers/types.d.ts.map +1 -1
- package/dist/shared/providers/types.js.map +1 -1
- package/dist/shared/utils/locationChange.d.ts +34 -0
- package/dist/shared/utils/locationChange.d.ts.map +1 -0
- package/dist/shared/utils/locationChange.js +28 -0
- package/dist/shared/utils/locationChange.js.map +1 -0
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.d.ts.map +1 -1
- package/dist/shared/version.js +1 -1
- package/dist/shared/version.js.map +1 -1
- package/dist/vanillajs/auth/AuthenticationEvents.d.ts +10 -1
- package/dist/vanillajs/auth/AuthenticationEvents.d.ts.map +1 -1
- package/dist/vanillajs/auth/AuthenticationEvents.js +29 -0
- package/dist/vanillajs/auth/AuthenticationEvents.js.map +1 -1
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.d.ts.map +1 -1
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js +2 -2
- package/dist/vanillajs/auth/BackendAuthenticationRefresher.js.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.d.ts +32 -0
- package/dist/vanillajs/auth/CivicAuth.d.ts.map +1 -1
- package/dist/vanillajs/auth/CivicAuth.js +255 -55
- package/dist/vanillajs/auth/CivicAuth.js.map +1 -1
- package/dist/vanillajs/auth/SessionManager.d.ts +3 -2
- package/dist/vanillajs/auth/SessionManager.d.ts.map +1 -1
- package/dist/vanillajs/auth/SessionManager.js +33 -7
- package/dist/vanillajs/auth/SessionManager.js.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.d.ts.map +1 -1
- package/dist/vanillajs/auth/config/ConfigProcessor.js +2 -14
- package/dist/vanillajs/auth/config/ConfigProcessor.js.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js +64 -11
- package/dist/vanillajs/auth/handlers/IframeAuthHandler.js.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js +4 -1
- package/dist/vanillajs/auth/handlers/MessageHandler.js.map +1 -1
- package/dist/vanillajs/auth/handlers/PopupHandler.d.ts.map +1 -1
- package/dist/vanillajs/auth/handlers/PopupHandler.js +3 -1
- package/dist/vanillajs/auth/handlers/PopupHandler.js.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts +11 -1
- package/dist/vanillajs/auth/types/AuthTypes.d.ts.map +1 -1
- package/dist/vanillajs/auth/types/AuthTypes.js.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.d.ts +22 -1
- package/dist/vanillajs/iframe/IframeManager.d.ts.map +1 -1
- package/dist/vanillajs/iframe/IframeManager.js +184 -22
- package/dist/vanillajs/iframe/IframeManager.js.map +1 -1
- package/dist/vanillajs/types/index.d.ts +1 -1
- package/dist/vanillajs/types/index.d.ts.map +1 -1
- package/dist/vanillajs/types/index.js +1 -1
- package/dist/vanillajs/types/index.js.map +1 -1
- package/dist/vanillajs/ui/LoadingComponents.d.ts +4 -0
- package/dist/vanillajs/ui/LoadingComponents.d.ts.map +1 -1
- package/dist/vanillajs/ui/LoadingComponents.js +51 -1
- package/dist/vanillajs/ui/LoadingComponents.js.map +1 -1
- package/package.json +3 -3
- package/dist/nextjs/hooks/index.d.ts +0 -2
- package/dist/nextjs/hooks/index.d.ts.map +0 -1
- package/dist/nextjs/hooks/index.js +0 -2
- package/dist/nextjs/hooks/index.js.map +0 -1
- package/dist/nextjs/hooks/usePrevious.d.ts +0 -2
- package/dist/nextjs/hooks/usePrevious.d.ts.map +0 -1
- package/dist/nextjs/hooks/usePrevious.js +0 -9
- package/dist/nextjs/hooks/usePrevious.js.map +0 -1
- package/dist/nextjs/hooks/useUserCookie.d.ts +0 -9
- package/dist/nextjs/hooks/useUserCookie.d.ts.map +0 -1
- package/dist/nextjs/hooks/useUserCookie.js +0 -109
- package/dist/nextjs/hooks/useUserCookie.js.map +0 -1
- package/dist/react-router-7/components/UserButtonPresentation.d.ts.map +0 -1
- package/dist/react-router-7/components/UserButtonPresentation.js.map +0 -1
- package/dist/shared/components/BlockDisplay.d.ts +0 -6
- package/dist/shared/components/BlockDisplay.d.ts.map +0 -1
- package/dist/shared/components/BlockDisplay.js +0 -30
- package/dist/shared/components/BlockDisplay.js.map +0 -1
- package/dist/shared/components/CivicAuthIframe.d.ts +0 -10
- package/dist/shared/components/CivicAuthIframe.d.ts.map +0 -1
- package/dist/shared/components/CivicAuthIframe.js +0 -49
- package/dist/shared/components/CivicAuthIframe.js.map +0 -1
- package/dist/shared/components/CivicAuthIframeContainer.d.ts +0 -15
- package/dist/shared/components/CivicAuthIframeContainer.d.ts.map +0 -1
- package/dist/shared/components/CivicAuthIframeContainer.js +0 -177
- package/dist/shared/components/CivicAuthIframeContainer.js.map +0 -1
- package/dist/shared/components/CivicAuthLogoutIframeContainer.d.ts +0 -6
- package/dist/shared/components/CivicAuthLogoutIframeContainer.d.ts.map +0 -1
- package/dist/shared/components/CivicAuthLogoutIframeContainer.js +0 -51
- package/dist/shared/components/CivicAuthLogoutIframeContainer.js.map +0 -1
- package/dist/shared/components/IFrameAndLoading.d.ts +0 -7
- package/dist/shared/components/IFrameAndLoading.d.ts.map +0 -1
- package/dist/shared/components/IFrameAndLoading.js +0 -66
- package/dist/shared/components/IFrameAndLoading.js.map +0 -1
- package/dist/shared/hooks/useAuth.d.ts +0 -3
- package/dist/shared/hooks/useAuth.d.ts.map +0 -1
- package/dist/shared/hooks/useAuth.js +0 -12
- package/dist/shared/hooks/useAuth.js.map +0 -1
- package/dist/shared/hooks/useIframe.d.ts +0 -3
- package/dist/shared/hooks/useIframe.d.ts.map +0 -1
- package/dist/shared/hooks/useIframe.js +0 -13
- package/dist/shared/hooks/useIframe.js.map +0 -1
- package/dist/shared/hooks/useIsInIframe.d.ts +0 -7
- package/dist/shared/hooks/useIsInIframe.d.ts.map +0 -1
- package/dist/shared/hooks/useIsInIframe.js +0 -23
- package/dist/shared/hooks/useIsInIframe.js.map +0 -1
- package/dist/shared/hooks/useSignIn.d.ts +0 -20
- package/dist/shared/hooks/useSignIn.d.ts.map +0 -1
- package/dist/shared/hooks/useSignIn.js +0 -358
- package/dist/shared/hooks/useSignIn.js.map +0 -1
- package/dist/shared/providers/IframeProvider.d.ts +0 -28
- package/dist/shared/providers/IframeProvider.d.ts.map +0 -1
- package/dist/shared/providers/IframeProvider.js +0 -64
- package/dist/shared/providers/IframeProvider.js.map +0 -1
- /package/dist/{react-router-7 → shared}/components/UserButtonPresentation.d.ts +0 -0
- /package/dist/{react-router-7 → shared}/components/UserButtonPresentation.js +0 -0
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CivicAuth } from "@civic/auth/server";
|
|
2
|
+
import { LOGOUT_SUCCESS_TEXT } from "../constants.js";
|
|
2
3
|
import { loggers } from "../lib/logger.js";
|
|
3
|
-
import { displayModeFromState
|
|
4
|
+
import { displayModeFromState } from "../lib/oauth.js";
|
|
4
5
|
import { resolveAuthConfig } from "../nextjs/config.js";
|
|
5
6
|
import { clearAuthCookies, NextjsCookieStorage } from "../nextjs/cookies.js";
|
|
6
|
-
import {
|
|
7
|
-
import { resolveCallbackUrl } from "../nextjs/utils.js";
|
|
8
|
-
import { resolveOAuthAccessCode } from "../server/login.js";
|
|
9
|
-
import { GenericPublicClientPKCEProducer } from "../services/PKCE.js";
|
|
10
|
-
import { CodeVerifier, OAuthTokenTypes } from "../shared/lib/types.js";
|
|
11
|
-
import { GenericUserSession } from "../shared/lib/UserSession.js";
|
|
12
|
-
import { clearTokens, generateOauthLogoutUrl } from "../shared/lib/util.js";
|
|
13
|
-
import { revalidatePath } from "next/cache.js";
|
|
7
|
+
import { CodeVerifier, UserStorage } from "../shared/lib/types.js";
|
|
14
8
|
import { NextResponse } from "next/server.js";
|
|
15
|
-
import { NextServerAuthenticationRefresherImpl } from "./NextServerAuthenticationRefresherImpl.js";
|
|
16
9
|
const logger = loggers.nextjs.handlers.auth;
|
|
17
10
|
class AuthError extends Error {
|
|
18
11
|
status;
|
|
@@ -22,397 +15,270 @@ class AuthError extends Error {
|
|
|
22
15
|
this.name = "AuthError";
|
|
23
16
|
}
|
|
24
17
|
}
|
|
25
|
-
const tryUriDecode = (value) => {
|
|
26
|
-
try {
|
|
27
|
-
return decodeURIComponent(value);
|
|
28
|
-
}
|
|
29
|
-
catch (e) {
|
|
30
|
-
logger.error("Error decoding URI component:", e);
|
|
31
|
-
return value;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
const getDecodedQueryParam = (request, paramName) => {
|
|
35
|
-
const queryParam = request.nextUrl.searchParams.get(paramName);
|
|
36
|
-
if (queryParam) {
|
|
37
|
-
return tryUriDecode(queryParam);
|
|
38
|
-
}
|
|
39
|
-
return null;
|
|
40
|
-
};
|
|
41
|
-
const getCookieOrQueryParam = (request, cookieName, queryName) => {
|
|
42
|
-
// First check the cookie as it might have the full path with base directory
|
|
43
|
-
const cookieValue = request.cookies.get(cookieName)?.value;
|
|
44
|
-
if (cookieValue) {
|
|
45
|
-
return tryUriDecode(cookieValue);
|
|
46
|
-
}
|
|
47
|
-
// Fallback to query parameter
|
|
48
|
-
return getDecodedQueryParam(request, queryName);
|
|
49
|
-
};
|
|
50
|
-
const getAppUrl = (request) => getCookieOrQueryParam(request, CodeVerifier.APP_URL, "appUrl");
|
|
51
|
-
// The loginSuccessUrl can either be decoded from the state parameter, or passed as a cookie or query parameter
|
|
52
|
-
const getLoginSuccessUrl = (request, baseUrl) => {
|
|
53
|
-
const loginSuccessUrl = loginSuccessUrlFromState(request.nextUrl.searchParams.get("state")) ||
|
|
54
|
-
getDecodedQueryParam(request, "loginSuccessUrl");
|
|
55
|
-
if (!loginSuccessUrl) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
return baseUrl ? new URL(loginSuccessUrl, baseUrl).href : loginSuccessUrl;
|
|
59
|
-
};
|
|
60
|
-
const getIdToken = async (config) => {
|
|
61
|
-
const cookieStorage = new NextjsCookieStorage(config.cookies?.tokens ?? {});
|
|
62
|
-
return cookieStorage.get(OAuthTokenTypes.ID_TOKEN);
|
|
63
|
-
};
|
|
64
18
|
/**
|
|
65
|
-
*
|
|
66
|
-
* saving the verifier in a cookie for later use
|
|
67
|
-
* @returns {Promise<NextResponse>}
|
|
19
|
+
* Helper to convert NextRequest to UrlDetectionRequest for framework-agnostic URL handling
|
|
68
20
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
21
|
+
const toUrlDetectionRequest = (request) => ({
|
|
22
|
+
url: request.url,
|
|
23
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
24
|
+
searchParams: {
|
|
25
|
+
get: (name) => request.nextUrl.searchParams.get(name),
|
|
26
|
+
},
|
|
27
|
+
cookies: {
|
|
28
|
+
get: (name) => request.cookies.get(name),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Helper to create CivicAuth instance for a request
|
|
33
|
+
* Now handles appUrl detection for proxy environments
|
|
34
|
+
*/
|
|
35
|
+
const createCivicAuth = (request, config) => {
|
|
36
|
+
const resolvedConfig = resolveAuthConfig(config);
|
|
37
|
+
const cookieStorage = new NextjsCookieStorage({
|
|
38
|
+
...resolvedConfig.cookies?.tokens,
|
|
39
|
+
[UserStorage.USER]: resolvedConfig.cookies?.user,
|
|
40
|
+
});
|
|
41
|
+
// Convert to framework-agnostic request format
|
|
42
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
43
|
+
// Get appUrl from client (for proxy environments)
|
|
44
|
+
const clientAppUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
45
|
+
// Use baseUrl from config, then client appUrl, then request origin
|
|
46
|
+
// This matches the main branch priority: config > client > request
|
|
47
|
+
const appUrl = resolvedConfig.baseUrl ||
|
|
48
|
+
clientAppUrl ||
|
|
49
|
+
new URL(urlDetectionRequest.url).origin;
|
|
50
|
+
// Build absolute URLs using detected appUrl or request origin
|
|
51
|
+
const absoluteCallbackUrl = resolvedConfig.callbackUrl.startsWith("http")
|
|
52
|
+
? resolvedConfig.callbackUrl
|
|
53
|
+
: CivicAuth.toAbsoluteUrl(urlDetectionRequest, resolvedConfig.callbackUrl, appUrl);
|
|
54
|
+
const absoluteLogoutCallbackUrl = resolvedConfig.logoutCallbackUrl.startsWith("http")
|
|
55
|
+
? resolvedConfig.logoutCallbackUrl
|
|
56
|
+
: CivicAuth.toAbsoluteUrl(urlDetectionRequest, resolvedConfig.logoutCallbackUrl, appUrl);
|
|
57
|
+
const civicAuth = new CivicAuth(cookieStorage, {
|
|
58
|
+
clientId: resolvedConfig.clientId,
|
|
59
|
+
redirectUrl: absoluteCallbackUrl,
|
|
60
|
+
oauthServer: resolvedConfig.oauthServer,
|
|
61
|
+
postLogoutRedirectUrl: absoluteLogoutCallbackUrl,
|
|
62
|
+
loginSuccessUrl: request.url,
|
|
84
63
|
});
|
|
64
|
+
return {
|
|
65
|
+
civicAuth,
|
|
66
|
+
cookieStorage,
|
|
67
|
+
appUrl, // Return appUrl for use in other functions
|
|
68
|
+
urlDetectionRequest, // Return for use in handlers
|
|
69
|
+
};
|
|
85
70
|
};
|
|
86
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Login handler - backend OAuth login initiation endpoint
|
|
73
|
+
* Uses CivicAuth.buildLoginUrl()
|
|
74
|
+
*/
|
|
75
|
+
async function handleLogin(request, config) {
|
|
87
76
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
88
|
-
// TODO This is messy, better would be to fix the config.cookies type to always be <name: settings>
|
|
89
|
-
// rather than nesting the tokens-related ones *and* code-verifier inside "tokens"
|
|
90
|
-
// (despite code-verifier not relating directly to tokens)
|
|
91
|
-
const cookieStorage = getCookieStorageWithUserOverrides(config);
|
|
92
|
-
const callbackUrl = resolveCallbackUrl(resolvedConfigs, appUrl);
|
|
93
77
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
const frontendState = request.nextUrl.searchParams.get("state");
|
|
79
|
+
// Store appUrl in cookie if provided as query parameter
|
|
80
|
+
const appUrlFromQuery = request.nextUrl.searchParams.get("appUrl");
|
|
81
|
+
if (appUrlFromQuery) {
|
|
82
|
+
const cookieStorage = new NextjsCookieStorage(resolvedConfigs.cookies?.tokens ?? {});
|
|
83
|
+
await cookieStorage.set(CodeVerifier.APP_URL, appUrlFromQuery);
|
|
84
|
+
}
|
|
85
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
86
|
+
const url = await civicAuth.buildLoginUrl({
|
|
87
|
+
state: frontendState || undefined,
|
|
97
88
|
});
|
|
89
|
+
logger.info("[LOGIN_HANDLER] Redirecting to OAuth login URL", {
|
|
90
|
+
loginUrl: url.toString(),
|
|
91
|
+
});
|
|
92
|
+
return NextResponse.redirect(url.toString());
|
|
98
93
|
}
|
|
99
94
|
catch (error) {
|
|
100
|
-
logger.error("
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!user) {
|
|
105
|
-
throw new AuthError("Failed to get user info", 401);
|
|
95
|
+
logger.error("[LOGIN_HANDLER] Backend login error:", error);
|
|
96
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
97
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
98
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/?error=login_failed", appUrl));
|
|
106
99
|
}
|
|
107
|
-
const userSession = new GenericUserSession(cookieStorage);
|
|
108
|
-
await userSession.set(user);
|
|
109
100
|
}
|
|
110
101
|
async function handleRefresh(request, config) {
|
|
111
102
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
112
|
-
const cookieStorage = getCookieStorageWithUserOverrides(config);
|
|
113
|
-
const userSession = new GenericUserSession(cookieStorage);
|
|
114
|
-
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
115
103
|
try {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const refresher = await NextServerAuthenticationRefresherImpl.build({
|
|
121
|
-
clientId: resolvedConfigs.clientId,
|
|
122
|
-
oauthServer: resolvedConfigs.oauthServer,
|
|
123
|
-
redirectUrl: resolvedConfigs.callbackUrl,
|
|
124
|
-
refreshUrl: resolvedConfigs.refreshUrl,
|
|
125
|
-
}, cookieStorage, onError);
|
|
126
|
-
const tokens = await refresher.refreshAccessToken();
|
|
127
|
-
const user = await getUser();
|
|
128
|
-
if (!user) {
|
|
129
|
-
throw new AuthError("Failed to get user info", 401);
|
|
130
|
-
}
|
|
131
|
-
await userSession.set(user);
|
|
132
|
-
logger.debug("handleRefresh: Token refresh successful");
|
|
104
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
105
|
+
await civicAuth.refreshTokens();
|
|
106
|
+
logger.info("[REFRESH_HANDLER] Tokens refreshed successfully");
|
|
107
|
+
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
133
108
|
if (targetUrl) {
|
|
134
109
|
// Success: clear the refresh attempt tracking cookie and redirect to target
|
|
135
110
|
const response = NextResponse.redirect(targetUrl);
|
|
136
111
|
response.cookies.delete("_civic_last_refresh");
|
|
137
112
|
return response;
|
|
138
113
|
}
|
|
139
|
-
|
|
140
|
-
const response = NextResponse.json({
|
|
114
|
+
return NextResponse.json({
|
|
141
115
|
status: "success",
|
|
142
|
-
|
|
116
|
+
message: "Tokens refreshed",
|
|
143
117
|
});
|
|
144
|
-
return response;
|
|
145
118
|
}
|
|
146
119
|
catch (error) {
|
|
147
|
-
logger.error("
|
|
148
|
-
|
|
149
|
-
await userSession.clear();
|
|
120
|
+
logger.error("[REFRESH_HANDLER] Token refresh error:", error);
|
|
121
|
+
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
150
122
|
if (targetUrl) {
|
|
151
|
-
logger.warn("handleRefresh: Refresh failed, redirecting to targetUrl");
|
|
152
123
|
return NextResponse.redirect(targetUrl);
|
|
153
124
|
}
|
|
154
|
-
return NextResponse.json({
|
|
125
|
+
return NextResponse.json({ error: "Token refresh failed" }, { status: 500 });
|
|
155
126
|
}
|
|
156
127
|
}
|
|
157
|
-
const generateHtmlResponseWithCallback = (request, callbackUrl, loginSuccessUrl) => {
|
|
158
|
-
// we need to replace the URL with resolved config in case the server is hosted
|
|
159
|
-
// behind a reverse proxy or load balancer
|
|
160
|
-
const requestUrl = new URL(request.url);
|
|
161
|
-
const fetchUrl = `${callbackUrl}?${requestUrl.searchParams.toString()}&sameDomainCallback=true`;
|
|
162
|
-
const loginSuccessSegment = loginSuccessUrl
|
|
163
|
-
? `&loginSuccessUrl=${encodeURIComponent(loginSuccessUrl)}`
|
|
164
|
-
: "";
|
|
165
|
-
const html = `<html lang="en">
|
|
166
|
-
<body>
|
|
167
|
-
<span style="display:none">
|
|
168
|
-
<script>
|
|
169
|
-
window.onload = function () {
|
|
170
|
-
// Get the complete URL including origin and path
|
|
171
|
-
// This ensures we capture any base path like /directory
|
|
172
|
-
const appUrl = window.location.href.substring(
|
|
173
|
-
0,
|
|
174
|
-
window.location.href.indexOf("/api/auth")
|
|
175
|
-
);
|
|
176
|
-
fetch('${fetchUrl}&appUrl=' + encodeURIComponent(appUrl) + '${loginSuccessSegment}').then((response) => {
|
|
177
|
-
response.json().then((jsonResponse) => {
|
|
178
|
-
// For login: Redirect back to the callback route, so Case 2 in handleTokenExchangeComplete will be triggered
|
|
179
|
-
// For logout: Redirect to the postLogoutRedirectUrl
|
|
180
|
-
if(jsonResponse.redirectUrl) {
|
|
181
|
-
window.location.href = jsonResponse.redirectUrl;
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
};
|
|
186
|
-
</script>
|
|
187
|
-
</span>
|
|
188
|
-
</body>
|
|
189
|
-
</html>
|
|
190
|
-
`;
|
|
191
|
-
const response = new NextResponse(html);
|
|
192
|
-
response.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
193
|
-
return response;
|
|
194
|
-
};
|
|
195
|
-
const handleTokenExchangeComplete = async (params) => {
|
|
196
|
-
const { request, config, appUrl, loginSuccessUrl, state } = params;
|
|
197
|
-
// 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.
|
|
198
|
-
if (isCalledFromBrowserFetch(request)) {
|
|
199
|
-
logger.debug("CASE 1: sameDomainCallback=true, returning JSON response with redirect URL", { appUrl, loginSuccessUrl, callbackUrl: config.callbackUrl });
|
|
200
|
-
const currentUrl = new URL(request.url);
|
|
201
|
-
// 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.
|
|
202
|
-
// So we strip off the sameDomainCallback parameter from the URL.
|
|
203
|
-
const newSearchParams = new URLSearchParams(currentUrl.search);
|
|
204
|
-
newSearchParams.delete("sameDomainCallback");
|
|
205
|
-
// We strip off the origin so reverse proxies don't break the redirect.
|
|
206
|
-
const redirectUrl = `${currentUrl.pathname}?${newSearchParams.toString()}${currentUrl.hash}`;
|
|
207
|
-
return NextResponse.json({
|
|
208
|
-
status: "success",
|
|
209
|
-
// This makes the iframe redirect back to this route, so Case 2 below will be triggered.
|
|
210
|
-
redirectUrl,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
// Case 2: We are already authenticated and in iframe mode.
|
|
214
|
-
// 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.
|
|
215
|
-
// 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,
|
|
216
|
-
// which means the iframe container no longer exists and so can't action the redirect.
|
|
217
|
-
const user = await getUser();
|
|
218
|
-
if (!!user && displayModeFromState(state, "iframe") === "iframe") {
|
|
219
|
-
if (loginSuccessUrl) {
|
|
220
|
-
logger.debug("CASE 2a: iframe mode with loginSuccessUrl configured. Returning TOKEN_EXCHANGE_SUCCESS_TEXT to trigger redirect to loginSuccessUrl if any", { loginSuccessUrl });
|
|
221
|
-
const response = new NextResponse(`<html lang="en"><span style="display:none">${TOKEN_EXCHANGE_SUCCESS_TEXT}</span></html>`);
|
|
222
|
-
response.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
223
|
-
return response;
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
logger.debug("CASE 2b: iframe mode with no loginSuccessUrl configured. Doing a normal redirect without relying on the iframe container to redirect.");
|
|
227
|
-
return NextResponse.redirect(`${appUrl}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
// 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.
|
|
231
|
-
logger.debug("CASE 3: non-iframe mode, redirecting to loginSuccessUrl");
|
|
232
|
-
return NextResponse.redirect(`${loginSuccessUrl || appUrl}`);
|
|
233
|
-
};
|
|
234
128
|
async function handleCallback(request, config) {
|
|
235
129
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
236
130
|
const code = request.nextUrl.searchParams.get("code");
|
|
237
131
|
const state = request.nextUrl.searchParams.get("state");
|
|
132
|
+
const error = request.nextUrl.searchParams.get("error");
|
|
133
|
+
if (error) {
|
|
134
|
+
logger.error("OAuth error in callback:", error);
|
|
135
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
136
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
137
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/?error=oauth_error", appUrl));
|
|
138
|
+
}
|
|
238
139
|
if (!code || !state)
|
|
239
140
|
throw new AuthError("Bad parameters", 400);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
};
|
|
254
|
-
const user = await getUser();
|
|
255
|
-
if (user) {
|
|
256
|
-
// User already authenticated.
|
|
257
|
-
return handleTokenExchangeComplete(tokenExchangeCompleteParams);
|
|
258
|
-
}
|
|
259
|
-
// User not authenticated yet.
|
|
260
|
-
// If we have a code_verifier cookie and the appUrl, we can do a token exchange.
|
|
261
|
-
// Otherwise, just render an empty page.
|
|
262
|
-
// The initial redirect back from the auth server does not send cookies, because the redirect is from a 3rd-party domain.
|
|
263
|
-
// The client will make an additional call to this route with cookies included, at which point we do the token exchange.
|
|
264
|
-
const codeVerifier = request.cookies.get(CodeVerifier.COOKIE_NAME);
|
|
265
|
-
if (!codeVerifier || !appUrl) {
|
|
266
|
-
logger.debug("handleCallback no code_verifier found", {
|
|
141
|
+
try {
|
|
142
|
+
const { civicAuth, appUrl, urlDetectionRequest } = createCivicAuth(request, resolvedConfigs);
|
|
143
|
+
// Convert NextRequest to the format expected by handleCallback
|
|
144
|
+
const handleCallbackRequest = {
|
|
145
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
146
|
+
url: request.url.toString(),
|
|
147
|
+
};
|
|
148
|
+
// Get loginSuccessUrl with proper baseUrl handling
|
|
149
|
+
const loginSuccessUrl = CivicAuth.getLoginSuccessUrl(urlDetectionRequest, appUrl);
|
|
150
|
+
const frontendUrl = loginSuccessUrl || resolvedConfigs.loginSuccessUrl || "/";
|
|
151
|
+
// Use CivicAuth's smart callback handler
|
|
152
|
+
const result = await civicAuth.handleCallback({
|
|
153
|
+
code,
|
|
267
154
|
state,
|
|
268
|
-
|
|
155
|
+
req: handleCallbackRequest,
|
|
156
|
+
}, {
|
|
157
|
+
// Pass the properly resolved frontendUrl
|
|
158
|
+
frontendUrl: frontendUrl,
|
|
269
159
|
});
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
// from the same domain, allowing it access to the code_verifier cookie
|
|
273
|
-
// we only need to do this in redirect mode, as the iframe already triggers a client-side token exchange
|
|
274
|
-
// if no code-verifier cookie is found
|
|
275
|
-
if (state && serverTokenExchangeFromState(state)) {
|
|
276
|
-
logger.debug("handleCallback serverTokenExchangeFromState, launching redirect page...", {
|
|
277
|
-
requestUrl: request.url,
|
|
278
|
-
configCallbackUrl: resolvedConfigs.callbackUrl,
|
|
279
|
-
});
|
|
280
|
-
// generate a page that will callback to the same domain, allowing access
|
|
281
|
-
// to the code_verifier cookie and passing the appUrl.
|
|
282
|
-
response = generateHtmlResponseWithCallback(request, resolvedConfigs.callbackUrl, loginSuccessUrl || undefined);
|
|
160
|
+
if (result.redirectTo) {
|
|
161
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, result.redirectTo, appUrl));
|
|
283
162
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// and we should use it as the redirect target.
|
|
302
|
-
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
303
|
-
if (targetUrl) {
|
|
304
|
-
// If a targetUrl is provided, use it as the redirect target.
|
|
305
|
-
// This is useful for redirecting to a specific page after logout.
|
|
306
|
-
return targetUrl;
|
|
307
|
-
}
|
|
308
|
-
const redirectTarget = loginUrl ?? "/";
|
|
309
|
-
// if the optional loginUrl is provided and it is an absolute URL,
|
|
310
|
-
// use it as the redirect target
|
|
311
|
-
const isAbsoluteRedirect = /^(https?:\/\/|www\.).+/i.test(redirectTarget);
|
|
312
|
-
if (isAbsoluteRedirect) {
|
|
313
|
-
return redirectTarget;
|
|
314
|
-
}
|
|
315
|
-
// if loginUrl is not defined, the appUrl is passed from the client to the server
|
|
316
|
-
// in the query string or cookies. This is necessary because the server does not
|
|
317
|
-
// have access to the client's window.location and can not accurately determine
|
|
318
|
-
// the appUrl (specially if the app is behind a reverse proxy).
|
|
319
|
-
const appUrl = getAppUrl(request);
|
|
320
|
-
if (appUrl)
|
|
321
|
-
return getAbsoluteRedirectPath(redirectTarget, appUrl);
|
|
322
|
-
// 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.
|
|
323
|
-
return request.nextUrl.origin;
|
|
324
|
-
};
|
|
325
|
-
const revalidateUrlPath = async (url) => {
|
|
326
|
-
try {
|
|
327
|
-
const path = new URL(url).pathname;
|
|
328
|
-
revalidatePath(path);
|
|
163
|
+
if (result.content) {
|
|
164
|
+
// Handle both string content and object content
|
|
165
|
+
if (typeof result.content === "string") {
|
|
166
|
+
return new NextResponse(result.content, {
|
|
167
|
+
status: 200,
|
|
168
|
+
headers: {
|
|
169
|
+
"Content-Type": "text/html",
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Object content (JSON response)
|
|
175
|
+
return NextResponse.json(result.content);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Fallback redirect
|
|
179
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/", appUrl));
|
|
329
180
|
}
|
|
330
181
|
catch (error) {
|
|
331
|
-
logger.
|
|
182
|
+
logger.error("[CALLBACK_HANDLER] OAuth callback error:", error);
|
|
183
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
184
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
185
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/?error=callback_failed", appUrl));
|
|
332
186
|
}
|
|
333
|
-
}
|
|
187
|
+
}
|
|
334
188
|
export async function handleLogout(request, config) {
|
|
335
189
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
190
|
+
try {
|
|
191
|
+
logger.info("[LOGOUT_HANDLER] Backend logout endpoint called");
|
|
192
|
+
// Check for client-provided logoutRedirectUrl query parameter
|
|
193
|
+
const requestUrl = new URL(request.url);
|
|
194
|
+
const clientLogoutRedirectUrl = requestUrl.searchParams.get("logoutRedirectUrl");
|
|
195
|
+
const frontendState = new URL(request.url).searchParams.get("state");
|
|
196
|
+
// If client provided a logoutRedirectUrl, override the config
|
|
197
|
+
let configToUse = resolvedConfigs;
|
|
198
|
+
if (clientLogoutRedirectUrl) {
|
|
199
|
+
configToUse = {
|
|
200
|
+
...resolvedConfigs,
|
|
201
|
+
logoutCallbackUrl: clientLogoutRedirectUrl,
|
|
202
|
+
};
|
|
203
|
+
logger.info("[LOGOUT_HANDLER] Overriding logout callback URL", {
|
|
204
|
+
original: resolvedConfigs.logoutCallbackUrl,
|
|
205
|
+
override: clientLogoutRedirectUrl,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
const { civicAuth } = createCivicAuth(request, configToUse);
|
|
209
|
+
const logoutUrl = await civicAuth.buildLogoutRedirectUrl({
|
|
210
|
+
state: frontendState || undefined,
|
|
348
211
|
});
|
|
349
|
-
await
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
if (displayMode === "iframe") {
|
|
357
|
-
// clear auth cookies immediately before calling the logout endpoint to give faster UX
|
|
358
|
-
await clearAuthCookies();
|
|
359
|
-
await revalidateUrlPath(request.url);
|
|
360
|
-
}
|
|
361
|
-
const logoutUrl = await generateOauthLogoutUrl({
|
|
362
|
-
clientId: resolvedConfigs.clientId,
|
|
363
|
-
idToken,
|
|
364
|
-
state,
|
|
365
|
-
redirectUrl: postLogoutUrl.href,
|
|
366
|
-
oauthServer: resolvedConfigs.oauthServer,
|
|
367
|
-
});
|
|
368
|
-
return NextResponse.redirect(`${logoutUrl.href}`);
|
|
369
|
-
}
|
|
370
|
-
const isCalledFromBrowserFetch = (request) => request.url.includes("sameDomainCallback=true");
|
|
371
|
-
const handleLogoutComplete = async (params) => {
|
|
372
|
-
const { request, resolvedConfigs } = params;
|
|
373
|
-
const state = request.nextUrl.searchParams.get("state");
|
|
374
|
-
const postLogoutRedirectUrl = getPostLogoutRedirectUrl(request, resolvedConfigs);
|
|
375
|
-
// If this is a FETCH call, we can only return json. Trying to redirect or return HTML will fail.
|
|
376
|
-
if (isCalledFromBrowserFetch(request)) {
|
|
377
|
-
logger.debug("handleLogoutComplete: sameDomainCallback=true, returning JSON response with redirect URL", { postLogoutRedirectUrl });
|
|
378
|
-
// The client-side JS will do a window.location.href redirect to postLogoutRedirectUrl when this request returns success.
|
|
379
|
-
return NextResponse.json({
|
|
380
|
-
status: "success",
|
|
381
|
-
redirectUrl: postLogoutRedirectUrl,
|
|
212
|
+
await civicAuth.clearTokens();
|
|
213
|
+
// Convert to URL object to modify parameters
|
|
214
|
+
const url = new URL(logoutUrl.toString());
|
|
215
|
+
// Remove the state parameter to avoid it showing up in the frontend URL
|
|
216
|
+
url.searchParams.delete("state");
|
|
217
|
+
logger.info("[LOGOUT_HANDLER] Returning logout URL for client-side iframe handling", {
|
|
218
|
+
logoutUrl: url.toString(),
|
|
382
219
|
});
|
|
220
|
+
return NextResponse.redirect(url.toString());
|
|
383
221
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
222
|
+
catch (error) {
|
|
223
|
+
logger.error("[LOGOUT_HANDLER] Logout error:", error);
|
|
224
|
+
// If logout URL generation fails, clear tokens and redirect to home
|
|
225
|
+
try {
|
|
226
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
227
|
+
await civicAuth.clearTokens();
|
|
228
|
+
// Use client-provided logoutRedirectUrl for fallback too
|
|
229
|
+
const requestUrl = new URL(request.url);
|
|
230
|
+
const clientLogoutRedirectUrl = requestUrl.searchParams.get("logoutRedirectUrl");
|
|
231
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
232
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
233
|
+
const fallbackUrl = clientLogoutRedirectUrl || resolvedConfigs.logoutCallbackUrl;
|
|
234
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, fallbackUrl, appUrl));
|
|
235
|
+
}
|
|
236
|
+
catch (clearError) {
|
|
237
|
+
logger.error("[LOGOUT_HANDLER] Failed to clear tokens:", clearError);
|
|
238
|
+
// Final fallback
|
|
239
|
+
const requestUrl = new URL(request.url);
|
|
240
|
+
const clientLogoutRedirectUrl = requestUrl.searchParams.get("logoutRedirectUrl");
|
|
241
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
242
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
243
|
+
const fallbackUrl = clientLogoutRedirectUrl || resolvedConfigs.logoutCallbackUrl;
|
|
244
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, fallbackUrl, appUrl));
|
|
245
|
+
}
|
|
393
246
|
}
|
|
394
|
-
|
|
395
|
-
postLogoutRedirectUrl,
|
|
396
|
-
});
|
|
397
|
-
revalidateUrlPath(postLogoutRedirectUrl);
|
|
398
|
-
return NextResponse.redirect(postLogoutRedirectUrl);
|
|
399
|
-
};
|
|
247
|
+
}
|
|
400
248
|
export async function handleLogoutCallback(request, config) {
|
|
401
249
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
logger.debug("handleLogoutCallback can access cookies: clearing auth cookies");
|
|
250
|
+
try {
|
|
251
|
+
logger.info("[LOGOUT_CALLBACK_HANDLER] Backend logout callback endpoint called");
|
|
252
|
+
// Clear authentication cookies
|
|
406
253
|
await clearAuthCookies();
|
|
407
|
-
|
|
254
|
+
// Get framework-agnostic request and create CivicAuth instance
|
|
255
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
256
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
257
|
+
// Get the state parameter for iframe detection
|
|
258
|
+
const state = request.nextUrl.searchParams.get("state");
|
|
259
|
+
// If this is an iframe request, return HTML with logout success signal
|
|
260
|
+
if (state && displayModeFromState(state, "iframe") === "iframe") {
|
|
261
|
+
// For iframe mode, include the post-logout redirect URL in the HTML
|
|
262
|
+
const postLogoutRedirectUrl = civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);
|
|
263
|
+
const response = new NextResponse(`<html lang="en"><span style="display:none">${LOGOUT_SUCCESS_TEXT}<a href="${postLogoutRedirectUrl}" rel="civic-auth-post-logout-redirect-url"></a></span></html>`);
|
|
264
|
+
response.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
265
|
+
logger.info("[LOGOUT_CALLBACK_HANDLER] Returning iframe logout success HTML", { postLogoutRedirectUrl });
|
|
266
|
+
return response;
|
|
267
|
+
}
|
|
268
|
+
// For non-iframe requests, redirect to the logout callback URL or post-logout URL
|
|
269
|
+
const redirectUrl = civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);
|
|
270
|
+
logger.info("[LOGOUT_CALLBACK_HANDLER] Redirecting to logout callback URL", {
|
|
271
|
+
logoutCallbackUrl: resolvedConfigs.logoutCallbackUrl,
|
|
272
|
+
redirectUrl,
|
|
273
|
+
});
|
|
274
|
+
return NextResponse.redirect(redirectUrl);
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
logger.error("[LOGOUT_CALLBACK_HANDLER] Logout callback error:", error);
|
|
278
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
279
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
280
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, resolvedConfigs.logoutCallbackUrl, appUrl));
|
|
408
281
|
}
|
|
409
|
-
logger.debug("handleLogoutCallback cannot access cookies: generating HTML response with callback");
|
|
410
|
-
// If we don't have access to cookies, render some javascript to the client that will:
|
|
411
|
-
// 1. make a same-domain fetch call back to this endpoint and receive a '{status: "success"}' back.
|
|
412
|
-
// 2. On status: success, set the window.location.href to the post-logout redirect URL (usually the appUrl).
|
|
413
|
-
return generateHtmlResponseWithCallback(request,
|
|
414
|
-
// The client-side JS will make a fetch call back to this URL.
|
|
415
|
-
resolvedConfigs.logoutCallbackUrl);
|
|
416
282
|
}
|
|
417
283
|
/**
|
|
418
284
|
* Creates an authentication handler for Next.js API routes
|
|
@@ -434,7 +300,8 @@ export const handler = (authConfig = {}) => async (request) => {
|
|
|
434
300
|
const lastSegment = pathSegments[pathSegments.length - 1];
|
|
435
301
|
switch (lastSegment) {
|
|
436
302
|
case "challenge":
|
|
437
|
-
|
|
303
|
+
case "login":
|
|
304
|
+
return await handleLogin(request, config);
|
|
438
305
|
case "callback":
|
|
439
306
|
return await handleCallback(request, config);
|
|
440
307
|
case "refresh":
|
|
@@ -443,6 +310,8 @@ export const handler = (authConfig = {}) => async (request) => {
|
|
|
443
310
|
return await handleLogout(request, config);
|
|
444
311
|
case "logoutcallback":
|
|
445
312
|
return await handleLogoutCallback(request, config);
|
|
313
|
+
case "user":
|
|
314
|
+
return await handleUser(request, config);
|
|
446
315
|
default:
|
|
447
316
|
throw new AuthError(`Invalid auth route: ${pathname}`, 404);
|
|
448
317
|
}
|
|
@@ -456,4 +325,24 @@ export const handler = (authConfig = {}) => async (request) => {
|
|
|
456
325
|
return response;
|
|
457
326
|
}
|
|
458
327
|
};
|
|
328
|
+
/**
|
|
329
|
+
* User endpoint - returns current user data as JSON
|
|
330
|
+
* Uses CivicAuth.isLoggedIn() and getUser()
|
|
331
|
+
*/
|
|
332
|
+
async function handleUser(request, config) {
|
|
333
|
+
const resolvedConfigs = resolveAuthConfig(config);
|
|
334
|
+
try {
|
|
335
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
336
|
+
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
337
|
+
if (!isLoggedIn) {
|
|
338
|
+
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
|
339
|
+
}
|
|
340
|
+
const user = await civicAuth.getUser();
|
|
341
|
+
return NextResponse.json({ user });
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
logger.error("[USER_HANDLER] User endpoint error:", error);
|
|
345
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
459
348
|
//# sourceMappingURL=routeHandler.js.map
|