Handle invalid return-to payloads without misclassifying login as failed

Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
2026-03-08 13:52:47 +08:00
parent d5464ebc2d
commit 2721aed928
2 changed files with 25 additions and 5 deletions

View File

@@ -71,16 +71,20 @@ export const useLoginStore = defineStore("loginStore", {
// However, if the user pasted a URL while not logged in,
// redirect them to the remembered return-to URL after login.
if (this.rememberedReturnToUrl !== "") {
const decodedUrl = atob(this.rememberedReturnToUrl);
let decodedUrl = "";
try {
decodedUrl = atob(this.rememberedReturnToUrl);
} catch {
this.$router.push("/files");
return;
}
// Only allow relative paths to prevent open redirect attacks
if (decodedUrl.startsWith("/") && !decodedUrl.startsWith("//")) {
window.location.href = decodedUrl;
} else {
this.$router.push("/files");
return;
}
} else {
this.$router.push("/files");
}
this.$router.push("/files");
} catch (error) {
this.isInvalid = true;
}

View File

@@ -130,6 +130,22 @@ describe("loginStore", () => {
window.location = originalLocation;
});
it("falls back to /files when return-to is not valid base64", async () => {
axios.post.mockResolvedValue({
data: {
access_token: "token",
refresh_token: "refresh",
},
});
store.rememberedReturnToUrl = "@@@not-base64@@@";
await store.signIn();
expect(store.isLoggedIn).toBe(true);
expect(store.isInvalid).toBe(false);
expect(store.$router.push).toHaveBeenCalledWith("/files");
});
it("sets isInvalid on error", async () => {
axios.post.mockRejectedValue(new Error("401"));