-
Notifications
You must be signed in to change notification settings - Fork 943
fix(site): sanitize login redirect #15208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
cc25e09
b1b1a3b
0857113
d39455a
e42fc84
237d292
5b3d48c
65d7992
b72750d
ac54ce0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,14 @@ export const LoginPage: FC = () => { | |
const navigate = useNavigate(); | ||
const { metadata } = useEmbeddedMetadata(); | ||
const buildInfoQuery = useQuery(buildInfo(metadata["build-info"])); | ||
let redirectUrl: URL | null = null; | ||
try { | ||
redirectUrl = new URL(redirectTo); | ||
} catch { | ||
// Do nothing | ||
} | ||
|
||
const isApiRouteRedirect = redirectTo.startsWith("/api/v2"); | ||
|
||
useEffect(() => { | ||
if (!buildInfoQuery.data || isSignedIn) { | ||
|
@@ -41,42 +49,19 @@ 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, | ||
}); | ||
} | ||
|
||
// 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 serve 8000 d 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 />; | ||
// 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 (isSignedIn && isApiRouteRedirect) { | ||
const sanitizedUrl = new URL(redirectTo, window.location.origin); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not work for subdomain apps though right? If they are on another domain? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They're not on another domain. The redirect from a workspace proxy subdomain app is to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll trust you 👍 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I could be wrong but that's what I always noticed in my testing. |
||
window.location.href = sanitizedUrl.pathname + sanitizedUrl.search; | ||
return null; | ||
coadler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
if (isSignedIn && !isApiRouteRedirect) { | ||
return ( | ||
<Navigate to={redirectUrl ? redirectUrl.pathname : redirectTo} replace /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I poked around a bit more of the code, and I'm just making sure: there's a second file that calls the Because right now, the control flow is set up so that the component:
Not sure how the callback logic works, but would the updates to this file catch any issues from there? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, good find. This is being sent directly to the backend which handles approving the redirect URLs, so it doesn't need any validation from the frontend. |
||
); | ||
} | ||
|
||
if (isConfiguringTheFirstUser) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
biome extension doesn't work in vscode unless you have dep in root dir 😢