From 32c2ed7a30dcc91c3f946f6558f2a5dfd730c68b Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Tue, 28 May 2024 19:44:07 -0500 Subject: [PATCH 1/2] CLDR-7405 Forgotten Password flow - remove some dead code from LoginButton - add a new ResetPassword and CldrLogin panels - add a new option to the KeepLoggedInManager to generate a 1-hour token for use in email At present the flow is that if you forgot your password, you will be given an option to send a link in the mail. For smoketest, the link shows up on the console for testing. Currently, the link takes you to a panel where you can remain logged into the Survey Tool. In the future we can add logic to allow changing the password from there. --- tools/cldr-apps/js/src/esm/cldrComponents.mjs | 2 + tools/cldr-apps/js/src/esm/cldrLoad.mjs | 39 ++++++++ tools/cldr-apps/js/src/esm/cldrStatus.mjs | 16 ++++ tools/cldr-apps/js/src/esm/cldrText.mjs | 2 + tools/cldr-apps/js/src/esm/cldrVueMap.mjs | 6 +- tools/cldr-apps/js/src/views/CldrLogin.vue | 73 +++++++++++++++ tools/cldr-apps/js/src/views/LoginButton.vue | 38 ++++++-- .../cldr-apps/js/src/views/ResetPassword.vue | 60 ++++++++++++ .../unicode/cldr/web/KeepLoggedInManager.java | 16 ++++ .../org/unicode/cldr/web/UserRegistry.java | 26 ++++++ .../java/org/unicode/cldr/web/api/Auth.java | 91 ++++++++++++++++++- .../unicode/cldr/web/api/LoginRequest.java | 2 + .../unicode/cldr/web/api/LoginResponse.java | 12 ++- .../unicode/cldr/web/api/ResetRequest.java | 8 ++ 14 files changed, 373 insertions(+), 18 deletions(-) create mode 100644 tools/cldr-apps/js/src/views/CldrLogin.vue create mode 100644 tools/cldr-apps/js/src/views/ResetPassword.vue create mode 100644 tools/cldr-apps/src/main/java/org/unicode/cldr/web/api/ResetRequest.java diff --git a/tools/cldr-apps/js/src/esm/cldrComponents.mjs b/tools/cldr-apps/js/src/esm/cldrComponents.mjs index 80dcb462ec2..8c236532436 100644 --- a/tools/cldr-apps/js/src/esm/cldrComponents.mjs +++ b/tools/cldr-apps/js/src/esm/cldrComponents.mjs @@ -9,6 +9,7 @@ import { App } from "vue"; // local components import CldrError from "../views/CldrError.vue"; +import CldrUser from "../views/CldrUser.vue"; import CldrValue from "../views/CldrValue.vue"; import LoginButton from "../views/LoginButton.vue"; import OverallErrors from "../views/OverallErrors.vue"; @@ -87,6 +88,7 @@ function setup(app) { app.component("cldr-overall-errors", OverallErrors); app.component("cldr-report-response", ReportResponse); app.component("cldr-searchbutton", SearchButton); + app.component("cldr-user", CldrUser); app.component("cldr-value", CldrValue); } diff --git a/tools/cldr-apps/js/src/esm/cldrLoad.mjs b/tools/cldr-apps/js/src/esm/cldrLoad.mjs index bc363df18dc..d9d14ea473e 100644 --- a/tools/cldr-apps/js/src/esm/cldrLoad.mjs +++ b/tools/cldr-apps/js/src/esm/cldrLoad.mjs @@ -5,6 +5,7 @@ import * as cldrAccount from "./cldrAccount.mjs"; import * as cldrAdmin from "./cldrAdmin.mjs"; import * as cldrAjax from "./cldrAjax.mjs"; import * as cldrBulkClosePosts from "./cldrBulkClosePosts.mjs"; +import * as cldrClient from "./cldrClient.mjs"; import * as cldrCoverage from "./cldrCoverage.mjs"; import * as cldrCreateLogin from "./cldrCreateLogin.mjs"; import * as cldrDom from "./cldrDom.mjs"; @@ -134,6 +135,8 @@ function doHashChange(event) { function parseHashAndUpdate(hash) { if (hash) { const pieces = hash.split("/"); + // always set the pieces + cldrStatus.setCurrentPieces(pieces); // pieces[1] is ALWAYS assumed to be locale or empty if (pieces.length > 1) { cldrStatus.setCurrentLocale(pieces[1]); // could be null @@ -159,6 +162,7 @@ function parseHashAndUpdate(hash) { cldrStatus.setCurrentId(""); cldrStatus.setCurrentPage(""); cldrStatus.setCurrentSection(""); + cldrStatus.setCurrentPieces([]); } updateWindowTitle(); @@ -1132,6 +1136,39 @@ function linkToLocale(subLoc) { ); } +/** + * Login with emailed jwt. returns {sessionId, user} + * @param {String} jwt + */ +async function loginWithJwt(jwt) { + const client = await cldrClient.getClient(); + + const r = await client.apis.auth.login( + { remember: true }, + { requestBody: { jwt } } + ); + if (!r) { + throw Error(`Error: login() did not return a value.`); + } + const { body } = r; + if (!body) { + throw Error(`Error: body was not returned from login() API`); + } + return body; +} + +/** + * Send a reset hash + * @param {String} email + * @param {String} session + */ +async function sendResetHash(email, session) { + const client = await cldrClient.getClient(); + + const r = await client.apis.auth.reset({}, { requestBody: { email } }); + return; +} + export { appendLocaleLink, continueInitializing, @@ -1147,10 +1184,12 @@ export { insertLocaleSpecialNote, linkToLocale, localeSpecialNote, + loginWithJwt, myLoad, parseHashAndUpdate, reloadV, replaceHash, + sendResetHash, setLoading, setTheLocaleMap, showCurrentId, diff --git a/tools/cldr-apps/js/src/esm/cldrStatus.mjs b/tools/cldr-apps/js/src/esm/cldrStatus.mjs index da6b0d94d29..8c453a889d3 100644 --- a/tools/cldr-apps/js/src/esm/cldrStatus.mjs +++ b/tools/cldr-apps/js/src/esm/cldrStatus.mjs @@ -9,6 +9,7 @@ const refs = { surveyUser: ref(null), currentId: ref(null), sessionId: ref(null), + currentPieces: ref(null), }; /** @@ -144,6 +145,18 @@ function setContextPath(path) { } } +/** An array of additional pieces of the hashes. */ +let currentPieces = []; + +function setCurrentPieces(pieces) { + currentPieces = pieces; + setRef("currentPieces", pieces); +} + +function getCurrentPieces() { + return currentPieces; +} + /** * A string such as '' (empty), or '821c2a2fc5c206d' (identifying an xpath), * or '12345' (identifying a user) or other string (identifying a forum post) @@ -154,6 +167,7 @@ function getCurrentId() { return currentId; } +/** This also calls setPieces([]) */ function setCurrentId(id) { if (!id) { currentId = ""; @@ -472,6 +486,7 @@ export { getCurrentLocale, getCurrentLocaleName, getCurrentPage, + getCurrentPieces, getCurrentSection, getCurrentSpecial, getIsPhaseBeta, @@ -498,6 +513,7 @@ export { setCurrentLocale, setCurrentLocaleName, setCurrentPage, + setCurrentPieces, setCurrentSection, setCurrentSpecial, setIsDisconnected, diff --git a/tools/cldr-apps/js/src/esm/cldrText.mjs b/tools/cldr-apps/js/src/esm/cldrText.mjs index 9e8faa7f4e3..baed7d2bedc 100644 --- a/tools/cldr-apps/js/src/esm/cldrText.mjs +++ b/tools/cldr-apps/js/src/esm/cldrText.mjs @@ -490,11 +490,13 @@ const strings = { special_list_emails: "List Email Addresses", special_list_users: "List Users", special_locales: "Locale List", + special_login: "Login from Email Link", special_lock_account: "Lock (Disable) My Account", special_lookup: "Look up a code or xpath", special_mail: "Notifications (SMOKETEST ONLY)", special_menu: "☰", special_oldvotes: "Import Old Votes", + special_reset: "Forgot Password", special_upload: "Upload (Bulk Import)", // The special_r_* are the names of reports, used by ReportResponse.vue and others special_r_compact: "Numbers", diff --git a/tools/cldr-apps/js/src/esm/cldrVueMap.mjs b/tools/cldr-apps/js/src/esm/cldrVueMap.mjs index 5254489a8de..172aa8f3056 100644 --- a/tools/cldr-apps/js/src/esm/cldrVueMap.mjs +++ b/tools/cldr-apps/js/src/esm/cldrVueMap.mjs @@ -1,12 +1,14 @@ import AboutPanel from "../views/AboutPanel.vue"; -import AnnouncePanel from "../views/AnnouncePanel.vue"; import AddUser from "../views/AddUser.vue"; +import AnnouncePanel from "../views/AnnouncePanel.vue"; import AutoImport from "../views/AutoImport.vue"; +import CldrLogin from "../views/CldrLogin.vue"; import DowngradedVotes from "../views/DowngradedVotes.vue"; import GeneralInfo from "../views/GeneralInfo.vue"; import LockAccount from "../views/LockAccount.vue"; import LookUp from "../views/LookUp.vue"; import MainMenu from "../views/MainMenu.vue"; +import ResetPassword from "../views/ResetPassword.vue"; import TestPanel from "../views/TestPanel.vue"; import TransferVotes from "../views/TransferVotes.vue"; import UnknownPanel from "../views/UnknownPanel.vue"; @@ -26,8 +28,10 @@ const specialToComponentMap = { downgraded: DowngradedVotes, general: GeneralInfo, lock_account: LockAccount, + login: CldrLogin, lookup: LookUp, menu: MainMenu, + reset: ResetPassword, retry: WaitingPanel, retry_inplace: WaitingPanel, // Like retry, but do NOT redirect after resume. test_panel: TestPanel, // for testing diff --git a/tools/cldr-apps/js/src/views/CldrLogin.vue b/tools/cldr-apps/js/src/views/CldrLogin.vue new file mode 100644 index 00000000000..fe668466881 --- /dev/null +++ b/tools/cldr-apps/js/src/views/CldrLogin.vue @@ -0,0 +1,73 @@ + + + diff --git a/tools/cldr-apps/js/src/views/LoginButton.vue b/tools/cldr-apps/js/src/views/LoginButton.vue index d6a65226f46..abf951f9c1c 100644 --- a/tools/cldr-apps/js/src/views/LoginButton.vue +++ b/tools/cldr-apps/js/src/views/LoginButton.vue @@ -22,10 +22,20 @@ Stay Logged In   + message="Username or Password Incorrect" + description="Please check the email address and password carefully." + > + + Forgot Password