Compare commits

...

3 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
cbf79b7d13 Add redirect URL validation to prevent open redirect vulnerability
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-01-28 15:49:04 +00:00
copilot-swe-agent[bot]
d4a79098ba Add support for redirect parameter in login flow
Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com>
2026-01-28 15:41:51 +00:00
copilot-swe-agent[bot]
0218c797eb Initial plan 2026-01-28 15:36:09 +00:00
6 changed files with 68 additions and 3 deletions

View File

@@ -151,7 +151,18 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
resp = &Response{Status: "ok", Msg: "", Data: userId, Data3: user.NeedUpdatePassword}
// Validate redirect URL if provided
validatedRedirect := ""
if form.Redirect != "" {
if application.IsRedirectUriValid(form.Redirect) {
validatedRedirect = form.Redirect
} else {
util.LogInfo(c.Ctx, "Invalid redirect URL: %s", form.Redirect)
}
}
resp = &Response{Status: "ok", Msg: "", Data: userId, Data2: validatedRedirect, Data3: user.NeedUpdatePassword}
} else if form.Type == ResponseTypeCode {
clientId := c.Ctx.Input.Query("clientId")
responseType := c.Ctx.Input.Query("responseType")

View File

@@ -73,6 +73,8 @@ type AuthForm struct {
FaceId []float64 `json:"faceId"`
FaceIdImage []string `json:"faceIdImage"`
UserCode string `json:"userCode"`
Redirect string `json:"redirect"`
}
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {

View File

@@ -56,6 +56,14 @@ class EntryPage extends React.Component {
renderLoginIfNotLoggedIn(component) {
if (this.props.account === null) {
sessionStorage.setItem("from", window.location.pathname);
// Preserve redirect parameter if present and valid
const params = new URLSearchParams(window.location.search);
const redirectParam = params.get("redirect");
if (redirectParam !== null && redirectParam !== "" && Setting.isValidRedirectUrl(redirectParam)) {
return <Redirect to={`/login?redirect=${encodeURIComponent(redirectParam)}`} />;
}
return <Redirect to="/login" />;
} else if (this.props.account === undefined) {
return null;

View File

@@ -1683,7 +1683,44 @@ export function getRandomNumber() {
return Math.random().toString(10).slice(-11);
}
export function getFromLink() {
export function isValidRedirectUrl(url) {
// Basic validation to prevent obvious security issues
// Note: Primary validation is done on the backend
if (!url || url === "") {
return false;
}
// Allow relative URLs (same-domain)
if (url.startsWith("/")) {
return true;
}
// For absolute URLs, check if they are valid HTTP/HTTPS
try {
const urlObj = new URL(url);
return urlObj.protocol === "http:" || urlObj.protocol === "https:";
} catch (e) {
return false;
}
}
export function getFromLink(redirectUrl) {
// First check if a redirect URL was passed (e.g., from login response)
// The backend should have already validated this, but we check basic validity
if (redirectUrl !== null && redirectUrl !== undefined && redirectUrl !== "") {
if (isValidRedirectUrl(redirectUrl)) {
return redirectUrl;
}
}
// Fall back to URL parameter 'redirect'
const params = new URLSearchParams(window.location.search);
const redirectParam = params.get("redirect");
if (redirectParam !== null && redirectParam !== "" && isValidRedirectUrl(redirectParam)) {
return redirectParam;
}
// Finally, fall back to sessionStorage
const from = sessionStorage.getItem("from");
if (from === null) {
return "/";

View File

@@ -215,7 +215,7 @@ class AuthCallback extends React.Component {
}
Setting.showMessage("success", "Logged in successfully");
// Setting.goToLinkSoft(this, "/");
const link = Setting.getFromLink();
const link = Setting.getFromLink(res.data2);
Setting.goToLink(link);
} else if (responseType === "code") {
if (res.data3) {

View File

@@ -341,6 +341,13 @@ class LoginPage extends React.Component {
values["type"] = "saml";
values["relayState"] = oAuthParams.relayState;
}
// Add redirect parameter if present and valid
const params = new URLSearchParams(this.props.location.search);
const redirectParam = params.get("redirect");
if (redirectParam !== null && redirectParam !== "" && Setting.isValidRedirectUrl(redirectParam)) {
values["redirect"] = redirectParam;
}
}
sendPopupData(message, redirectUri) {