mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-02-14 17:19:35 +01:00
Compare commits
5 commits
63b30a3705
...
1f45db1886
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f45db1886 | ||
|
|
b4683e97a7 | ||
|
|
86347eb5ad | ||
|
|
994142fc29 | ||
|
|
ba6d97c7fe |
22 changed files with 9382 additions and 8969 deletions
|
|
@ -1,5 +1,5 @@
|
|||
# --- 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 ---
|
||||
EMAIL_PURGE_TIME=48 # Time value for when to purge
|
||||
|
|
@ -24,7 +24,7 @@ IMAP_CONCURRENCY=6 # Number of conc
|
|||
|
||||
# --- HTTP / WEB CONFIGURATION ---
|
||||
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:
|
||||
# 0 = no change,
|
||||
# 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]
|
||||
patreon: crazyco
|
||||
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
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
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
|
||||
node_modules
|
||||
db/*
|
||||
package-lock.json
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -71,7 +71,7 @@ User=user
|
|||
Group=user
|
||||
|
||||
WorkingDirectory=/opt/48hr-email
|
||||
ExecStart=npm run start
|
||||
ExecStart=npm run prod
|
||||
|
||||
Restart=on-failure
|
||||
TimeoutStartSec=0
|
||||
|
|
@ -113,13 +113,13 @@ WantedBy=multi-user.target
|
|||
### Screenshots:
|
||||
|
||||
- #### Inbox:
|
||||
<img align="center" src="https://i.imgur.com/JJmSe7S.png">
|
||||
<img align="center" src=".github/assets/inbox.png">
|
||||
|
||||
- #### Email with CSS:
|
||||
<img align="center" src="https://i.imgur.com/x8OBoI7.png">
|
||||
- #### Email using HTML and CSS:
|
||||
<img align="center" src=".github/assets/html.png">
|
||||
|
||||
- #### Email without CSS:
|
||||
<img align="center" src="https://i.imgur.com/VPZ8IG6.png">
|
||||
<img align="center" src=".github/assets/raw.png">
|
||||
|
||||
<br><br>
|
||||
|
||||
|
|
|
|||
11
app.js
11
app.js
|
|
@ -3,6 +3,7 @@
|
|||
/* eslint unicorn/no-process-exit: 0 */
|
||||
|
||||
const config = require('./application/config')
|
||||
const debug = require('debug')('48hr-email:app')
|
||||
|
||||
// Until node 11 adds flatmap, we use this:
|
||||
require('array.prototype.flatmap').shim()
|
||||
|
|
@ -14,40 +15,50 @@ const MailProcessingService = require('./application/mail-processing-service')
|
|||
const MailRepository = require('./domain/mail-repository')
|
||||
|
||||
const clientNotification = new ClientNotification()
|
||||
debug('Client notification service initialized')
|
||||
clientNotification.use(io)
|
||||
|
||||
const imapService = new ImapService(config)
|
||||
debug('IMAP service initialized')
|
||||
const mailProcessingService = new MailProcessingService(
|
||||
new MailRepository(),
|
||||
imapService,
|
||||
clientNotification,
|
||||
config
|
||||
)
|
||||
debug('Mail processing service initialized')
|
||||
|
||||
// Put everything together:
|
||||
imapService.on(ImapService.EVENT_NEW_MAIL, mail =>
|
||||
mailProcessingService.onNewMail(mail)
|
||||
)
|
||||
debug('Bound IMAP new mail event handler')
|
||||
imapService.on(ImapService.EVENT_INITIAL_LOAD_DONE, () =>
|
||||
mailProcessingService.onInitialLoadDone()
|
||||
)
|
||||
debug('Bound IMAP initial load done event handler')
|
||||
imapService.on(ImapService.EVENT_DELETED_MAIL, mail =>
|
||||
mailProcessingService.onMailDeleted(mail)
|
||||
)
|
||||
debug('Bound IMAP deleted mail event handler')
|
||||
|
||||
mailProcessingService.on('error', err => {
|
||||
debug('Fatal error from mail processing service:', err.message)
|
||||
console.error('Error from mailProcessingService, stopping.', err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
imapService.on(ImapService.EVENT_ERROR, error => {
|
||||
debug('Fatal error from IMAP service:', error.message)
|
||||
console.error('Fatal error from IMAP service', error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
app.set('mailProcessingService', mailProcessingService)
|
||||
|
||||
debug('Starting IMAP connection and message loading')
|
||||
imapService.connectAndLoadMessages().catch(error => {
|
||||
debug('Failed to connect to IMAP:', error.message)
|
||||
console.error('Fatal error from IMAP service', error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
|
|||
149
app.json
149
app.json
|
|
@ -1,71 +1,84 @@
|
|||
{
|
||||
"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.",
|
||||
"repository": "https://github.com/Crazyco-xyz/48hr.email",
|
||||
"logo": "https://github.com/Crazyco-xyz/48hr.email/blob/main/infrastructure/web/public/images/logo.png",
|
||||
"keywords": [
|
||||
"node",
|
||||
"disposable-mail"
|
||||
],
|
||||
"env": {
|
||||
"EMAIL_DOMAINS": {
|
||||
"description": "Email domains"
|
||||
},
|
||||
"EMAIL_PURGE_TIME": {
|
||||
"description": "Config for when to purge the emails",
|
||||
"value": {
|
||||
"time": 48,
|
||||
"unit": "hours",
|
||||
"convert": true
|
||||
}
|
||||
},
|
||||
"EMAIL_EXAMPLES": {
|
||||
"description": "Examples of the domains",
|
||||
"value": {
|
||||
"account": "example@48hr.email",
|
||||
"uids": [1, 2, 3]
|
||||
}
|
||||
},
|
||||
"IMAP_USER": {
|
||||
"description": "Username to login to the imap server"
|
||||
},
|
||||
"IMAP_PASSWORD": {
|
||||
"description": "Password to login to the imap server"
|
||||
},
|
||||
"IMAP_SERVER": {
|
||||
"description": "Hostname of the server (usually imap.example.com or mx.example.com)"
|
||||
},
|
||||
"IMAP_PORT": {
|
||||
"description": "Port of the server (usually 993)",
|
||||
"value": 993
|
||||
},
|
||||
"IMAP_TLS": {
|
||||
"description": "Use tls or not",
|
||||
"value": true
|
||||
},
|
||||
"IMAP_AUTHTIMEOUT": {
|
||||
"description": "Timeout for the auth",
|
||||
"value": 3000
|
||||
},
|
||||
"IMAP_REFRESH_INTERVAL_SECONDS": {
|
||||
"description": "How often to refresh the imap messages manually",
|
||||
"value": 60
|
||||
},
|
||||
"HTTP_PORT": {
|
||||
"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": "Sort the emails for use",
|
||||
"value": 0
|
||||
},
|
||||
"HTTP_HIDE_OTHER": {
|
||||
"description": "Hide other emails from the list besides the first",
|
||||
"value": false
|
||||
"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.",
|
||||
"repository": "https://github.com/Crazyco-xyz/48hr.email",
|
||||
"logo": "https://github.com/Crazyco-xyz/48hr.email/blob/main/infrastructure/web/public/images/logo.png",
|
||||
"keywords": [
|
||||
"node",
|
||||
"disposable-mail"
|
||||
],
|
||||
"env": {
|
||||
"EMAIL_DOMAINS": {
|
||||
"description": "List of domains your service handles"
|
||||
},
|
||||
"EMAIL_PURGE_TIME": {
|
||||
"description": "Time value for when to purge",
|
||||
"value": 48
|
||||
},
|
||||
"EMAIL_PURGE_UNIT": {
|
||||
"description": "Time unit for purging (minutes, hours, days)",
|
||||
"value": "hours"
|
||||
},
|
||||
"EMAIL_PURGE_CONVERT": {
|
||||
"description": "Convert to highest sensible unit and round",
|
||||
"value": true
|
||||
},
|
||||
"EMAIL_EXAMPLE_ACCOUNT": {
|
||||
"description": "Example email account to preserve",
|
||||
"value": "example@48hr.email"
|
||||
},
|
||||
"EMAIL_EXAMPLE_UIDS": {
|
||||
"description": "Example UIDs to preserve",
|
||||
"value": [1, 2, 3]
|
||||
},
|
||||
"IMAP_USER": {
|
||||
"description": "Username to login to the imap server"
|
||||
},
|
||||
"IMAP_PASSWORD": {
|
||||
"description": "Password to login to the imap server"
|
||||
},
|
||||
"IMAP_SERVER": {
|
||||
"description": "Hostname of the server (usually imap.example.com or mx.example.com)"
|
||||
},
|
||||
"IMAP_PORT": {
|
||||
"description": "Port of the server (usually 993)",
|
||||
"value": 993
|
||||
},
|
||||
"IMAP_TLS": {
|
||||
"description": "Use tls or not",
|
||||
"value": true
|
||||
},
|
||||
"IMAP_AUTH_TIMEOUT": {
|
||||
"description": "Timeout for the auth in milliseconds",
|
||||
"value": 3000
|
||||
},
|
||||
"IMAP_REFRESH_INTERVAL_SECONDS": {
|
||||
"description": "How often to refresh the imap messages manually",
|
||||
"value": 60
|
||||
},
|
||||
"IMAP_FETCH_CHUNK": {
|
||||
"description": "Number of UIDs per fetch chunk during initial load",
|
||||
"value": 200
|
||||
},
|
||||
"IMAP_CONCURRENCY": {
|
||||
"description": "Number of concurrent fetch workers during initial load",
|
||||
"value": 6
|
||||
},
|
||||
"HTTP_PORT": {
|
||||
"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
|
||||
require("dotenv").config({ quiet: true });
|
||||
const debug = require('debug')('48hr-email:config')
|
||||
|
||||
/**
|
||||
* Safely parse a value from env.
|
||||
|
|
@ -63,12 +64,17 @@ const config = {
|
|||
};
|
||||
|
||||
// validation
|
||||
debug('Validating configuration...')
|
||||
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.");
|
||||
}
|
||||
|
||||
if (!config.email.domains.length) {
|
||||
debug('Email domains validation failed: no 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;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const config = require('./config')
|
||||
const moment = require('moment')
|
||||
const debug = require('debug')('48hr-email:helper')
|
||||
|
||||
class Helper {
|
||||
|
||||
|
|
@ -8,9 +9,11 @@ class Helper {
|
|||
* @returns {Date}
|
||||
*/
|
||||
purgeTimeStamp() {
|
||||
return moment()
|
||||
const cutoff = moment()
|
||||
.subtract(config.email.purgeTime.time, config.email.purgeTime.unit)
|
||||
.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 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),
|
||||
* rounding if necessary and prefixing "~" when rounded.
|
||||
|
|
@ -76,9 +81,8 @@ class Helper {
|
|||
}
|
||||
|
||||
const footer = `<label title="${Tooltip}">
|
||||
<h4 style="display: inline;"><u><i>${time}</i></u></h4>
|
||||
</Label>`
|
||||
|
||||
<h4 style="display: inline;"><u><i>${time}</i></u></h4>
|
||||
</Label>`
|
||||
return footer
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +91,6 @@ class Helper {
|
|||
* @param {Array} array
|
||||
* @returns {Array}
|
||||
*/
|
||||
|
||||
shuffleArray(array) {
|
||||
for (let i = array.length - 1; i >= 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
|
|
@ -101,7 +104,6 @@ class Helper {
|
|||
* @param {Array} array
|
||||
* @returns {Array}
|
||||
*/
|
||||
|
||||
shuffleFirstItem(array) {
|
||||
let first = array[Math.floor(Math.random() * array.length)]
|
||||
array = array.filter((value) => value != first);
|
||||
|
|
@ -126,17 +128,26 @@ class Helper {
|
|||
* Get a domain list from config for use
|
||||
* @returns {Array}
|
||||
*/
|
||||
|
||||
getDomains() {
|
||||
debug(`Getting domains with displaySort: ${config.http.displaySort}`)
|
||||
let result;
|
||||
switch (config.http.displaySort) {
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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 pSeries = require('p-series')
|
||||
const retry = require('async-retry')
|
||||
const debug = require('debug')('48hr-email:imap')
|
||||
const debug = require('debug')('48hr-email:imap-manager')
|
||||
const _ = require('lodash')
|
||||
const moment = require('moment')
|
||||
const Mail = require('../domain/mail')
|
||||
|
|
@ -133,7 +133,7 @@ class ImapService extends EventEmitter {
|
|||
})
|
||||
|
||||
await this.connection.openBox('INBOX')
|
||||
debug('Connected to imap')
|
||||
debug('Connected to imap Server at ' + this.config.imap.host)
|
||||
}, {
|
||||
retries: 5
|
||||
}
|
||||
|
|
@ -173,8 +173,14 @@ class ImapService extends EventEmitter {
|
|||
debug('Load skipped: another load already in progress')
|
||||
return
|
||||
}
|
||||
|
||||
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 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}`)
|
||||
|
|
@ -219,7 +225,7 @@ class ImapService extends EventEmitter {
|
|||
}
|
||||
|
||||
this.loadingInProgress = false
|
||||
debug('Load finished')
|
||||
debug('Finished updating mail summary list')
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 ImapService = require('./imap-service')
|
||||
const Helper = require('./helper')
|
||||
|
|
@ -33,29 +33,36 @@ class MailProcessingService extends EventEmitter {
|
|||
}
|
||||
|
||||
getMailSummaries(address) {
|
||||
debug('Getting mail summaries for', address)
|
||||
return this.mailRepository.getForRecipient(address)
|
||||
}
|
||||
|
||||
deleteSpecificEmail(adress, uid) {
|
||||
debug('Deleting specific email', adress, uid)
|
||||
if (this.mailRepository.removeUid(uid, adress) == true) {
|
||||
this.imapService.deleteSpecificEmail(uid)
|
||||
}
|
||||
}
|
||||
|
||||
getOneFullMail(address, uid, raw = false) {
|
||||
debug('Cache lookup for', address + ':' + uid, raw ? '(raw)' : '(parsed)')
|
||||
return this.cachedFetchFullMail(address, uid, raw)
|
||||
}
|
||||
|
||||
getAllMailSummaries() {
|
||||
debug('Getting all mail summaries')
|
||||
return this.mailRepository.getAll()
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return this.mailRepository.mailCount()
|
||||
const count = this.mailRepository.mailCount()
|
||||
debug('Mail count requested:', count)
|
||||
return count
|
||||
}
|
||||
|
||||
onInitialLoadDone() {
|
||||
this.initialLoadDone = true
|
||||
debug('Initial load completed, total mails:', this.mailRepository.mailCount())
|
||||
console.log(`Initial load done, got ${this.mailRepository.mailCount()} mails`)
|
||||
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`)
|
||||
|
|
@ -69,7 +76,9 @@ class MailProcessingService extends EventEmitter {
|
|||
}
|
||||
|
||||
mail.to.forEach(to => {
|
||||
debug('Adding mail to repository for recipient:', to)
|
||||
this.mailRepository.add(to, mail)
|
||||
debug('Emitting notification for:', to)
|
||||
return this.clientNotification.emit(to)
|
||||
})
|
||||
}
|
||||
|
|
@ -81,8 +90,11 @@ class MailProcessingService extends EventEmitter {
|
|||
|
||||
async _deleteOldMails() {
|
||||
try {
|
||||
debug('Starting deletion of old mails')
|
||||
await this.imapService.deleteOldMails(helper.purgeTimeStamp())
|
||||
debug('Completed deletion of old mails')
|
||||
} catch (error) {
|
||||
debug('Error deleting old messages:', error.message)
|
||||
console.log('Cant delete old messages', error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
padding: 10px 10px 0px;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #131516;
|
||||
padding: 20px 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: linear-gradient( 135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)), #131516;
|
||||
color: #cccccc;
|
||||
backdrop-filter: blur(18px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(18px) saturate(120%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
|
|
@ -21,6 +25,11 @@ a {
|
|||
color: #cccccc;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #9b4cda;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
|
@ -43,10 +52,14 @@ p {
|
|||
}
|
||||
|
||||
iframe {
|
||||
background-color: #1D2021;
|
||||
color: white;
|
||||
width: 80%;
|
||||
height: 60vh;
|
||||
border: 1px dotted black;
|
||||
margin-left: 10%;
|
||||
margin: 2rem auto;
|
||||
display: block;
|
||||
border: none;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
|
|
@ -71,11 +84,33 @@ text-muted {
|
|||
|
||||
.action-links {
|
||||
float: right;
|
||||
text-align: end;
|
||||
justify-content: flex-end;
|
||||
/* aligns all links to the right */
|
||||
flex-wrap: nowrap;
|
||||
/* ensures they stay in one line */
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.action-links a {
|
||||
display: block;
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 10px 16px;
|
||||
border-radius: 16px;
|
||||
text-align: center;
|
||||
background: rgba(155, 77, 202, 0.2);
|
||||
border: 1px solid rgba(155, 77, 202, 0.35);
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
backdrop-filter: blur(12px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(120%);
|
||||
box-shadow: 0 5px 15px rgba(155, 77, 202, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
transition: transform 0.2s ease, background 0.2s ease;
|
||||
}
|
||||
|
||||
.action-links a:hover {
|
||||
background: rgba(155, 77, 202, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px rgba(155, 77, 202, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -100,6 +135,13 @@ select:hover {
|
|||
flex-direction: column;
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
background: linear-gradient( 135deg, rgba(155, 77, 202, 0.12), rgba(155, 77, 202, 0.04)), rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(20px) saturate(125%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(125%);
|
||||
border-radius: 22px;
|
||||
border: 1px solid rgba(155, 77, 202, 0.25);
|
||||
padding: 40px 36px;
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(155, 77, 202, 0.08) inset;
|
||||
}
|
||||
|
||||
#login h1,
|
||||
|
|
@ -124,7 +166,7 @@ select:hover {
|
|||
line-height: 1;
|
||||
padding: 0 6px;
|
||||
font-size: 1.4rem;
|
||||
background-color: #131516;
|
||||
background: linear-gradient( 135deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.02)), #131516;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
|
|
@ -156,16 +198,32 @@ select:hover {
|
|||
}
|
||||
|
||||
#login .buttons {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
/* keeps them aligned right like action-links */
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
#login .buttons>* {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
font-size: 1.3rem;
|
||||
flex: 1 1 auto;
|
||||
min-width: 120px;
|
||||
border-radius: 20px;
|
||||
color: #fff;
|
||||
background: rgba(155, 77, 202, 0.2);
|
||||
border: 1px solid rgba(155, 77, 202, 0.35);
|
||||
backdrop-filter: blur(12px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(120%);
|
||||
box-shadow: 0 8px 20px rgba(155, 77, 202, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
#login .buttons>*:hover {
|
||||
background: rgba(155, 77, 202, 0.3);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 25px rgba(155, 77, 202, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.mail_attachments {
|
||||
|
|
|
|||
|
|
@ -4,26 +4,38 @@ const router = new express.Router()
|
|||
const config = require('../../../application/config')
|
||||
const Helper = require('../../../application/helper')
|
||||
const helper = new(Helper)
|
||||
const debug = require('debug')('48hr-email:routes')
|
||||
|
||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
||||
|
||||
router.get('/:address/:errorCode', async(req, res) => {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
const count = await mailProcessingService.getCount()
|
||||
router.get('/:address/:errorCode', async(req, res, next) => {
|
||||
try {
|
||||
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
|
||||
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
|
||||
})
|
||||
debug(`Rendering error page ${errorCode} with message: ${message}`)
|
||||
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
|
||||
})
|
||||
} 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
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
const express = require('express')
|
||||
const router = new express.Router()
|
||||
const { param } = require('express-validator')
|
||||
const debug = require('debug')('48hr-email:routes')
|
||||
|
||||
const config = require('../../../application/config')
|
||||
const Helper = require('../../../application/helper')
|
||||
|
|
@ -16,17 +17,28 @@ const sanitizeAddress = param('address').customSanitizer(
|
|||
}
|
||||
)
|
||||
|
||||
router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, async(req, res, _next) => {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
const count = await mailProcessingService.getCount()
|
||||
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,
|
||||
})
|
||||
router.get('^/:address([^@/]+@[^@/]+)', sanitizeAddress, async(req, res, next) => {
|
||||
try {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
if (!mailProcessingService) {
|
||||
throw new Error('Mail processing service not available')
|
||||
}
|
||||
debug(`Inbox request for ${req.params.address}`)
|
||||
const count = await mailProcessingService.getCount()
|
||||
debug(`Rendering inbox with ${count} total mails`)
|
||||
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(
|
||||
|
|
@ -35,6 +47,7 @@ router.get(
|
|||
async(req, res, next) => {
|
||||
try {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
debug(`Viewing email ${req.params.uid} for ${req.params.address}`)
|
||||
const count = await mailProcessingService.getCount()
|
||||
const mail = await mailProcessingService.getOneFullMail(
|
||||
req.params.address,
|
||||
|
|
@ -48,6 +61,7 @@ router.get(
|
|||
|
||||
// Emails are immutable, cache if found
|
||||
res.set('Cache-Control', 'private, max-age=600')
|
||||
debug(`Rendering email view for UID ${req.params.uid}`)
|
||||
res.render('mail', {
|
||||
title: mail.subject + " | " + req.params.address,
|
||||
purgeTime: purgeTime,
|
||||
|
|
@ -58,10 +72,12 @@ router.get(
|
|||
branding: config.http.branding,
|
||||
})
|
||||
} 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!'
|
||||
res.redirect(`/error/${req.params.address}/404`)
|
||||
}
|
||||
} catch (error) {
|
||||
debug(`Error fetching email ${req.params.uid} for ${req.params.address}:`, error.message)
|
||||
console.error('Error while fetching email', error)
|
||||
next(error)
|
||||
}
|
||||
|
|
@ -75,12 +91,15 @@ router.get(
|
|||
async(req, res, next) => {
|
||||
try {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
debug(`Deleting all emails for ${req.params.address}`)
|
||||
const mailSummaries = await mailProcessingService.getMailSummaries(req.params.address)
|
||||
for (mail in mailSummaries) {
|
||||
await mailProcessingService.deleteSpecificEmail(req.params.address, mailSummaries[mail].uid)
|
||||
}
|
||||
debug(`Deleted all emails for ${req.params.address}`)
|
||||
res.redirect(`/inbox/${req.params.address}`)
|
||||
} catch (error) {
|
||||
debug(`Error deleting all emails for ${req.params.address}:`, error.message)
|
||||
console.error('Error while deleting email', error)
|
||||
next(error)
|
||||
}
|
||||
|
|
@ -95,9 +114,12 @@ router.get(
|
|||
async(req, res, next) => {
|
||||
try {
|
||||
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)
|
||||
debug(`Successfully deleted email ${req.params.uid} for ${req.params.address}`)
|
||||
res.redirect(`/inbox/${req.params.address}`)
|
||||
} catch (error) {
|
||||
debug(`Error deleting email ${req.params.uid} for ${req.params.address}:`, error.message)
|
||||
console.error('Error while deleting email', error)
|
||||
next(error)
|
||||
}
|
||||
|
|
@ -110,11 +132,13 @@ router.get(
|
|||
async(req, res, next) => {
|
||||
try {
|
||||
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 count = await mailProcessingService.getCount()
|
||||
|
||||
// Validate UID is a valid integer
|
||||
if (isNaN(uid) || uid <= 0) {
|
||||
debug(`Invalid UID provided: ${req.params.uid}`)
|
||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
||||
return res.redirect(`/error/${req.params.address}/400`)
|
||||
}
|
||||
|
|
@ -125,6 +149,7 @@ router.get(
|
|||
)
|
||||
|
||||
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!'
|
||||
return res.redirect(`/error/${req.params.address}/404`)
|
||||
}
|
||||
|
|
@ -134,20 +159,24 @@ router.get(
|
|||
|
||||
if (attachment) {
|
||||
try {
|
||||
debug(`Serving attachment: ${attachment.filename}`)
|
||||
res.set('Content-Disposition', `attachment; filename=${attachment.filename}`);
|
||||
res.set('Content-Type', attachment.contentType);
|
||||
res.send(attachment.content);
|
||||
return;
|
||||
} catch (error) {
|
||||
debug(`Error serving attachment: ${error.message}`)
|
||||
console.error('Error while fetching attachment', error);
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
} 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!'
|
||||
return res.redirect(`/error/${req.params.address}/404`)
|
||||
}
|
||||
} catch (error) {
|
||||
debug(`Error fetching attachment: ${error.message}`)
|
||||
console.error('Error while fetching attachment', error)
|
||||
next(error)
|
||||
}
|
||||
|
|
@ -162,11 +191,13 @@ router.get(
|
|||
async(req, res, next) => {
|
||||
try {
|
||||
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 count = await mailProcessingService.getCount()
|
||||
|
||||
// Validate UID is a valid integer
|
||||
if (isNaN(uid) || uid <= 0) {
|
||||
debug(`Invalid UID provided for raw view: ${req.params.uid}`)
|
||||
req.session.errorMessage = 'Invalid/Malformed UID provided.'
|
||||
return res.redirect(`/error/${req.params.address}/400`)
|
||||
}
|
||||
|
|
@ -180,15 +211,18 @@ router.get(
|
|||
mail = mail.replace(/(?:\r\n|\r|\n)/g, '<br>')
|
||||
// Emails are immutable, cache if found
|
||||
res.set('Cache-Control', 'private, max-age=600')
|
||||
debug(`Rendering raw email view for UID ${req.params.uid}`)
|
||||
res.render('raw', {
|
||||
title: req.params.uid + " | raw | " + req.params.address,
|
||||
mail
|
||||
})
|
||||
} 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!'
|
||||
res.redirect(`/error/${req.params.address}/404`)
|
||||
}
|
||||
} catch (error) {
|
||||
debug(`Error fetching raw email ${req.params.uid}: ${error.message}`)
|
||||
console.error('Error while fetching raw email', error)
|
||||
next(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const express = require('express')
|
||||
const router = new express.Router()
|
||||
const { check, validationResult } = require('express-validator')
|
||||
const debug = require('debug')('48hr-email:routes')
|
||||
|
||||
const randomWord = require('random-word')
|
||||
const config = require('../../../application/config')
|
||||
|
|
@ -9,21 +10,36 @@ const helper = new(Helper)
|
|||
|
||||
const purgeTime = helper.purgeTimeElemetBuilder()
|
||||
|
||||
router.get('/', async(req, res, _next) => {
|
||||
const count = await req.app.get('mailProcessingService').getCount()
|
||||
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,
|
||||
})
|
||||
router.get('/', async(req, res, next) => {
|
||||
try {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
if (!mailProcessingService) {
|
||||
throw new Error('Mail processing service not available')
|
||||
}
|
||||
debug('Login page requested')
|
||||
const count = await mailProcessingService.getCount()
|
||||
debug(`Rendering login page with ${count} total mails`)
|
||||
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) => {
|
||||
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) => {
|
||||
|
|
@ -40,22 +56,35 @@ router.post(
|
|||
check('username').isLength({ min: 1 }),
|
||||
check('domain').isIn(config.email.domains)
|
||||
],
|
||||
async(req, res) => {
|
||||
const errors = validationResult(req)
|
||||
const count = await req.app.get('mailProcessingService').getCount()
|
||||
if (!errors.isEmpty()) {
|
||||
return res.render('login', {
|
||||
userInputError: true,
|
||||
title: `${config.http.branding[0]} | Your temporary Inbox`,
|
||||
purgeTime: purgeTime,
|
||||
username: randomWord(),
|
||||
domains: helper.getDomains(),
|
||||
count: count,
|
||||
branding: config.http.branding,
|
||||
})
|
||||
}
|
||||
async(req, res, next) => {
|
||||
try {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
if (!mailProcessingService) {
|
||||
throw new Error('Mail processing service not available')
|
||||
}
|
||||
const errors = validationResult(req)
|
||||
const count = await mailProcessingService.getCount()
|
||||
if (!errors.isEmpty()) {
|
||||
debug(`Login validation failed for ${req.body.username}@${req.body.domain}: ${errors.array().map(e => e.msg).join(', ')}`)
|
||||
return res.render('login', {
|
||||
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>
|
||||
<meta charset="UTF-8">
|
||||
<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 property="og:image" content="/images/logo.png">
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
<title>{{ title }}</title>
|
||||
<meta charset="UTF-8">
|
||||
<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 property="og:image" content="/images/logo.png">
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@ const Helper = require('../../application/helper')
|
|||
const helper = new(Helper)
|
||||
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
|
||||
const app = express()
|
||||
app.use(helmet())
|
||||
|
|
@ -34,7 +41,7 @@ app.use(express.urlencoded({ extended: false }))
|
|||
|
||||
// Session middleware
|
||||
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,
|
||||
saveUninitialized: true,
|
||||
cookie: { maxAge: 1000 * 60 * 60 * 24 } // 24 hours
|
||||
|
|
@ -82,22 +89,29 @@ app.use((req, res, next) => {
|
|||
|
||||
// Error handler
|
||||
app.use(async(err, req, res, _next) => {
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
const count = await mailProcessingService.getCount()
|
||||
try {
|
||||
debug('Error handler triggered:', err.message)
|
||||
const mailProcessingService = req.app.get('mailProcessingService')
|
||||
const count = await mailProcessingService.getCount()
|
||||
|
||||
// Set locals, only providing error in development
|
||||
res.locals.message = err.message
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {}
|
||||
// Set locals, only providing error in development
|
||||
res.locals.message = err.message
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {}
|
||||
|
||||
// Render the error page
|
||||
res.status(err.status || 500)
|
||||
res.render('error', {
|
||||
purgeTime: purgeTime,
|
||||
address: req.params.address,
|
||||
count: count,
|
||||
branding: config.http.branding
|
||||
|
||||
})
|
||||
// Render the error page
|
||||
res.status(err.status || 500)
|
||||
res.render('error', {
|
||||
purgeTime: purgeTime,
|
||||
address: req.params && req.params.address,
|
||||
count: count,
|
||||
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')
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
|
|
|
|||
17614
package-lock.json
generated
17614
package-lock.json
generated
File diff suppressed because it is too large
Load diff
160
package.json
160
package.json
|
|
@ -1,83 +1,85 @@
|
|||
{
|
||||
"name": "48hr.email",
|
||||
"version": "1.6.1",
|
||||
"private": false,
|
||||
"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-validator": "^7.2.0",
|
||||
"helmet": "^3.23.3",
|
||||
"http-errors": "~1.6.2",
|
||||
"imap-simple": "^4.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mailparser": "^3.7.1",
|
||||
"mem": "^4.3.0",
|
||||
"mnemonist": "^0.27.2",
|
||||
"moment": "^2.30.1",
|
||||
"morgan": "~1.9.0",
|
||||
"nodemailer": "^6.9.15",
|
||||
"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"
|
||||
},
|
||||
"xo": {
|
||||
"semicolon": false,
|
||||
"prettier": true,
|
||||
"rules": {
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"argsIgnorePattern": "^_"
|
||||
}
|
||||
]
|
||||
"name": "48hr.email",
|
||||
"version": "1.6.3",
|
||||
"private": false,
|
||||
"description": "48hr.email is your favorite open-source tempmail client. ",
|
||||
"keywords": [
|
||||
"tempmail",
|
||||
"48hr.email",
|
||||
"disposable-email"
|
||||
],
|
||||
"homepage": "https://48hr.email/",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Crazyco-xyz/48hr.email/issues"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "public/javascripts/*.js",
|
||||
"esnext": false,
|
||||
"env": [
|
||||
"browser"
|
||||
],
|
||||
"globals": [
|
||||
"io"
|
||||
"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",
|
||||
"debug": "DEBUG=48hr-email:* node --nolazy --inspect-brk=9229 --trace-warnings ./app.js",
|
||||
"test": "xo"
|
||||
},
|
||||
"dependencies": {
|
||||
"array.prototype.flatmap": "^1.3.3",
|
||||
"async-retry": "^1.3.3",
|
||||
"compression": "^1.8.1",
|
||||
"debug": "^4.4.3",
|
||||
"dotenv": "^17.2.3",
|
||||
"encodings": "^1.0.0",
|
||||
"express": "^4.22.1",
|
||||
"express-session": "^1.18.2",
|
||||
"express-validator": "^7.3.1",
|
||||
"helmet": "^3.23.3",
|
||||
"http-errors": "~1.6.2",
|
||||
"imap-simple": "^1.6.3",
|
||||
"lodash": "^4.17.21",
|
||||
"mailparser": "^3.9.1",
|
||||
"mem": "^4.3.0",
|
||||
"mnemonist": "^0.27.2",
|
||||
"moment": "^2.30.1",
|
||||
"morgan": "^1.10.1",
|
||||
"nodemailer": "^7.0.12",
|
||||
"p-series": "^2.1.0",
|
||||
"random-word": "^2.0.0",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"semver": "^7.7.3",
|
||||
"socket.io": "^4.8.3",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": "22.x"
|
||||
},
|
||||
"description": "48hr.email is your favorite open-source tempmail client. ",
|
||||
"main": "app.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Crazyco-xyz/48hr.email.git"
|
||||
},
|
||||
"keywords": [
|
||||
"tempmail",
|
||||
"48hr.email",
|
||||
"disposable-email"
|
||||
],
|
||||
"author": "ClaraCrazy",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Crazyco-xyz/48hr.email/issues"
|
||||
},
|
||||
"homepage": "https://48hr.email/"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue