Selaa lähdekoodia

Merge pull request #18318 from overleaf/dp-revert-secondary-email

Revert "Merge pull request #18139 from overleaf/dp-add-secondary-prompt-ui"

GitOrigin-RevId: d8af37a5c332ac21ddd4c85679553df0bda326f9
David 2 vuotta sitten
vanhempi
sitoutus
6718729087

+ 3 - 70
services/web/app/src/Features/User/UserEmailsController.js

@@ -17,9 +17,7 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager')
 const UserPrimaryEmailCheckHandler = require('../User/UserPrimaryEmailCheckHandler')
 const UserAuditLogHandler = require('./UserAuditLogHandler')
 const { RateLimiter } = require('../../infrastructure/RateLimiter')
-const Features = require('../../infrastructure/Features')
 const tsscmp = require('tsscmp')
-const Modules = require('../../infrastructure/Modules')
 
 const AUDIT_LOG_TOKEN_PREFIX_LENGTH = 10
 
@@ -210,7 +208,9 @@ async function addWithConfirmationCode(req, res) {
       confirmCodeExpiresTimestamp,
     }
 
-    return res.sendStatus(200)
+    return res.json({
+      redir: '/user/emails/confirm-secondary',
+    })
   } catch (err) {
     if (err.name === 'EmailExistsError') {
       return res.status(409).json({
@@ -414,45 +414,6 @@ async function resendSecondaryEmailConfirmationCode(req, res) {
   }
 }
 
-async function confirmSecondaryEmailPage(req, res) {
-  const userId = SessionManager.getLoggedInUserId(req.session)
-
-  if (!req.session.pendingSecondaryEmail) {
-    const redirectURL =
-      AuthenticationController.getRedirectFromSession(req) || '/project'
-    return res.redirect(redirectURL)
-  }
-
-  AnalyticsManager.recordEventForUser(
-    userId,
-    'confirm-secondary-email-page-displayed'
-  )
-
-  res.render('user/confirmSecondaryEmail', {
-    email: req.session.pendingSecondaryEmail.email,
-  })
-}
-
-async function addSecondaryEmailPage(req, res) {
-  const userId = SessionManager.getLoggedInUserId(req.session)
-
-  const confirmedEmails =
-    await UserGetter.promises.getUserConfirmedEmails(userId)
-
-  if (confirmedEmails.length >= 2) {
-    const redirectURL =
-      AuthenticationController.getRedirectFromSession(req) || '/project'
-    return res.redirect(redirectURL)
-  }
-
-  AnalyticsManager.recordEventForUser(
-    userId,
-    'add-secondary-email-page-displayed'
-  )
-
-  res.render('user/addSecondaryEmail')
-}
-
 async function primaryEmailCheckPage(req, res) {
   const userId = SessionManager.getLoggedInUserId(req.session)
   const user = await UserGetter.promises.getUser(userId, {
@@ -479,30 +440,6 @@ async function primaryEmailCheck(req, res) {
     $set: { lastPrimaryEmailCheck: new Date() },
   })
   AnalyticsManager.recordEventForUser(userId, 'primary-email-check-done')
-
-  // We want to redirect to prompt a user to add a secondary email if their primary
-  // is an institutional email and they dont' already have a secondary.
-  if (Features.hasFeature('affiliations')) {
-    const confirmedEmails =
-      await UserGetter.promises.getUserConfirmedEmails(userId)
-
-    if (confirmedEmails.length < 2) {
-      const primaryEmail = await UserGetter.promises.getUserEmail(userId)
-      const primaryEmailDomain = EmailHelper.getDomain(primaryEmail)
-
-      const institution = (
-        await Modules.promises.hooks.fire(
-          'getInstitutionViaDomain',
-          primaryEmailDomain
-        )
-      )?.[0]
-
-      if (institution) {
-        return AsyncFormHelper.redirect(req, res, '/user/emails/add-secondary')
-      }
-    }
-  }
-
   AsyncFormHelper.redirect(req, res, '/project')
 }
 
@@ -614,10 +551,6 @@ const UserEmailsController = {
 
   sendReconfirmation,
 
-  addSecondaryEmailPage: expressify(addSecondaryEmailPage),
-
-  confirmSecondaryEmailPage: expressify(confirmSecondaryEmailPage),
-
   primaryEmailCheckPage: expressify(primaryEmailCheckPage),
 
   primaryEmailCheck: expressify(primaryEmailCheck),

+ 0 - 14
services/web/app/src/Features/User/UserGetter.js

@@ -79,18 +79,6 @@ async function getUserFullEmails(userId) {
   )
 }
 
-async function getUserConfirmedEmails(userId) {
-  const user = await UserGetter.promises.getUser(userId, {
-    emails: 1,
-  })
-
-  if (!user) {
-    throw new Error('User not Found')
-  }
-
-  return user.emails.filter(email => !!email.confirmedAt)
-}
-
 async function getSsoUsersAtInstitution(institutionId, projection) {
   if (!projection) {
     throw new Error('missing projection')
@@ -136,8 +124,6 @@ const UserGetter = {
 
   getUserFullEmails: callbackify(getUserFullEmails),
 
-  getUserConfirmedEmails: callbackify(getUserConfirmedEmails),
-
   getUserByMainEmail(email, projection, callback) {
     email = email.trim()
     if (arguments.length === 2) {

+ 0 - 15
services/web/app/src/router.js

@@ -348,7 +348,6 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
       CaptchaMiddleware.validateCaptcha('addEmail'),
       UserEmailsController.add
     )
-
     webRouter.post(
       '/user/emails/delete',
       AuthenticationController.requireLogin(),
@@ -391,20 +390,6 @@ function initialize(webRouter, privateApiRouter, publicApiRouter) {
       RateLimiterMiddleware.rateLimit(rateLimiters.resendConfirmationCode),
       UserEmailsController.resendSecondaryEmailConfirmationCode
     )
-
-    webRouter.get(
-      '/user/emails/add-secondary',
-      AuthenticationController.requireLogin(),
-      PermissionsController.requirePermission('add-secondary-email'),
-      UserEmailsController.addSecondaryEmailPage
-    )
-
-    webRouter.get(
-      '/user/emails/confirm-secondary',
-      AuthenticationController.requireLogin(),
-      PermissionsController.requirePermission('add-secondary-email'),
-      UserEmailsController.confirmSecondaryEmailPage
-    )
   }
 
   webRouter.get(

+ 0 - 14
services/web/app/views/user/addSecondaryEmail.pug

@@ -1,14 +0,0 @@
-extends ../layout-marketing
-
-block vars
-	- var suppressNavbar = true
-	- var suppressSkipToContent = true
-
-block entrypointVar
-	- entrypoint = 'pages/user/add-secondary-email'
-
-block append meta
-
-block content
-	main.content.content-alt
-		#add-secondary-email

+ 0 - 15
services/web/app/views/user/confirmSecondaryEmail.pug

@@ -1,15 +0,0 @@
-extends ../layout-marketing
-
-block vars
-	- var suppressNavbar = true
-	- var suppressSkipToContent = true
-
-block entrypointVar
-	- entrypoint = 'pages/user/confirm-secondary-email'
-
-block append meta
-	meta(name="ol-email" content=email)
-
-block content
-	main.content.content-alt
-		#confirm-secondary-email

+ 1 - 7
services/web/frontend/extracted-translations.json

@@ -33,7 +33,6 @@
   "actions": "",
   "active": "",
   "add": "",
-  "add_a_recovery_email_address": "",
   "add_additional_certificate": "",
   "add_affiliation": "",
   "add_another_address_line": "",
@@ -42,7 +41,6 @@
   "add_comma_separated_emails_help": "",
   "add_comment": "",
   "add_company_details": "",
-  "add_email_address": "",
   "add_email_to_claim_features": "",
   "add_files": "",
   "add_more_collaborators": "",
@@ -207,6 +205,7 @@
   "confirm_primary_email_change": "",
   "confirm_remove_sso_config_enter_email": "",
   "confirm_your_email": "",
+  "confirmation_code_message": "",
   "confirming": "",
   "conflicting_paths_found": "",
   "congratulations_youve_successfully_join_group": "",
@@ -357,7 +356,6 @@
   "email_confirmed_onboarding_message": "",
   "email_limit_reached": "",
   "email_link_expired": "",
-  "email_must_be_linked_to_institution": "",
   "email_or_password_wrong_try_again": "",
   "emails_and_affiliations_explanation": "",
   "emails_and_affiliations_title": "",
@@ -371,7 +369,6 @@
   "enter_6_digit_code": "",
   "enter_any_size_including_units_or_valid_latex_command": "",
   "enter_image_url": "",
-  "enter_the_confirmation_code": "",
   "error": "",
   "error_opening_document": "",
   "error_opening_document_detail": "",
@@ -639,7 +636,6 @@
   "justify": "",
   "keep_current_plan": "",
   "keep_personal_projects_separate": "",
-  "keep_your_account_safe_add_another_email": "",
   "keybindings": "",
   "labels_help_you_to_easily_reference_your_figures": "",
   "labels_help_you_to_reference_your_tables": "",
@@ -664,7 +660,6 @@
   "layout": "",
   "layout_processing": "",
   "learn_more": "",
-  "learn_more_about_account": "",
   "learn_more_about_link_sharing": "",
   "learn_more_about_managed_users": "",
   "learn_more_about_other_causes_of_compile_timeouts": "",
@@ -1311,7 +1306,6 @@
   "test_configuration_successful": "",
   "tex_live_version": "",
   "thank_you_exclamation": "",
-  "thanks_for_confirming_your_email_address": "",
   "thanks_for_subscribing": "",
   "thanks_for_subscribing_you_help_sl": "",
   "thanks_settings_updated": "",

+ 0 - 151
services/web/frontend/js/features/settings/components/emails/add-secondary-email-prompt.tsx

@@ -1,151 +0,0 @@
-import { Interstitial } from '@/shared/components/interstitial'
-import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
-import { Button } from 'react-bootstrap'
-import { Trans, useTranslation } from 'react-i18next'
-import EmailInput from './add-email/input'
-import { useState } from 'react'
-import MaterialIcon from '@/shared/components/material-icon'
-import { sendMB } from '@/infrastructure/event-tracking'
-
-import { postJSON } from '../../../../infrastructure/fetch-json'
-
-type AddSecondaryEmailError = {
-  name: string
-  data: any
-}
-
-export function AddSecondaryEmailPrompt() {
-  const isReady = useWaitForI18n()
-  const { t } = useTranslation()
-  const [email, setEmail] = useState<string>()
-  const [error, setError] = useState<AddSecondaryEmailError | undefined>()
-  const [isSubmitting, setIsSubmitting] = useState<boolean>(false)
-
-  if (!isReady) {
-    return null
-  }
-
-  const onEmailChange = (newEmail: string) => {
-    if (newEmail !== email) {
-      setEmail(newEmail)
-      setError(undefined)
-    }
-  }
-
-  const errorHandler = (err: any) => {
-    let errorName = 'generic_something_went_wrong'
-
-    if (err?.response?.status === 409) {
-      errorName = 'email_already_registered'
-    } else if (err?.response?.status === 429) {
-      errorName = 'too_many_attempts'
-    } else if (err?.response?.status === 422) {
-      errorName = 'email_must_be_linked_to_institution'
-    }
-
-    setError({ name: errorName, data: err?.data })
-    sendMB('add-secondary-email-error', { errorName })
-  }
-
-  const handleSubmit = async (e?: React.FormEvent<HTMLFormElement>) => {
-    if (e) {
-      e.preventDefault()
-    }
-
-    setIsSubmitting(true)
-
-    await postJSON('/user/emails/secondary', {
-      body: {
-        email,
-      },
-    })
-      .then(() => {
-        location.assign('/user/emails/confirm-secondary')
-      })
-      .catch(errorHandler)
-      .finally(() => {
-        setIsSubmitting(false)
-      })
-  }
-
-  return (
-    <>
-      <Interstitial showLogo title={t('add_a_recovery_email_address')}>
-        <form className="add-secondary-email" onSubmit={handleSubmit}>
-          <p>{t('keep_your_account_safe_add_another_email')}</p>
-
-          <EmailInput
-            onChange={onEmailChange}
-            handleAddNewEmail={handleSubmit}
-          />
-
-          <div aria-live="polite">
-            {error && <ErrorMessage error={error} />}
-          </div>
-
-          <Button
-            bsStyle={null}
-            disabled={isSubmitting}
-            className="btn-primary"
-            type="submit"
-          >
-            {isSubmitting ? <>{t('adding')}&hellip;</> : t('add_email_address')}
-          </Button>
-          <Button
-            bsStyle={null}
-            disabled={isSubmitting}
-            className="btn-secondary"
-            href="/project"
-          >
-            {t('not_now')}
-          </Button>
-          <p className="add-secondary-email-learn-more">
-            <Trans
-              i18nKey="learn_more_about_account"
-              components={[
-                // eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
-                <a href="/learn/how-to/Keeping_your_account_secure" />,
-              ]}
-            />
-          </p>
-        </form>
-      </Interstitial>
-    </>
-  )
-}
-
-function ErrorMessage({ error }: { error: AddSecondaryEmailError }) {
-  const { t } = useTranslation()
-
-  let errorText
-
-  switch (error.name) {
-    case 'email_already_registered':
-      errorText = t('email_already_registered')
-      break
-    case 'too_many_attempts':
-      errorText = t('too_many_attempts')
-      break
-    case 'email_must_be_linked_to_institution':
-      errorText = (
-        <Trans
-          i18nKey="email_must_be_linked_to_institution"
-          values={{ institutionName: error?.data?.institutionName }}
-          shouldUnescape
-          tOptions={{ interpolation: { escapeValue: true } }}
-          /* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
-          components={[<a href="/account/settings" />]}
-        />
-      )
-      break
-    default:
-      errorText = t('generic_something_went_wrong')
-  }
-
-  return (
-    <div className="add-secondary-email-error small text-danger">
-      <MaterialIcon className="icon" type="error" />
-      <div>{errorText}</div>
-    </div>
-  )
-}

+ 0 - 326
services/web/frontend/js/features/settings/components/emails/confirm-email.tsx

@@ -1,326 +0,0 @@
-import { postJSON } from '@/infrastructure/fetch-json'
-import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
-import Notification from '@/shared/components/notification'
-import getMeta from '@/utils/meta'
-import { FormEvent, useState } from 'react'
-import { Button } from 'react-bootstrap'
-import { Trans, useTranslation } from 'react-i18next'
-import LoadingSpinner from '@/shared/components/loading-spinner'
-import MaterialIcon from '@/shared/components/material-icon'
-import { sendMB } from '@/infrastructure/event-tracking'
-import { Interstitial } from '@/shared/components/interstitial'
-
-type Feedback = {
-  type: 'input' | 'alert'
-  style: 'error' | 'info'
-  message: string
-}
-
-type ConfirmEmailFormProps = {
-  isRegistrationForm: boolean
-}
-
-export function ConfirmEmailForm({
-  isRegistrationForm,
-}: ConfirmEmailFormProps) {
-  const { t } = useTranslation()
-  const [confirmationCode, setConfirmationCode] = useState('')
-  const [feedback, setFeedback] = useState<Feedback | null>(null)
-  const [isConfirming, setIsConfirming] = useState(false)
-  const [isResending, setIsResending] = useState(false)
-  const [successRedirectPath, setSuccessRedirectPath] = useState('')
-  const email = getMeta('ol-email')
-  const { isReady } = useWaitForI18n()
-
-  const errorHandler = (err: any, actionType?: string) => {
-    let errorName = err?.data?.message?.key || 'generic_something_went_wrong'
-
-    if (err?.response?.status === 429) {
-      if (actionType === 'confirm') {
-        errorName = 'too_many_confirm_code_verification_attempts'
-      } else if (actionType === 'resend') {
-        errorName = 'too_many_confirm_code_resend_attempts'
-      }
-      setFeedback({
-        type: 'alert',
-        style: 'error',
-        message: errorName,
-      })
-    } else {
-      setFeedback({
-        type: 'input',
-        style: 'error',
-        message: errorName,
-      })
-    }
-
-    sendMB('email-verification-error', {
-      errorName,
-      flow: isRegistrationForm ? 'registration' : 'secondary',
-    })
-  }
-
-  const invalidFormHandler = () => {
-    if (!confirmationCode) {
-      return setFeedback({
-        type: 'input',
-        style: 'error',
-        message: 'please_enter_confirmation_code',
-      })
-    }
-  }
-
-  const submitHandler = (e: FormEvent<HTMLFormElement>) => {
-    e.preventDefault()
-    setIsConfirming(true)
-    setFeedback(null)
-
-    const requestPath = isRegistrationForm
-      ? '/registration/confirm-email'
-      : '/user/emails/confirm-secondary'
-
-    const requestBody = isRegistrationForm
-      ? {
-          _csrf: getMeta('ol-csrfToken'),
-          code: confirmationCode,
-          email,
-        }
-      : {
-          code: confirmationCode,
-        }
-
-    postJSON(requestPath, {
-      body: requestBody,
-    })
-      .then(data => {
-        setSuccessRedirectPath(data?.redir || '/')
-      })
-      .catch(err => {
-        errorHandler(err, 'confirm')
-      })
-      .finally(() => {
-        setIsConfirming(false)
-      })
-
-    sendMB('email-verification-click', {
-      button: 'verify',
-      flow: isRegistrationForm ? 'registration' : 'secondary',
-    })
-  }
-
-  const resendHandler = (e: FormEvent<Button>) => {
-    setIsResending(true)
-    setFeedback(null)
-
-    const requestPath = isRegistrationForm
-      ? '/registration/confirm-email/resend'
-      : '/user/emails/resend-secondary-confirmation'
-
-    const requestBody = isRegistrationForm
-      ? {
-          _csrf: getMeta('ol-csrfToken'),
-          email,
-        }
-      : {}
-
-    postJSON(requestPath, {
-      body: requestBody,
-    })
-      .then(data => {
-        setIsResending(false)
-        if (data?.message?.key) {
-          setFeedback({
-            type: 'alert',
-            style: 'info',
-            message: data.message.key,
-          })
-        }
-      })
-      .catch(err => {
-        errorHandler(err, 'resend')
-      })
-      .finally(() => {
-        setIsResending(false)
-      })
-
-    sendMB('email-verification-click', {
-      button: 'resend',
-      flow: isRegistrationForm ? 'registration' : 'secondary',
-    })
-  }
-
-  const changeHandler = (e: FormEvent<HTMLInputElement>) => {
-    setConfirmationCode(e.currentTarget.value)
-    setFeedback(null)
-  }
-
-  if (!isReady) {
-    return (
-      <Interstitial className="confirm-email" showLogo>
-        <LoadingSpinner />
-      </Interstitial>
-    )
-  }
-
-  if (successRedirectPath) {
-    return (
-      <ConfirmEmailSuccessfullForm
-        isRegistrationForm={isRegistrationForm}
-        redirectTo={successRedirectPath}
-      />
-    )
-  }
-
-  return (
-    <Interstitial className="confirm-email" showLogo>
-      <form onSubmit={submitHandler} onInvalid={invalidFormHandler}>
-        {feedback?.type === 'alert' && (
-          <Notification
-            ariaLive="polite"
-            className="confirm-email-alert"
-            type={feedback.style}
-            content={<ErrorMessage error={feedback.message} />}
-          />
-        )}
-
-        <h1 className="h3 interstitial-header">{t('confirm_your_email')}</h1>
-
-        <p className="small">{t('enter_the_confirmation_code', { email })}</p>
-        <input
-          className="form-control"
-          placeholder={t('enter_6_digit_code')}
-          inputMode="numeric"
-          required
-          value={confirmationCode}
-          onChange={changeHandler}
-          data-ol-dirty={feedback ? 'true' : undefined}
-          maxLength={6}
-          autoComplete="one-time-code"
-          autoFocus // eslint-disable-line jsx-a11y/no-autofocus
-        />
-        <div aria-live="polite">
-          {feedback?.type === 'input' && (
-            <div className="small text-danger">
-              <MaterialIcon className="icon" type="error" />
-              <div>
-                <ErrorMessage error={feedback.message} />
-              </div>
-            </div>
-          )}
-        </div>
-
-        <div className="form-actions">
-          <Button
-            disabled={isConfirming || isResending}
-            type="submit"
-            bsStyle={null}
-            className="btn-primary"
-          >
-            {isConfirming ? (
-              <>
-                {t('confirming')}
-                <span>&hellip;</span>
-              </>
-            ) : (
-              t('confirm')
-            )}
-          </Button>
-          <Button
-            disabled={isConfirming || isResending}
-            onClick={resendHandler}
-            bsStyle={null}
-            className="btn-secondary"
-          >
-            {isResending ? (
-              <>
-                {t('resending_confirmation_code')}
-                <span>&hellip;</span>
-              </>
-            ) : (
-              t('resend_confirmation_code')
-            )}
-          </Button>
-        </div>
-      </form>
-    </Interstitial>
-  )
-}
-
-function ConfirmEmailSuccessfullForm({
-  isRegistrationForm,
-  redirectTo,
-}: {
-  isRegistrationForm: boolean
-  redirectTo: string
-}) {
-  const { t } = useTranslation()
-  const submitHandler = (e: FormEvent<HTMLFormElement>) => {
-    e.preventDefault()
-    location.assign(redirectTo)
-  }
-
-  return (
-    <Interstitial className="confirm-email" showLogo>
-      <form onSubmit={submitHandler}>
-        <div aria-live="polite">
-          <h1 className="h3 interstitial-header">
-            {isRegistrationForm
-              ? t('email_confirmed_onboarding')
-              : t('thanks_for_confirming_your_email_address')}
-          </h1>
-          <p className="small">
-            {isRegistrationForm && (
-              <Trans
-                i18nKey="email_confirmed_onboarding_message"
-                /* eslint-disable-next-line react/jsx-key */
-                components={[<strong />]}
-              />
-            )}
-          </p>
-        </div>
-
-        <div className="form-actions">
-          <Button type="submit" bsStyle={null} className="btn-primary">
-            {isRegistrationForm ? t('continue') : t('go_to_overleaf')}
-          </Button>
-        </div>
-      </form>
-    </Interstitial>
-  )
-}
-
-function ErrorMessage({ error }: { error: string }) {
-  const { t } = useTranslation()
-
-  switch (error) {
-    case 'invalid_confirmation_code':
-      return <span>{t('invalid_confirmation_code')}</span>
-
-    case 'expired_confirmation_code':
-      return (
-        <Trans
-          i18nKey="expired_confirmation_code"
-          /* eslint-disable-next-line react/jsx-key  */
-          components={[<strong />]}
-        />
-      )
-
-    case 'email_already_registered':
-      return <span>{t('email_already_registered')}</span>
-
-    case 'too_many_confirm_code_resend_attempts':
-      return <span>{t('too_many_confirm_code_resend_attempts')}</span>
-
-    case 'too_many_confirm_code_verification_attempts':
-      return <span>{t('too_many_confirm_code_verification_attempts')}</span>
-
-    case 'we_sent_new_code':
-      return <span>{t('we_sent_new_code')}</span>
-
-    case 'please_enter_confirmation_code':
-      return <span>{t('please_enter_confirmation_code')}</span>
-
-    default:
-      return <span>{t('generic_something_went_wrong')}</span>
-  }
-}

+ 0 - 12
services/web/frontend/js/pages/user/add-secondary-email.tsx

@@ -1,12 +0,0 @@
-import '../../marketing'
-
-import ReactDOM from 'react-dom'
-import { AddSecondaryEmailPrompt } from '../../features/settings/components/emails/add-secondary-email-prompt'
-
-const addSecondaryEmailContainer = document.getElementById(
-  'add-secondary-email'
-)
-
-if (addSecondaryEmailContainer) {
-  ReactDOM.render(<AddSecondaryEmailPrompt />, addSecondaryEmailContainer)
-}

+ 0 - 13
services/web/frontend/js/pages/user/confirm-secondary-email.tsx

@@ -1,13 +0,0 @@
-import '../../marketing'
-
-import ReactDOM from 'react-dom'
-import { ConfirmEmailForm } from '../../features/settings/components/emails/confirm-email'
-
-const confirmEmailContainer = document.getElementById('confirm-secondary-email')
-
-if (confirmEmailContainer) {
-  ReactDOM.render(
-    <ConfirmEmailForm isRegistrationForm={false} />,
-    confirmEmailContainer
-  )
-}

+ 0 - 16
services/web/frontend/stylesheets/app/add-secondary-email-prompt.less

@@ -1,16 +0,0 @@
-.add-secondary-email {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-
-  .add-secondary-email-error {
-    display: flex;
-    gap: 6px;
-    padding: 4px;
-  }
-
-  .add-secondary-email-learn-more {
-    margin-top: 12px;
-    margin-bottom: 0;
-  }
-}

+ 0 - 23
services/web/frontend/stylesheets/app/confirm-email.less

@@ -1,23 +0,0 @@
-.confirm-email {
-  form {
-    .text-danger {
-      display: flex;
-      gap: 6px;
-      padding: 4px;
-
-      .icon {
-        padding-top: 2px;
-      }
-    }
-    .form-actions {
-      display: flex;
-      flex-direction: column;
-      gap: 12px;
-      padding-top: 20px;
-    }
-  }
-
-  .confirm-email-alert {
-    margin-bottom: 12px;
-  }
-}

+ 0 - 1
services/web/frontend/stylesheets/components/interstitial.less

@@ -9,7 +9,6 @@
   .logo {
     width: 130px;
     margin: 0 auto;
-    margin-bottom: 24px;
   }
 
   .btn {

+ 0 - 3
services/web/frontend/stylesheets/main-style.less

@@ -136,9 +136,6 @@
 @import 'app/admin-hub.less';
 @import 'app/import.less';
 @import 'app/website-redesign.less';
-@import 'app/add-secondary-email-prompt.less';
-@import 'app/confirm-email.less';
-
 // Pages
 @import 'app/about.less';
 @import 'app/blog-posts.less';

+ 22 - 0
services/web/frontend/stylesheets/modules/overleaf-integration.less

@@ -1,3 +1,25 @@
+.onboarding-confirm-email {
+  gap: 24px;
+
+  form {
+    .text-danger {
+      display: flex;
+      gap: 6px;
+      padding: 4px;
+
+      .icon {
+        padding-top: 2px;
+      }
+    }
+    .form-actions {
+      display: flex;
+      flex-direction: column;
+      gap: 12px;
+      padding-top: 20px;
+    }
+  }
+}
+
 .onboarding-data-collection-wrapper {
   max-width: 720px;
   margin: 0 auto;

+ 1 - 7
services/web/locales/en.json

@@ -52,7 +52,6 @@
   "activation_token_expired": "Your activation token has expired, you will need to get another one sent to you.",
   "active": "Active",
   "add": "Add",
-  "add_a_recovery_email_address": "Add a recovery email address",
   "add_additional_certificate": "Add another certificate",
   "add_affiliation": "Add Affiliation",
   "add_another_address_line": "Add another address line",
@@ -62,7 +61,6 @@
   "add_comment": "Add comment",
   "add_company_details": "Add Company Details",
   "add_email": "Add Email",
-  "add_email_address": "Add email address",
   "add_email_to_claim_features": "Add an institutional email address to claim your features.",
   "add_files": "Add Files",
   "add_more_collaborators": "Add more collaborators",
@@ -299,6 +297,7 @@
   "confirm_primary_email_change": "Confirm primary email change",
   "confirm_remove_sso_config_enter_email": "To confirm you want to remove your SSO configuration, enter your email address:",
   "confirm_your_email": "Confirm your email address",
+  "confirmation_code_message": "We’ve sent a 6-digit confirmation code to __email__. Please enter it below to confirm your email address.",
   "confirmation_link_broken": "Sorry, something is wrong with your confirmation link. Please try copy and pasting the link from the bottom of your confirmation email.",
   "confirmation_token_invalid": "Sorry, your confirmation token is invalid or has expired. Please request a new email confirmation link.",
   "confirming": "Confirming",
@@ -500,7 +499,6 @@
   "email_does_not_belong_to_university": "We don’t recognize that domain as being affiliated with your university. Please contact us to add the affiliation.",
   "email_limit_reached": "You can have a maximum of <0>__emailAddressLimit__ email addresses</0> on this account. To add another email address, please delete an existing one.",
   "email_link_expired": "Email link expired, please request a new one.",
-  "email_must_be_linked_to_institution": "As a member of __institutionName__, this email address can only be added via single sign-on on your <0>account settings</0> page. Please add a different recovery email address.",
   "email_or_password_wrong_try_again": "Your email or password is incorrect. Please try again.",
   "email_or_password_wrong_try_again_or_reset": "Your email or password is incorrect. Please try again, or <0>set or reset your password</0>.",
   "email_required": "Email required",
@@ -520,7 +518,6 @@
   "enter_6_digit_code": "Enter 6-digit code",
   "enter_any_size_including_units_or_valid_latex_command": "Enter any size (including units) or valid LaTeX command",
   "enter_image_url": "Enter image URL",
-  "enter_the_confirmation_code": "Enter the 6-digit confirmation code sent to __email__.",
   "enter_your_email_address": "Enter your email address",
   "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_new_password": "Enter your new password",
@@ -931,7 +928,6 @@
   "keep_current_plan": "Keep my current plan",
   "keep_personal_projects_separate": "Keep personal projects separate",
   "keep_your_account_safe": "Keep your account safe",
-  "keep_your_account_safe_add_another_email": "Keep your account safe and make sure you don’t lose access to it by adding another email address.",
   "keep_your_email_updated": "Keep your email updated so that you don’t lose access to your account and data.",
   "keybindings": "Keybindings",
   "knowledge_base": "knowledge base",
@@ -973,7 +969,6 @@
   "ldap_create_admin_instructions": "Choose an email address for the first __appName__ admin account. This should correspond to an account in the LDAP system. You will then be asked to log in with this account.",
   "learn": "Learn",
   "learn_more": "Learn more",
-  "learn_more_about_account": "<0>Learn more</0> about managing your __appName__ account.",
   "learn_more_about_emails": "<0>Learn more</0> about managing your __appName__ emails.",
   "learn_more_about_link_sharing": "Learn more about Link Sharing",
   "learn_more_about_managed_users": "Learn more about Managed Users.",
@@ -1867,7 +1862,6 @@
   "thank_you_for_being_part_of_our_beta_program": "Thank you for being part of our Beta Program, where you can have <0>early access to new features</0> and help us understand your needs better",
   "thank_you_for_being_part_of_our_labs_program": "Thank you for being part of our Labs program, where you can have <0>early access to experimental features</0> and help us explore innovative ideas that help you work more quickly and effectively",
   "thanks": "Thanks",
-  "thanks_for_confirming_your_email_address": "Thanks for confirming your email address",
   "thanks_for_subscribing": "Thanks for subscribing!",
   "thanks_for_subscribing_you_help_sl": "Thank you for subscribing to the __planName__ plan. It’s support from people like yourself that allows __appName__ to continue to grow and improve.",
   "thanks_settings_updated": "Thanks, your settings have been updated.",

+ 1 - 0
services/web/locales/zh-CN.json

@@ -297,6 +297,7 @@
   "confirm_primary_email_change": "确认主电子邮件更改",
   "confirm_remove_sso_config_enter_email": "要确认您要删除 SSO 配置,请输入您的电子邮件地址:",
   "confirm_your_email": "确认您的电子邮件地址",
+  "confirmation_code_message": "我们已向__email__发送了一个6位数的验证码。请在下面输入以确认您的电子邮件地址。",
   "confirmation_link_broken": "抱歉,您的确认链接有问题。请尝试复制并粘贴邮件底部的链接。",
   "confirmation_token_invalid": "抱歉,您的确认令牌无效或已过期。请请求新的电子邮件确认链接。",
   "confirming": "确认",

+ 2 - 1
services/web/test/acceptance/src/AddSecondaryEmailTests.js

@@ -46,8 +46,9 @@ describe('Add secondary email address confirmation code email', function () {
     spy.restore()
   })
 
-  it('should send email with confirmation code', function () {
+  it('should redirect to confirm secondary email page', function () {
     expect(res.response.statusCode).to.equal(200)
+    expect(res.body.redir).to.equal('/user/emails/confirm-secondary')
     expect(confirmCode.length).to.equal(6)
   })
 

+ 9 - 81
services/web/test/acceptance/src/PrimaryEmailCheckTests.js

@@ -2,15 +2,9 @@ const UserHelper = require('./helpers/UserHelper')
 const Settings = require('@overleaf/settings')
 const { expect } = require('chai')
 const Features = require('../../../app/src/infrastructure/Features')
-const MockV1ApiClass = require('./mocks/MockV1Api')
 
 describe('PrimaryEmailCheck', function () {
   let userHelper
-  let MockV1Api
-
-  before(function () {
-    MockV1Api = MockV1ApiClass.instance()
-  })
 
   beforeEach(async function () {
     userHelper = await UserHelper.createUser()
@@ -206,84 +200,18 @@ describe('PrimaryEmailCheck', function () {
       await UserHelper.updateUser(userHelper.user._id, {
         $set: { lastPrimaryEmailCheck: new Date(time) },
       })
-    })
-
-    describe('when the user has a secondary email address', function () {
-      before(async function () {
-        if (!Features.hasFeature('saas')) {
-          this.skip()
-        }
-      })
-
-      beforeEach(async function () {
-        await userHelper.confirmEmail(
-          userHelper.user._id,
-          userHelper.user.email
-        )
-        await userHelper.addEmailAndConfirm(
-          userHelper.user._id,
-          'secondary@overleaf.com'
-        )
-
-        checkResponse = await userHelper.fetch(
-          '/user/emails/primary-email-check',
-          { method: 'POST' }
-        )
-      })
-
-      it('should be redirected to the project list page', function () {
-        expect(checkResponse.status).to.equal(302)
-        expect(checkResponse.headers.get('location')).to.equal(
-          UserHelper.url('/project').toString()
-        )
-      })
-    })
-
-    describe('when the user has an institutional email and no secondary', function () {
-      before(async function () {
-        if (!Features.hasFeature('saas')) {
-          this.skip()
-        }
 
-        if (!Features.hasFeature('saml')) {
-          this.skip()
-        }
-      })
-
-      beforeEach(async function () {
-        MockV1Api.createInstitution({
-          name: 'Exampe Institution',
-          hostname: 'example.com',
-          licence: 'pro_plus',
-          confirmed: true,
-        })
-        MockV1Api.addAffiliation(userHelper.user._id, userHelper.user.email)
-
-        checkResponse = await userHelper.fetch(
-          '/user/emails/primary-email-check',
-          { method: 'POST' }
-        )
-      })
-
-      it('should be redirected to the add secondary email page', function () {
-        expect(checkResponse.status).to.equal(302)
-        expect(checkResponse.headers.get('location')).to.equal(
-          UserHelper.url('/user/emails/add-secondary').toString()
-        )
-      })
+      checkResponse = await userHelper.fetch(
+        '/user/emails/primary-email-check',
+        { method: 'POST' }
+      )
     })
-  })
-
-  describe('when user has checked their primary email address', function () {
-    beforeEach(async function () {
-      const time = Date.now() - Settings.primary_email_check_expiration * 2
-      await UserHelper.updateUser(userHelper.user._id, {
-        $set: { lastPrimaryEmailCheck: new Date(time) },
-      })
 
-      await userHelper.fetch('/user/emails/primary-email-check', {
-        method: 'POST',
-      })
+    it('should be redirected to the project list page', function () {
+      expect(checkResponse.status).to.equal(302)
+      expect(checkResponse.headers.get('location')).to.equal(
+        UserHelper.url('/project').toString()
+      )
     })
 
     it("shouldn't be redirected from project list to the primary email check page any longer", async function () {

+ 2 - 2
services/web/test/unit/src/User/UserEmailsControllerTests.js

@@ -301,8 +301,8 @@ describe('UserEmailsController', function () {
 
     it('sends an email confirmation', function (done) {
       this.UserEmailsController.addWithConfirmationCode(this.req, {
-        sendStatus: code => {
-          code.should.equal(200)
+        json: ({ redir }) => {
+          redir.should.equal('/user/emails/confirm-secondary')
           assertCalledWith(
             this.UserEmailsConfirmationHandler.promises.sendConfirmationCode,
             this.newEmail,

+ 0 - 39
services/web/test/unit/src/User/UserGetterTests.js

@@ -1008,45 +1008,6 @@ describe('UserGetter', function () {
     })
   })
 
-  describe('getUserConfirmedEmails', function () {
-    beforeEach(function () {
-      this.fakeUser = {
-        emails: [
-          {
-            email: 'email1@foo.bar',
-            reversedHostname: 'rab.oof',
-            confirmedAt: new Date(),
-          },
-          { email: 'email2@foo.bar', reversedHostname: 'rab.oof' },
-          {
-            email: 'email3@foo.bar',
-            reversedHostname: 'rab.oof',
-            confirmedAt: new Date(),
-          },
-        ],
-      }
-      this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
-    })
-
-    it('should get user', async function () {
-      const projection = { emails: 1 }
-      await this.UserGetter.promises.getUserConfirmedEmails(this.fakeUser._id)
-
-      this.UserGetter.promises.getUser
-        .calledWith(this.fakeUser._id, projection)
-        .should.equal(true)
-    })
-
-    it('should return only confirmed emails', async function () {
-      const confirmedEmails =
-        await this.UserGetter.promises.getUserConfirmedEmails(this.fakeUser._id)
-
-      expect(confirmedEmails.length).to.equal(2)
-      expect(confirmedEmails[0].email).to.equal('email1@foo.bar')
-      expect(confirmedEmails[1].email).to.equal('email3@foo.bar')
-    })
-  })
-
   describe('getUserbyMainEmail', function () {
     it('query user by main email', function (done) {
       const email = 'hello@world.com'