mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #752 from sharelatex/ja-confirmation-ui-and-reconfirm
Show confirmation status in email UI, with button to resend confirmation
This commit is contained in:
commit
a5b943d5b6
7 changed files with 195 additions and 37 deletions
|
@ -61,6 +61,19 @@ module.exports = UserEmailsController =
|
|||
return next(error) if error?
|
||||
res.sendStatus 204
|
||||
|
||||
resendConfirmation: (req, res, next) ->
|
||||
userId = AuthenticationController.getLoggedInUserId(req)
|
||||
email = EmailHelper.parseEmail(req.body.email)
|
||||
return res.sendStatus 422 unless email?
|
||||
UserGetter.getUserByAnyEmail email, {_id:1}, (error, user) ->
|
||||
return next(error) if error?
|
||||
if !user? or user?._id?.toString() != userId
|
||||
logger.log {userId, email, foundUserId: user?._id}, "email doesn't match logged in user"
|
||||
return res.sendStatus 422
|
||||
logger.log {userId, email}, 'resending email confirmation token'
|
||||
UserEmailsConfirmationHandler.sendConfirmationEmail userId, email, (error) ->
|
||||
return next(error) if error?
|
||||
res.sendStatus 200
|
||||
|
||||
showConfirm: (req, res, next) ->
|
||||
res.render 'user/confirm_email', {
|
||||
|
|
|
@ -115,6 +115,9 @@ module.exports = class Router
|
|||
UserEmailsController.showConfirm
|
||||
webRouter.post '/user/emails/confirm',
|
||||
UserEmailsController.confirm
|
||||
webRouter.post '/user/emails/resend_confirmation',
|
||||
AuthenticationController.requireLogin(),
|
||||
UserEmailsController.resendConfirmation
|
||||
|
||||
if Features.hasFeature 'affiliations'
|
||||
webRouter.post '/user/emails',
|
||||
|
|
|
@ -17,27 +17,37 @@ form.row(
|
|||
tr(
|
||||
ng-repeat="userEmail in userEmails"
|
||||
)
|
||||
td {{ userEmail.email + (userEmail.default ? ' (default)' : '') }}
|
||||
td
|
||||
| {{ userEmail.email + (userEmail.default ? ' (default)' : '') }}
|
||||
div(ng-if="!userEmail.confirmedAt").small
|
||||
strong #{translate('unconfirmed')}.
|
||||
|
|
||||
| #{translate('please_check_your_inbox')}.
|
||||
br
|
||||
a(
|
||||
href,
|
||||
ng-click="resendConfirmationEmail(userEmail)"
|
||||
) #{translate('resend_confirmation_email')}
|
||||
td
|
||||
div(ng-if="userEmail.affiliation.institution")
|
||||
div {{ userEmail.affiliation.institution.name }}
|
||||
a(
|
||||
href
|
||||
ng-if="!isChangingAffiliation(userEmail.email) && !userEmail.affiliation.role && !userEmail.affiliation.department"
|
||||
ng-click="changeAffiliation(userEmail);"
|
||||
) #{translate("add_role_and_department")}
|
||||
div(
|
||||
span.small
|
||||
a(
|
||||
href
|
||||
ng-if="!isChangingAffiliation(userEmail.email) && !userEmail.affiliation.role && !userEmail.affiliation.department"
|
||||
ng-click="changeAffiliation(userEmail);"
|
||||
) #{translate("add_role_and_department")}
|
||||
div.small(
|
||||
ng-if="!isChangingAffiliation(userEmail.email) && (userEmail.affiliation.role || userEmail.affiliation.department)"
|
||||
)
|
||||
span(ng-if="userEmail.affiliation.role") {{ userEmail.affiliation.role }}
|
||||
span(ng-if="userEmail.affiliation.role && userEmail.affiliation.department") ,
|
||||
span(ng-if="userEmail.affiliation.department") {{ userEmail.affiliation.department }}
|
||||
| (
|
||||
br
|
||||
a(
|
||||
href
|
||||
ng-click="changeAffiliation(userEmail);"
|
||||
) #{translate("change")}
|
||||
| )
|
||||
.affiliation-change-container(
|
||||
ng-if="isChangingAffiliation(userEmail.email)"
|
||||
)
|
||||
|
@ -46,7 +56,7 @@ form.row(
|
|||
show-university-and-country="false"
|
||||
show-role-and-department="true"
|
||||
)
|
||||
.affiliation-change-actions
|
||||
.affiliation-change-actions.small
|
||||
a(
|
||||
href
|
||||
ng-click="saveAffiliationChange();"
|
||||
|
@ -56,18 +66,27 @@ form.row(
|
|||
href
|
||||
ng-click="cancelAffiliationChange();"
|
||||
) #{translate("save_or_cancel-cancel")}
|
||||
td
|
||||
a.affiliations-table-inline-action(
|
||||
href
|
||||
ng-if="!userEmail.default"
|
||||
td.affiliations-table-inline-actions
|
||||
// Disabled buttons don't work with tooltips, due to pointer-events: none,
|
||||
// so create a wrapper for the tooltip
|
||||
div.affiliations-table-inline-action-disabled-wrapper(
|
||||
tooltip=translate("please_confirm_your_email_before_making_it_default")
|
||||
ng-if="!userEmail.default && !userEmail.confirmedAt"
|
||||
)
|
||||
button.btn.btn-sm.btn-success.affiliations-table-inline-action(
|
||||
disabled
|
||||
) #{translate("make_default")}
|
||||
button.btn.btn-sm.btn-success.affiliations-table-inline-action(
|
||||
ng-if="!userEmail.default && userEmail.confirmedAt"
|
||||
ng-click="setDefaultUserEmail(userEmail)"
|
||||
) #{translate("make_default")}
|
||||
br
|
||||
a.affiliations-table-inline-action(
|
||||
href
|
||||
) #{translate("make_default")}
|
||||
|
|
||||
button.btn.btn-sm.btn-danger.affiliations-table-inline-action(
|
||||
ng-if="!userEmail.default"
|
||||
ng-click="removeUserEmail(userEmail)"
|
||||
) #{translate("remove")}
|
||||
tooltip=translate("remove")
|
||||
)
|
||||
i.fa.fa-fw.fa-trash
|
||||
tr.affiliations-table-highlighted-row(
|
||||
ng-if="ui.isLoadingEmails"
|
||||
)
|
||||
|
@ -100,15 +119,17 @@ form.row(
|
|||
input-required="true"
|
||||
)
|
||||
td
|
||||
.affiliations-table-label(
|
||||
p.affiliations-table-label(
|
||||
ng-if="newAffiliation.university && !ui.showManualUniversitySelectionUI"
|
||||
)
|
||||
| {{ newAffiliation.university.name }} (
|
||||
a(
|
||||
href
|
||||
ng-click="selectUniversityManually();"
|
||||
) #{translate("change")}
|
||||
| )
|
||||
| {{ newAffiliation.university.name }}
|
||||
span.small
|
||||
| (
|
||||
a(
|
||||
href
|
||||
ng-click="selectUniversityManually();"
|
||||
) #{translate("change")}
|
||||
| )
|
||||
.affiliations-table-label(
|
||||
ng-if="!newAffiliation.university && !ui.isValidEmail && !ui.showManualUniversitySelectionUI"
|
||||
) #{translate("start_by_adding_your_email")}
|
||||
|
@ -127,7 +148,7 @@ form.row(
|
|||
show-role-and-department="ui.isValidEmail && newAffiliation.university"
|
||||
)
|
||||
td
|
||||
button.btn.btn-primary(
|
||||
button.btn.btn-sm.btn-primary(
|
||||
ng-disabled="affiliationsForm.$invalid || ui.isAddingNewEmail"
|
||||
ng-click="addNewEmail()"
|
||||
)
|
||||
|
|
|
@ -128,6 +128,13 @@ define [
|
|||
.then () -> _getUserEmails()
|
||||
.catch () -> $scope.ui.hasError = true
|
||||
|
||||
$scope.resendConfirmationEmail = (userEmail) ->
|
||||
$scope.ui.isLoadingEmails = true
|
||||
UserAffiliationsDataService
|
||||
.resendConfirmationEmail userEmail.email
|
||||
.then () -> _getUserEmails()
|
||||
.catch () -> $scope.ui.hasError = true
|
||||
|
||||
$scope.acknowledgeError = () ->
|
||||
_reset()
|
||||
_getUserEmails()
|
||||
|
|
|
@ -104,6 +104,12 @@ define [
|
|||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
resendConfirmationEmail = (email) ->
|
||||
$http.post "/user/emails/resend_confirmation", {
|
||||
email,
|
||||
_csrf: window.csrfToken
|
||||
}
|
||||
|
||||
isDomainBlacklisted = (domain) ->
|
||||
domain.toLowerCase() of domainsBlackList
|
||||
|
||||
|
@ -121,6 +127,7 @@ define [
|
|||
addRoleAndDepartment
|
||||
setDefaultUserEmail
|
||||
removeUserEmail
|
||||
resendConfirmationEmail
|
||||
isDomainBlacklisted
|
||||
}
|
||||
]
|
|
@ -24,11 +24,14 @@
|
|||
width: 40%;
|
||||
}
|
||||
.affiliations-table-inline-actions {
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
}
|
||||
.affiliations-table-inline-action {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.affiliations-table-inline-action-disabled-wrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
.affiliations-table-highlighted-row {
|
||||
background-color: tint(@content-alt-bg-color, 6%);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ describe "UserEmails", ->
|
|||
token = null
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json:
|
||||
|
@ -45,7 +45,7 @@ describe "UserEmails", ->
|
|||
token = tokens[0].token
|
||||
cb()
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/confirm',
|
||||
json:
|
||||
|
@ -80,7 +80,7 @@ describe "UserEmails", ->
|
|||
(cb) => @user2.login cb
|
||||
(cb) =>
|
||||
# Create email for first user
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json: {@email}
|
||||
|
@ -99,21 +99,21 @@ describe "UserEmails", ->
|
|||
cb()
|
||||
(cb) =>
|
||||
# Delete the email from the first user
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/delete',
|
||||
json: {@email}
|
||||
}, cb
|
||||
(cb) =>
|
||||
# Create email for second user
|
||||
@user2.request {
|
||||
@user2.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json: {@email}
|
||||
}, cb
|
||||
(cb) =>
|
||||
# Original confirmation token should no longer work
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/confirm',
|
||||
json:
|
||||
|
@ -158,7 +158,7 @@ describe "UserEmails", ->
|
|||
token = null
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json:
|
||||
|
@ -183,12 +183,12 @@ describe "UserEmails", ->
|
|||
db.tokens.update {
|
||||
token: token
|
||||
}, {
|
||||
$set: {
|
||||
$set: {
|
||||
expiresAt: new Date(Date.now() - 1000000)
|
||||
}
|
||||
}, cb
|
||||
(cb) =>
|
||||
@user.request {
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/confirm',
|
||||
json:
|
||||
|
@ -198,3 +198,107 @@ describe "UserEmails", ->
|
|||
expect(response.statusCode).to.equal 404
|
||||
cb()
|
||||
], done
|
||||
|
||||
describe 'resending the confirmation', ->
|
||||
it 'should generate a new token', (done) ->
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails',
|
||||
json:
|
||||
email: 'reconfirmation-email@example.com'
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 204
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
# There should only be one confirmation token at the moment
|
||||
expect(tokens.length).to.equal 1
|
||||
expect(tokens[0].data.email).to.equal 'reconfirmation-email@example.com'
|
||||
expect(tokens[0].data.user_id).to.equal @user._id
|
||||
cb()
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/resend_confirmation',
|
||||
json:
|
||||
email: 'reconfirmation-email@example.com'
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 200
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
# There should be two tokens now
|
||||
expect(tokens.length).to.equal 2
|
||||
expect(tokens[0].data.email).to.equal 'reconfirmation-email@example.com'
|
||||
expect(tokens[0].data.user_id).to.equal @user._id
|
||||
expect(tokens[1].data.email).to.equal 'reconfirmation-email@example.com'
|
||||
expect(tokens[1].data.user_id).to.equal @user._id
|
||||
cb()
|
||||
], done
|
||||
|
||||
it 'should create a new token if none exists', (done) ->
|
||||
# This should only be for users that have sign up with their main
|
||||
# emails before the confirmation system existed
|
||||
async.series [
|
||||
(cb) =>
|
||||
db.tokens.remove {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, cb
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/resend_confirmation',
|
||||
json:
|
||||
email: @user.email
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 200
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
# There should still only be one confirmation token
|
||||
expect(tokens.length).to.equal 1
|
||||
expect(tokens[0].data.email).to.equal @user.email
|
||||
expect(tokens[0].data.user_id).to.equal @user._id
|
||||
cb()
|
||||
], done
|
||||
|
||||
it "should not allow reconfirmation if the email doesn't match the user", (done) ->
|
||||
async.series [
|
||||
(cb) =>
|
||||
@user.request {
|
||||
method: 'POST',
|
||||
url: '/user/emails/resend_confirmation',
|
||||
json:
|
||||
email: 'non-matching-email@example.com'
|
||||
}, (error, response, body) =>
|
||||
return done(error) if error?
|
||||
expect(response.statusCode).to.equal 422
|
||||
cb()
|
||||
(cb) =>
|
||||
db.tokens.find {
|
||||
use: 'email_confirmation',
|
||||
'data.user_id': @user._id,
|
||||
usedAt: { $exists: false }
|
||||
}, (error, tokens) =>
|
||||
expect(tokens.length).to.equal 0
|
||||
cb()
|
||||
], done
|
||||
|
|
Loading…
Reference in a new issue