mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2025-12-19 10:09:35 +01:00
Compare commits
No commits in common. "2ac23719633dc554b90f41b86033614a6305e6c0" and "2902d0fcc584b4165751936685bc4b13c7103d52" have entirely different histories.
2ac2371963
...
2902d0fcc5
9 changed files with 105 additions and 128 deletions
|
|
@ -5,7 +5,6 @@ EMAIL_DOMAINS=["example.com","example.net"] # List of domain
|
||||||
EMAIL_PURGE_TIME=48 # Time value for when to purge
|
EMAIL_PURGE_TIME=48 # Time value for when to purge
|
||||||
EMAIL_PURGE_UNIT="hours" # minutes, hours, days
|
EMAIL_PURGE_UNIT="hours" # minutes, hours, days
|
||||||
EMAIL_PURGE_CONVERT=true # Convert to highest sensible unit (and round)
|
EMAIL_PURGE_CONVERT=true # Convert to highest sensible unit (and round)
|
||||||
# /\ Example: 120 minutes = 2 hours, 121 minutes = ~2 hours with added tooltip
|
|
||||||
|
|
||||||
# --- Example emails to keep clean ---
|
# --- Example emails to keep clean ---
|
||||||
EMAIL_EXAMPLE_ACCOUNT="example@48hr.email" # example email to preserve
|
EMAIL_EXAMPLE_ACCOUNT="example@48hr.email" # example email to preserve
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,5 +4,3 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
copilot-instructions.md
|
copilot-instructions.md
|
||||||
node_modules
|
node_modules
|
||||||
db/*
|
|
||||||
package-lock.json
|
|
||||||
|
|
|
||||||
|
|
@ -12,22 +12,12 @@ class MailRepository {
|
||||||
|
|
||||||
getForRecipient(address) {
|
getForRecipient(address) {
|
||||||
let mails = this.mailSummaries.get(address) || []
|
let mails = this.mailSummaries.get(address) || []
|
||||||
const mailsToDelete = []
|
|
||||||
|
|
||||||
mails.forEach(mail => {
|
mails.forEach(mail => {
|
||||||
if (mail.to == this.config.email.examples.account && !this.config.email.examples.uids.includes(parseInt(mail.uid))) {
|
if (mail.to == this.config.email.examples.account && !this.config.email.examples.uids.includes(parseInt(mail.uid))) {
|
||||||
mailsToDelete.push(mail.uid)
|
mails = mails.filter(m => m.uid != mail.uid)
|
||||||
debug('Marking non-example email for deletion from example inbox', mail.uid)
|
debug('Prevented non-example email from being shown in example inbox', mail.uid)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Delete the non-example mails
|
|
||||||
mailsToDelete.forEach(uid => {
|
|
||||||
this.removeUid(uid, address)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Get fresh list after deletions
|
|
||||||
mails = this.mailSummaries.get(address) || []
|
|
||||||
return _.orderBy(mails, mail => Date.parse(mail.date), ['desc'])
|
return _.orderBy(mails, mail => Date.parse(mail.date), ['desc'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const script = document.currentScript;
|
|
||||||
const address = script ? script.dataset.address : '';
|
|
||||||
if (address) {
|
|
||||||
enableNewMessageNotifications(address, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -2,56 +2,56 @@
|
||||||
/* eslint no-undef: 0 */
|
/* eslint no-undef: 0 */
|
||||||
|
|
||||||
function showNewMailsNotification(address, reloadPage) {
|
function showNewMailsNotification(address, reloadPage) {
|
||||||
// We want the page to be reloaded. But then when clicking the notification, it can not find the tab and will open a new one.
|
// We want the page to be reloaded. But then when clicking the notification, it can not find the tab and will open a new one.
|
||||||
|
|
||||||
const notification = new Notification(address, {
|
const notification = new Notification(address, {
|
||||||
body: 'You have new messages',
|
body: 'You have new messages',
|
||||||
icon: '/images/logo.png',
|
icon: '/images/logo.png',
|
||||||
tag: '48hr-email-replace-notification',
|
tag: '48hr-email-replace-notification',
|
||||||
renotify: true
|
renotify: true
|
||||||
})
|
})
|
||||||
notification.addEventListener('click', event => {
|
notification.addEventListener('click', event => {
|
||||||
event.preventDefault()
|
// TODO: does not work after reloading the page, see #1
|
||||||
window.focus()
|
event.preventDefault()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (reloadPage) {
|
if (reloadPage) {
|
||||||
location.reload()
|
location.reload()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableNewMessageNotifications(address, reloadPage) {
|
function enableNewMessageNotifications(address, reloadPage) {
|
||||||
enableNotifications()
|
enableNotifications()
|
||||||
const socket = io()
|
const socket = io()
|
||||||
socket.emit('sign in', address)
|
socket.emit('sign in', address)
|
||||||
|
|
||||||
socket.on('reconnect', () => {
|
socket.on('reconnect', () => {
|
||||||
socket.emit('sign in', address)
|
socket.emit('sign in', address)
|
||||||
})
|
})
|
||||||
socket.on('new emails', () => {
|
socket.on('new emails', () => {
|
||||||
showNewMailsNotification(address, reloadPage)
|
showNewMailsNotification(address, reloadPage)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableNotifications() {
|
function enableNotifications() {
|
||||||
// Let's check if the browser supports notifications
|
// Let's check if the browser supports notifications
|
||||||
if (!('Notification' in window)) {
|
if (!('Notification' in window)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's check whether notification permissions have already been granted
|
// Let's check whether notification permissions have already been granted
|
||||||
if (Notification.permission === 'granted') {
|
if (Notification.permission === 'granted') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we need to ask the user for permission
|
// Otherwise, we need to ask the user for permission
|
||||||
if (Notification.permission !== 'denied') {
|
if (Notification.permission !== 'denied') {
|
||||||
Notification.requestPermission(permission => {
|
Notification.requestPermission(permission => {
|
||||||
// If the user accepts, let's create a notification
|
// If the user accepts, let's create a notification
|
||||||
return permission === 'granted'
|
return permission === 'granted'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, if the user has denied notifications and you
|
// Finally, if the user has denied notifications and you
|
||||||
// want to be respectful there is no need to bother them any more.
|
// want to be respectful there is no need to bother them any more.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
const express = require('express')
|
|
||||||
|
|
||||||
const router = new express.Router()
|
|
||||||
const config = require('../../../application/config')
|
|
||||||
const Helper = require('../../../application/helper')
|
|
||||||
const helper = new(Helper)
|
|
||||||
|
|
||||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
|
||||||
|
|
||||||
router.get('/:address/:errorCode', async(req, res) => {
|
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
|
||||||
const count = await mailProcessingService.getCount()
|
|
||||||
|
|
||||||
const errorCode = parseInt(req.params.errorCode) || 404
|
|
||||||
const message = req.query.message || (req.session && req.session.errorMessage) || 'An error occurred'
|
|
||||||
|
|
||||||
res.status(errorCode)
|
|
||||||
res.render('error', {
|
|
||||||
title: `${config.http.branding[0]} | ${errorCode}`,
|
|
||||||
purgeTime: purgeTime,
|
|
||||||
address: req.params.address,
|
|
||||||
count: count,
|
|
||||||
message: message,
|
|
||||||
status: errorCode,
|
|
||||||
branding: config.http.branding
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
|
||||||
|
|
@ -58,8 +58,16 @@ router.get(
|
||||||
branding: config.http.branding,
|
branding: config.http.branding,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
req.session.errorMessage = 'This mail could not be found. It either does not exist or has been deleted from our servers!'
|
res.render(
|
||||||
res.redirect(`/error/${req.params.address}/404`)
|
'error', {
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
message: 'This mail could not be found. It either does not exist or has been deleted from our servers!',
|
||||||
|
branding: config.http.branding
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error while fetching email', error)
|
console.error('Error while fetching email', error)
|
||||||
|
|
@ -68,16 +76,6 @@ router.get(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Catch-all for invalid UIDs (non-numeric)
|
|
||||||
router.get(
|
|
||||||
'^/:address/:uid',
|
|
||||||
sanitizeAddress,
|
|
||||||
async(req, res) => {
|
|
||||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
|
||||||
res.redirect(`/error/${req.params.address}/400`)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'^/:address/delete-all',
|
'^/:address/delete-all',
|
||||||
sanitizeAddress,
|
sanitizeAddress,
|
||||||
|
|
@ -123,8 +121,15 @@ router.get(
|
||||||
|
|
||||||
// Validate UID is a valid integer
|
// Validate UID is a valid integer
|
||||||
if (isNaN(uid) || uid <= 0) {
|
if (isNaN(uid) || uid <= 0) {
|
||||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
return res.render(
|
||||||
return res.redirect(`/error/${req.params.address}/400`)
|
'error', {
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
message: 'Invalid/Malformed UID provided.',
|
||||||
|
branding: config.http.branding,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mail = await mailProcessingService.getOneFullMail(
|
const mail = await mailProcessingService.getOneFullMail(
|
||||||
|
|
@ -133,8 +138,15 @@ router.get(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!mail || !mail.attachments) {
|
if (!mail || !mail.attachments) {
|
||||||
req.session.errorMessage = 'This email could not be found. It either does not exist or has been deleted from our servers!'
|
return res.render(
|
||||||
return res.redirect(`/error/${req.params.address}/404`)
|
'error', {
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
message: 'This email could not be found. It either does not exist or has been deleted from our servers!',
|
||||||
|
branding: config.http.branding,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = mail.attachments.findIndex(attachment => attachment.checksum === req.params.checksum);
|
var index = mail.attachments.findIndex(attachment => attachment.checksum === req.params.checksum);
|
||||||
|
|
@ -152,8 +164,15 @@ router.get(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
req.session.errorMessage = 'This attachment could not be found. It either does not exist or has been deleted from our servers!'
|
return res.render(
|
||||||
return res.redirect(`/error/${req.params.address}/404`)
|
'error', {
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
message: 'This attachment could not be found. It either does not exist or has been deleted from our servers!',
|
||||||
|
branding: config.http.branding,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error while fetching attachment', error)
|
console.error('Error while fetching attachment', error)
|
||||||
|
|
@ -175,8 +194,15 @@ router.get(
|
||||||
|
|
||||||
// Validate UID is a valid integer
|
// Validate UID is a valid integer
|
||||||
if (isNaN(uid) || uid <= 0) {
|
if (isNaN(uid) || uid <= 0) {
|
||||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
return res.render(
|
||||||
return res.redirect(`/error/${req.params.address}/400`)
|
'error', {
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
message: 'Invalid/Malformed UID provided.',
|
||||||
|
branding: config.http.branding,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
mail = await mailProcessingService.getOneFullMail(
|
mail = await mailProcessingService.getOneFullMail(
|
||||||
|
|
@ -193,8 +219,15 @@ router.get(
|
||||||
mail
|
mail
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
req.session.errorMessage = 'This mail could not be found. It either does not exist or has been deleted from our servers!'
|
res.render(
|
||||||
res.redirect(`/error/${req.params.address}/404`)
|
'error', {
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
message: 'This mail could not be found. It either does not exist or has been deleted from our servers!',
|
||||||
|
branding: config.http.branding,
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error while fetching raw email', error)
|
console.error('Error while fetching raw email', error)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
{% extends 'layout.twig' %}
|
{% extends 'layout.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<script src="/javascripts/inbox-init.js" defer data-address="{{ address }}"></script>
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
enableNewMessageNotifications('{{ address }}', true)
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="action-links">
|
<div class="action-links">
|
||||||
<a href="/inbox/{{ address }}/delete-all">Wipe Inbox</a>
|
<a href="/inbox/{{ address }}/delete-all">Wipe Inbox</a>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ const path = require('path')
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
const debug = require('debug')('48hr-email:server')
|
const debug = require('debug')('48hr-email:server')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const session = require('express-session')
|
|
||||||
const logger = require('morgan')
|
const logger = require('morgan')
|
||||||
const Twig = require('twig')
|
const Twig = require('twig')
|
||||||
const compression = require('compression')
|
const compression = require('compression')
|
||||||
|
|
@ -12,7 +11,6 @@ const socketio = require('socket.io')
|
||||||
const config = require('../../application/config')
|
const config = require('../../application/config')
|
||||||
const inboxRouter = require('./routes/inbox')
|
const inboxRouter = require('./routes/inbox')
|
||||||
const loginRouter = require('./routes/login')
|
const loginRouter = require('./routes/login')
|
||||||
const errorRouter = require('./routes/error')
|
|
||||||
const { sanitizeHtmlTwigFilter } = require('./views/twig-filters')
|
const { sanitizeHtmlTwigFilter } = require('./views/twig-filters')
|
||||||
|
|
||||||
const Helper = require('../../application/helper')
|
const Helper = require('../../application/helper')
|
||||||
|
|
@ -32,14 +30,6 @@ app.use(logger('dev'))
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
app.use(express.urlencoded({ extended: false }))
|
app.use(express.urlencoded({ extended: false }))
|
||||||
|
|
||||||
// Session middleware
|
|
||||||
app.use(session({
|
|
||||||
secret: '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ', // They will hate me for this
|
|
||||||
resave: false,
|
|
||||||
saveUninitialized: true,
|
|
||||||
cookie: { maxAge: 1000 * 60 * 60 * 24 } // 24 hours
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Remove trailing slash middleware (except for root)
|
// Remove trailing slash middleware (except for root)
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
if (req.path.length > 1 && req.path.endsWith('/')) {
|
if (req.path.length > 1 && req.path.endsWith('/')) {
|
||||||
|
|
@ -73,7 +63,6 @@ app.get('/', (req, res, _next) => {
|
||||||
|
|
||||||
app.use('/', loginRouter)
|
app.use('/', loginRouter)
|
||||||
app.use('/inbox', inboxRouter)
|
app.use('/inbox', inboxRouter)
|
||||||
app.use('/error', errorRouter)
|
|
||||||
|
|
||||||
// Catch 404 and forward to error handler
|
// Catch 404 and forward to error handler
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue