mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-22 19:47:33 +00:00
Confirm email new routes (#15377)
* confirm email routes * Style the email confirmation template (#15196) * error handling * prettier * error message * rename variables * message codes change * v1 redirect * fix assigning to session * rename rate limitter * rate limitter per email * add try/catch * added stub * prettier * confirm email acceptance test * confirm when created * tests * added rate limit tests * new email text * subscribe to newsletter * beforeEach/afterEach test both variants * move tests to OverleafAuthenticationTests * Revert "move tests to OverleafAuthenticationTests" This reverts commit 3c745382815da1594044a811882ba3daa24a7a3a. * cacheflow reset after each * remove test archive request * use crypto for random code * rate limit in userEmailsConfirmationHandler * ratelimiter per type * req.session.pendingUserRegistration * spy in before/after each * without deleteMany * delete staffUser in afterEach * stub response, format * rate limiter outside userEmailConfirmationHandler * mock ratelimitter * fix subscribe promise * add email to logger * logger calls * using tsscmp * fix lint * resendConfirmationCode rate limiter in router * remove redirect --------- Co-authored-by: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> GitOrigin-RevId: 786c477966cf2c5f6e28417fe486146ee5c10884
This commit is contained in:
parent
79b4f1ec7f
commit
af4b22fab5
4 changed files with 70 additions and 3 deletions
services/web/app/src/Features
|
@ -29,9 +29,14 @@ module.exports = _.template(`\
|
|||
<%= paragraph %>
|
||||
</p>
|
||||
<% }) %>
|
||||
</td>
|
||||
<% if (highlightedText) { %>
|
||||
<div style="text-align: center; color: #1B222C; font-size: 20px; margin: 16px 0; padding: 16px 8px; border-radius: 8px; background: #F4F5F6;">
|
||||
<b><%= highlightedText %></b>
|
||||
</div>
|
||||
<% } %>
|
||||
</td>
|
||||
</tr>
|
||||
</tr>
|
||||
</tr>
|
||||
</table>
|
||||
</th>
|
||||
</tr>
|
||||
|
|
|
@ -112,6 +112,10 @@ The ${settings.appName} Team - ${settings.siteUrl}\
|
|||
title:
|
||||
typeof content.title === 'function' ? content.title(opts) : undefined,
|
||||
greeting: content.greeting(opts),
|
||||
highlightedText:
|
||||
typeof content.highlightedText === 'function'
|
||||
? content.highlightedText(opts)
|
||||
: undefined,
|
||||
message: content.message(opts),
|
||||
StringHelper,
|
||||
})
|
||||
|
@ -242,6 +246,32 @@ templates.confirmEmail = ctaTemplate({
|
|||
},
|
||||
})
|
||||
|
||||
templates.confirmCode = NoCTAEmailTemplate({
|
||||
greeting(opts) {
|
||||
return ''
|
||||
},
|
||||
subject(opts) {
|
||||
return `Confirm your email address on Overleaf (${opts.confirmCode})`
|
||||
},
|
||||
title(opts) {
|
||||
return 'Confirm your email address'
|
||||
},
|
||||
message(opts, isPlainText) {
|
||||
const msg = [
|
||||
`Welcome to Overleaf! We're so glad you joined us.`,
|
||||
'Use this 6-digit confirmation code to finish your setup.',
|
||||
]
|
||||
|
||||
if (isPlainText && opts.confirmCode) {
|
||||
msg.push(opts.confirmCode)
|
||||
}
|
||||
return msg
|
||||
},
|
||||
highlightedText(opts) {
|
||||
return opts.confirmCode
|
||||
},
|
||||
})
|
||||
|
||||
templates.projectInvite = ctaTemplate({
|
||||
subject(opts) {
|
||||
const safeName = SpamSafe.isSafeProjectName(opts.project.name)
|
||||
|
|
|
@ -94,13 +94,19 @@ async function createNewUser(attributes, options = {}) {
|
|||
emailData.samlProviderId = attributes.samlIdentifiers[0].providerId
|
||||
}
|
||||
|
||||
const affiliationOptions = options.affiliationOptions || {}
|
||||
|
||||
if (options.confirmedAt) {
|
||||
emailData.confirmedAt = options.confirmedAt
|
||||
affiliationOptions.confirmedAt = options.confirmedAt
|
||||
}
|
||||
user.emails = [emailData]
|
||||
|
||||
user = await user.save()
|
||||
|
||||
if (Features.hasFeature('affiliations')) {
|
||||
try {
|
||||
user = await _addAffiliation(user, options.affiliationOptions || {})
|
||||
user = await _addAffiliation(user, affiliationOptions)
|
||||
} catch (error) {
|
||||
if (options.requireAffiliation) {
|
||||
await UserDeleter.promises.deleteMongoUser(user._id)
|
||||
|
|
|
@ -6,10 +6,12 @@ const Errors = require('../Errors/Errors')
|
|||
const UserUpdater = require('./UserUpdater')
|
||||
const UserGetter = require('./UserGetter')
|
||||
const { callbackify, promisify } = require('util')
|
||||
const crypto = require('crypto')
|
||||
|
||||
// Reject email confirmation tokens after 90 days
|
||||
const TOKEN_EXPIRY_IN_S = 90 * 24 * 60 * 60
|
||||
const TOKEN_USE = 'email_confirmation'
|
||||
const CONFIRMATION_CODE_EXPIRY_IN_S = 10 * 60
|
||||
|
||||
function sendConfirmationEmail(userId, email, emailTemplate, callback) {
|
||||
if (arguments.length === 3) {
|
||||
|
@ -46,6 +48,26 @@ function sendConfirmationEmail(userId, email, emailTemplate, callback) {
|
|||
)
|
||||
}
|
||||
|
||||
async function sendConfirmationCode(email) {
|
||||
if (!EmailHelper.parseEmail(email)) {
|
||||
throw new Error('invalid email')
|
||||
}
|
||||
|
||||
const confirmCode = crypto.randomInt(0, 1_000_000).toString().padStart(6, '0')
|
||||
const confirmCodeExpiresTimestamp =
|
||||
Date.now() + CONFIRMATION_CODE_EXPIRY_IN_S * 1000
|
||||
|
||||
await EmailHandler.promises.sendEmail('confirmCode', {
|
||||
to: email,
|
||||
confirmCode,
|
||||
})
|
||||
|
||||
return {
|
||||
confirmCode,
|
||||
confirmCodeExpiresTimestamp,
|
||||
}
|
||||
}
|
||||
|
||||
async function sendReconfirmationEmail(userId, email) {
|
||||
email = EmailHelper.parseEmail(email)
|
||||
if (!email) {
|
||||
|
@ -112,6 +134,10 @@ const UserEmailsConfirmationHandler = {
|
|||
|
||||
UserEmailsConfirmationHandler.promises = {
|
||||
sendConfirmationEmail: promisify(sendConfirmationEmail),
|
||||
confirmEmailFromToken: promisify(
|
||||
UserEmailsConfirmationHandler.confirmEmailFromToken
|
||||
),
|
||||
sendConfirmationCode,
|
||||
}
|
||||
|
||||
module.exports = UserEmailsConfirmationHandler
|
||||
|
|
Loading…
Add table
Reference in a new issue