diff --git a/src/stores/login.ts b/src/stores/login.ts index 6ab13ec..c85d460 100644 --- a/src/stores/login.ts +++ b/src/stores/login.ts @@ -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; } diff --git a/tests/stores/login.test.js b/tests/stores/login.test.js index b0619c5..87bd233 100644 --- a/tests/stores/login.test.js +++ b/tests/stores/login.test.js @@ -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"));