@civic/auth 0.9.6-beta.1 → 0.10.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/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 +113 -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 +240 -341
- 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 +1 -3
- 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/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/logout.d.ts.map +1 -1
- package/dist/server/logout.js +11 -2
- package/dist/server/logout.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 +296 -17
- 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 +270 -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,12 @@
|
|
|
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";
|
|
7
|
+
import { CodeVerifier, UserStorage } from "../shared/lib/types.js";
|
|
13
8
|
import { revalidatePath } from "next/cache.js";
|
|
14
9
|
import { NextResponse } from "next/server.js";
|
|
15
|
-
import { NextServerAuthenticationRefresherImpl } from "./NextServerAuthenticationRefresherImpl.js";
|
|
16
10
|
const logger = loggers.nextjs.handlers.auth;
|
|
17
11
|
class AuthError extends Error {
|
|
18
12
|
status;
|
|
@@ -22,306 +16,176 @@ class AuthError extends Error {
|
|
|
22
16
|
this.name = "AuthError";
|
|
23
17
|
}
|
|
24
18
|
}
|
|
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
19
|
/**
|
|
65
|
-
*
|
|
66
|
-
* saving the verifier in a cookie for later use
|
|
67
|
-
* @returns {Promise<NextResponse>}
|
|
20
|
+
* Helper to convert NextRequest to UrlDetectionRequest for framework-agnostic URL handling
|
|
68
21
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
22
|
+
const toUrlDetectionRequest = (request) => ({
|
|
23
|
+
url: request.url,
|
|
24
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
25
|
+
searchParams: {
|
|
26
|
+
get: (name) => request.nextUrl.searchParams.get(name),
|
|
27
|
+
},
|
|
28
|
+
cookies: {
|
|
29
|
+
get: (name) => request.cookies.get(name),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Helper to create CivicAuth instance for a request
|
|
34
|
+
* Now handles appUrl detection for proxy environments
|
|
35
|
+
*/
|
|
36
|
+
const createCivicAuth = (request, config) => {
|
|
37
|
+
const resolvedConfig = resolveAuthConfig(config);
|
|
38
|
+
const cookieStorage = new NextjsCookieStorage({
|
|
39
|
+
...resolvedConfig.cookies?.tokens,
|
|
40
|
+
[UserStorage.USER]: resolvedConfig.cookies?.user,
|
|
41
|
+
});
|
|
42
|
+
// Convert to framework-agnostic request format
|
|
43
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
44
|
+
// Get appUrl from client (for proxy environments)
|
|
45
|
+
const clientAppUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
46
|
+
// Use baseUrl from config, then client appUrl, then request origin
|
|
47
|
+
// This matches the main branch priority: config > client > request
|
|
48
|
+
const appUrl = resolvedConfig.baseUrl ||
|
|
49
|
+
clientAppUrl ||
|
|
50
|
+
new URL(urlDetectionRequest.url).origin;
|
|
51
|
+
// Build absolute URLs using detected appUrl or request origin
|
|
52
|
+
const absoluteCallbackUrl = resolvedConfig.callbackUrl.startsWith("http")
|
|
53
|
+
? resolvedConfig.callbackUrl
|
|
54
|
+
: CivicAuth.toAbsoluteUrl(urlDetectionRequest, resolvedConfig.callbackUrl, appUrl);
|
|
55
|
+
const absoluteLogoutCallbackUrl = resolvedConfig.logoutCallbackUrl.startsWith("http")
|
|
56
|
+
? resolvedConfig.logoutCallbackUrl
|
|
57
|
+
: CivicAuth.toAbsoluteUrl(urlDetectionRequest, resolvedConfig.logoutCallbackUrl, appUrl);
|
|
58
|
+
const civicAuth = new CivicAuth(cookieStorage, {
|
|
59
|
+
clientId: resolvedConfig.clientId,
|
|
60
|
+
redirectUrl: absoluteCallbackUrl,
|
|
61
|
+
oauthServer: resolvedConfig.oauthServer,
|
|
62
|
+
postLogoutRedirectUrl: absoluteLogoutCallbackUrl,
|
|
63
|
+
loginSuccessUrl: request.url,
|
|
84
64
|
});
|
|
65
|
+
return {
|
|
66
|
+
civicAuth,
|
|
67
|
+
cookieStorage,
|
|
68
|
+
appUrl, // Return appUrl for use in other functions
|
|
69
|
+
urlDetectionRequest, // Return for use in handlers
|
|
70
|
+
};
|
|
85
71
|
};
|
|
86
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Login handler - backend OAuth login initiation endpoint
|
|
74
|
+
* Uses CivicAuth.buildLoginUrl()
|
|
75
|
+
*/
|
|
76
|
+
async function handleLogin(request, config) {
|
|
87
77
|
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
78
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
79
|
+
const frontendState = request.nextUrl.searchParams.get("state");
|
|
80
|
+
// Store appUrl in cookie if provided as query parameter
|
|
81
|
+
const appUrlFromQuery = request.nextUrl.searchParams.get("appUrl");
|
|
82
|
+
if (appUrlFromQuery) {
|
|
83
|
+
const cookieStorage = new NextjsCookieStorage(resolvedConfigs.cookies?.tokens ?? {});
|
|
84
|
+
await cookieStorage.set(CodeVerifier.APP_URL, appUrlFromQuery);
|
|
85
|
+
}
|
|
86
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
87
|
+
const url = await civicAuth.buildLoginUrl({
|
|
88
|
+
state: frontendState || undefined,
|
|
97
89
|
});
|
|
90
|
+
logger.info("[LOGIN_HANDLER] Redirecting to OAuth login URL", {
|
|
91
|
+
loginUrl: url.toString(),
|
|
92
|
+
});
|
|
93
|
+
return NextResponse.redirect(url.toString());
|
|
98
94
|
}
|
|
99
95
|
catch (error) {
|
|
100
|
-
logger.error("
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (!user) {
|
|
105
|
-
throw new AuthError("Failed to get user info", 401);
|
|
96
|
+
logger.error("[LOGIN_HANDLER] Backend login error:", error);
|
|
97
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
98
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
99
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/?error=login_failed", appUrl));
|
|
106
100
|
}
|
|
107
|
-
const userSession = new GenericUserSession(cookieStorage);
|
|
108
|
-
await userSession.set(user);
|
|
109
101
|
}
|
|
110
102
|
async function handleRefresh(request, config) {
|
|
111
103
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
112
|
-
const cookieStorage = getCookieStorageWithUserOverrides(config);
|
|
113
|
-
const userSession = new GenericUserSession(cookieStorage);
|
|
114
|
-
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
115
104
|
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");
|
|
105
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
106
|
+
await civicAuth.refreshTokens();
|
|
107
|
+
logger.info("[REFRESH_HANDLER] Tokens refreshed successfully");
|
|
108
|
+
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
133
109
|
if (targetUrl) {
|
|
134
110
|
// Success: clear the refresh attempt tracking cookie and redirect to target
|
|
135
111
|
const response = NextResponse.redirect(targetUrl);
|
|
136
112
|
response.cookies.delete("_civic_last_refresh");
|
|
137
113
|
return response;
|
|
138
114
|
}
|
|
139
|
-
|
|
140
|
-
const response = NextResponse.json({
|
|
115
|
+
return NextResponse.json({
|
|
141
116
|
status: "success",
|
|
142
|
-
|
|
117
|
+
message: "Tokens refreshed",
|
|
143
118
|
});
|
|
144
|
-
return response;
|
|
145
119
|
}
|
|
146
120
|
catch (error) {
|
|
147
|
-
logger.error("
|
|
148
|
-
|
|
149
|
-
await userSession.clear();
|
|
121
|
+
logger.error("[REFRESH_HANDLER] Token refresh error:", error);
|
|
122
|
+
const targetUrl = request.nextUrl.searchParams.get("targetUrl");
|
|
150
123
|
if (targetUrl) {
|
|
151
|
-
logger.warn("handleRefresh: Refresh failed, redirecting to targetUrl");
|
|
152
124
|
return NextResponse.redirect(targetUrl);
|
|
153
125
|
}
|
|
154
|
-
return NextResponse.json({
|
|
126
|
+
return NextResponse.json({ error: "Token refresh failed" }, { status: 500 });
|
|
155
127
|
}
|
|
156
128
|
}
|
|
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
129
|
async function handleCallback(request, config) {
|
|
235
130
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
236
131
|
const code = request.nextUrl.searchParams.get("code");
|
|
237
132
|
const state = request.nextUrl.searchParams.get("state");
|
|
133
|
+
const error = request.nextUrl.searchParams.get("error");
|
|
134
|
+
if (error) {
|
|
135
|
+
logger.error("OAuth error in callback:", error);
|
|
136
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
137
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
138
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/?error=oauth_error", appUrl));
|
|
139
|
+
}
|
|
238
140
|
if (!code || !state)
|
|
239
141
|
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", {
|
|
142
|
+
try {
|
|
143
|
+
const { civicAuth, appUrl, urlDetectionRequest } = createCivicAuth(request, resolvedConfigs);
|
|
144
|
+
// Convert NextRequest to the format expected by handleCallback
|
|
145
|
+
const handleCallbackRequest = {
|
|
146
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
147
|
+
url: request.url.toString(),
|
|
148
|
+
};
|
|
149
|
+
// Get loginSuccessUrl with proper baseUrl handling
|
|
150
|
+
const loginSuccessUrl = CivicAuth.getLoginSuccessUrl(urlDetectionRequest, appUrl);
|
|
151
|
+
const frontendUrl = loginSuccessUrl || resolvedConfigs.loginSuccessUrl || "/";
|
|
152
|
+
// Use CivicAuth's smart callback handler
|
|
153
|
+
const result = await civicAuth.handleCallback({
|
|
154
|
+
code,
|
|
267
155
|
state,
|
|
268
|
-
|
|
156
|
+
req: handleCallbackRequest,
|
|
157
|
+
}, {
|
|
158
|
+
// Pass the properly resolved frontendUrl
|
|
159
|
+
frontendUrl: frontendUrl,
|
|
269
160
|
});
|
|
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);
|
|
161
|
+
if (result.redirectTo) {
|
|
162
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, result.redirectTo, appUrl));
|
|
283
163
|
}
|
|
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;
|
|
164
|
+
if (result.content) {
|
|
165
|
+
// Handle both string content and object content
|
|
166
|
+
if (typeof result.content === "string") {
|
|
167
|
+
return new NextResponse(result.content, {
|
|
168
|
+
status: 200,
|
|
169
|
+
headers: {
|
|
170
|
+
"Content-Type": "text/html",
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Object content (JSON response)
|
|
176
|
+
return NextResponse.json(result.content);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Fallback redirect
|
|
180
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/", appUrl));
|
|
307
181
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return redirectTarget;
|
|
182
|
+
catch (error) {
|
|
183
|
+
logger.error("[CALLBACK_HANDLER] OAuth callback error:", error);
|
|
184
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
185
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
186
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, "/?error=callback_failed", appUrl));
|
|
314
187
|
}
|
|
315
|
-
|
|
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
|
-
};
|
|
188
|
+
}
|
|
325
189
|
const revalidateUrlPath = async (url) => {
|
|
326
190
|
try {
|
|
327
191
|
const path = new URL(url).pathname;
|
|
@@ -333,86 +197,98 @@ const revalidateUrlPath = async (url) => {
|
|
|
333
197
|
};
|
|
334
198
|
export async function handleLogout(request, config) {
|
|
335
199
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
336
|
-
//
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// read the id_token from the cookies
|
|
341
|
-
const idToken = await getIdToken(resolvedConfigs);
|
|
342
|
-
// read the state from the query parameters
|
|
200
|
+
// Get framework-agnostic request for URL utilities
|
|
201
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
202
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
203
|
+
// Read the state from the query parameters
|
|
343
204
|
const state = request.nextUrl.searchParams.get("state");
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
205
|
+
const clientLogoutRedirectUrl = request.nextUrl.searchParams.get("logoutRedirectUrl");
|
|
206
|
+
try {
|
|
207
|
+
logger.info("[LOGOUT_HANDLER] Backend logout endpoint called");
|
|
208
|
+
// If client provided a logoutRedirectUrl, override the config
|
|
209
|
+
let configToUse = resolvedConfigs;
|
|
210
|
+
if (clientLogoutRedirectUrl) {
|
|
211
|
+
configToUse = {
|
|
212
|
+
...resolvedConfigs,
|
|
213
|
+
logoutCallbackUrl: clientLogoutRedirectUrl,
|
|
214
|
+
};
|
|
215
|
+
logger.info("[LOGOUT_HANDLER] Overriding logout callback URL", {
|
|
216
|
+
original: resolvedConfigs.logoutCallbackUrl,
|
|
217
|
+
override: clientLogoutRedirectUrl,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const { civicAuth } = createCivicAuth(request, configToUse);
|
|
221
|
+
// Always redirect to OAuth logout (like main branch)
|
|
222
|
+
// Don't validate session - even invalid local sessions should hit OAuth logout
|
|
223
|
+
logger.info("[LOGOUT_HANDLER] Processing logout request", {
|
|
224
|
+
state: !!state,
|
|
348
225
|
});
|
|
349
|
-
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
226
|
+
// Always redirect to OAuth logout endpoint (like main branch)
|
|
227
|
+
// Client-side iframe logic will handle completion and redirect appropriately
|
|
228
|
+
const logoutUrl = await civicAuth.buildLogoutRedirectUrl({
|
|
229
|
+
state: state || undefined,
|
|
230
|
+
});
|
|
231
|
+
try {
|
|
232
|
+
await civicAuth.clearTokens();
|
|
233
|
+
// Revalidate current path to update authentication state in server components
|
|
234
|
+
await revalidateUrlPath(request.url);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
logger.error("[LOGOUT_HANDLER] Error clearing tokens:", error);
|
|
238
|
+
}
|
|
239
|
+
// Remove state parameter from logout URL to prevent it from appearing in frontend URL
|
|
240
|
+
const cleanLogoutUrl = new URL(logoutUrl);
|
|
241
|
+
cleanLogoutUrl.searchParams.delete("state");
|
|
242
|
+
logger.info("[LOGOUT_HANDLER] Redirecting to OAuth logout endpoint", {
|
|
243
|
+
logoutUrl: cleanLogoutUrl.toString(),
|
|
244
|
+
});
|
|
245
|
+
return NextResponse.redirect(cleanLogoutUrl.toString());
|
|
354
246
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
//
|
|
247
|
+
catch (error) {
|
|
248
|
+
logger.error("[LOGOUT_HANDLER] Logout error:", error);
|
|
249
|
+
// If logout URL generation fails, clear tokens and redirect to home
|
|
358
250
|
await clearAuthCookies();
|
|
359
|
-
|
|
251
|
+
const fallbackUrl = clientLogoutRedirectUrl || resolvedConfigs.logoutCallbackUrl;
|
|
252
|
+
const finalFallbackUrl = CivicAuth.toAbsoluteUrl(urlDetectionRequest, fallbackUrl, appUrl);
|
|
253
|
+
return NextResponse.redirect(finalFallbackUrl);
|
|
360
254
|
}
|
|
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
255
|
}
|
|
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,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
// 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.
|
|
385
|
-
const user = await getUser();
|
|
386
|
-
if (!user && !!state && displayModeFromState(state, "iframe") === "iframe") {
|
|
387
|
-
// User is logged out while in an iframe redirect (not a FETCH call).
|
|
388
|
-
// Render some text to make the CivicLogoutIframeContainer redirect to the postLogoutRedirectUrl.
|
|
389
|
-
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>`);
|
|
390
|
-
response.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
391
|
-
logger.debug("handleLogoutComplete: iframe mode, rendering HTML with logout success text", { postLogoutRedirectUrl });
|
|
392
|
-
return response;
|
|
393
|
-
}
|
|
394
|
-
logger.debug("handleLogoutComplete: redirecting to postLogoutRedirectUrl", {
|
|
395
|
-
postLogoutRedirectUrl,
|
|
396
|
-
});
|
|
397
|
-
revalidateUrlPath(postLogoutRedirectUrl);
|
|
398
|
-
return NextResponse.redirect(postLogoutRedirectUrl);
|
|
399
|
-
};
|
|
400
256
|
export async function handleLogoutCallback(request, config) {
|
|
401
257
|
const resolvedConfigs = resolveAuthConfig(config);
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
logger.debug("handleLogoutCallback can access cookies: clearing auth cookies");
|
|
258
|
+
try {
|
|
259
|
+
logger.info("[LOGOUT_CALLBACK_HANDLER] Backend logout callback endpoint called");
|
|
260
|
+
// Clear authentication cookies
|
|
406
261
|
await clearAuthCookies();
|
|
407
|
-
|
|
262
|
+
// Get framework-agnostic request and create CivicAuth instance
|
|
263
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
264
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
265
|
+
// Get the state parameter for iframe detection
|
|
266
|
+
const state = request.nextUrl.searchParams.get("state");
|
|
267
|
+
// If this is an iframe request, return HTML with logout success signal
|
|
268
|
+
if (state && displayModeFromState(state, "iframe") === "iframe") {
|
|
269
|
+
// For iframe mode, include the post-logout redirect URL in the HTML
|
|
270
|
+
const postLogoutRedirectUrl = civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);
|
|
271
|
+
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>`);
|
|
272
|
+
response.headers.set("Content-Type", "text/html; charset=utf-8");
|
|
273
|
+
logger.info("[LOGOUT_CALLBACK_HANDLER] Returning iframe logout success HTML", { postLogoutRedirectUrl });
|
|
274
|
+
return response;
|
|
275
|
+
}
|
|
276
|
+
// For non-iframe requests, redirect to the logout callback URL or post-logout URL
|
|
277
|
+
const redirectUrl = civicAuth.getPostLogoutRedirectUrl(urlDetectionRequest);
|
|
278
|
+
logger.info("[LOGOUT_CALLBACK_HANDLER] Redirecting to logout callback URL", {
|
|
279
|
+
logoutCallbackUrl: resolvedConfigs.logoutCallbackUrl,
|
|
280
|
+
redirectUrl,
|
|
281
|
+
});
|
|
282
|
+
// Revalidate the redirect path to update authentication state in server components
|
|
283
|
+
await revalidateUrlPath(redirectUrl);
|
|
284
|
+
return NextResponse.redirect(redirectUrl);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
logger.error("[LOGOUT_CALLBACK_HANDLER] Logout callback error:", error);
|
|
288
|
+
const urlDetectionRequest = toUrlDetectionRequest(request);
|
|
289
|
+
const appUrl = CivicAuth.getAppUrl(urlDetectionRequest);
|
|
290
|
+
return NextResponse.redirect(CivicAuth.toAbsoluteUrl(urlDetectionRequest, resolvedConfigs.logoutCallbackUrl, appUrl));
|
|
408
291
|
}
|
|
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
292
|
}
|
|
417
293
|
/**
|
|
418
294
|
* Creates an authentication handler for Next.js API routes
|
|
@@ -434,7 +310,8 @@ export const handler = (authConfig = {}) => async (request) => {
|
|
|
434
310
|
const lastSegment = pathSegments[pathSegments.length - 1];
|
|
435
311
|
switch (lastSegment) {
|
|
436
312
|
case "challenge":
|
|
437
|
-
|
|
313
|
+
case "login":
|
|
314
|
+
return await handleLogin(request, config);
|
|
438
315
|
case "callback":
|
|
439
316
|
return await handleCallback(request, config);
|
|
440
317
|
case "refresh":
|
|
@@ -443,6 +320,8 @@ export const handler = (authConfig = {}) => async (request) => {
|
|
|
443
320
|
return await handleLogout(request, config);
|
|
444
321
|
case "logoutcallback":
|
|
445
322
|
return await handleLogoutCallback(request, config);
|
|
323
|
+
case "user":
|
|
324
|
+
return await handleUser(request, config);
|
|
446
325
|
default:
|
|
447
326
|
throw new AuthError(`Invalid auth route: ${pathname}`, 404);
|
|
448
327
|
}
|
|
@@ -456,4 +335,24 @@ export const handler = (authConfig = {}) => async (request) => {
|
|
|
456
335
|
return response;
|
|
457
336
|
}
|
|
458
337
|
};
|
|
338
|
+
/**
|
|
339
|
+
* User endpoint - returns current user data as JSON
|
|
340
|
+
* Uses CivicAuth.isLoggedIn() and getUser()
|
|
341
|
+
*/
|
|
342
|
+
async function handleUser(request, config) {
|
|
343
|
+
const resolvedConfigs = resolveAuthConfig(config);
|
|
344
|
+
try {
|
|
345
|
+
const { civicAuth } = createCivicAuth(request, resolvedConfigs);
|
|
346
|
+
const isLoggedIn = await civicAuth.isLoggedIn();
|
|
347
|
+
if (!isLoggedIn) {
|
|
348
|
+
return NextResponse.json({ error: "Not authenticated" }, { status: 401 });
|
|
349
|
+
}
|
|
350
|
+
const user = await civicAuth.getUser();
|
|
351
|
+
return NextResponse.json({ user });
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
logger.error("[USER_HANDLER] User endpoint error:", error);
|
|
355
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
459
358
|
//# sourceMappingURL=routeHandler.js.map
|