overleaf/services/web/frontend/js/main/affiliations/controllers/UserAffiliationsController.js
Miguel Serrano 176ead8983 Primary Email Check (#6471)
* added primary-email-check page, route and controllers
* add `#add-email` internal link in settings to display new email form
* added primary-email-check redirection with split test
* update `lastPrimaryEmailCheck` when the default email address is set
* added `lastPrimaryCheck` to admin panel
* translations for primary-email-check
* acceptance tests for primary-email-check
* [web] multi-submit for primary email check
* Using `confirmedAt` to prevent from displaying primary-email-check page

Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
Co-Authored-By: Miguel Serrano <mserranom@gmail.com>
GitOrigin-RevId: d8e3a280439da08038a4487d8bfd7b3b0596e3b5
2022-02-04 09:03:34 +00:00

363 lines
12 KiB
JavaScript

import _ from 'lodash'
/* eslint-disable
max-len,
no-return-assign,
no-useless-escape,
*/
import App from '../../../base'
import getMeta from '../../../utils/meta'
export default App.controller(
'UserAffiliationsController',
function ($scope, $rootScope, UserAffiliationsDataService, $q, $window) {
$scope.userEmails = []
$scope.linkedInstitutionIds = []
$scope.hideInstitutionNotifications = {}
$scope.closeInstitutionNotification = type => {
$scope.hideInstitutionNotifications[type] = true
}
$scope.samlBetaSession = ExposedSettings.hasSamlBeta
$scope.samlInitPath = ExposedSettings.samlInitPath
$scope.reconfirmationRemoveEmail = getMeta('ol-reconfirmationRemoveEmail')
$scope.reconfirmedViaSAML = getMeta('ol-reconfirmedViaSAML')
$scope.showInstitutionalLeaversSurvey = false
$scope.dismissInstitutionalLeaversSurvey = () => {
try {
localStorage.setItem('hideInstitutionalLeaversSurvey', true)
} catch (e) {}
$scope.showInstitutionalLeaversSurvey = false
}
function maybeShowInstitutionalLeaversSurvey() {
if (localStorage.getItem('hideInstitutionalLeaversSurvey')) return
if (
localStorage.getItem('showInstitutionalLeaversSurveyUntil') > Date.now()
) {
$scope.showInstitutionalLeaversSurvey = true
}
}
maybeShowInstitutionalLeaversSurvey()
const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
const EMAIL_REGEX =
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const _matchLocalAndDomain = function (userEmailInput) {
const match = userEmailInput
? userEmailInput.match(LOCAL_AND_DOMAIN_REGEX)
: undefined
if (match) {
return { local: match[1], domain: match[2] }
} else {
return { local: null, domain: null }
}
}
const _ssoAvailableForAffiliation = affiliation => {
if (!affiliation) return false
const institution = affiliation.institution
if (!_ssoAvailableForInstitution(institution)) return false
if (!institution.confirmed) return false // domain is confirmed, not the email
return true
}
const _ssoAvailableForDomain = domain => {
if (!domain) return false
if (!domain.confirmed) return false // domain is confirmed, not the email
const institution = domain.university
if (!_ssoAvailableForInstitution(institution)) return false
return true
}
const _ssoAvailableForInstitution = institution => {
if (!ExposedSettings.hasSamlFeature) return false
if (!institution) return false
if (institution.ssoEnabled) return true
if ($scope.samlBetaSession && institution.ssoBeta) return true
return false
}
$scope.getEmailSuggestion = function (userInput) {
const userInputLocalAndDomain = _matchLocalAndDomain(userInput)
$scope.ui.isValidEmail = EMAIL_REGEX.test(userInput)
$scope.ui.isBlacklistedEmail = false
$scope.ui.showManualUniversitySelectionUI = false
if (userInputLocalAndDomain.domain) {
$scope.ui.isBlacklistedEmail =
UserAffiliationsDataService.isDomainBlacklisted(
userInputLocalAndDomain.domain
)
return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
userInputLocalAndDomain.domain
)
.then(function (universityDomain) {
const currentUserInputLocalAndDomain = _matchLocalAndDomain(
$scope.newAffiliation.email
)
if (
currentUserInputLocalAndDomain.domain ===
universityDomain.hostname
) {
$scope.newAffiliation.university = universityDomain.university
$scope.newAffiliation.department = universityDomain.department
$scope.newAffiliation.ssoAvailable =
_ssoAvailableForDomain(universityDomain)
} else {
_resetAffiliationSuggestion()
}
return $q.resolve(
`${userInputLocalAndDomain.local}@${universityDomain.hostname}`
)
})
.catch(function () {
_resetAffiliationSuggestion()
return $q.reject(null)
})
} else {
_resetAffiliationSuggestion()
return $q.reject(null)
}
}
$scope.linkInstitutionAcct = function (email, institutionId) {
_resetMakingRequestType()
$scope.ui.isMakingRequest = true
$scope.ui.isProcessing = true
$window.location.href = `${$scope.samlInitPath}?university_id=${institutionId}&auto=/user/settings&email=${email}`
}
$scope.selectUniversityManually = function () {
_resetAffiliationSuggestion()
$scope.ui.showManualUniversitySelectionUI = true
}
$scope.changeAffiliation = function (userEmail) {
if (_.get(userEmail, ['affiliation', 'institution', 'id'])) {
UserAffiliationsDataService.getUniversityDetails(
userEmail.affiliation.institution.id
).then(
universityDetails =>
($scope.affiliationToChange.university = universityDetails)
)
}
$scope.affiliationToChange.email = userEmail.email
$scope.affiliationToChange.role = userEmail.affiliation.role
$scope.affiliationToChange.department = userEmail.affiliation.department
}
$scope.saveAffiliationChange = function (userEmail) {
userEmail.affiliation.role = $scope.affiliationToChange.role
userEmail.affiliation.department = $scope.affiliationToChange.department
_resetAffiliationToChange()
return _monitorRequest(
UserAffiliationsDataService.addRoleAndDepartment(
userEmail.email,
userEmail.affiliation.role,
userEmail.affiliation.department
)
).then(() => setTimeout(() => _getUserEmails()))
}
$scope.cancelAffiliationChange = email => _resetAffiliationToChange()
$scope.isChangingAffiliation = email =>
$scope.affiliationToChange.email === email
$scope.showAddEmailForm = () => ($scope.ui.showAddEmailUI = true)
$scope.addNewEmail = function () {
let addEmailPromise
if (!$scope.newAffiliation.university) {
addEmailPromise = UserAffiliationsDataService.addUserEmail(
$scope.newAffiliation.email
)
} else {
if ($scope.newAffiliation.university.isUserSuggested) {
addEmailPromise =
UserAffiliationsDataService.addUserAffiliationWithUnknownUniversity(
$scope.newAffiliation.email,
$scope.newAffiliation.university.name,
$scope.newAffiliation.country.code,
$scope.newAffiliation.role,
$scope.newAffiliation.department
)
} else {
addEmailPromise = UserAffiliationsDataService.addUserAffiliation(
$scope.newAffiliation.email,
$scope.newAffiliation.university.id,
$scope.newAffiliation.role,
$scope.newAffiliation.department
)
}
}
$scope.ui.isAddingNewEmail = true
$scope.ui.showAddEmailUI = false
return _monitorRequest(addEmailPromise)
.then(function () {
_resetNewAffiliation()
_resetAddingEmail()
setTimeout(() => _getUserEmails())
})
.finally(() => ($scope.ui.isAddingNewEmail = false))
}
$scope.setDefaultUserEmail = userEmail =>
_monitorRequest(
UserAffiliationsDataService.setDefaultUserEmail(userEmail.email)
).then(function () {
for (const email of $scope.userEmails || []) {
email.default = false
}
userEmail.default = true
window.usersEmail = userEmail.email
$rootScope.usersEmail = userEmail.email
})
$scope.removeUserEmail = function (userEmail) {
$scope.userEmails = $scope.userEmails.filter(ue => ue !== userEmail)
return _monitorRequest(
UserAffiliationsDataService.removeUserEmail(userEmail.email)
)
.then(() => {
if (
userEmail.emailHasInstitutionLicence ||
(userEmail.affiliation && userEmail.affiliation.pastReconfirmDate)
) {
const stillHasLicenseAccess = $scope.userEmails.some(
ue => ue.emailHasInstitutionLicence
)
if (!stillHasLicenseAccess) {
try {
localStorage.setItem(
'showInstitutionalLeaversSurveyUntil',
Date.now() + ONE_WEEK_IN_MS
)
maybeShowInstitutionalLeaversSurvey()
} catch (e) {}
}
}
})
.catch(() => {
$scope.userEmails.push(userEmail)
})
}
$scope.resendConfirmationEmail = function (userEmail) {
_resetMakingRequestType()
$scope.ui.isResendingConfirmation = true
return _monitorRequest(
UserAffiliationsDataService.resendConfirmationEmail(userEmail.email)
).finally(() => ($scope.ui.isResendingConfirmation = false))
}
$scope.acknowledgeError = function () {
_reset()
return _getUserEmails()
}
const _resetAffiliationToChange = () =>
($scope.affiliationToChange = {
email: '',
university: null,
role: null,
department: null,
})
const _resetNewAffiliation = () =>
($scope.newAffiliation = {
email: '',
country: null,
university: null,
role: null,
department: null,
})
function _resetAddingEmail() {
$scope.ui.showAddEmailUI = false
$scope.ui.isValidEmail = false
$scope.ui.isBlacklistedEmail = false
$scope.ui.showManualUniversitySelectionUI = false
}
const _resetAffiliationSuggestion = () => {
$scope.newAffiliation = {
email: $scope.newAffiliation.email,
}
}
function _resetMakingRequestType() {
$scope.ui.isLoadingEmails = false
$scope.ui.isProcessing = false
$scope.ui.isResendingConfirmation = false
}
function _reset() {
$scope.ui = {
hasError: false,
errorMessage: '',
showChangeAffiliationUI: false,
isMakingRequest: false,
isLoadingEmails: false,
isAddingNewEmail: false,
}
_resetAffiliationToChange()
_resetNewAffiliation()
return _resetAddingEmail()
}
_reset()
$scope.ui.showAddEmailUI = window.location.hash === '#add-email'
function _monitorRequest(promise) {
$scope.ui.hasError = false
$scope.ui.isMakingRequest = true
promise
.catch(function (response) {
$scope.ui.hasError = true
$scope.ui.errorMessage = _.get(response, ['data', 'message'])
})
.finally(() => ($scope.ui.isMakingRequest = false))
return promise
}
$scope.inReconfirmNotificationPeriod = function (emailData) {
return _.get(emailData, ['affiliation', 'inReconfirmNotificationPeriod'])
}
$scope.institutionAlreadyLinked = function (emailData) {
const institutionId =
emailData.affiliation &&
emailData.affiliation.institution &&
emailData.affiliation.institution &&
emailData.affiliation.institution.id
? emailData.affiliation.institution.id.toString()
: undefined
return $scope.linkedInstitutionIds.indexOf(institutionId) !== -1
}
// Populates the emails table
function _getUserEmails() {
_resetMakingRequestType()
$scope.ui.isLoadingEmails = true
_monitorRequest(
UserAffiliationsDataService.getUserEmailsEnsureAffiliation()
)
.then(emails => {
$scope.userEmails = emails.map(email => {
email.ssoAvailable = _ssoAvailableForAffiliation(email.affiliation)
return email
})
$scope.linkedInstitutionIds = emails
.filter(email => {
return !!email.samlProviderId
})
.map(email => email.samlProviderId)
})
.finally(() => ($scope.ui.isLoadingEmails = false))
}
_getUserEmails()
}
)