mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #3623 from overleaf/jel-ui-reconfirm-settings
Add reconfirm UI to settings GitOrigin-RevId: cf9c4648cab07784782e24d752154089dc32196a
This commit is contained in:
parent
f50a50a5ea
commit
ddb6163b65
10 changed files with 182 additions and 4 deletions
|
@ -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)
|
||||||
|
|
39
services/web/app/views/_mixins/reconfirm_affiliation.pug
Normal file
39
services/web/app/views/_mixins/reconfirm_affiliation.pug
Normal 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')}
|
|
@ -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 || {})}
|
||||||
|
|
|
@ -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"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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,}))$/
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -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";
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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!"
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue