mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-01-09 19:29:34 +01:00
[Chore]: Add extensive debug logging and improve config clarity
Introduces detailed debug logging throughout the application to aid troubleshooting and monitoring, unifying the debug namespace usage. Refactors configuration files for clarity, adds missing environment variables, and updates example values and documentation. Enhances screenshots management by hosting assets locally. Updates scripts for better development and production workflows. Improves comments for maintainability and adjusts minor UI meta tags.
This commit is contained in:
parent
ba6d97c7fe
commit
994142fc29
21 changed files with 9034 additions and 8985 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
# --- EMAIL CONFIGURATION ---
|
# --- EMAIL CONFIGURATION ---
|
||||||
EMAIL_DOMAINS=["example.com","example.net"] # List of domains your service handles (list)
|
EMAIL_DOMAINS=["example.com","example.net"] # List of domains your service handles ['example.com', 'example.net']
|
||||||
|
|
||||||
# --- Purge configuration ---
|
# --- Purge configuration ---
|
||||||
EMAIL_PURGE_TIME=48 # Time value for when to purge
|
EMAIL_PURGE_TIME=48 # Time value for when to purge
|
||||||
|
|
@ -24,7 +24,7 @@ IMAP_CONCURRENCY=6 # Number of conc
|
||||||
|
|
||||||
# --- HTTP / WEB CONFIGURATION ---
|
# --- HTTP / WEB CONFIGURATION ---
|
||||||
HTTP_PORT=3000 # Port
|
HTTP_PORT=3000 # Port
|
||||||
HTTP_BRANDING=["48hr.email","CrazyCo","https://crazyco.xyz"] # [service_title, company_name, company_url]
|
HTTP_BRANDING=["48hr.email","CrazyCo","https://crazyco.xyz"] # ['service_title', 'company_name', 'company_url']
|
||||||
HTTP_DISPLAY_SORT=2 # Domain display sorting:
|
HTTP_DISPLAY_SORT=2 # Domain display sorting:
|
||||||
# 0 = no change,
|
# 0 = no change,
|
||||||
# 1 = alphabetical,
|
# 1 = alphabetical,
|
||||||
|
|
|
||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
|
|
@ -3,7 +3,7 @@
|
||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
patreon: crazyco
|
patreon: crazyco
|
||||||
open_collective: # Replace with a single Open Collective username
|
open_collective: # Replace with a single Open Collective username
|
||||||
ko_fi: # Replace with a single Ko-fi username
|
ko_fi: crazyco
|
||||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
liberapay: # Replace with a single Liberapay username
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
|
|
||||||
BIN
.github/assets/html.png
vendored
Normal file
BIN
.github/assets/html.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
.github/assets/inbox.png
vendored
Normal file
BIN
.github/assets/inbox.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
BIN
.github/assets/raw.png
vendored
Normal file
BIN
.github/assets/raw.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,4 +5,3 @@
|
||||||
copilot-instructions.md
|
copilot-instructions.md
|
||||||
node_modules
|
node_modules
|
||||||
db/*
|
db/*
|
||||||
package-lock.json
|
|
||||||
|
|
|
||||||
10
README.md
10
README.md
|
|
@ -71,7 +71,7 @@ User=user
|
||||||
Group=user
|
Group=user
|
||||||
|
|
||||||
WorkingDirectory=/opt/48hr-email
|
WorkingDirectory=/opt/48hr-email
|
||||||
ExecStart=npm run start
|
ExecStart=npm run prod
|
||||||
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
TimeoutStartSec=0
|
TimeoutStartSec=0
|
||||||
|
|
@ -113,13 +113,13 @@ WantedBy=multi-user.target
|
||||||
### Screenshots:
|
### Screenshots:
|
||||||
|
|
||||||
- #### Inbox:
|
- #### Inbox:
|
||||||
<img align="center" src="https://i.imgur.com/JJmSe7S.png">
|
<img align="center" src=".github/assets/inbox.png">
|
||||||
|
|
||||||
- #### Email with CSS:
|
- #### Email using HTML and CSS:
|
||||||
<img align="center" src="https://i.imgur.com/x8OBoI7.png">
|
<img align="center" src=".github/assets/html.png">
|
||||||
|
|
||||||
- #### Email without CSS:
|
- #### Email without CSS:
|
||||||
<img align="center" src="https://i.imgur.com/VPZ8IG6.png">
|
<img align="center" src=".github/assets/raw.png">
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
|
|
||||||
11
app.js
11
app.js
|
|
@ -3,6 +3,7 @@
|
||||||
/* eslint unicorn/no-process-exit: 0 */
|
/* eslint unicorn/no-process-exit: 0 */
|
||||||
|
|
||||||
const config = require('./application/config')
|
const config = require('./application/config')
|
||||||
|
const debug = require('debug')('48hr-email:app')
|
||||||
|
|
||||||
// Until node 11 adds flatmap, we use this:
|
// Until node 11 adds flatmap, we use this:
|
||||||
require('array.prototype.flatmap').shim()
|
require('array.prototype.flatmap').shim()
|
||||||
|
|
@ -14,40 +15,50 @@ const MailProcessingService = require('./application/mail-processing-service')
|
||||||
const MailRepository = require('./domain/mail-repository')
|
const MailRepository = require('./domain/mail-repository')
|
||||||
|
|
||||||
const clientNotification = new ClientNotification()
|
const clientNotification = new ClientNotification()
|
||||||
|
debug('Client notification service initialized')
|
||||||
clientNotification.use(io)
|
clientNotification.use(io)
|
||||||
|
|
||||||
const imapService = new ImapService(config)
|
const imapService = new ImapService(config)
|
||||||
|
debug('IMAP service initialized')
|
||||||
const mailProcessingService = new MailProcessingService(
|
const mailProcessingService = new MailProcessingService(
|
||||||
new MailRepository(),
|
new MailRepository(),
|
||||||
imapService,
|
imapService,
|
||||||
clientNotification,
|
clientNotification,
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
|
debug('Mail processing service initialized')
|
||||||
|
|
||||||
// Put everything together:
|
// Put everything together:
|
||||||
imapService.on(ImapService.EVENT_NEW_MAIL, mail =>
|
imapService.on(ImapService.EVENT_NEW_MAIL, mail =>
|
||||||
mailProcessingService.onNewMail(mail)
|
mailProcessingService.onNewMail(mail)
|
||||||
)
|
)
|
||||||
|
debug('Bound IMAP new mail event handler')
|
||||||
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () =>
|
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () =>
|
||||||
mailProcessingService.onInitialLoadDone()
|
mailProcessingService.onInitialLoadDone()
|
||||||
)
|
)
|
||||||
|
debug('Bound IMAP initial load done event handler')
|
||||||
imapService.on(ImapService.EVENT_DELETED_MAIL, mail =>
|
imapService.on(ImapService.EVENT_DELETED_MAIL, mail =>
|
||||||
mailProcessingService.onMailDeleted(mail)
|
mailProcessingService.onMailDeleted(mail)
|
||||||
)
|
)
|
||||||
|
debug('Bound IMAP deleted mail event handler')
|
||||||
|
|
||||||
mailProcessingService.on('error', err => {
|
mailProcessingService.on('error', err => {
|
||||||
|
debug('Fatal error from mail processing service:', err.message)
|
||||||
console.error('Error from mailProcessingService, stopping.', err)
|
console.error('Error from mailProcessingService, stopping.', err)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
imapService.on(ImapService.EVENT_ERROR, error => {
|
imapService.on(ImapService.EVENT_ERROR, error => {
|
||||||
|
debug('Fatal error from IMAP service:', error.message)
|
||||||
console.error('Fatal error from IMAP service', error)
|
console.error('Fatal error from IMAP service', error)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.set('mailProcessingService', mailProcessingService)
|
app.set('mailProcessingService', mailProcessingService)
|
||||||
|
|
||||||
|
debug('Starting IMAP connection and message loading')
|
||||||
imapService.connectAndLoadMessages().catch(error => {
|
imapService.connectAndLoadMessages().catch(error => {
|
||||||
|
debug('Failed to connect to IMAP:', error.message)
|
||||||
console.error('Fatal error from IMAP service', error)
|
console.error('Fatal error from IMAP service', error)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
151
app.json
151
app.json
|
|
@ -1,71 +1,84 @@
|
||||||
{
|
{
|
||||||
"name": "48hr.email | Disposable email",
|
"name": "48hr.email | Disposable email",
|
||||||
"description": "A simple and fast disposable mail service that works directly with your already existing imap server. No database required.",
|
"description": "A simple and fast disposable mail service that works directly with your already existing imap server. No database required.",
|
||||||
"repository": "https://github.com/Crazyco-xyz/48hr.email",
|
"repository": "https://github.com/Crazyco-xyz/48hr.email",
|
||||||
"logo": "https://github.com/Crazyco-xyz/48hr.email/blob/main/infrastructure/web/public/images/logo.png",
|
"logo": "https://github.com/Crazyco-xyz/48hr.email/blob/main/infrastructure/web/public/images/logo.png",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"node",
|
"node",
|
||||||
"disposable-mail"
|
"disposable-mail"
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"EMAIL_DOMAINS": {
|
"EMAIL_DOMAINS": {
|
||||||
"description": "Email domains"
|
"description": "List of domains your service handles"
|
||||||
},
|
},
|
||||||
"EMAIL_PURGE_TIME": {
|
"EMAIL_PURGE_TIME": {
|
||||||
"description": "Config for when to purge the emails",
|
"description": "Time value for when to purge",
|
||||||
"value": {
|
"value": 48
|
||||||
"time": 48,
|
},
|
||||||
"unit": "hours",
|
"EMAIL_PURGE_UNIT": {
|
||||||
"convert": true
|
"description": "Time unit for purging (minutes, hours, days)",
|
||||||
}
|
"value": "hours"
|
||||||
},
|
},
|
||||||
"EMAIL_EXAMPLES": {
|
"EMAIL_PURGE_CONVERT": {
|
||||||
"description": "Examples of the domains",
|
"description": "Convert to highest sensible unit and round",
|
||||||
"value": {
|
"value": true
|
||||||
"account": "example@48hr.email",
|
},
|
||||||
"uids": [1, 2, 3]
|
"EMAIL_EXAMPLE_ACCOUNT": {
|
||||||
}
|
"description": "Example email account to preserve",
|
||||||
},
|
"value": "example@48hr.email"
|
||||||
"IMAP_USER": {
|
},
|
||||||
"description": "Username to login to the imap server"
|
"EMAIL_EXAMPLE_UIDS": {
|
||||||
},
|
"description": "Example UIDs to preserve",
|
||||||
"IMAP_PASSWORD": {
|
"value": [1, 2, 3]
|
||||||
"description": "Password to login to the imap server"
|
},
|
||||||
},
|
"IMAP_USER": {
|
||||||
"IMAP_SERVER": {
|
"description": "Username to login to the imap server"
|
||||||
"description": "Hostname of the server (usually imap.example.com or mx.example.com)"
|
},
|
||||||
},
|
"IMAP_PASSWORD": {
|
||||||
"IMAP_PORT": {
|
"description": "Password to login to the imap server"
|
||||||
"description": "Port of the server (usually 993)",
|
},
|
||||||
"value": 993
|
"IMAP_SERVER": {
|
||||||
},
|
"description": "Hostname of the server (usually imap.example.com or mx.example.com)"
|
||||||
"IMAP_TLS": {
|
},
|
||||||
"description": "Use tls or not",
|
"IMAP_PORT": {
|
||||||
"value": true
|
"description": "Port of the server (usually 993)",
|
||||||
},
|
"value": 993
|
||||||
"IMAP_AUTHTIMEOUT": {
|
},
|
||||||
"description": "Timeout for the auth",
|
"IMAP_TLS": {
|
||||||
"value": 3000
|
"description": "Use tls or not",
|
||||||
},
|
"value": true
|
||||||
"IMAP_REFRESH_INTERVAL_SECONDS": {
|
},
|
||||||
"description": "How often to refresh the imap messages manually",
|
"IMAP_AUTH_TIMEOUT": {
|
||||||
"value": 60
|
"description": "Timeout for the auth in milliseconds",
|
||||||
},
|
"value": 3000
|
||||||
"HTTP_PORT": {
|
},
|
||||||
"description": "Port to listen on",
|
"IMAP_REFRESH_INTERVAL_SECONDS": {
|
||||||
"value": 3000
|
"description": "How often to refresh the imap messages manually",
|
||||||
},
|
"value": 60
|
||||||
"HTTP_BRANDING": {
|
},
|
||||||
"description": "The branding of the site",
|
"IMAP_FETCH_CHUNK": {
|
||||||
"value": ["48hr.email", "Crazyco", "https://crazyco.xyz"]
|
"description": "Number of UIDs per fetch chunk during initial load",
|
||||||
},
|
"value": 200
|
||||||
"HTTP_DISPLAY_SORT": {
|
},
|
||||||
"description": "Sort the emails for use",
|
"IMAP_CONCURRENCY": {
|
||||||
"value": 0
|
"description": "Number of concurrent fetch workers during initial load",
|
||||||
},
|
"value": 6
|
||||||
"HTTP_HIDE_OTHER": {
|
},
|
||||||
"description": "Hide other emails from the list besides the first",
|
"HTTP_PORT": {
|
||||||
"value": false
|
"description": "Port to listen on",
|
||||||
|
"value": 3000
|
||||||
|
},
|
||||||
|
"HTTP_BRANDING": {
|
||||||
|
"description": "The branding of the site",
|
||||||
|
"value": ["48hr.email", "Crazyco", "https://crazyco.xyz"]
|
||||||
|
},
|
||||||
|
"HTTP_DISPLAY_SORT": {
|
||||||
|
"description": "Domain display sorting: 0 = no change, 1 = alphabetical, 2 = alphabetical + first item shuffled, 3 = shuffle all",
|
||||||
|
"value": 2
|
||||||
|
},
|
||||||
|
"HTTP_HIDE_OTHER": {
|
||||||
|
"description": "Hide other emails from the list besides the first",
|
||||||
|
"value": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// config.js
|
// config.js
|
||||||
require("dotenv").config({ quiet: true });
|
require("dotenv").config({ quiet: true });
|
||||||
|
const debug = require('debug')('48hr-email:config')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safely parse a value from env.
|
* Safely parse a value from env.
|
||||||
|
|
@ -63,12 +64,17 @@ const config = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// validation
|
// validation
|
||||||
|
debug('Validating configuration...')
|
||||||
if (!config.imap.user || !config.imap.password || !config.imap.host) {
|
if (!config.imap.user || !config.imap.password || !config.imap.host) {
|
||||||
|
debug('IMAP configuration validation failed: missing user, password, or host')
|
||||||
throw new Error("IMAP is not configured. Check IMAP_* env vars.");
|
throw new Error("IMAP is not configured. Check IMAP_* env vars.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!config.email.domains.length) {
|
if (!config.email.domains.length) {
|
||||||
|
debug('Email domains validation failed: no domains configured')
|
||||||
throw new Error("No EMAIL_DOMAINS configured.");
|
throw new Error("No EMAIL_DOMAINS configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug(`Configuration validated successfully: ${config.email.domains.length} domains, IMAP host: ${config.imap.host}`)
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
const config = require('./config')
|
const config = require('./config')
|
||||||
const moment = require('moment')
|
const moment = require('moment')
|
||||||
|
const debug = require('debug')('48hr-email:helper')
|
||||||
|
|
||||||
class Helper {
|
class Helper {
|
||||||
|
|
||||||
|
|
@ -8,9 +9,11 @@ class Helper {
|
||||||
* @returns {Date}
|
* @returns {Date}
|
||||||
*/
|
*/
|
||||||
purgeTimeStamp() {
|
purgeTimeStamp() {
|
||||||
return moment()
|
const cutoff = moment()
|
||||||
.subtract(config.email.purgeTime.time, config.email.purgeTime.unit)
|
.subtract(config.email.purgeTime.time, config.email.purgeTime.unit)
|
||||||
.toDate()
|
.toDate()
|
||||||
|
debug(`Purge cutoff calculated: ${cutoff} (${config.email.purgeTime.time} ${config.email.purgeTime.unit} ago)`)
|
||||||
|
return cutoff
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,10 +28,12 @@ class Helper {
|
||||||
const nowMs = now instanceof Date ? now.getTime() : now;
|
const nowMs = now instanceof Date ? now.getTime() : now;
|
||||||
const pastMs = past instanceof Date ? past.getTime() : new Date(past).getTime();
|
const pastMs = past instanceof Date ? past.getTime() : new Date(past).getTime();
|
||||||
|
|
||||||
return (nowMs - pastMs) >= DAY_IN_MS;
|
const diffMs = nowMs - pastMs;
|
||||||
|
const result = diffMs >= DAY_IN_MS;
|
||||||
|
debug(`Time difference check: ${diffMs}ms >= ${DAY_IN_MS}ms = ${result}`)
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert time to highest possible unit (minutes → hours → days),
|
* Convert time to highest possible unit (minutes → hours → days),
|
||||||
* rounding if necessary and prefixing "~" when rounded.
|
* rounding if necessary and prefixing "~" when rounded.
|
||||||
|
|
@ -76,9 +81,8 @@ class Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
const footer = `<label title="${Tooltip}">
|
const footer = `<label title="${Tooltip}">
|
||||||
<h4 style="display: inline;"><u><i>${time}</i></u></h4>
|
<h4 style="display: inline;"><u><i>${time}</i></u></h4>
|
||||||
</Label>`
|
</Label>`
|
||||||
|
|
||||||
return footer
|
return footer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,7 +91,6 @@ class Helper {
|
||||||
* @param {Array} array
|
* @param {Array} array
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
shuffleArray(array) {
|
shuffleArray(array) {
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
const j = Math.floor(Math.random() * (i + 1));
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
|
@ -101,7 +104,6 @@ class Helper {
|
||||||
* @param {Array} array
|
* @param {Array} array
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
shuffleFirstItem(array) {
|
shuffleFirstItem(array) {
|
||||||
let first = array[Math.floor(Math.random() * array.length)]
|
let first = array[Math.floor(Math.random() * array.length)]
|
||||||
array = array.filter((value) => value != first);
|
array = array.filter((value) => value != first);
|
||||||
|
|
@ -126,17 +128,26 @@ class Helper {
|
||||||
* Get a domain list from config for use
|
* Get a domain list from config for use
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
getDomains() {
|
getDomains() {
|
||||||
|
debug(`Getting domains with displaySort: ${config.http.displaySort}`)
|
||||||
|
let result;
|
||||||
switch (config.http.displaySort) {
|
switch (config.http.displaySort) {
|
||||||
case 0:
|
case 0:
|
||||||
return this.hideOther(config.email.domains) // No modification
|
result = this.hideOther(config.email.domains) // No modification
|
||||||
|
debug(`Domain sort 0: no modification, ${result.length} domains`)
|
||||||
|
return result
|
||||||
case 1:
|
case 1:
|
||||||
return this.hideOther(config.email.domains.sort()) // Sort alphabetically
|
result = this.hideOther(config.email.domains.sort()) // Sort alphabetically
|
||||||
|
debug(`Domain sort 1: alphabetical sort, ${result.length} domains`)
|
||||||
|
return result
|
||||||
case 2:
|
case 2:
|
||||||
return this.hideOther(this.shuffleFirstItem(config.email.domains.sort())) // Sort alphabetically and shuffle first item
|
result = this.hideOther(this.shuffleFirstItem(config.email.domains.sort())) // Sort alphabetically and shuffle first item
|
||||||
|
debug(`Domain sort 2: alphabetical + shuffle first, ${result.length} domains`)
|
||||||
|
return result
|
||||||
case 3:
|
case 3:
|
||||||
return this.hideOther(this.shuffleArray(config.email.domains)) // Shuffle all
|
result = this.hideOther(this.shuffleArray(config.email.domains)) // Shuffle all
|
||||||
|
debug(`Domain sort 3: shuffle all, ${result.length} domains`)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ const { simpleParser } = require('mailparser')
|
||||||
const addressparser = require('nodemailer/lib/addressparser')
|
const addressparser = require('nodemailer/lib/addressparser')
|
||||||
const pSeries = require('p-series')
|
const pSeries = require('p-series')
|
||||||
const retry = require('async-retry')
|
const retry = require('async-retry')
|
||||||
const debug = require('debug')('48hr-email:imap')
|
const debug = require('debug')('48hr-email:imap-manager')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const moment = require('moment')
|
const moment = require('moment')
|
||||||
const Mail = require('../domain/mail')
|
const Mail = require('../domain/mail')
|
||||||
|
|
@ -133,7 +133,7 @@ class ImapService extends EventEmitter {
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.connection.openBox('INBOX')
|
await this.connection.openBox('INBOX')
|
||||||
debug('Connected to imap')
|
debug('Connected to imap Server at ' + this.config.imap.host)
|
||||||
}, {
|
}, {
|
||||||
retries: 5
|
retries: 5
|
||||||
}
|
}
|
||||||
|
|
@ -173,8 +173,14 @@ class ImapService extends EventEmitter {
|
||||||
debug('Load skipped: another load already in progress')
|
debug('Load skipped: another load already in progress')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadingInProgress = true
|
this.loadingInProgress = true
|
||||||
debug('Starting load of mail summaries')
|
if (this.initialLoadDone) {
|
||||||
|
debug('Updating mail summaries from server...')
|
||||||
|
} else {
|
||||||
|
debug('Fetching mail summaries from server...')
|
||||||
|
}
|
||||||
|
|
||||||
const uids = await this._getAllUids()
|
const uids = await this._getAllUids()
|
||||||
const newUids = uids.filter(uid => !this.loadedUids.has(uid))
|
const newUids = uids.filter(uid => !this.loadedUids.has(uid))
|
||||||
debug(`UIDs on server: ${uids.length}, new UIDs to fetch: ${newUids.length}, already loaded: ${this.loadedUids.size}`)
|
debug(`UIDs on server: ${uids.length}, new UIDs to fetch: ${newUids.length}, already loaded: ${this.loadedUids.size}`)
|
||||||
|
|
@ -219,7 +225,7 @@ class ImapService extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadingInProgress = false
|
this.loadingInProgress = false
|
||||||
debug('Load finished')
|
debug('Finished updating mail summary list')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const debug = require('debug')('48hr-email:imap-manager')
|
const debug = require('debug')('48hr-email:imap-processor')
|
||||||
const mem = require('mem')
|
const mem = require('mem')
|
||||||
const ImapService = require('./imap-service')
|
const ImapService = require('./imap-service')
|
||||||
const Helper = require('./helper')
|
const Helper = require('./helper')
|
||||||
|
|
@ -33,29 +33,36 @@ class MailProcessingService extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
getMailSummaries(address) {
|
getMailSummaries(address) {
|
||||||
|
debug('Getting mail summaries for', address)
|
||||||
return this.mailRepository.getForRecipient(address)
|
return this.mailRepository.getForRecipient(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteSpecificEmail(adress, uid) {
|
deleteSpecificEmail(adress, uid) {
|
||||||
|
debug('Deleting specific email', adress, uid)
|
||||||
if (this.mailRepository.removeUid(uid, adress) == true) {
|
if (this.mailRepository.removeUid(uid, adress) == true) {
|
||||||
this.imapService.deleteSpecificEmail(uid)
|
this.imapService.deleteSpecificEmail(uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getOneFullMail(address, uid, raw = false) {
|
getOneFullMail(address, uid, raw = false) {
|
||||||
|
debug('Cache lookup for', address + ':' + uid, raw ? '(raw)' : '(parsed)')
|
||||||
return this.cachedFetchFullMail(address, uid, raw)
|
return this.cachedFetchFullMail(address, uid, raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllMailSummaries() {
|
getAllMailSummaries() {
|
||||||
|
debug('Getting all mail summaries')
|
||||||
return this.mailRepository.getAll()
|
return this.mailRepository.getAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
getCount() {
|
getCount() {
|
||||||
return this.mailRepository.mailCount()
|
const count = this.mailRepository.mailCount()
|
||||||
|
debug('Mail count requested:', count)
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
onInitialLoadDone() {
|
onInitialLoadDone() {
|
||||||
this.initialLoadDone = true
|
this.initialLoadDone = true
|
||||||
|
debug('Initial load completed, total mails:', this.mailRepository.mailCount())
|
||||||
console.log(`Initial load done, got ${this.mailRepository.mailCount()} mails`)
|
console.log(`Initial load done, got ${this.mailRepository.mailCount()} mails`)
|
||||||
console.log(`Fetching and deleting mails every ${this.config.imap.refreshIntervalSeconds} seconds`)
|
console.log(`Fetching and deleting mails every ${this.config.imap.refreshIntervalSeconds} seconds`)
|
||||||
console.log(`Mails older than ${this.config.email.purgeTime.time} ${this.config.email.purgeTime.unit} will be deleted`)
|
console.log(`Mails older than ${this.config.email.purgeTime.time} ${this.config.email.purgeTime.unit} will be deleted`)
|
||||||
|
|
@ -69,7 +76,9 @@ class MailProcessingService extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
mail.to.forEach(to => {
|
mail.to.forEach(to => {
|
||||||
|
debug('Adding mail to repository for recipient:', to)
|
||||||
this.mailRepository.add(to, mail)
|
this.mailRepository.add(to, mail)
|
||||||
|
debug('Emitting notification for:', to)
|
||||||
return this.clientNotification.emit(to)
|
return this.clientNotification.emit(to)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -81,8 +90,11 @@ class MailProcessingService extends EventEmitter {
|
||||||
|
|
||||||
async _deleteOldMails() {
|
async _deleteOldMails() {
|
||||||
try {
|
try {
|
||||||
|
debug('Starting deletion of old mails')
|
||||||
await this.imapService.deleteOldMails(helper.purgeTimeStamp())
|
await this.imapService.deleteOldMails(helper.purgeTimeStamp())
|
||||||
|
debug('Completed deletion of old mails')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug('Error deleting old messages:', error.message)
|
||||||
console.log('Cant delete old messages', error)
|
console.log('Cant delete old messages', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,38 @@ const router = new express.Router()
|
||||||
const config = require('../../../application/config')
|
const config = require('../../../application/config')
|
||||||
const Helper = require('../../../application/helper')
|
const Helper = require('../../../application/helper')
|
||||||
const helper = new(Helper)
|
const helper = new(Helper)
|
||||||
|
const debug = require('debug')('48hr-email:routes')
|
||||||
|
|
||||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
const purgeTime = helper.purgeTimeElemetBuilder()
|
||||||
|
|
||||||
router.get('/:address/:errorCode', async(req, res) => {
|
router.get('/:address/:errorCode', async(req, res, next) => {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
try {
|
||||||
const count = await mailProcessingService.getCount()
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
if (!mailProcessingService) {
|
||||||
|
throw new Error('Mail processing service not available')
|
||||||
|
}
|
||||||
|
debug(`Error page requested: ${req.params.errorCode} for ${req.params.address}`)
|
||||||
|
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'
|
||||||
|
|
||||||
const errorCode = parseInt(req.params.errorCode) || 404
|
debug(`Rendering error page ${errorCode} with message: ${message}`)
|
||||||
const message = req.query.message || (req.session && req.session.errorMessage) || 'An error occurred'
|
res.status(errorCode)
|
||||||
|
res.render('error', {
|
||||||
res.status(errorCode)
|
title: `${config.http.branding[0]} | ${errorCode}`,
|
||||||
res.render('error', {
|
purgeTime: purgeTime,
|
||||||
title: `${config.http.branding[0]} | ${errorCode}`,
|
address: req.params.address,
|
||||||
purgeTime: purgeTime,
|
count: count,
|
||||||
address: req.params.address,
|
message: message,
|
||||||
count: count,
|
status: errorCode,
|
||||||
message: message,
|
branding: config.http.branding
|
||||||
status: errorCode,
|
})
|
||||||
branding: config.http.branding
|
} catch (error) {
|
||||||
})
|
debug('Error loading error page:', error.message)
|
||||||
|
console.error('Error while loading error page', error)
|
||||||
|
// For error pages, we should still try to render something basic
|
||||||
|
res.status(500).send('Internal Server Error')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const express = require('express')
|
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 debug = require('debug')('48hr-email:routes')
|
||||||
|
|
||||||
const config = require('../../../application/config')
|
const config = require('../../../application/config')
|
||||||
const Helper = require('../../../application/helper')
|
const Helper = require('../../../application/helper')
|
||||||
|
|
@ -16,17 +17,28 @@ const sanitizeAddress = param('address').customSanitizer(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, async(req, res, _next) => {
|
router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, async(req, res, next) => {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
try {
|
||||||
const count = await mailProcessingService.getCount()
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
res.render('inbox', {
|
if (!mailProcessingService) {
|
||||||
title: `${config.http.branding[0]} | ` + req.params.address,
|
throw new Error('Mail processing service not available')
|
||||||
purgeTime: purgeTime,
|
}
|
||||||
address: req.params.address,
|
debug(`Inbox request for ${req.params.address}`)
|
||||||
count: count,
|
const count = await mailProcessingService.getCount()
|
||||||
mailSummaries: mailProcessingService.getMailSummaries(req.params.address),
|
debug(`Rendering inbox with ${count} total mails`)
|
||||||
branding: config.http.branding,
|
res.render('inbox', {
|
||||||
})
|
title: `${config.http.branding[0]} | ` + req.params.address,
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
address: req.params.address,
|
||||||
|
count: count,
|
||||||
|
mailSummaries: mailProcessingService.getMailSummaries(req.params.address),
|
||||||
|
branding: config.http.branding,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
debug(`Error loading inbox for ${req.params.address}:`, error.message)
|
||||||
|
console.error('Error while loading inbox', error)
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
|
|
@ -35,6 +47,7 @@ router.get(
|
||||||
async(req, res, next) => {
|
async(req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
debug(`Viewing email ${req.params.uid} for ${req.params.address}`)
|
||||||
const count = await mailProcessingService.getCount()
|
const count = await mailProcessingService.getCount()
|
||||||
const mail = await mailProcessingService.getOneFullMail(
|
const mail = await mailProcessingService.getOneFullMail(
|
||||||
req.params.address,
|
req.params.address,
|
||||||
|
|
@ -48,6 +61,7 @@ router.get(
|
||||||
|
|
||||||
// Emails are immutable, cache if found
|
// Emails are immutable, cache if found
|
||||||
res.set('Cache-Control', 'private, max-age=600')
|
res.set('Cache-Control', 'private, max-age=600')
|
||||||
|
debug(`Rendering email view for UID ${req.params.uid}`)
|
||||||
res.render('mail', {
|
res.render('mail', {
|
||||||
title: mail.subject + " | " + req.params.address,
|
title: mail.subject + " | " + req.params.address,
|
||||||
purgeTime: purgeTime,
|
purgeTime: purgeTime,
|
||||||
|
|
@ -58,10 +72,12 @@ router.get(
|
||||||
branding: config.http.branding,
|
branding: config.http.branding,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
debug(`Email ${req.params.uid} not found for ${req.params.address}`)
|
||||||
req.session.errorMessage = 'This mail could not be found. It either does not exist or has been deleted from our servers!'
|
req.session.errorMessage = 'This mail could not be found. It either does not exist or has been deleted from our servers!'
|
||||||
res.redirect(`/error/${req.params.address}/404`)
|
res.redirect(`/error/${req.params.address}/404`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug(`Error fetching email ${req.params.uid} for ${req.params.address}:`, error.message)
|
||||||
console.error('Error while fetching email', error)
|
console.error('Error while fetching email', error)
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -75,12 +91,15 @@ router.get(
|
||||||
async(req, res, next) => {
|
async(req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
debug(`Deleting all emails for ${req.params.address}`)
|
||||||
const mailSummaries = await mailProcessingService.getMailSummaries(req.params.address)
|
const mailSummaries = await mailProcessingService.getMailSummaries(req.params.address)
|
||||||
for (mail in mailSummaries) {
|
for (mail in mailSummaries) {
|
||||||
await mailProcessingService.deleteSpecificEmail(req.params.address, mailSummaries[mail].uid)
|
await mailProcessingService.deleteSpecificEmail(req.params.address, mailSummaries[mail].uid)
|
||||||
}
|
}
|
||||||
|
debug(`Deleted all emails for ${req.params.address}`)
|
||||||
res.redirect(`/inbox/${req.params.address}`)
|
res.redirect(`/inbox/${req.params.address}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug(`Error deleting all emails for ${req.params.address}:`, error.message)
|
||||||
console.error('Error while deleting email', error)
|
console.error('Error while deleting email', error)
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -95,9 +114,12 @@ router.get(
|
||||||
async(req, res, next) => {
|
async(req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
debug(`Deleting email ${req.params.uid} for ${req.params.address}`)
|
||||||
await mailProcessingService.deleteSpecificEmail(req.params.address, req.params.uid)
|
await mailProcessingService.deleteSpecificEmail(req.params.address, req.params.uid)
|
||||||
|
debug(`Successfully deleted email ${req.params.uid} for ${req.params.address}`)
|
||||||
res.redirect(`/inbox/${req.params.address}`)
|
res.redirect(`/inbox/${req.params.address}`)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug(`Error deleting email ${req.params.uid} for ${req.params.address}:`, error.message)
|
||||||
console.error('Error while deleting email', error)
|
console.error('Error while deleting email', error)
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -110,11 +132,13 @@ router.get(
|
||||||
async(req, res, next) => {
|
async(req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
debug(`Fetching attachment ${req.params.checksum} for email ${req.params.uid} (${req.params.address})`)
|
||||||
const uid = parseInt(req.params.uid, 10)
|
const uid = parseInt(req.params.uid, 10)
|
||||||
const count = await mailProcessingService.getCount()
|
const count = await mailProcessingService.getCount()
|
||||||
|
|
||||||
// Validate UID is a valid integer
|
// Validate UID is a valid integer
|
||||||
if (isNaN(uid) || uid <= 0) {
|
if (isNaN(uid) || uid <= 0) {
|
||||||
|
debug(`Invalid UID provided: ${req.params.uid}`)
|
||||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
||||||
return res.redirect(`/error/${req.params.address}/400`)
|
return res.redirect(`/error/${req.params.address}/400`)
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +149,7 @@ router.get(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!mail || !mail.attachments) {
|
if (!mail || !mail.attachments) {
|
||||||
|
debug(`Email ${uid} or attachments not found for ${req.params.address}`)
|
||||||
req.session.errorMessage = 'This email could not be found. It either does not exist or has been deleted from our servers!'
|
req.session.errorMessage = 'This email could not be found. It either does not exist or has been deleted from our servers!'
|
||||||
return res.redirect(`/error/${req.params.address}/404`)
|
return res.redirect(`/error/${req.params.address}/404`)
|
||||||
}
|
}
|
||||||
|
|
@ -134,20 +159,24 @@ router.get(
|
||||||
|
|
||||||
if (attachment) {
|
if (attachment) {
|
||||||
try {
|
try {
|
||||||
|
debug(`Serving attachment: ${attachment.filename}`)
|
||||||
res.set('Content-Disposition', `attachment; filename=${attachment.filename}`);
|
res.set('Content-Disposition', `attachment; filename=${attachment.filename}`);
|
||||||
res.set('Content-Type', attachment.contentType);
|
res.set('Content-Type', attachment.contentType);
|
||||||
res.send(attachment.content);
|
res.send(attachment.content);
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug(`Error serving attachment: ${error.message}`)
|
||||||
console.error('Error while fetching attachment', error);
|
console.error('Error while fetching attachment', error);
|
||||||
next(error);
|
next(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
debug(`Attachment ${req.params.checksum} not found in email ${uid}`)
|
||||||
req.session.errorMessage = 'This attachment could not be found. It either does not exist or has been deleted from our servers!'
|
req.session.errorMessage = 'This attachment could not be found. It either does not exist or has been deleted from our servers!'
|
||||||
return res.redirect(`/error/${req.params.address}/404`)
|
return res.redirect(`/error/${req.params.address}/404`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug(`Error fetching attachment: ${error.message}`)
|
||||||
console.error('Error while fetching attachment', error)
|
console.error('Error while fetching attachment', error)
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -162,11 +191,13 @@ router.get(
|
||||||
async(req, res, next) => {
|
async(req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
debug(`Fetching raw email ${req.params.uid} for ${req.params.address}`)
|
||||||
const uid = parseInt(req.params.uid, 10)
|
const uid = parseInt(req.params.uid, 10)
|
||||||
const count = await mailProcessingService.getCount()
|
const count = await mailProcessingService.getCount()
|
||||||
|
|
||||||
// Validate UID is a valid integer
|
// Validate UID is a valid integer
|
||||||
if (isNaN(uid) || uid <= 0) {
|
if (isNaN(uid) || uid <= 0) {
|
||||||
|
debug(`Invalid UID provided for raw view: ${req.params.uid}`)
|
||||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
||||||
return res.redirect(`/error/${req.params.address}/400`)
|
return res.redirect(`/error/${req.params.address}/400`)
|
||||||
}
|
}
|
||||||
|
|
@ -180,15 +211,18 @@ router.get(
|
||||||
mail = mail.replace(/(?:\r\n|\r|\n)/g, '<br>')
|
mail = mail.replace(/(?:\r\n|\r|\n)/g, '<br>')
|
||||||
// Emails are immutable, cache if found
|
// Emails are immutable, cache if found
|
||||||
res.set('Cache-Control', 'private, max-age=600')
|
res.set('Cache-Control', 'private, max-age=600')
|
||||||
|
debug(`Rendering raw email view for UID ${req.params.uid}`)
|
||||||
res.render('raw', {
|
res.render('raw', {
|
||||||
title: req.params.uid + " | raw | " + req.params.address,
|
title: req.params.uid + " | raw | " + req.params.address,
|
||||||
mail
|
mail
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
debug(`Raw email ${uid} not found for ${req.params.address}`)
|
||||||
req.session.errorMessage = 'This mail could not be found. It either does not exist or has been deleted from our servers!'
|
req.session.errorMessage = 'This mail could not be found. It either does not exist or has been deleted from our servers!'
|
||||||
res.redirect(`/error/${req.params.address}/404`)
|
res.redirect(`/error/${req.params.address}/404`)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
debug(`Error fetching raw email ${req.params.uid}: ${error.message}`)
|
||||||
console.error('Error while fetching raw email', error)
|
console.error('Error while fetching raw email', error)
|
||||||
next(error)
|
next(error)
|
||||||
}
|
}
|
||||||
|
|
@ -206,4 +240,4 @@ router.get(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
module.exports = router
|
module.exports = router
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = new express.Router()
|
const router = new express.Router()
|
||||||
const { check, validationResult } = require('express-validator')
|
const { check, validationResult } = require('express-validator')
|
||||||
|
const debug = require('debug')('48hr-email:routes')
|
||||||
|
|
||||||
const randomWord = require('random-word')
|
const randomWord = require('random-word')
|
||||||
const config = require('../../../application/config')
|
const config = require('../../../application/config')
|
||||||
|
|
@ -9,21 +10,36 @@ const helper = new(Helper)
|
||||||
|
|
||||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
const purgeTime = helper.purgeTimeElemetBuilder()
|
||||||
|
|
||||||
router.get('/', async(req, res, _next) => {
|
router.get('/', async(req, res, next) => {
|
||||||
const count = await req.app.get('mailProcessingService').getCount()
|
try {
|
||||||
res.render('login', {
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
title: `${config.http.branding[0]} | Your temporary Inbox`,
|
if (!mailProcessingService) {
|
||||||
username: randomWord(),
|
throw new Error('Mail processing service not available')
|
||||||
purgeTime: purgeTime,
|
}
|
||||||
domains: helper.getDomains(),
|
debug('Login page requested')
|
||||||
count: count,
|
const count = await mailProcessingService.getCount()
|
||||||
branding: config.http.branding,
|
debug(`Rendering login page with ${count} total mails`)
|
||||||
example: config.email.examples.account,
|
res.render('login', {
|
||||||
})
|
title: `${config.http.branding[0]} | Your temporary Inbox`,
|
||||||
|
username: randomWord(),
|
||||||
|
purgeTime: purgeTime,
|
||||||
|
domains: helper.getDomains(),
|
||||||
|
count: count,
|
||||||
|
branding: config.http.branding,
|
||||||
|
example: config.email.examples.account,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
debug('Error loading login page:', error.message)
|
||||||
|
console.error('Error while loading login page', error)
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/inbox/random', (req, res, _next) => {
|
router.get('/inbox/random', (req, res, _next) => {
|
||||||
res.redirect(`/inbox/${randomWord()}@${config.email.domains[Math.floor(Math.random() * config.email.domains.length)]}`)
|
const randomDomain = config.email.domains[Math.floor(Math.random() * config.email.domains.length)]
|
||||||
|
const inbox = `${randomWord()}@${randomDomain}`
|
||||||
|
debug(`Generated random inbox: ${inbox}`)
|
||||||
|
res.redirect(`/inbox/${inbox}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/logout', (req, res, _next) => {
|
router.get('/logout', (req, res, _next) => {
|
||||||
|
|
@ -40,22 +56,35 @@ router.post(
|
||||||
check('username').isLength({ min: 1 }),
|
check('username').isLength({ min: 1 }),
|
||||||
check('domain').isIn(config.email.domains)
|
check('domain').isIn(config.email.domains)
|
||||||
],
|
],
|
||||||
async(req, res) => {
|
async(req, res, next) => {
|
||||||
const errors = validationResult(req)
|
try {
|
||||||
const count = await req.app.get('mailProcessingService').getCount()
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
if (!errors.isEmpty()) {
|
if (!mailProcessingService) {
|
||||||
return res.render('login', {
|
throw new Error('Mail processing service not available')
|
||||||
userInputError: true,
|
}
|
||||||
title: `${config.http.branding[0]} | Your temporary Inbox`,
|
const errors = validationResult(req)
|
||||||
purgeTime: purgeTime,
|
const count = await mailProcessingService.getCount()
|
||||||
username: randomWord(),
|
if (!errors.isEmpty()) {
|
||||||
domains: helper.getDomains(),
|
debug(`Login validation failed for ${req.body.username}@${req.body.domain}: ${errors.array().map(e => e.msg).join(', ')}`)
|
||||||
count: count,
|
return res.render('login', {
|
||||||
branding: config.http.branding,
|
userInputError: true,
|
||||||
})
|
title: `${config.http.branding[0]} | Your temporary Inbox`,
|
||||||
}
|
purgeTime: purgeTime,
|
||||||
|
username: randomWord(),
|
||||||
|
domains: helper.getDomains(),
|
||||||
|
count: count,
|
||||||
|
branding: config.http.branding,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
res.redirect(`/inbox/${req.body.username}@${req.body.domain}`)
|
const inbox = `${req.body.username}@${req.body.domain}`
|
||||||
|
debug(`Login successful, redirecting to inbox: ${inbox}`)
|
||||||
|
res.redirect(`/inbox/${inbox}`)
|
||||||
|
} catch (error) {
|
||||||
|
debug('Error processing login:', error.message)
|
||||||
|
console.error('Error while processing login', error)
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||||
<meta name="darkreader" content="stfu">
|
<meta name="darkreader-lock">
|
||||||
|
|
||||||
<meta name="description" content="Dont give shady companies your real email. Use 48hr.email to protect your privacy!">
|
<meta name="description" content="Dont give shady companies your real email. Use 48hr.email to protect your privacy!">
|
||||||
<meta property="og:image" content="/images/logo.png">
|
<meta property="og:image" content="/images/logo.png">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimal-ui">
|
||||||
<meta name="darkreader" content="stfu">
|
<meta name="darkreader-lock">
|
||||||
|
|
||||||
<meta name="description" content="Dont give shady companies your real email. Use 48hr.email to protect your privacy!">
|
<meta name="description" content="Dont give shady companies your real email. Use 48hr.email to protect your privacy!">
|
||||||
<meta property="og:image" content="/images/logo.png">
|
<meta property="og:image" content="/images/logo.png">
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@ const Helper = require('../../application/helper')
|
||||||
const helper = new(Helper)
|
const helper = new(Helper)
|
||||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
const purgeTime = helper.purgeTimeElemetBuilder()
|
||||||
|
|
||||||
|
// Utility function for consistent error handling in routes
|
||||||
|
const handleRouteError = (error, req, res, next, context = 'route') => {
|
||||||
|
debug(`Error in ${context}:`, error.message)
|
||||||
|
console.error(`Error in ${context}`, error)
|
||||||
|
next(error)
|
||||||
|
}
|
||||||
|
|
||||||
// Init express middleware
|
// Init express middleware
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(helmet())
|
app.use(helmet())
|
||||||
|
|
@ -34,7 +41,7 @@ app.use(express.urlencoded({ extended: false }))
|
||||||
|
|
||||||
// Session middleware
|
// Session middleware
|
||||||
app.use(session({
|
app.use(session({
|
||||||
secret: '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ', // They will hate me for this
|
secret: '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ', // They will hate me for this, its temporary tho, I swear!
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: true,
|
saveUninitialized: true,
|
||||||
cookie: { maxAge: 1000 * 60 * 60 * 24 } // 24 hours
|
cookie: { maxAge: 1000 * 60 * 60 * 24 } // 24 hours
|
||||||
|
|
@ -82,22 +89,29 @@ app.use((req, res, next) => {
|
||||||
|
|
||||||
// Error handler
|
// Error handler
|
||||||
app.use(async(err, req, res, _next) => {
|
app.use(async(err, req, res, _next) => {
|
||||||
const mailProcessingService = req.app.get('mailProcessingService')
|
try {
|
||||||
const count = await mailProcessingService.getCount()
|
debug('Error handler triggered:', err.message)
|
||||||
|
const mailProcessingService = req.app.get('mailProcessingService')
|
||||||
|
const count = await mailProcessingService.getCount()
|
||||||
|
|
||||||
// Set locals, only providing error in development
|
// Set locals, only providing error in development
|
||||||
res.locals.message = err.message
|
res.locals.message = err.message
|
||||||
res.locals.error = req.app.get('env') === 'development' ? err : {}
|
res.locals.error = req.app.get('env') === 'development' ? err : {}
|
||||||
|
|
||||||
// Render the error page
|
// Render the error page
|
||||||
res.status(err.status || 500)
|
res.status(err.status || 500)
|
||||||
res.render('error', {
|
res.render('error', {
|
||||||
purgeTime: purgeTime,
|
purgeTime: purgeTime,
|
||||||
address: req.params.address,
|
address: req.params && req.params.address,
|
||||||
count: count,
|
count: count,
|
||||||
branding: config.http.branding
|
branding: config.http.branding
|
||||||
|
})
|
||||||
})
|
} catch (renderError) {
|
||||||
|
debug('Error in error handler:', renderError.message)
|
||||||
|
console.error('Critical error in error handler', renderError)
|
||||||
|
// Fallback: send plain text error if rendering fails
|
||||||
|
res.status(500).send('Internal Server Error')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -116,4 +130,4 @@ server.on('listening', () => {
|
||||||
debug('Listening on ' + bind)
|
debug('Listening on ' + bind)
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = { app, io, server }
|
module.exports = { app, io, server }
|
||||||
17364
package-lock.json
generated
17364
package-lock.json
generated
File diff suppressed because it is too large
Load diff
164
package.json
164
package.json
|
|
@ -1,85 +1,83 @@
|
||||||
{
|
{
|
||||||
"name": "48hr.email",
|
"name": "48hr.email",
|
||||||
"version": "1.6.2",
|
"version": "1.6.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "48hr.email is your favorite open-source tempmail client. ",
|
"description": "48hr.email is your favorite open-source tempmail client. ",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"tempmail",
|
"tempmail",
|
||||||
"48hr.email",
|
"48hr.email",
|
||||||
"disposable-email"
|
"disposable-email"
|
||||||
],
|
],
|
||||||
"homepage": "https://48hr.email/",
|
"homepage": "https://48hr.email/",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Crazyco-xyz/48hr.email/issues"
|
"url": "https://github.com/Crazyco-xyz/48hr.email/issues"
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/Crazyco-xyz/48hr.email.git"
|
|
||||||
},
|
|
||||||
"license": "GPL-3.0",
|
|
||||||
"author": "ClaraCrazy",
|
|
||||||
"type": "commonjs",
|
|
||||||
"main": "app.js",
|
|
||||||
"scripts": {
|
|
||||||
"start": "node --trace-warnings ./app.js",
|
|
||||||
"test": "xo",
|
|
||||||
"debug": "node --nolazy --inspect-brk=9229 ./app.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"array.prototype.flatmap": "^1.3.2",
|
|
||||||
"async-retry": "^1.3.3",
|
|
||||||
"compression": "^1.7.4",
|
|
||||||
"debug": "^2.6.9",
|
|
||||||
"dotenv": "^17.2.3",
|
|
||||||
"encodings": "^1.0.0",
|
|
||||||
"express": "^4.21.1",
|
|
||||||
"express-session": "^1.18.2",
|
|
||||||
"express-validator": "^7.2.0",
|
|
||||||
"helmet": "^3.23.3",
|
|
||||||
"http-errors": "~1.6.2",
|
|
||||||
"imap-simple": "^1.6.3",
|
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"mailparser": "^3.7.1",
|
|
||||||
"mem": "^4.3.0",
|
|
||||||
"mnemonist": "^0.27.2",
|
|
||||||
"moment": "^2.30.1",
|
|
||||||
"morgan": "^1.10.1",
|
|
||||||
"nodemailer": "^7.0.11",
|
|
||||||
"p-series": "^2.1.0",
|
|
||||||
"random-word": "^2.0.0",
|
|
||||||
"sanitize-html": "^2.13.0",
|
|
||||||
"semver": "^7.6.3",
|
|
||||||
"socket.io": "^4.8.0",
|
|
||||||
"twig": "^0.10.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"xo": "^0.59.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "22.x"
|
|
||||||
},
|
|
||||||
"xo": {
|
|
||||||
"semicolon": false,
|
|
||||||
"prettier": true,
|
|
||||||
"rules": {
|
|
||||||
"no-unused-vars": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"argsIgnorePattern": "^_"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"overrides": [
|
"repository": {
|
||||||
{
|
"type": "git",
|
||||||
"files": "public/javascripts/*.js",
|
"url": "git+https://github.com/Crazyco-xyz/48hr.email.git"
|
||||||
"esnext": false,
|
},
|
||||||
"env": [
|
"license": "GPL-3.0",
|
||||||
"browser"
|
"author": "ClaraCrazy",
|
||||||
],
|
"type": "commonjs",
|
||||||
"globals": [
|
"main": "app.js",
|
||||||
"io"
|
"scripts": {
|
||||||
]
|
"start": "node --trace-warnings ./app.js",
|
||||||
}
|
"debug": "DEBUG=48hr-email:* node --nolazy --inspect-brk=9229 --trace-warnings ./app.js",
|
||||||
]
|
"test": "xo"
|
||||||
}
|
},
|
||||||
}
|
"dependencies": {
|
||||||
|
"array.prototype.flatmap": "^1.3.2",
|
||||||
|
"async-retry": "^1.3.3",
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"encodings": "^1.0.0",
|
||||||
|
"express": "^4.21.1",
|
||||||
|
"express-session": "^1.18.2",
|
||||||
|
"express-validator": "^7.2.0",
|
||||||
|
"helmet": "^3.23.3",
|
||||||
|
"http-errors": "~1.6.2",
|
||||||
|
"imap-simple": "^1.6.3",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mailparser": "^3.7.1",
|
||||||
|
"mem": "^4.3.0",
|
||||||
|
"mnemonist": "^0.27.2",
|
||||||
|
"moment": "^2.30.1",
|
||||||
|
"morgan": "^1.10.1",
|
||||||
|
"nodemailer": "^7.0.11",
|
||||||
|
"p-series": "^2.1.0",
|
||||||
|
"random-word": "^2.0.0",
|
||||||
|
"sanitize-html": "^2.13.0",
|
||||||
|
"semver": "^7.6.3",
|
||||||
|
"socket.io": "^4.8.0",
|
||||||
|
"twig": "^0.10.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"xo": "^0.59.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "22.x"
|
||||||
|
},
|
||||||
|
"xo": {
|
||||||
|
"semicolon": false,
|
||||||
|
"prettier": true,
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"argsIgnorePattern": "^_"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"overrides": [{
|
||||||
|
"files": "public/javascripts/*.js",
|
||||||
|
"esnext": false,
|
||||||
|
"env": [
|
||||||
|
"browser"
|
||||||
|
],
|
||||||
|
"globals": [
|
||||||
|
"io"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue