diff --git a/services/web/app/src/Features/PasswordReset/PasswordResetController.mjs b/services/web/app/src/Features/PasswordReset/PasswordResetController.mjs
index a4182e50d5..57a1e3874e 100644
--- a/services/web/app/src/Features/PasswordReset/PasswordResetController.mjs
+++ b/services/web/app/src/Features/PasswordReset/PasswordResetController.mjs
@@ -9,6 +9,7 @@ import OError from '@overleaf/o-error'
import EmailsHelper from '../Helpers/EmailHelper.mjs'
import { expressify } from '@overleaf/promise-utils'
import { z, validateReq } from '../../infrastructure/Validation.mjs'
+import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
const setNewUserPasswordSchema = z.object({
body: z.object({
@@ -186,16 +187,20 @@ async function renderSetPasswordForm(req, res, next) {
return res.redirect('/user/password/reset?error=token_expired')
}
req.session.resetToken = query.passwordResetToken
- let emailQuery = ''
+ const params = new URLSearchParams()
if (typeof query.email === 'string') {
const email = EmailsHelper.parseEmail(query.email)
if (email) {
- emailQuery = `?email=${encodeURIComponent(email)}`
+ params.append('email', email)
}
}
-
- return res.redirect('/user/password/set' + emailQuery)
+ if (req.query.uniaccessphase1) {
+ // Preserve uniaccessphase1 flag in the redirect so it can be tested
+ params.append('uniaccessphase1', req.query.uniaccessphase1)
+ }
+ const queryString = params.toString() ? `?${params.toString()}` : ''
+ return res.redirect('/user/password/set' + queryString)
} catch (err) {
if (err.name === 'ForbiddenError') {
return next(err)
@@ -214,11 +219,22 @@ async function renderSetPasswordForm(req, res, next) {
const passwordResetToken = req.session.resetToken
delete req.session.resetToken
- res.render('user/setPassword', {
- title: 'set_password',
- email,
- passwordResetToken,
- })
+ const ciamAssignment = await SplitTestHandler.promises.getAssignment(
+ req,
+ res,
+ 'uniaccessphase1'
+ )
+
+ res.render(
+ ciamAssignment.variant === 'enabled'
+ ? 'user/setPasswordCiam'
+ : 'user/setPassword',
+ {
+ title: 'set_password',
+ email,
+ passwordResetToken,
+ }
+ )
}
const renderRequestResetFormSchema = z.object({
@@ -235,10 +251,21 @@ async function renderRequestResetForm(req, res) {
error = 'password_reset_token_expired'
}
- res.render('user/passwordReset', {
- title: 'reset_password',
- error,
- })
+ const ciamAssignment = await SplitTestHandler.promises.getAssignment(
+ req,
+ res,
+ 'uniaccessphase1'
+ )
+
+ res.render(
+ ciamAssignment.variant === 'enabled'
+ ? 'user/passwordResetCiam'
+ : 'user/passwordReset',
+ {
+ title: 'reset_password',
+ error,
+ }
+ )
}
export default {
diff --git a/services/web/app/views/_mixins/ciam_mixins.pug b/services/web/app/views/_mixins/ciam_mixins.pug
index 6cf7a9b986..84a226a8a9 100644
--- a/services/web/app/views/_mixins/ciam_mixins.pug
+++ b/services/web/app/views/_mixins/ciam_mixins.pug
@@ -1,6 +1,5 @@
include terms_of_service
include recaptcha
-include ../../../modules/saas-authentication/app/views/_mixins
mixin ciamLogo
header.ciam-logo
@@ -10,7 +9,7 @@ mixin ciamLogo
mixin ciamCardSeparator
hr.ciam-card-separator
-mixin ciamCardFooter
+mixin ciamCardFooter(short)
section.ciam-card-footer
+ciamCardSeparator
.ciam-footer-ds-logo
@@ -24,7 +23,10 @@ mixin ciamCardFooter
alt='Digital Science — home'
)
p
- | !{translate('advancing_research_with', null, [{ name: 'a', attrs: { href: 'https://www.overleaf.com/', target: '_blank', rel: 'noopener noreferrer' }}, { name: 'a', attrs: { href: 'https://www.papersapp.com/', target: '_blank', rel: 'noopener noreferrer' }}])}
+ if short
+ | !{translate('overleaf_is_a_ds_product')}
+ else
+ | !{translate('advancing_research_with', null, [{ name: 'a', attrs: { href: 'https://www.overleaf.com/', target: '_blank', rel: 'noopener noreferrer' }}, { name: 'a', attrs: { href: 'https://www.papersapp.com/', target: '_blank', rel: 'noopener noreferrer' }}])}
mixin ciamTermsOfServiceAgreement
p
@@ -36,7 +38,7 @@ mixin ciamRecaptchaConditions
mixin ciamCustomFormDangerMessage(key)
div(
- class='notification ciam-notification notification-type-error'
+ class='notification notification-ds notification-type-error'
hidden
data-ol-custom-form-message=key
role='alert'
@@ -47,10 +49,6 @@ mixin ciamCustomFormDangerMessage(key)
.notification-content.text-left
block
-mixin ciamSamlErrorNotLoggedIn(error)
- +samlErrorNotLoggedIn(error)
- ph-warning-circle(aria-hidden='true')
-
mixin ciamFooter
footer
.footer-links
diff --git a/services/web/app/views/user/passwordResetCiam.pug b/services/web/app/views/user/passwordResetCiam.pug
new file mode 100644
index 0000000000..5647e187b1
--- /dev/null
+++ b/services/web/app/views/user/passwordResetCiam.pug
@@ -0,0 +1,98 @@
+extends ../layout-website-redesign
+include ../_mixins/recaptcha
+include ../_mixins/notification
+include ../_mixins/ciam_mixins
+
+block vars
+ - var suppressNavbar = true
+ - var suppressFooter = true
+ - var suppressSkipToContent = true
+ - isWebsiteRedesign = true
+
+block content
+ - var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
+
+ if showCaptcha
+ script(
+ type='text/javascript'
+ nonce=scriptNonce
+ src='https://www.recaptcha.net/recaptcha/api.js?render=explicit'
+ )
+ div(
+ id='recaptcha'
+ class='g-recaptcha'
+ data-sitekey=settings.recaptcha.siteKey
+ data-size='invisible'
+ data-badge='inline'
+ )
+
+ .ciam-enabled.ciam-layout.ciam-password-reset(
+ data-ol-captcha-retry-trigger-area=''
+ )
+ +ciamLogo
+ .ciam-container
+ main#main-content.ciam-card
+ form(
+ name='passwordResetForm'
+ captcha-action-name=showCaptcha ? 'passwordReset' : false
+ data-ciam-form
+ data-ol-async-form
+ action='/user/password/reset'
+ method='POST'
+ captcha=showCaptcha ? '' : false
+ )
+ if error === 'password_reset_token_expired'
+ h1 #{translate("sorry_your_token_expired")}
+ p.intro-p #{translate('please_request_a_new_password_reset_email_and_follow_the_link')}.
+ else
+ h1(data-ol-not-sent) #{translate("reset_your_password")}
+ h1(hidden data-ol-sent) #{translate("check_your_inbox")}
+ p.intro-p(data-ol-not-sent) #{translate("enter_your_email_and_we_will_send_reset_instructions")}
+
+ div(data-ol-not-sent)
+ +formMessagesNewStyle
+ if error && error !== 'password_reset_token_expired'
+ +ciamErrorNotification
+ p #{translate(error)}
+
+ div(data-ol-custom-form-message='no-password-allowed-due-to-sso' hidden)
+ +ciamErrorNotification
+ p !{translate('you_cant_reset_password_due_to_sso', {}, [{name: 'a', attrs: {href: '/sso-login'}}])}
+ input(name='_csrf' type='hidden' value=csrfToken)
+ .form-group.form-group-ds
+ label.form-label(for='email') #{translate("email")}
+ input#email.form-control.form-control-ds.form-control-lg(
+ name='email'
+ type='email'
+ required
+ autocomplete='username'
+ autofocus
+ )
+ .actions
+ button.btn.btn-ds.btn-lg.btn-primary.w-100(
+ type='submit'
+ data-ol-disabled-inflight
+ data-ol-spinner-inflight
+ aria-label=translate('send_reset_link')
+ )
+ +ciamButtonContentLoading(`${translate('requesting_password_reset')}…`)= translate('send_reset_link')
+ p.ciam-login-text
+ a(href='/login') #{translate("back_to_log_in")}
+ div(hidden data-ol-sent)
+ p.intro-p !{translate('if_theres_an_account_youll_get_reset_email', {email: 'your email'})}
+ p.ciam-login-text.text-start
+ a(href='/login') #{translate('back_to_log_in')}
+
+ if showCaptcha
+ .ciam-disclaimers
+ +ciamRecaptchaConditions
+
+ +ciamCardFooter(true)
+
+ // retrieve and display the email used for password reset after form is sent
+ script(nonce=scriptNonce).
+ document.querySelector('form[name="passwordResetForm"]').addEventListener('sent', function () {
+ const email = this.querySelector('input[name="email"]').value
+ const display = document.getElementById('sent-email-display')
+ if (display) display.textContent = email
+ })
diff --git a/services/web/app/views/user/setPassword.pug b/services/web/app/views/user/setPassword.pug
index 5081d22409..dc3b1db0bc 100644
--- a/services/web/app/views/user/setPassword.pug
+++ b/services/web/app/views/user/setPassword.pug
@@ -28,11 +28,11 @@ block content
+formMessagesNewStyle
+customFormMessageNewStyle('password-contains-email', 'danger')
- | #{translate('invalid_password_contains_email')}.
+ | #{translate('invalid_password_contains_email')}
| #{translate('use_a_different_password')}.
+customFormMessageNewStyle('password-too-similar', 'danger')
- | #{translate('invalid_password_too_similar')}.
+ | #{translate('invalid_password_too_similar')}
| #{translate('use_a_different_password')}.
+customFormMessageNewStyle('token-expired', 'danger')
@@ -65,7 +65,7 @@ block content
| #{translate('password_cant_be_the_same_as_current_one')}.
+customValidationMessageNewStyle('password-must-be-strong')
- | !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
+ | !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}
| #{translate('use_a_different_password')}.
input(name='passwordResetToken' type='hidden' value=passwordResetToken)
diff --git a/services/web/app/views/user/setPasswordCiam.pug b/services/web/app/views/user/setPasswordCiam.pug
new file mode 100644
index 0000000000..3e3540c9f3
--- /dev/null
+++ b/services/web/app/views/user/setPasswordCiam.pug
@@ -0,0 +1,110 @@
+extends ../layout-website-redesign
+include ../_mixins/ciam_mixins
+
+block vars
+ - var suppressNavbar = true
+ - var suppressFooter = true
+ - var suppressSkipToContent = true
+ - isWebsiteRedesign = true
+
+block content
+ .ciam-enabled.ciam-layout.ciam-password-set
+ +ciamLogo
+ .ciam-container
+ main#main-content.ciam-card
+ form(
+ name='passwordResetForm'
+ data-ciam-form
+ data-ol-async-form
+ action='/user/password/set'
+ method='POST'
+ data-ol-hide-on-error='token-expired'
+ )
+ div(hidden data-ol-sent)
+ h1 #{translate("your_password_has_been_reset")}
+ p.intro-p #{translate("you_can_now_sign_in_with_new_password")}.
+ a.btn.btn-ds.btn-lg.btn-primary.w-100(href='/login') #{translate("go_to_sign_in")}
+
+ div(data-ol-not-sent)
+ h1 #{translate("choose_a_new_password")}
+ +formMessagesNewStyle
+
+ +ciamCustomFormDangerMessage('password-contains-email')
+ | #{translate('invalid_password_contains_email')}
+ | #{translate('use_a_different_password')}.
+
+ +ciamCustomFormDangerMessage('password-too-similar')
+ | #{translate('invalid_password_too_similar')}
+ | #{translate('use_a_different_password')}.
+
+ +ciamCustomFormDangerMessage('token-expired')
+ | #{translate('password_reset_token_expired')}
+ br
+ a(href='/user/password/reset')
+ | #{translate('request_new_password_reset_email')}
+
+ +ciamCustomFormDangerMessage('invalid-password')
+ | #{translate('invalid_password')}.
+
+ +ciamCustomFormDangerMessage('password-must-be-different')
+ | #{translate('password_cant_be_the_same_as_current_one')}.
+
+ +ciamCustomFormDangerMessage('password-must-be-strong')
+ | !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}
+ | #{translate('use_a_different_password')}.
+
+ input(name='_csrf' type='hidden' value=csrfToken)
+ input(
+ name='email'
+ type='text'
+ hidden
+ autocomplete='username'
+ value=email
+ )
+
+ .form-group.form-group-ds
+ label.form-label(
+ for='passwordField'
+ data-ol-hide-on-error-message='token-expired'
+ ) #{translate("password")}
+ .form-group-password
+ .form-group-password-input
+ .form-complex-input-container
+ input#passwordField.form-control.form-control-ds.form-control-lg(
+ name='password'
+ type='password'
+ autocomplete='new-password'
+ autofocus
+ required
+ minlength=settings.passwordStrengthOptions.length.min
+ data-ol-password-visibility-target
+ )
+ button.visibility-toggle(
+ type='button'
+ data-ol-password-visibility-toggle='visibilityOn'
+ aria-controls='password'
+ aria-label=translate('turn_on_password_visibility')
+ )
+ ph-eye.form-input-icon-ds
+ button.visibility-toggle(
+ type='button'
+ data-ol-password-visibility-toggle='visibilityOff'
+ aria-controls='password'
+ aria-label=translate('turn_off_password_visibility')
+ hidden
+ )
+ ph-eye-slash.form-input-icon-ds
+
+ input(name='passwordResetToken' type='hidden' value=passwordResetToken)
+ div(data-ol-hide-on-error-message='token-expired')
+ p.password-policy #{translate('must_be_at_least_n_characters', {n: settings.passwordStrengthOptions.length.min})}
+ .actions
+ button.btn.btn-ds.btn-lg.btn-primary.w-100(
+ type='submit'
+ data-ol-disabled-inflight
+ data-ol-spinner-inflight
+ aria-label=translate('save_new_password')
+ )
+ +ciamButtonContentLoading(`${translate('processing')}…`)= translate('save_new_password')
+
+ +ciamCardFooter(true)
diff --git a/services/web/frontend/js/features/form-helpers/hydrate-form.ts b/services/web/frontend/js/features/form-helpers/hydrate-form.ts
index eb2abdb0ca..33b0e9b576 100644
--- a/services/web/frontend/js/features/form-helpers/hydrate-form.ts
+++ b/services/web/frontend/js/features/form-helpers/hydrate-form.ts
@@ -304,6 +304,8 @@ function showMessagesNewStyle(
el.hidden = true
})
+ const isDsBranded = formEl.dataset.ciamForm !== undefined
+
// Render messages
messageBag.forEach(message => {
const customErrorElements = message.key
@@ -322,6 +324,7 @@ function showMessagesNewStyle(
messageElContainer.className = classNames('notification', {
'notification-type-error': message.type === 'error',
'notification-type-success': message.type !== 'error',
+ 'notification-ds': isDsBranded,
})
const messageEl = document.createElement('div')
@@ -344,7 +347,6 @@ function showMessagesNewStyle(
}
// create the left icon
- const isDsBranded = formEl.dataset.ciamForm !== undefined
const messageIcon = document.createElement('div')
messageIcon.className = 'notification-icon'
if (
diff --git a/services/web/frontend/js/features/settings/components/password-section.tsx b/services/web/frontend/js/features/settings/components/password-section.tsx
index ebfa9183bc..9128119dc3 100644
--- a/services/web/frontend/js/features/settings/components/password-section.tsx
+++ b/services/web/frontend/js/features/settings/components/password-section.tsx
@@ -177,16 +177,16 @@ function PasswordForm() {
/>,
]}
/>
- . {t('use_a_different_password')}.
+ {t('use_a_different_password')}.
>
) : getErrorMessageKey(error) === 'password-contains-email' ? (
<>
- {t('invalid_password_contains_email')}.{' '}
+ {t('invalid_password_contains_email')}{' '}
{t('use_a_different_password')}.
>
) : getErrorMessageKey(error) === 'password-too-similar' ? (
<>
- {t('invalid_password_too_similar')}.{' '}
+ {t('invalid_password_too_similar')}{' '}
{t('use_a_different_password')}.
>
) : (
diff --git a/services/web/frontend/stylesheets/ciam/all.scss b/services/web/frontend/stylesheets/ciam/all.scss
index c5eb0fd43e..6e73cd2fbe 100644
--- a/services/web/frontend/stylesheets/ciam/all.scss
+++ b/services/web/frontend/stylesheets/ciam/all.scss
@@ -2,6 +2,7 @@
@import 'ciam-layout';
@import 'ciam-links';
@import 'ciam-login';
+@import 'ciam-password-reset';
@import 'ciam-register';
@import 'ciam-six-digits';
@import 'ciam-try-premium';
diff --git a/services/web/frontend/stylesheets/ciam/ciam-layout.scss b/services/web/frontend/stylesheets/ciam/ciam-layout.scss
index 2281c55812..8d9ed3aeb2 100644
--- a/services/web/frontend/stylesheets/ciam/ciam-layout.scss
+++ b/services/web/frontend/stylesheets/ciam/ciam-layout.scss
@@ -108,7 +108,6 @@
border-radius: var(--ds-border-radius-400);
max-width: 464px;
margin: 0 auto;
- min-height: 500px;
@include media-breakpoint-up(sm) {
padding: var(--ds-spacing-1300);
diff --git a/services/web/frontend/stylesheets/ciam/ciam-password-reset.scss b/services/web/frontend/stylesheets/ciam/ciam-password-reset.scss
new file mode 100644
index 0000000000..d51835d370
--- /dev/null
+++ b/services/web/frontend/stylesheets/ciam/ciam-password-reset.scss
@@ -0,0 +1,28 @@
+.ciam-password-set,
+.ciam-password-reset {
+ h1 {
+ margin-bottom: var(--ds-spacing-600);
+ }
+
+ p.intro-p {
+ margin-bottom: var(--ds-spacing-800);
+ }
+
+ .notification {
+ margin-bottom: var(--ds-spacing-800);
+ }
+
+ .password-policy {
+ color: var(--ds-color-text-secondary);
+ }
+
+ .form-group-ds {
+ margin-bottom: var(--ds-spacing-400);
+ }
+
+ .actions {
+ display: flex;
+ flex-direction: column;
+ gap: var(--ds-spacing-400);
+ }
+}
diff --git a/services/web/frontend/stylesheets/pages/login-register.scss b/services/web/frontend/stylesheets/pages/login-register.scss
index 72131cdcb7..e1ca000eef 100644
--- a/services/web/frontend/stylesheets/pages/login-register.scss
+++ b/services/web/frontend/stylesheets/pages/login-register.scss
@@ -36,7 +36,9 @@
flex-direction: column;
.form-group-password-input {
- input.form-control {
+ input.form-control,
+ // needlessly specific selectors to override competing styles
+ input.form-control.form-control-ds.form-control-lg {
padding-right: var(--password-visibility-toggle-width);
}
}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index 5502ac64b8..cc4fa87425 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -330,10 +330,12 @@
"chat_error": "Could not load chat messages, please try again.",
"chat_with_sales_team_50_or_more": "Chat with our sales team about larger discounts for groups of 50 or more.",
"check_your_email": "Check your email",
+ "check_your_inbox": "Check your inbox",
"checking": "Checking",
"checking_dropbox_status": "Checking Dropbox status",
"checking_project_github_status": "Checking project status in GitHub",
"choose_a_custom_color": "Choose a custom color",
+ "choose_a_new_password": "Choose a new password",
"choose_from_group_members": "Choose from group members",
"choose_how_you_search_your_references": "Choose how you search your references",
"choose_which_experiments": "Choose which experiments you’d like to try.",
@@ -720,6 +722,7 @@
"enter_the_number_of_licenses_youd_like_to_add_to_see_the_cost_breakdown": "Enter the number of licenses you’d like to add to see the cost breakdown.",
"enter_your_email": "Enter your email",
"enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password": "Enter your email address below, and we will send you a link to reset your password",
+ "enter_your_email_and_we_will_send_reset_instructions": "Enter your email and we’ll send reset instructions.",
"enter_your_password": "Enter your password",
"equation_generator": "Equation Generator",
"equation_preview": "Equation preview",
@@ -953,6 +956,7 @@
"go_to_pdf_location_in_code": "Go to PDF location in code (Tip: double click on the PDF for best results)",
"go_to_previous_page": "Go to previous page",
"go_to_settings": "Go to settings",
+ "go_to_sign_in": "Go to sign in",
"go_to_subscriptions": "Go to Subscriptions",
"go_to_writefull": "Go to Writefull",
"good_news_you_already_purchased_this_add_on": "Good news! You already have this add-on, so no need to pay again.",
@@ -1058,6 +1062,7 @@
"id": "ID",
"if_have_existing_can_link": "If you have an existing __appName__ account on another email, you can link it to your __institutionName__ account by clicking __clickText__.",
"if_owner_can_link": "If you own the __appName__ account with __email__, you will be allowed to link it to your __institutionName__ institutional account.",
+ "if_theres_an_account_youll_get_reset_email": "If there’s an account for __email__, you’ll get an email with reset instructions.",
"if_you_find_any_issues_with_texlive": "If you find any issues with TeX Live, packages, or compilation, please <0>provide feedback0>. If you hit problems, you can <1>switch your compiler1>.",
"if_you_need_to_customize_your_table_further_you_can": "If you need to customize your table further, you can. Using LaTeX code, you can change anything from table styles and border styles to colors and column widths. <0>Read our guide0> to using tables in LaTeX to help you get started.",
"if_you_need_to_delete_your_writefull_account": "If you need to delete your Writefull account, go to your Writefull account settings.",
@@ -1597,6 +1602,7 @@
"overleaf_group_plans": "Overleaf group plans",
"overleaf_history_system": "Overleaf History System",
"overleaf_individual_plans": "Overleaf individual plans",
+ "overleaf_is_a_ds_product": "Overleaf is a Digital Science product.",
"overleaf_is_easy_to_use": "Overleaf is easy to use.",
"overleaf_labs": "Overleaf Labs",
"overleaf_logo": "Overleaf logo",
@@ -2003,6 +2009,7 @@
"saml_response": "SAML Response",
"save": "Save",
"save_20_percent": "save 20%",
+ "save_new_password": "Save new password",
"save_or_cancel-cancel": "Cancel",
"save_or_cancel-or": "or",
"save_or_cancel-save": "Save",
@@ -2083,6 +2090,7 @@
"send_first_message": "Send your first message to your collaborators",
"send_message": "Send message",
"send_request": "Send request",
+ "send_reset_link": "Send reset link",
"send_test_email": "Send a test email",
"sending": "Sending",
"sent": "Sent",
@@ -2801,6 +2809,7 @@
"you_can_now_enable_sso": "You can now enable SSO on your group settings page.",
"you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features0>.",
"you_can_now_search_and_add_references_from_your_rm_library_without_needing_to_import_files": "You can now search and add references from your __referenceManager__ library without needing to import files—just type \\cite{} in your .tex file. Learn more",
+ "you_can_now_sign_in_with_new_password": "You can now sign in with your new password.",
"you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out0> of the program at any time on this page",
"you_can_request_a_maximum_of_limit_fixes_per_day": "You can request a maximum of __limit__ fixes per day. Please try again tomorrow.",
"you_can_select_or_invite_collaborator": "You can select or invite __count__ collaborator on your current plan. Upgrade to add more editors or reviewers.",
@@ -2853,6 +2862,7 @@
"your_message_to_collaborators": "Send a message to your collaborators",
"your_name_and_email_address_will_be_visible_to_the_project_owner_and_other_editors": "Your name and email address will be visible to the project owner and other editors.",
"your_new_plan": "Your new plan",
+ "your_password_has_been_reset": "Your password has been reset",
"your_password_has_been_successfully_changed": "Your password has been successfully changed",
"your_password_was_detected": "Your password is on a <0>public list of known compromised passwords0>. Keep your account safe by changing your password now.",
"your_plan": "Your plan",
diff --git a/services/web/test/unit/src/PasswordReset/PasswordResetController.test.mjs b/services/web/test/unit/src/PasswordReset/PasswordResetController.test.mjs
index 19c9b87956..e8db5c0af4 100644
--- a/services/web/test/unit/src/PasswordReset/PasswordResetController.test.mjs
+++ b/services/web/test/unit/src/PasswordReset/PasswordResetController.test.mjs
@@ -57,6 +57,11 @@ describe('PasswordResetController', function () {
removeReconfirmFlag: sinon.stub().resolves(),
},
}
+ ctx.SplitTestHandler = {
+ promises: {
+ getAssignment: sinon.stub().resolves({ variant: 'default' }),
+ },
+ }
vi.doMock('@overleaf/settings', () => ({
default: ctx.settings,
@@ -105,6 +110,13 @@ describe('PasswordResetController', function () {
default: ctx.UserUpdater,
}))
+ vi.doMock(
+ '../../../../app/src/Features/SplitTests/SplitTestHandler',
+ () => ({
+ default: ctx.SplitTestHandler,
+ })
+ )
+
ctx.PasswordResetController = (await import(MODULE_PATH)).default
})