Merge pull request #3623 from overleaf/jel-ui-reconfirm-settings

Add reconfirm UI to settings

GitOrigin-RevId: cf9c4648cab07784782e24d752154089dc32196a
This commit is contained in:
Jessica Lawshe 2021-02-22 09:01:02 -06:00 committed by Copybot
parent f50a50a5ea
commit ddb6163b65
10 changed files with 182 additions and 4 deletions

View file

@ -66,6 +66,7 @@ const UserPagesController = {
settingsPage(req, res, next) { settingsPage(req, res, next) {
const userId = AuthenticationController.getLoggedInUserId(req) const userId = AuthenticationController.getLoggedInUserId(req)
const reconfirmationRemoveEmail = req.query.remove
// SSO // SSO
const ssoError = req.session.ssoError const ssoError = req.session.ssoError
if (ssoError) { if (ssoError) {
@ -91,6 +92,8 @@ const UserPagesController = {
'saml', 'saml',
'requestedEmail' 'requestedEmail'
]) ])
const reconfirmedViaSAML = _.get(req.session, ['saml', 'reconfirmed'])
delete req.session.saml delete req.session.saml
let shouldAllowEditingDetails = true let shouldAllowEditingDetails = true
if (Settings.ldap && Settings.ldap.updateUserDetailsOnLogin) { if (Settings.ldap && Settings.ldap.updateUserDetailsOnLogin) {
@ -123,6 +126,8 @@ const UserPagesController = {
institutionEmailNonCanonical && institutionRequestedEmail institutionEmailNonCanonical && institutionRequestedEmail
? institutionEmailNonCanonical ? institutionEmailNonCanonical
: undefined, : undefined,
reconfirmedViaSAML,
reconfirmationRemoveEmail,
samlBeta: req.session.samlBeta, samlBeta: req.session.samlBeta,
ssoError: ssoError, ssoError: ssoError,
thirdPartyIds: UserPagesController._restructureThirdPartyIds(user) thirdPartyIds: UserPagesController._restructureThirdPartyIds(user)

View file

@ -0,0 +1,39 @@
mixin reconfirmAffiliationNotification(location)
.reconfirm-notification(ng-controller="UserAffiliationsReconfirmController")
div(ng-if="!reconfirm[userEmail.email].reconfirmationSent" style="width:100%;")
i.fa.fa-warning
button.btn-reconfirm.btn.btn-sm.btn-info(
data-location=location
ng-if="!ui.sentReconfirmation"
ng-click="requestReconfirmation($event, userEmail)"
ng-disabled="ui.isMakingRequest"
) #{translate("confirm_affiliation")}
| !{translate("are_you_still_at", {institutionName: '{{userEmail.affiliation.institution.name}}'}, ['strong'])}
|  
if location == '/user/settings'
| !{translate('please_reconfirm_institutional_email', {}, [{ name: 'span' }])}
span(ng-if="userEmail.default")  #{translate('need_to_add_new_primary_before_remove')}
else
| !{translate("please_reconfirm_institutional_email", {}, [{name: 'a', attrs: {href: '/user/settings?remove={{userEmail.email}}' }}])}
|  
a(href="/learn/how-to/Reconfirm_an_institutional_email_address") #{translate("learn_more")}
div(ng-if="reconfirm[userEmail.email].reconfirmationSent")
| !{translate("please_check_your_inbox_to_confirm", {institutionName: '{{userEmail.affiliation.institution.name}}'}, ['strong'])}
|  
a(
href
ng-click="requestReconfirmation($event, userEmail)"
ng-disabled="ui.isMakingRequest"
) #{translate('resend_confirmation_email')}
mixin reconfirmedAffiliationNotification()
.alert.alert-info
.reconfirm-notification
div
//- extra div for flex styling
| !{translate("your_affiliation_is_confirmed", {institutionName: '{{userEmail.affiliation.institution.name}}'}, ['strong'])} #{translate('thank_you')}

View file

@ -281,7 +281,13 @@ block content
) )
span(ng-hide="state.inflight") #{translate("delete")} span(ng-hide="state.inflight") #{translate("delete")}
span(ng-show="state.inflight") #{translate("deleting")}… span(ng-show="state.inflight") #{translate("deleting")}…
script#data(type="application/json").
!{StringHelper.stringifyJsonForScript({ reconfirmationRemoveEmail, reconfirmedViaSAML })}
script(type="text/javascript").
window.data = JSON.parse(document.querySelector("#data").text);
script(type='text/javascript'). script(type='text/javascript').
window.usersEmail = !{StringHelper.stringifyJsonForScript(user.email)}; window.usersEmail = !{StringHelper.stringifyJsonForScript(user.email)};
window.passwordStrengthOptions = !{StringHelper.stringifyJsonForScript(settings.passwordStrengthOptions || {})} window.passwordStrengthOptions = !{StringHelper.stringifyJsonForScript(settings.passwordStrengthOptions || {})}

View file

@ -1,3 +1,5 @@
include ../../_mixins/reconfirm_affiliation
mixin aboutInstitutionLink() mixin aboutInstitutionLink()
a(href="/learn/how-to/Institutional_Login") #{translate("find_out_more_about_institution_login")}. a(href="/learn/how-to/Institutional_Login") #{translate("find_out_more_about_institution_login")}.
@ -116,7 +118,7 @@ form.row(
) #{translate("make_primary")} ) #{translate("make_primary")}
|   |  
+btnRemoveEmail() +btnRemoveEmail()
tr.affiliations-table-saml-row(ng-repeat-end ng-if="userEmail.affiliation && userEmail.affiliation && userEmail.ssoAvailable") tr.affiliations-table-saml-row(ng-if="userEmail.affiliation && userEmail.affiliation && userEmail.ssoAvailable")
td td
td(ng-attr-colspan="{{userEmail.samlProviderId ? '2' : '1'}}" ng-class="institutionAlreadyLinked(userEmail) ? '' : 'with-border'") td(ng-attr-colspan="{{userEmail.samlProviderId ? '2' : '1'}}" ng-class="institutionAlreadyLinked(userEmail) ? '' : 'with-border'")
p.small(ng-if="userEmail.samlProviderId") p.small(ng-if="userEmail.samlProviderId")
@ -137,6 +139,23 @@ form.row(
type="button" type="button"
) )
| #{translate("link_accounts")} | #{translate("link_accounts")}
tr(
class="reconfirm-row"
ng-if="userEmail.samlIdentifier && userEmail.samlIdentifier.providerId === reconfirmedViaSAML"
)
td(colspan="3")
+reconfirmedAffiliationNotification()
tr(
class="reconfirm-row"
ng-repeat-end
)
td(
colspan="3"
ng-if="userEmail.affiliation && userEmail.affiliation.inReconfirmNotificationPeriod"
)
div(ng-class="{'alert alert-info': reconfirmationRemoveEmail === userEmail.email}")
+reconfirmAffiliationNotification('/user/settings')
tr.affiliations-table-highlighted-row( tr.affiliations-table-highlighted-row(
ng-if="!ui.showAddEmailUI && !ui.isMakingRequest" ng-if="!ui.showAddEmailUI && !ui.isMakingRequest"
) )

View file

@ -33,6 +33,7 @@ import './main/affiliations/components/affiliationForm'
import './main/affiliations/components/inputSuggestions' import './main/affiliations/components/inputSuggestions'
import './main/affiliations/controllers/UserAffiliationsController' import './main/affiliations/controllers/UserAffiliationsController'
import './main/affiliations/factories/UserAffiliationsDataService' import './main/affiliations/factories/UserAffiliationsDataService'
import './main/affiliations/controllers/UserAffiliationsReconfirmController'
import './main/oauth/controllers/UserOauthController' import './main/oauth/controllers/UserOauthController'
import './main/keys' import './main/keys'
import './main/importing' import './main/importing'

View file

@ -22,6 +22,8 @@ export default App.controller('UserAffiliationsController', function(
} }
$scope.samlBetaSession = ExposedSettings.hasSamlBeta $scope.samlBetaSession = ExposedSettings.hasSamlBeta
$scope.samlInitPath = ExposedSettings.samlInitPath $scope.samlInitPath = ExposedSettings.samlInitPath
$scope.reconfirmationRemoveEmail = $window.data.reconfirmationRemoveEmail
$scope.reconfirmedViaSAML = $window.data.reconfirmedViaSAML
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/ 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 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,}))$/

View file

@ -0,0 +1,56 @@
import _ from 'lodash'
import App from '../../../base'
export default App.controller('UserAffiliationsReconfirmController', function(
$scope,
UserAffiliationsDataService,
$window
) {
$scope.reconfirm = {}
// For portals:
$scope.userEmails = window.data.userEmails
// For settings page:
$scope.ui = $scope.ui || {} // $scope.ui inherited on settings page
// For dashboard:
$scope.allInReconfirmNotificationPeriods =
window.data.allInReconfirmNotificationPeriods
function sendReconfirmEmail(email) {
$scope.ui.hasError = false
$scope.ui.isMakingRequest = true
UserAffiliationsDataService.resendConfirmationEmail(email)
.then(() => {
$scope.reconfirm[email].reconfirmationSent = true
})
.catch(error => {
$scope.ui.hasError = true
})
.finally(() => ($scope.ui.isMakingRequest = false))
}
$scope.reconfirmationRemoveEmail = $window.data.reconfirmationRemoveEmail
$scope.reconfirmedViaSAML = $window.data.reconfirmedViaSAML
$scope.requestReconfirmation = function(obj, userEmail) {
const email = userEmail.email
// For the settings page, disable other parts of affiliation UI
$scope.ui.isMakingRequest = true
$scope.ui.isProcessing = true
// create UI scope for requested email
$scope.reconfirm[email] = $scope.reconfirm[email] || {} // keep existing scope for resend email requests
const location = obj.currentTarget.getAttribute('data-location')
const institutionId = _.get(userEmail, ['affiliation', 'institution', 'id'])
const ssoEnabled = _.get(userEmail, [
'affiliation',
'institution',
'ssoEnabled'
])
if (ssoEnabled) {
$window.location.href = `${$scope.samlInitPath}?university_id=${institutionId}&reconfirm=${location}`
} else {
sendReconfirmEmail(email)
}
}
})

View file

@ -36,6 +36,7 @@
@import 'components/footer.less'; @import 'components/footer.less';
//@import "components/breadcrumbs.less"; //@import "components/breadcrumbs.less";
//@import "components/pagination.less"; //@import "components/pagination.less";
@import 'components/notifications.less';
@import 'components/pager.less'; @import 'components/pager.less';
@import 'components/labels.less'; @import 'components/labels.less';
//@import "components/badges.less"; //@import "components/badges.less";

View file

@ -0,0 +1,41 @@
.reconfirm-notification {
display: flex;
width: 100%;
.fa-warning {
margin-right: @margin-sm;
}
.btn-reconfirm {
float: right;
margin-left: @margin-sm;
text-transform: capitalize;
}
}
// Settings page
.affiliations-table {
.reconfirm-notification {
margin: 0px auto @margin-sm auto !important;
padding: @padding-md;
}
.reconfirm-row {
td {
border: 0;
.alert {
border: 0;
padding: 0;
}
:not(.alert) {
.reconfirm-notification {
background-color: @ol-blue-gray-0;
border-radius: @border-radius-base;
.fa-warning {
color: @brand-warning;
}
}
}
}
}
}

View file

@ -1347,5 +1347,13 @@
"did_you_know_institution_providing_professional": "Did you know that __institutionName__ is providing <0>free __appName__ Professional features</0> to everyone at __institutionName__?", "did_you_know_institution_providing_professional": "Did you know that __institutionName__ is providing <0>free __appName__ Professional features</0> to everyone at __institutionName__?",
"add_email_to_claim_features": "Add an institutional email address to claim your features.", "add_email_to_claim_features": "Add an institutional email address to claim your features.",
"please_change_primary_to_remove": "Please change your primary email in order to remove", "please_change_primary_to_remove": "Please change your primary email in order to remove",
"dropbox_duplicate_project_names": "We detected an update from Dropbox to <0>__projectName__</0>, but you have multiple projects with that name. We are unable to process this, so have unlinked your Dropbox account. Please ensure your project names are unique across your active, archived and trashed projects, and then re-link your Dropbox account." "dropbox_duplicate_project_names": "We detected an update from Dropbox to <0>__projectName__</0>, but you have multiple projects with that name. We are unable to process this, so have unlinked your Dropbox account. Please ensure your project names are unique across your active, archived and trashed projects, and then re-link your Dropbox account.",
} "please_reconfirm_institutional_email": "Please take a moment to confirm your institutional email address or <0>remove it</0> from your account.",
"need_to_add_new_primary_before_remove": "You'll need to add a new primary email address before you can remove this one.",
"are_you_still_at": "Are you still at <0>__institutionName__</0>?",
"confirm_affiliation": "Confirm Affiliation",
"please_check_your_inbox_to_confirm": "Please check your email inbox to confirm your <0>__institutionName__</0> affiliation.",
"resend_confirmation_email": "Resend confirmation email",
"your_affiliation_is_confirmed": "Your <0>__institutionName__</0> affiliation is confirmed.",
"thank_you": "Thank you!"
}