Skip to content

Commit 379ced6

Browse files
sreyacoadler
andauthored
fix(site): sanitize login redirect (coder#15208) (coder#15219)
Co-authored-by: Colin Adler <colin1adler@gmail.com>
1 parent 971b1a8 commit 379ced6

File tree

2 files changed

+30
-37
lines changed

2 files changed

+30
-37
lines changed

site/src/pages/LoginPage/LoginPage.tsx

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ export const LoginPage: FC = () => {
2828
const navigate = useNavigate();
2929
const { metadata } = useEmbeddedMetadata();
3030
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
31+
let redirectError: Error | null = null;
32+
let redirectUrl: URL | null = null;
33+
try {
34+
redirectUrl = new URL(redirectTo);
35+
} catch {
36+
// Do nothing
37+
}
38+
39+
const isApiRouteRedirect = redirectTo.startsWith("/api/v2");
3140

3241
useEffect(() => {
3342
if (!buildInfoQuery.data || isSignedIn) {
@@ -42,41 +51,24 @@ export const LoginPage: FC = () => {
4251
}, [isSignedIn, buildInfoQuery.data, user?.id]);
4352

4453
if (isSignedIn) {
45-
if (buildInfoQuery.data) {
46-
// This uses `navigator.sendBeacon`, so window.href
47-
// will not stop the request from being sent!
48-
sendDeploymentEvent(buildInfoQuery.data, {
49-
type: "deployment_login",
50-
user_id: user?.id,
51-
});
54+
// The reason we need `window.location.href` for api redirects is that
55+
// we need the page to reload and make a request to the backend. If we
56+
// use `<Navigate>`, react would handle the redirect itself and never
57+
// request the page from the backend.
58+
if (isApiRouteRedirect) {
59+
const sanitizedUrl = new URL(redirectTo, window.location.origin);
60+
window.location.href = sanitizedUrl.pathname + sanitizedUrl.search;
61+
// Setting the href should immediately request a new page. Show an
62+
// error state if it doesn't.
63+
redirectError = new Error("unable to redirect");
64+
} else {
65+
return (
66+
<Navigate
67+
to={redirectUrl ? redirectUrl.pathname : redirectTo}
68+
replace
69+
/>
70+
);
5271
}
53-
54-
// If the redirect is going to a workspace application, and we
55-
// are missing authentication, then we need to change the href location
56-
// to trigger a HTTP request. This allows the BE to generate the auth
57-
// cookie required. Similarly for the OAuth2 exchange as the authorization
58-
// page is served by the backend.
59-
// If no redirect is present, then ignore this branched logic.
60-
if (redirectTo !== "" && redirectTo !== "/") {
61-
try {
62-
// This catches any absolute redirects. Relative redirects
63-
// will fail the try/catch. Subdomain apps are absolute redirects.
64-
const redirectURL = new URL(redirectTo);
65-
if (redirectURL.host !== window.location.host) {
66-
window.location.href = redirectTo;
67-
return null;
68-
}
69-
} catch {
70-
// Do nothing
71-
}
72-
// Path based apps and OAuth2.
73-
if (redirectTo.includes("/apps/") || redirectTo.includes("/oauth2/")) {
74-
window.location.href = redirectTo;
75-
return null;
76-
}
77-
}
78-
79-
return <Navigate to={redirectTo} replace />;
8072
}
8173

8274
if (isConfiguringTheFirstUser) {
@@ -90,14 +82,15 @@ export const LoginPage: FC = () => {
9082
</Helmet>
9183
<LoginPageView
9284
authMethods={authMethodsQuery.data}
93-
error={signInError}
85+
error={signInError ?? redirectError}
9486
isLoading={isLoading || authMethodsQuery.isLoading}
9587
buildInfo={buildInfoQuery.data}
9688
isSigningIn={isSigningIn}
9789
onSignIn={async ({ email, password }) => {
9890
await signIn(email, password);
9991
navigate("/");
10092
}}
93+
redirectTo={redirectTo}
10194
/>
10295
</>
10396
);

site/src/pages/LoginPage/LoginPageView.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { Loader } from "components/Loader/Loader";
66
import { type FC, useState } from "react";
77
import { useLocation } from "react-router-dom";
88
import { getApplicationName, getLogoURL } from "utils/appearance";
9-
import { retrieveRedirect } from "utils/redirect";
109
import { SignInForm } from "./SignInForm";
1110
import { TermsOfServiceLink } from "./TermsOfServiceLink";
1211

@@ -17,6 +16,7 @@ export interface LoginPageViewProps {
1716
buildInfo?: BuildInfoResponse;
1817
isSigningIn: boolean;
1918
onSignIn: (credentials: { email: string; password: string }) => void;
19+
redirectTo: string;
2020
}
2121

2222
export const LoginPageView: FC<LoginPageViewProps> = ({
@@ -26,9 +26,9 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
2626
buildInfo,
2727
isSigningIn,
2828
onSignIn,
29+
redirectTo,
2930
}) => {
3031
const location = useLocation();
31-
const redirectTo = retrieveRedirect(location.search);
3232
// This allows messages to be displayed at the top of the sign in form.
3333
// Helpful for any redirects that want to inform the user of something.
3434
const message = new URLSearchParams(location.search).get("message");

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy