Refactor email purge time configuration and usage to be more precise.

- New purgeTIme now allows to configure a purge to be every X minutes, hours or days.
- Also remove a bit more trust by pulling footer deletion time from config.
- TODO: implement 'convertUp' function, converting numbers up to the biggest possible value where `i > 2 (so 48hrs still works as per slogan and domain)`. I.e. 72hrs = 3 days, 360minutes = 6hrs, 1440minutes to 24hrs

Co-authored-by: Johannes Bülow <kontakt@jmbit.de>
pull/16/head
ClaraCrazy 2024-10-03 01:22:10 +02:00
parent d66d76c9f9
commit 71fd513bc0
No known key found for this signature in database
GPG Key ID: EBBC896ACB497011
7 changed files with 87 additions and 20 deletions

View File

@ -1,7 +1,11 @@
const config = { const config = {
email: { email: {
domains: process.env.EMAIL_DOMAINS, domains: process.env.EMAIL_DOMAINS,
deleteMailsOlderThanDays: process.env.EMAIL_DELETE_MAILS_OLDER_THAN_DAYS || 2 purgeTime: process.env.PURGE_TIME || {
time: 48,
unit: 'hours', // minutes, hours, days
convert: true, // Convert to highest sensible unit
}
}, },
imap: { imap: {
user: process.env.IMAP_USER, user: process.env.IMAP_USER,

41
application/helper.js Normal file
View File

@ -0,0 +1,41 @@
const config = require('./config')
const moment = require('moment')
class Helper {
/**
* Normalize our config into a proper timestamp, so we know what emails to purge
* @returns {Date}
*/
purgeTimeStamp() {
return moment()
.subtract(config.email.purgeTime.time, config.email.purgeTime.unit)
.toDate()
}
/**
* Check if time difference between now and purgeTimeStamp is more than one day
* @param {Date} now
* @param {Date} past
* @returns {Boolean}
*/
moreThanOneDay(now, past) {
const DAY_IN_MS = 24 * 60 * 60 * 1000;
if((now - past) / DAY_IN_MS >= 1){
return true
} else {
return false
}
}
/**
* Convert time to highest possible unit where i > 2
* @returns {Date}
*/
convertUp(time, unit) {
// TODO: Implement
return time +` ${unit}`
}
};
module.exports = Helper

View File

@ -8,7 +8,8 @@ const debug = require('debug')('48hr-email:imap')
const _ = require('lodash') const _ = require('lodash')
const moment = require('moment') const moment = require('moment')
const Mail = require('../domain/mail') const Mail = require('../domain/mail')
const Helper = require('./helper')
const helper = new(Helper)
// Just adding some missing functions to imap-simple... :-) // Just adding some missing functions to imap-simple... :-)
@ -199,22 +200,30 @@ class ImapService extends EventEmitter {
* @param {Date} deleteMailsBefore delete mails before this date instance * @param {Date} deleteMailsBefore delete mails before this date instance
*/ */
async deleteOldMails(deleteMailsBefore) { async deleteOldMails(deleteMailsBefore) {
let uids = await this._searchWithoutFetch([ let uids = []
['!DELETED'], if (helper.moreThanOneDay(moment(), deleteMailsBefore)) {
['BEFORE', deleteMailsBefore] //fetch mails from date -1day (calculated in MS) to avoid wasting resources
]) deleteMailsBefore = deleteMailsBefore - 24 * 60 * 60 * 1000
uids = await this._searchWithoutFetch([
['!DELETED'],
['BEFORE', deleteMailsBefore]
])
} else {
uids = await this._searchWithoutFetch([
['!DELETED']
])
}
if (uids.length === 0) { if (uids.length === 0) {
return return
} }
const DeleteOlderThan = moment() const DeleteOlderThan = helper.purgeTimeStamp()
.subtract(this.config.email.deleteMailsOlderThanDays, 'days')
.toDate()
const uidsWithHeaders = await this._getMailHeaders(uids) const uidsWithHeaders = await this._getMailHeaders(uids)
uidsWithHeaders.forEach(mail => { uidsWithHeaders.forEach(mail => {
if (mail['attributes'].date > DeleteOlderThan || this.config.http.examples.uids.includes(parseInt(mail['attributes'].id))) { if (mail['attributes'].date > DeleteOlderThan || this.config.http.examples.uids.includes(parseInt(mail['attributes'].id))) {
uids.filter(uid => uid !== mail['attributes'].uid) uids = uids.filter(uid => uid !== mail['attributes'].uid)
} }
}) })

View File

@ -3,6 +3,9 @@ const debug = require('debug')('48hr-email:imap-manager')
const mem = require('mem') const mem = require('mem')
const moment = require('moment') const moment = require('moment')
const ImapService = require('./imap-service') const ImapService = require('./imap-service')
const Helper = require('./helper')
const helper = new(Helper)
class MailProcessingService extends EventEmitter { class MailProcessingService extends EventEmitter {
constructor(mailRepository, imapService, clientNotification, config) { constructor(mailRepository, imapService, clientNotification, config) {
@ -71,15 +74,7 @@ class MailProcessingService extends EventEmitter {
async _deleteOldMails() { async _deleteOldMails() {
try { try {
await this.imapService.deleteOldMails( await this.imapService.deleteOldMails(helper.purgeTimeStamp())
moment()
// Because of how we have to handle the times (IMAP isnt time-aware), we need to subtract one day
// to get all mails in their last few hours before technical purge
//
// This is a bit of a hack, but it works. See imap-service.js#deleteOldMails (L211-227) for more info
.subtract(this.config.email.deleteMailsOlderThanDays - 1, 'days')
.toDate()
)
} catch (error) { } catch (error) {
console.log('can not delete old messages', error) console.log('can not delete old messages', error)
} }

View File

@ -3,6 +3,12 @@ const express = require('express')
const router = new express.Router() const router = new express.Router()
const {param} = require('express-validator') const {param} = require('express-validator')
const config = require('../../../application/config') const config = require('../../../application/config')
const Helper = require('../../../application/helper')
const helper = new(Helper)
const purgeTime = config.email.purgeTime.convert ? helper.convertUp(config.email.purgeTime.time, config.email.purgeTime.unit)
: config.email.purgeTime.time +` ${config.email.purgeTime.unit}`;
const sanitizeAddress = param('address').customSanitizer( const sanitizeAddress = param('address').customSanitizer(
(value, {req}) => { (value, {req}) => {
return req.params.address return req.params.address
@ -15,6 +21,7 @@ router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, (req, res, _next) => {
const mailProcessingService = req.app.get('mailProcessingService') const mailProcessingService = req.app.get('mailProcessingService')
res.render('inbox', { res.render('inbox', {
title: `${config.http.branding[0]} | ` + req.params.address, title: `${config.http.branding[0]} | ` + req.params.address,
purgeTime: purgeTime,
address: req.params.address, address: req.params.address,
mailSummaries: mailProcessingService.getMailSummaries(req.params.address), mailSummaries: mailProcessingService.getMailSummaries(req.params.address),
branding: config.http.branding, branding: config.http.branding,
@ -41,6 +48,7 @@ router.get(
res.set('Cache-Control', 'private, max-age=600') res.set('Cache-Control', 'private, max-age=600')
res.render('mail', { res.render('mail', {
title: mail.subject + " | " + req.params.address, title: mail.subject + " | " + req.params.address,
purgeTime: purgeTime,
address: req.params.address, address: req.params.address,
mail, mail,
uid: req.params.uid, uid: req.params.uid,
@ -50,6 +58,7 @@ router.get(
res.render( res.render(
'error', 'error',
{ {
purgeTime: purgeTime,
address: req.params.address, address: req.params.address,
message: 'This mail could not be found. It either does not exist or has been deleted from our servers!', message: 'This mail could not be found. It either does not exist or has been deleted from our servers!',
branding: config.http.branding branding: config.http.branding
@ -124,6 +133,7 @@ router.get(
res.render( res.render(
'error', 'error',
{ {
purgeTime: purgeTime,
address: req.params.address, address: req.params.address,
message: 'This attachment could not be found. It either does not exist or has been deleted from our servers!', message: 'This attachment could not be found. It either does not exist or has been deleted from our servers!',
branding: config.http.branding, branding: config.http.branding,
@ -163,6 +173,7 @@ router.get(
res.render( res.render(
'error', 'error',
{ {
purgeTime: purgeTime,
address: req.params.address, address: req.params.address,
message: 'This mail could not be found. It either does not exist or has been deleted from our servers!', message: 'This mail could not be found. It either does not exist or has been deleted from our servers!',
branding: config.http.branding, branding: config.http.branding,

View File

@ -4,11 +4,17 @@ const router = new express.Router()
const randomWord = require('random-word') const randomWord = require('random-word')
const {check, validationResult} = require('express-validator') const {check, validationResult} = require('express-validator')
const config = require('../../../application/config') const config = require('../../../application/config')
const Helper = require('../../../application/helper')
const helper = new(Helper)
const purgeTime = config.email.purgeTime.convert ? helper.convertUp(config.email.purgeTime.time, config.email.purgeTime.unit)
: config.email.purgeTime.time +` ${config.email.purgeTime.unit}`;
router.get('/', (req, res, _next) => { router.get('/', (req, res, _next) => {
res.render('login', { res.render('login', {
title: `${config.http.branding[0]} | Your temporary Inbox`, title: `${config.http.branding[0]} | Your temporary Inbox`,
username: randomWord(), username: randomWord(),
purgeTime: purgeTime,
domains: config.email.domains, domains: config.email.domains,
branding: config.http.branding, branding: config.http.branding,
}) })
@ -39,6 +45,7 @@ router.post(
return res.render('login', { return res.render('login', {
userInputError: true, userInputError: true,
title: `${config.http.branding[0]} | Your temporary Inbox`, title: `${config.http.branding[0]} | Your temporary Inbox`,
purgeTime: purgeTime,
username: randomWord(), username: randomWord(),
branding: config.http.branding, branding: config.http.branding,
}) })

View File

@ -28,7 +28,7 @@
{% block footer %} {% block footer %}
<section class="container footer"> <section class="container footer">
<hr> <hr>
<h4>{{ branding[0] }} offered by <a href="{{ branding[2] }}" style="text-decoration:underline" target="_blank">{{ branding[1] }}</a> | All Emails will be deleted after 48hrs | This project is <a href="https://github.com/crazyco-xyz/48hr.email" style="text-decoration:underline" target="_blank">open-source ♥</a></h4> <h4>{{ branding[0] }} offered by <a href="{{ branding[2] }}" style="text-decoration:underline" target="_blank">{{ branding[1] }}</a> | All Emails will be deleted after {{ purgeTime }} | This project is <a href="https://github.com/crazyco-xyz/48hr.email" style="text-decoration:underline" target="_blank">open-source ♥</a></h4>
</section> </section>
{% endblock %} {% endblock %}