diff --git a/client/src/components/Login/LoginForm.vue b/client/src/components/Login/LoginForm.vue index 4ad09ebb0d57..c57d496e9cbb 100644 --- a/client/src/components/Login/LoginForm.vue +++ b/client/src/components/Login/LoginForm.vue @@ -97,7 +97,7 @@ async function submitLogin() { } if (response.data.expired_user) { - window.location.href = withPrefix(`/root/login?expired_user=${response.data.expired_user}`); + window.location.href = withPrefix(`/login/start?expired_user=${response.data.expired_user}`); } else if (connectExternalProvider.value) { window.location.href = withPrefix("/user/external_ids?connect_external=true"); } else if (response.data.redirect) { @@ -125,20 +125,6 @@ function setRedirect(url: string) { localStorage.setItem("redirect_url", url); } -async function resetLogin() { - loading.value = true; - try { - const response = await axios.post(withPrefix("/user/reset_password"), { email: login.value }); - messageVariant.value = "info"; - messageText.value = response.data.message; - } catch (e) { - messageVariant.value = "danger"; - messageText.value = errorMessageAsString(e, "Password reset failed for an unknown reason."); - } finally { - loading.value = false; - } -} - function returnToLogin() { router.push("/login/start"); } @@ -205,7 +191,12 @@ function returnToLogin() { v-localize href="javascript:void(0)" role="button" - @click.prevent="resetLogin"> + @click.prevent=" + router.push({ + path: '/login/reset_password', + query: { email: login }, + }) + "> Click here to reset your password. diff --git a/client/src/entry/analysis/modules/ResetPassword.test.ts b/client/src/entry/analysis/modules/ResetPassword.test.ts new file mode 100644 index 000000000000..d172a1ecdce5 --- /dev/null +++ b/client/src/entry/analysis/modules/ResetPassword.test.ts @@ -0,0 +1,96 @@ +import { getLocalVue } from "@tests/jest/helpers"; +import { mount } from "@vue/test-utils"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; + +import ResetPassword from "./ResetPassword.vue"; + +const localVue = getLocalVue(true); + +const mockRouter = (query: object) => ({ + currentRoute: { + query, + }, +}); + +function mountResetPassword(routerQuery: object = {}) { + return mount(ResetPassword, { + localVue, + attachTo: document.body, + mocks: { + $router: mockRouter(routerQuery), + }, + }); +} + +describe("ResetPassword", () => { + it("query", async () => { + const email = "test"; + const wrapper = mountResetPassword({ email: "test" }); + + const emailField = wrapper.find("#reset-email"); + const emailValue = (emailField.element as HTMLInputElement).value; + expect(emailValue).toBe(email); + }); + + it("button text", async () => { + const wrapper = mountResetPassword(); + const submitButton = wrapper.find("#reset-password"); + (expect(submitButton.text()) as any).toBeLocalizationOf("Send password reset email"); + }); + + it("validate email", async () => { + const wrapper = mountResetPassword(); + const submitButton = wrapper.find("#reset-password"); + const emailField = wrapper.find("#reset-email"); + const emailElement = emailField.element as HTMLInputElement; + + let email = ""; + await emailField.setValue(email); + expect(emailElement.value).toBe(email); + await submitButton.trigger("click"); + expect(emailElement.checkValidity()).toBe(false); + + email = "test"; + await emailField.setValue(email); + expect(emailElement.value).toBe(email); + await submitButton.trigger("click"); + expect(emailElement.checkValidity()).toBe(false); + + email = "test@test.com"; + await emailField.setValue(email); + expect(emailElement.value).toBe(email); + await submitButton.trigger("click"); + expect(emailElement.checkValidity()).toBe(true); + }); + + it("display success message", async () => { + const wrapper = mountResetPassword({ email: "test@test.com" }); + const mockAxios = new MockAdapter(axios); + const submitButton = wrapper.find("#reset-password"); + + mockAxios.onPost("/user/reset_password").reply(200, { + message: "Reset link has been sent to your email.", + }); + await submitButton.trigger("click"); + setTimeout(async () => { + const alertSuccess = wrapper.find("#reset-password-alert"); + expect(alertSuccess.text()).toBe("Reset link has been sent to your email."); + }); + }); + + it("display error message", async () => { + const wrapper = mountResetPassword({ email: "test@test.com" }); + const submitButton = wrapper.find("#reset-password"); + + const mockAxios = new MockAdapter(axios); + mockAxios.onPost("/user/reset_password").reply(400, { + err_msg: "Please provide your email.", + }); + await submitButton.trigger("click"); + setTimeout(async () => { + const alertError = wrapper.find("#reset-password-alert"); + expect(alertError.text()).toBe("Please provide your email."); + }); + }); +}); diff --git a/client/src/entry/analysis/modules/ResetPassword.vue b/client/src/entry/analysis/modules/ResetPassword.vue new file mode 100644 index 000000000000..716626ba2f46 --- /dev/null +++ b/client/src/entry/analysis/modules/ResetPassword.vue @@ -0,0 +1,56 @@ + + + + + + + + + + {{ message }} + + + + + + + + Send password reset email + + + + + + + diff --git a/client/src/entry/analysis/router.js b/client/src/entry/analysis/router.js index 43a38292efa2..f02004f16171 100644 --- a/client/src/entry/analysis/router.js +++ b/client/src/entry/analysis/router.js @@ -45,6 +45,7 @@ import Analysis from "entry/analysis/modules/Analysis"; import CenterFrame from "entry/analysis/modules/CenterFrame"; import Home from "entry/analysis/modules/Home"; import Login from "entry/analysis/modules/Login"; +import ResetPassword from "entry/analysis/modules/ResetPassword"; import WorkflowEditorModule from "entry/analysis/modules/WorkflowEditor"; import AdminRoutes from "entry/analysis/routes/admin-routes"; import LibraryRoutes from "entry/analysis/routes/library-routes"; @@ -125,6 +126,13 @@ export function getRouter(Galaxy) { component: Login, redirect: redirectLoggedIn(), }, + /** Login entry route */ + { + path: "/login/reset_password", + component: ResetPassword, + props: (route) => ({ email: route.query.email }), + redirect: redirectLoggedIn(), + }, /** Page editor */ { path: "/pages/editor",