Aktueller Stand
This commit is contained in:
161
README.md
161
README.md
@@ -18,7 +18,7 @@ docker compose up --build
|
||||
|
||||
- Web UI: `http://localhost:${WEB_PORT}` (see root `.env`)
|
||||
- API: `http://localhost:${API_PORT}`
|
||||
- API Docs: `http://localhost:${API_PORT}/docs`
|
||||
- API Docs: `http://localhost:${API_PORT}/docs` (only if `ENABLE_SWAGGER=true`)
|
||||
|
||||
## API (initial)
|
||||
- `POST /auth/register` `{ tenantName, email, password }`
|
||||
@@ -29,7 +29,8 @@ docker compose up --build
|
||||
- `POST /mail/cleanup` (auth) `{ mailboxAccountId, dryRun, unsubscribeEnabled, routingEnabled }`
|
||||
- `GET /jobs` (auth)
|
||||
- `GET /jobs/:id/events` (auth)
|
||||
- `GET /jobs/:id/stream?token=...` (auth via query token, SSE)
|
||||
- `GET /jobs/:id/stream-token` (auth) -> short-lived SSE token
|
||||
- `GET /jobs/:id/stream?token=...` (SSE using short-lived token)
|
||||
- `GET /rules` (auth)
|
||||
- `POST /rules` (auth)
|
||||
- `PUT /rules/:id` (auth)
|
||||
@@ -54,7 +55,8 @@ docker compose up --build
|
||||
- `GET /admin/exports/:id/download` (admin)
|
||||
- `POST /admin/exports/purge` (admin)
|
||||
- `DELETE /admin/exports/:id` (admin)
|
||||
- `GET /jobs/exports/:id/stream` (auth, SSE)
|
||||
- `GET /jobs/exports/:id/stream-token` (admin) -> short-lived SSE token
|
||||
- `GET /jobs/exports/:id/stream?token=...` (SSE using short-lived token)
|
||||
|
||||
Export queue:
|
||||
- ZIP exports are queued via Redis/BullMQ and processed by the worker container.
|
||||
@@ -74,6 +76,30 @@ UI:
|
||||
- Weblink unsubscribe uses HTTP first, mailto fallback (SMTP required).
|
||||
- Worker scans headers and applies routing rules (MOVE/DELETE) when not in dry run.
|
||||
|
||||
## Cleanup job behavior (what the button does)
|
||||
When you click **“Bereinigung starten / Start cleanup”** a cleanup job is created and queued. The worker connects to the selected mailbox and:
|
||||
|
||||
1. Opens the INBOX (or first mailbox matching “inbox”).
|
||||
2. Fetches recent message headers (subject/from/headers).
|
||||
3. Detects newsletter candidates (List‑Unsubscribe, List‑Id, heuristics).
|
||||
4. Applies your routing rules (MOVE/ARCHIVE/LABEL/DELETE) if enabled.
|
||||
5. Attempts to unsubscribe using `List‑Unsubscribe` (HTTP one‑click or mailto).
|
||||
6. Logs all actions and progress as job events (visible in the UI).
|
||||
|
||||
### The three checkboxes explained
|
||||
**Dry run (keine Änderungen)**
|
||||
Runs the full scan and logs what *would* happen, but **does not move/delete/unsubscribe** any mail. Useful for testing rules safely.
|
||||
|
||||
**Unsubscribe aktiv**
|
||||
Enables `List‑Unsubscribe` handling.
|
||||
- HTTP links are called (one‑click POST when supported).
|
||||
- Mailto links are sent via SMTP (requires SMTP host + app password).
|
||||
|
||||
**Routing aktiv**
|
||||
Applies your configured rules (conditions → actions).
|
||||
- MOVE/ARCHIVE/LABEL/DELETE will be executed when not in dry run.
|
||||
- If disabled, no rule actions are executed (only detection + optional unsubscribe).
|
||||
|
||||
## Seed data
|
||||
```bash
|
||||
cd backend
|
||||
@@ -84,7 +110,56 @@ SEED_TENANT=Default Tenant \\
|
||||
SEED_TENANT_ID=seed-tenant \\
|
||||
npm run prisma:seed
|
||||
```
|
||||
- DSGVO: data storage is designed for tenant isolation; encryption at rest will be added.
|
||||
- DSGVO: tenant isolation supported; sensitive secrets are encrypted at rest when `ENCRYPTION_KEY` is set.
|
||||
|
||||
## Admin password reset (CLI)
|
||||
Reset an admin password via CLI:
|
||||
```
|
||||
docker compose exec api npm run admin:reset -- admin@simplemailcleaner.local NEW_PASSWORD
|
||||
```
|
||||
|
||||
Generate a temporary password (forces change on next login):
|
||||
```
|
||||
docker compose exec api npm run admin:reset -- admin@simplemailcleaner.local
|
||||
```
|
||||
|
||||
## Security hardening (public hosting)
|
||||
The app includes a security hardening pass for public deployments. Highlights:
|
||||
|
||||
- **No public DB/Redis ports** by default (only API/Web are bound, DB/Redis are internal to Docker).
|
||||
- **CORS locked down** via `CORS_ORIGINS`.
|
||||
- **Rate limiting** globally and stricter on auth endpoints.
|
||||
- **Short‑lived SSE tokens** instead of using the user JWT in URLs.
|
||||
- **OAuth state signed** to prevent token injection.
|
||||
- **SSRF protections** for List‑Unsubscribe HTTP and custom mail hosts.
|
||||
- **Secrets encrypted at rest** (OAuth tokens, app passwords, Google client secret).
|
||||
- **Swagger disabled** by default in production.
|
||||
- **Production env validation** rejects default secrets and missing encryption key.
|
||||
|
||||
### Findings and fixes (audit log)
|
||||
- **Open DB/Redis ports** → removed public port bindings in `docker-compose.yml`.
|
||||
- **Default secrets in production** → config validation blocks default JWT/seed secrets in `NODE_ENV=production`.
|
||||
- **Tokens/app passwords stored in plain text** → encrypted at rest with `ENCRYPTION_KEY`.
|
||||
- **SSRF via unsubscribe URLs / custom hosts** → private network block + scheme validation + timeouts.
|
||||
- **OAuth state not verifiable** → state is now a signed, expiring JWT.
|
||||
- **JWT in SSE URL** → replaced with short‑lived stream tokens.
|
||||
- **CORS allow‑all** → restricted by `CORS_ORIGINS`.
|
||||
- **Swagger exposed** → disabled by default in production.
|
||||
- **No rate limiting** → global and auth‑specific rate limits added.
|
||||
|
||||
### Required production settings
|
||||
Set these in `.env` before going public:
|
||||
- `NODE_ENV=production`
|
||||
- `JWT_SECRET=<strong secret>`
|
||||
- `ENCRYPTION_KEY=<min 32 chars>`
|
||||
- `CORS_ORIGINS=https://your-domain.tld`
|
||||
- `TRUST_PROXY=true` (when behind nginx)
|
||||
- `ENABLE_SWAGGER=false`
|
||||
- `SEED_ENABLED=false` (after initial setup)
|
||||
|
||||
### Optional hardening
|
||||
- `ALLOW_CUSTOM_MAIL_HOSTS=false` (default) to force provider defaults
|
||||
- `BLOCK_PRIVATE_NETWORKS=true` (default) to block private IPs in unsubscribe URLs
|
||||
|
||||
## Environment
|
||||
All config lives in the repo root `.env` (see `.env.example`).
|
||||
@@ -97,7 +172,85 @@ Proxy settings (Nginx Proxy Manager):
|
||||
- `TRUST_PROXY=true`
|
||||
- `VITE_API_URL=https://your-domain.tld`
|
||||
- `GOOGLE_REDIRECT_URI=https://your-domain.tld/oauth/gmail/callback`
|
||||
- `CORS_ORIGINS=https://your-domain.tld`
|
||||
|
||||
Local ports (override via `.env` in repo root):
|
||||
- `BIND_IP` (default `127.0.0.1`)
|
||||
- `API_PORT` (default `8000`, now set to `8201` in `.env`)
|
||||
- `WEB_PORT` (default `3000`, now set to `3201` in `.env`)
|
||||
|
||||
## Reverse proxy notes (Nginx)
|
||||
- Terminate TLS at nginx.
|
||||
- Only expose nginx (80/443) publicly.
|
||||
- Keep API/Web bound to `127.0.0.1` (or internal Docker network).
|
||||
- Set `TRUST_PROXY=true` so the app honors `X-Forwarded-*` headers.
|
||||
|
||||
## Nginx Proxy Manager (NPM) setup
|
||||
Minimal steps to run behind Nginx Proxy Manager with limited nginx customization.
|
||||
|
||||
### 1) Bind services locally
|
||||
In `.env`:
|
||||
```
|
||||
BIND_IP=127.0.0.1
|
||||
API_PORT=8201
|
||||
WEB_PORT=3201
|
||||
```
|
||||
|
||||
### 2) Put NPM and Mailcleaner in the same Docker network
|
||||
If NPM runs in Docker, attach both stacks to a shared network (example: `proxy`).
|
||||
|
||||
Create network once:
|
||||
```
|
||||
docker network create proxy
|
||||
```
|
||||
|
||||
Add to `docker-compose.yml`:
|
||||
```
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
Then attach services:
|
||||
```
|
||||
services:
|
||||
api:
|
||||
networks: [proxy]
|
||||
web:
|
||||
networks: [proxy]
|
||||
```
|
||||
|
||||
### 3) Create proxy hosts in NPM
|
||||
Create **two** Proxy Hosts:
|
||||
|
||||
**Frontend**
|
||||
- Domain: `app.your-domain.tld`
|
||||
- Scheme: `http`
|
||||
- Forward Hostname/IP: `mailcleaner-web`
|
||||
- Forward Port: `3000`
|
||||
- Websockets: ON
|
||||
- Block Common Exploits: ON
|
||||
- SSL: Let’s Encrypt, Force SSL
|
||||
|
||||
**API**
|
||||
- Domain: `api.your-domain.tld`
|
||||
- Scheme: `http`
|
||||
- Forward Hostname/IP: `mailcleaner-api`
|
||||
- Forward Port: `8201`
|
||||
- Websockets: ON
|
||||
- Block Common Exploits: ON
|
||||
- SSL: Let’s Encrypt, Force SSL
|
||||
|
||||
### 4) Environment for public hosting
|
||||
Set in `.env`:
|
||||
```
|
||||
NODE_ENV=production
|
||||
TRUST_PROXY=true
|
||||
CORS_ORIGINS=https://app.your-domain.tld
|
||||
VITE_API_URL=https://api.your-domain.tld
|
||||
GOOGLE_REDIRECT_URI=https://api.your-domain.tld/oauth/gmail/callback
|
||||
ENABLE_SWAGGER=false
|
||||
JWT_SECRET=<strong secret>
|
||||
ENCRYPTION_KEY=<min 32 chars>
|
||||
SEED_ENABLED=false
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user