mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): sanitize login redirect (#15208)
This commit is contained in:
@@ -28,6 +28,15 @@ export const LoginPage: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { metadata } = useEmbeddedMetadata();
|
||||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"]));
|
||||
let redirectError: Error | null = null;
|
||||
let redirectUrl: URL | null = null;
|
||||
try {
|
||||
redirectUrl = new URL(redirectTo);
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
const isApiRouteRedirect = redirectTo.startsWith("/api/v2");
|
||||
|
||||
useEffect(() => {
|
||||
if (!buildInfoQuery.data || isSignedIn) {
|
||||
@@ -42,41 +51,24 @@ export const LoginPage: FC = () => {
|
||||
}, [isSignedIn, buildInfoQuery.data, user?.id]);
|
||||
|
||||
if (isSignedIn) {
|
||||
if (buildInfoQuery.data) {
|
||||
// This uses `navigator.sendBeacon`, so window.href
|
||||
// will not stop the request from being sent!
|
||||
sendDeploymentEvent(buildInfoQuery.data, {
|
||||
type: "deployment_login",
|
||||
user_id: user?.id,
|
||||
});
|
||||
// The reason we need `window.location.href` for api redirects is that
|
||||
// we need the page to reload and make a request to the backend. If we
|
||||
// use `<Navigate>`, react would handle the redirect itself and never
|
||||
// request the page from the backend.
|
||||
if (isApiRouteRedirect) {
|
||||
const sanitizedUrl = new URL(redirectTo, window.location.origin);
|
||||
window.location.href = sanitizedUrl.pathname + sanitizedUrl.search;
|
||||
// Setting the href should immediately request a new page. Show an
|
||||
// error state if it doesn't.
|
||||
redirectError = new Error("unable to redirect");
|
||||
} else {
|
||||
return (
|
||||
<Navigate
|
||||
to={redirectUrl ? redirectUrl.pathname : redirectTo}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// If the redirect is going to a workspace application, and we
|
||||
// are missing authentication, then we need to change the href location
|
||||
// to trigger a HTTP request. This allows the BE to generate the auth
|
||||
// cookie required. Similarly for the OAuth2 exchange as the authorization
|
||||
// page is served by the backend.
|
||||
// If no redirect is present, then ignore this branched logic.
|
||||
if (redirectTo !== "" && redirectTo !== "/") {
|
||||
try {
|
||||
// This catches any absolute redirects. Relative redirects
|
||||
// will fail the try/catch. Subdomain apps are absolute redirects.
|
||||
const redirectURL = new URL(redirectTo);
|
||||
if (redirectURL.host !== window.location.host) {
|
||||
window.location.href = redirectTo;
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
// Do nothing
|
||||
}
|
||||
// Path based apps and OAuth2.
|
||||
if (redirectTo.includes("/apps/") || redirectTo.includes("/oauth2/")) {
|
||||
window.location.href = redirectTo;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return <Navigate to={redirectTo} replace />;
|
||||
}
|
||||
|
||||
if (isConfiguringTheFirstUser) {
|
||||
@@ -90,7 +82,7 @@ export const LoginPage: FC = () => {
|
||||
</Helmet>
|
||||
<LoginPageView
|
||||
authMethods={authMethodsQuery.data}
|
||||
error={signInError}
|
||||
error={signInError ?? redirectError}
|
||||
isLoading={isLoading || authMethodsQuery.isLoading}
|
||||
buildInfo={buildInfoQuery.data}
|
||||
isSigningIn={isSigningIn}
|
||||
@@ -98,6 +90,7 @@ export const LoginPage: FC = () => {
|
||||
await signIn(email, password);
|
||||
navigate("/");
|
||||
}}
|
||||
redirectTo={redirectTo}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import { CustomLogo } from "components/CustomLogo/CustomLogo";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { type FC, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { retrieveRedirect } from "utils/redirect";
|
||||
import { SignInForm } from "./SignInForm";
|
||||
import { TermsOfServiceLink } from "./TermsOfServiceLink";
|
||||
|
||||
@@ -16,6 +15,7 @@ export interface LoginPageViewProps {
|
||||
buildInfo?: BuildInfoResponse;
|
||||
isSigningIn: boolean;
|
||||
onSignIn: (credentials: { email: string; password: string }) => void;
|
||||
redirectTo: string;
|
||||
}
|
||||
|
||||
export const LoginPageView: FC<LoginPageViewProps> = ({
|
||||
@@ -25,9 +25,9 @@ export const LoginPageView: FC<LoginPageViewProps> = ({
|
||||
buildInfo,
|
||||
isSigningIn,
|
||||
onSignIn,
|
||||
redirectTo,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
const redirectTo = retrieveRedirect(location.search);
|
||||
// This allows messages to be displayed at the top of the sign in form.
|
||||
// Helpful for any redirects that want to inform the user of something.
|
||||
const message = new URLSearchParams(location.search).get("message");
|
||||
|
||||
Reference in New Issue
Block a user