#199 deduplicate email attempts

This commit is contained in:
Avris 2021-04-13 11:29:46 +02:00
parent 052bbfd5dc
commit eb3324018d
3 changed files with 57 additions and 8 deletions

10
migrations/024-emails.sql Normal file
View File

@ -0,0 +1,10 @@
-- Up
CREATE TABLE emails (
email TEXT NOT NULL,
sentAt INTEGER NOT NULL
);
-- Down
DROP TABLE emails;

View File

@ -42,6 +42,21 @@ const findAuthenticator = async (db, id, type) => {
return authenticator
}
const findLatestEmailAuthenticator = async (db, email, type) => {
const authenticator = await db.get(SQL`SELECT * FROM authenticators
WHERE payload LIKE ${'%"email":"' + email + '"%'}
AND type = ${type}
AND (validUntil IS NULL OR validUntil > ${now()})
ORDER BY id DESC
`);
if (authenticator) {
authenticator.payload = JSON.parse(authenticator.payload);
}
return authenticator
}
const invalidateAuthenticator = async (db, id) => {
await db.get(SQL`UPDATE authenticators
SET validUntil = ${now()}
@ -113,6 +128,17 @@ const validateEmail = async (email) => {
}
}
const deduplicateEmail = async (db, email, cbSuccess, cbFail) => {
const count = (await db.get(SQL`SELECT COUNT(*) AS c FROM emails WHERE email = ${email} AND sentAt >= ${now() - 5 * 60}`)).c;
if (count > 0) {
console.error('Duplicate email requests for ' + email);
if (cbFail) { await cbFail(); }
return;
}
await cbSuccess();
await db.get(SQL`INSERT INTO emails (email, sentAt) VALUES (${email}, ${now()});`);
}
const reloadUser = async (req, res, next) => {
if (!req.user) {
next();
@ -180,14 +206,27 @@ router.post('/user/init', async (req, res) => {
return res.json({ error: 'user.account.changeEmail.invalid' })
}
const codeKey = await saveAuthenticator(req.db, 'email', user, payload, 15);
let codeKey;
if (isTest) {
codeKey = await saveAuthenticator(req.db, 'email', user, payload, 15);
} else {
await deduplicateEmail(
req.db,
payload.email,
async () => {
codeKey = await saveAuthenticator(req.db, 'email', user, payload, 15);
if (!isTest) {
mailer(
payload.email,
`[${translations.title}] ${translations.user.login.email.subject.replace('%code%', payload.code)}`,
translations.user.login.email.content.replace('%code%', payload.code),
)
},
async () => {
const auth = await findLatestEmailAuthenticator(req.db, payload.email, 'email');
codeKey = auth ? auth.id : null;
},
);
}
return res.json({

View File

@ -135,7 +135,7 @@ export const camelCase = function (words) {
}
export const now = function () {
return Math.floor(Date.now() / 1000);
return parseInt(Date.now() / 1000);
}
export const isEmoji = char => {