update docs
This commit is contained in:
885
README.md
885
README.md
@@ -85,61 +85,180 @@ Backend API pre email a kontaktný manažment s podporou viacerých JMAP účtov
|
||||
## Services
|
||||
|
||||
### auth.service.js
|
||||
- `getUserById()` - Získanie usera z DB
|
||||
- `createUser()` - Vytvorenie nového usera s temp heslom
|
||||
- `validatePassword()` - Validácia hesla (bcrypt compare)
|
||||
- `changePassword()` - Zmena hesla s auditom
|
||||
Správa autentifikácie a používateľov.
|
||||
|
||||
**Funkcie:**
|
||||
- `getUserById(id)` - Získanie usera z DB podľa ID
|
||||
- `getUserByUsername(username)` - Nájdenie usera podľa username
|
||||
- `createUser(data)` - Vytvorenie nového usera s auto-generovaným temporary heslom
|
||||
- `validatePassword(user, password)` - Validácia hesla (bcrypt compare s temp_password alebo password)
|
||||
- `changePassword(userId, newPassword)` - Zmena hesla + audit log + update `changed_password = true`
|
||||
- `updateLastLogin(userId)` - Update `last_login` timestamp
|
||||
|
||||
### email-account.service.js
|
||||
- `getEmailAccounts()` - Zoznam účtov pre usera
|
||||
- `addEmailAccount()` - Pridanie JMAP účtu (šifrovanie hesla)
|
||||
- `getEmailAccountWithCredentials()` - Účet s dešifrovaným heslom
|
||||
- `getPrimaryEmailAccount()` - Primárny účet
|
||||
- `removeEmailAccount()` - Odstránenie s cascade delete
|
||||
- `setPrimaryAccount()` - Nastavenie primárneho
|
||||
Multi-account email management.
|
||||
|
||||
**Funkcie:**
|
||||
- `getEmailAccounts(userId)` - Zoznam všetkých email účtov usera
|
||||
- `addEmailAccount(userId, email, password)` - Pridanie JMAP účtu s validáciou credentials a šifrovaním hesla
|
||||
- `getEmailAccountWithCredentials(accountId, userId)` - Účet s dešifrovaným heslom (pre JMAP operácie)
|
||||
- `getPrimaryEmailAccount(userId)` - Vráti primárny účet usera
|
||||
- `removeEmailAccount(accountId, userId)` - Odstránenie účtu + cascade delete kontaktov a emailov
|
||||
- `setPrimaryAccount(accountId, userId)` - Nastavenie účtu ako primárneho (resetne ostatné)
|
||||
- `verifyAccountOwnership(accountId, userId)` - Overenie že účet patrí userovi
|
||||
|
||||
### jmap.service.js
|
||||
- `jmapRequest()` - HTTP request na JMAP server
|
||||
- `getJmapSession()` - Získanie JMAP session
|
||||
- `getJmapConfigFromAccount()` - Config z email účtu
|
||||
- `syncEmailsFromSender()` - Sync emailov od kontaktu
|
||||
- `searchEmailsJMAP()` - Full-text search v JMAP
|
||||
- `sendEmail()` - Poslanie odpovede cez JMAP
|
||||
- `discoverContactsFromJMAP()` - Objavenie odosielateľov
|
||||
JMAP protokol integrácia s Truemail.sk.
|
||||
|
||||
**Core funkcie:**
|
||||
- `jmapRequest(config, methodCalls)` - Low-level JMAP HTTP request (Basic Auth + JSON payload)
|
||||
- `getJmapSession(config)` - Získanie JMAP session (capabilities, accountId, upload URL)
|
||||
- `getJmapConfigFromAccount(accountId, userId)` - Vytvorenie JMAP config objektu z DB account
|
||||
|
||||
**Email operácie:**
|
||||
- `syncEmailsFromJMAP(config, userId, accountId, filters)` - Stiahnutie emailov z JMAP a uloženie do DB
|
||||
- `syncEmailsFromSender(config, userId, accountId, senderEmail, contactId)` - Sync všetkých emailov od konkrétneho odosielateľa
|
||||
- `searchEmailsJMAP(config, query, limit, offset)` - Full-text search v JMAP serveri
|
||||
- `sendEmail(config, emailAccountId, to, subject, body, inReplyTo)` - Vytvorenie a poslanie odpovede cez JMAP
|
||||
- `markEmailAsRead(config, userId, jmapId, isRead)` - Update `$seen` keywordu na JMAP serveri
|
||||
|
||||
**Discovery:**
|
||||
- `discoverContactsFromJMAP(config)` - Získanie všetkých unikátnych emailových adries z INBOX
|
||||
|
||||
**Helpers:**
|
||||
- `parseJmapResponse(response)` - Parsovanie JMAP response a error handling
|
||||
- `extractEmailAddress(emailObject)` - Extrahovanie email adresy z JMAP formátu `[{email: "..."}]`
|
||||
|
||||
### contact.service.js
|
||||
- `getUserContacts()` - Kontakty usera (s accountId filtrom)
|
||||
- `addContact()` - Pridanie + auto-sync emailov
|
||||
- `updateContact()` - Úprava kontaktu
|
||||
- `removeContact()` - Odstránenie + cascade delete
|
||||
Správa kontaktov s auto-sync funkciou.
|
||||
|
||||
**Funkcie:**
|
||||
- `getUserContacts(userId, accountId?)` - Zoznam kontaktov usera (s voliteľným account filtrom)
|
||||
- `getContactById(contactId, userId)` - Detail kontaktu s ownership validáciou
|
||||
- `addContact(userId, accountId, email, name?, notes?)` - Pridanie kontaktu + **automatický sync emailov od tohto odosielateľa**
|
||||
- `updateContact(contactId, userId, updates)` - Update mena alebo poznámok
|
||||
- `removeContact(contactId, userId)` - Odstránenie kontaktu (emaily zostávajú, `contact_id = null`)
|
||||
- `verifyContactOwnership(contactId, userId)` - Overenie ownership
|
||||
|
||||
### crm-email.service.js
|
||||
- `getUserEmails()` - Emaily usera (s accountId filtrom)
|
||||
- `getEmailThread()` - Thread konverzácia
|
||||
- `markThreadAsRead()` - Označenie threadu
|
||||
- `searchEmails()` - DB vyhľadávanie
|
||||
- `getUnreadCount()` - Počet neprecítaných per účet
|
||||
Email CRUD operácie a thread management.
|
||||
|
||||
**Funkcie:**
|
||||
- `getUserEmails(userId, accountId?, filters?)` - Zoznam emailov s pagination a filtrami (isRead, contactId, search)
|
||||
- `getEmailById(emailId, userId)` - Detail emailu
|
||||
- `getEmailThread(threadId, userId)` - Všetky emaily v konverzácii (sorted by date)
|
||||
- `markThreadAsRead(threadId, userId, isRead)` - Označenie všetkých emailov v threade
|
||||
- `searchEmails(userId, query, accountId?)` - DB full-text search (subject, body, from)
|
||||
- `getUnreadCount(userId, accountId?)` - Počet neprecítaných emailov per účet
|
||||
- `createEmail(data)` - Vytvorenie email záznamu v DB (používa jmap.service)
|
||||
- `updateEmailReadStatus(emailId, userId, isRead)` - Update single email read status
|
||||
|
||||
### audit.service.js
|
||||
- `logAuditEvent()` - Zaloguje akciu s IP, user-agent, timestamp
|
||||
Audit logging pre bezpečnostné účely.
|
||||
|
||||
**Funkcia:**
|
||||
- `logAuditEvent(data)` - Zaloguje akciu do audit_logs tabuľky
|
||||
```javascript
|
||||
{
|
||||
userId, action, resource, resourceId?,
|
||||
oldValue?, newValue?, success,
|
||||
errorMessage?, ipAddress?, userAgent?
|
||||
}
|
||||
```
|
||||
|
||||
**Tracked events:**
|
||||
- Login (success/fail)
|
||||
- Password changes
|
||||
- User creation/deletion
|
||||
- Email account add/remove
|
||||
- Role changes
|
||||
- Contact add/remove
|
||||
|
||||
### email.service.js
|
||||
(Legacy?) Basic email operácie - možno deprecated v prospech crm-email.service.js
|
||||
|
||||
## Middlewares
|
||||
|
||||
### Autentifikácia
|
||||
- `authenticate` - Validácia JWT tokenu, načítanie usera
|
||||
- `requireAdmin` - Overenie admin role
|
||||
- `requireOwnerOrAdmin` - Vlastník alebo admin
|
||||
|
||||
**authenticate.js**
|
||||
- Validuje JWT access token z httpOnly cookie
|
||||
- Načíta usera z DB a priradí k `req.user`
|
||||
- Ak token expiroval, vráti 401 Unauthorized
|
||||
- Ak token chýba, vráti 401 Unauthorized
|
||||
|
||||
**roleMiddleware.js**
|
||||
|
||||
`requireAdmin`
|
||||
- Overí že user má `role = 'admin'`
|
||||
- Používa sa pred admin-only endpointmi
|
||||
- Vráti 403 Forbidden ak nie je admin
|
||||
|
||||
`requireOwnerOrAdmin`
|
||||
- Overí že user je vlastník resource alebo admin
|
||||
- Používa sa napr. pri úprave vlastného profilu
|
||||
- Parameter: `resourceUserId` z req.params alebo req.body
|
||||
|
||||
### Bezpečnosť
|
||||
- `loginRateLimiter` - 5 pokusov / 15 min
|
||||
- `apiRateLimiter` - 100 requestov / 15 min (dev: 1000)
|
||||
- `sensitiveOperationLimiter` - 3 pokusy / 15 min
|
||||
- `validateBody` - Zod schema validácia
|
||||
- `validateParams` - Zod params validácia
|
||||
|
||||
**rateLimiter.js**
|
||||
|
||||
`loginRateLimiter`
|
||||
- Limit: 5 pokusov / 15 minút
|
||||
- Scope: Per IP adresa
|
||||
- Používa sa na `/api/auth/login`
|
||||
- Ochrana proti brute force útokom
|
||||
|
||||
`apiRateLimiter`
|
||||
- Limit: 100 requestov / 15 min (production)
|
||||
- Limit: 1000 requestov / 15 min (development)
|
||||
- Scope: Per IP adresa
|
||||
- Aplikuje sa na všetky `/api/*` routes
|
||||
|
||||
`sensitiveOperationLimiter`
|
||||
- Limit: 3 pokusy / 15 minút
|
||||
- Používa sa na password change, user delete, atď.
|
||||
- Extra ochrana pre kritické operácie
|
||||
|
||||
**validateInput.js**
|
||||
- XSS sanitizácia vstupných dát
|
||||
- Používa `xss-clean` knižnicu
|
||||
- Automaticky aplikované cez `xss-clean` middleware
|
||||
|
||||
### Validácia
|
||||
|
||||
**validateBody.js**
|
||||
- Middleware factory pre Zod schema validáciu
|
||||
- Použitie: `validateBody(schema)`
|
||||
- Pri validačnej chybe vráti 400 s detailmi
|
||||
|
||||
**validateParams.js**
|
||||
- Validácia URL parametrov (napr. `:id`)
|
||||
- Zod schemas pre params
|
||||
|
||||
**Príklad použitia:**
|
||||
```javascript
|
||||
router.post(
|
||||
'/login',
|
||||
validateBody(loginSchema),
|
||||
loginRateLimiter,
|
||||
loginController
|
||||
);
|
||||
```
|
||||
|
||||
### Global
|
||||
- `errorHandler` - Centrálne error handling
|
||||
- `notFound` - 404 handler
|
||||
|
||||
**errorHandler.js**
|
||||
- Centrálne spracovanie všetkých errorov
|
||||
- Rozpoznáva custom error classes (`BadRequestError`, `UnauthorizedError`, ...)
|
||||
- Production: Skryje stack traces
|
||||
- Development: Full error details
|
||||
- Loguje errory cez Winston
|
||||
|
||||
**notFound.js**
|
||||
- 404 handler pre neexistujúce routes
|
||||
- Musí byť zaregistrovaný **pred** errorHandler
|
||||
- Vráti: `{ success: false, error: 'Route not found' }`
|
||||
|
||||
## Database Schema
|
||||
|
||||
@@ -181,56 +300,404 @@ Backend API pre email a kontaktný manažment s podporou viacerých JMAP účtov
|
||||
|
||||
## Konfigurácia
|
||||
|
||||
**.env**
|
||||
### Environment Variables (.env)
|
||||
|
||||
```env
|
||||
# Server
|
||||
PORT=5000
|
||||
NODE_ENV=development
|
||||
# Server Configuration
|
||||
PORT=5000 # Port na ktorom beží Express server
|
||||
NODE_ENV=development # development | production (ovplyvňuje rate limits, error handling)
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=password
|
||||
DB_NAME=crm
|
||||
# Database Configuration
|
||||
DB_HOST=localhost # PostgreSQL host (docker-compose: 'postgres')
|
||||
DB_PORT=5432 # PostgreSQL port
|
||||
DB_USER=admin # DB username
|
||||
DB_PASSWORD=heslo123 # DB password
|
||||
DB_NAME=crm # Database name
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your-secret-key
|
||||
JWT_REFRESH_SECRET=your-refresh-secret
|
||||
JWT_EXPIRES_IN=1h
|
||||
# JWT Secrets (MUSIA byť dlhé a náhodné!)
|
||||
JWT_SECRET=your-secret-key # Pre access token (min 32 znakov)
|
||||
JWT_REFRESH_SECRET=your-refresh-secret # Pre refresh token (min 32 znakov)
|
||||
JWT_EXPIRES_IN=1h # Access token expiration (1h = 1 hodina)
|
||||
|
||||
# JMAP
|
||||
JMAP_SERVER=https://mail.truemail.sk/jmap/
|
||||
# JMAP Configuration
|
||||
JMAP_SERVER=https://mail.truemail.sk/jmap/ # JMAP endpoint pre Truemail.sk
|
||||
|
||||
# Encryption
|
||||
ENCRYPTION_KEY=your-32-char-encryption-key
|
||||
# Encryption (pre JMAP heslá)
|
||||
ENCRYPTION_KEY=your-32-char-encryption-key # PRESNE 32 znakov pre AES-256
|
||||
|
||||
# CORS
|
||||
CORS_ORIGIN=http://localhost:5173
|
||||
CORS_ORIGIN=http://localhost:5173 # Frontend URL (React dev server)
|
||||
|
||||
# Optional
|
||||
BCRYPT_ROUNDS=12 # Bcrypt cost factor (default: 12)
|
||||
```
|
||||
|
||||
### Ako vygenerovať secrets
|
||||
|
||||
**JWT_SECRET a JWT_REFRESH_SECRET:**
|
||||
```bash
|
||||
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
```
|
||||
|
||||
**ENCRYPTION_KEY (presne 32 znakov):**
|
||||
```bash
|
||||
node -e "console.log(require('crypto').randomBytes(32).toString('base64').slice(0, 32))"
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
**docker-compose.yml** obsahuje len PostgreSQL databázu:
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16
|
||||
container_name: postgres-db
|
||||
restart: "no"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./postgres:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
**Spustenie:**
|
||||
```bash
|
||||
docker-compose up -d postgres # Start databázy
|
||||
docker-compose down # Stop databázy (data zostanú)
|
||||
docker-compose down -v # Stop + vymazanie volumes (⚠️ stratíš dáta)
|
||||
```
|
||||
|
||||
### Drizzle Configuration
|
||||
|
||||
**drizzle.config.js** - Konfigurácia pre Drizzle Kit (migrations tool):
|
||||
```javascript
|
||||
{
|
||||
schema: './src/db/schema.js', // Kde je DB schema definovaná
|
||||
out: './src/db/migrations', // Kam sa uložia migrračné SQL súbory
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: { /* z .env */ },
|
||||
verbose: true, // Loguje SQL queries
|
||||
strict: true // Prísna validácia
|
||||
}
|
||||
```
|
||||
|
||||
**Používané príkazy:**
|
||||
- `drizzle-kit generate` → vygeneruje SQL migrácie zo schémy
|
||||
- `drizzle-kit push` → pushne schému priamo (skip migrations)
|
||||
- `drizzle-kit studio` → otvorí DB GUI na http://localhost:4983
|
||||
|
||||
## Skripty
|
||||
|
||||
### NPM Skripty
|
||||
|
||||
```bash
|
||||
npm run dev # Dev server s nodemon
|
||||
npm start # Production server
|
||||
npm run db:generate # Generovanie Drizzle migrácií
|
||||
npm run db:migrate # Aplikovanie migrácií
|
||||
npm run db:push # Push schema (bez migrácií)
|
||||
npm run db:studio # Drizzle Studio (DB GUI)
|
||||
npm run db:seed # Seed admin account
|
||||
npm test # Spustenie testov
|
||||
# Development & Production
|
||||
npm run dev # Spustí vývojový server s nodemon (hot reload)
|
||||
npm start # Production server (bez reloadovania)
|
||||
|
||||
# Database management
|
||||
npm run db:generate # Vygeneruje SQL migračné súbory z Drizzle schémy
|
||||
npm run db:migrate # Aplikuje migrácie na databázu
|
||||
npm run db:push # Pushne schému priamo (bez migračných súborov)
|
||||
npm run db:studio # Otvorí Drizzle Studio - GUI pre databázu
|
||||
|
||||
# Seeding
|
||||
npm run db:seed # Vytvorí admin účet s náhodným heslom
|
||||
npm run db:seed:testuser # Vytvorí testovacieho usera (testuser/testuser123!)
|
||||
|
||||
# Testing
|
||||
npm test # Spustí Jest testy
|
||||
```
|
||||
|
||||
### Utility Skripty
|
||||
|
||||
**check-tables.js** - Zoznam všetkých tabuliek v databáze
|
||||
```bash
|
||||
node check-tables.js
|
||||
```
|
||||
|
||||
**test-search.js** - Test JMAP search endpointu (vyžaduje TEST_COOKIE env)
|
||||
```bash
|
||||
TEST_COOKIE='session=xyz...' node test-search.js
|
||||
```
|
||||
|
||||
**mark-email-read-jmap.js** - Označí konkrétny email ako prečítaný cez JMAP
|
||||
```bash
|
||||
node mark-email-read-jmap.js
|
||||
```
|
||||
|
||||
**scripts/encrypt-password.js** - Zašifruje heslo pre manuálne vloženie do DB
|
||||
```bash
|
||||
node scripts/encrypt-password.js 'your-password'
|
||||
```
|
||||
|
||||
## Spustenie
|
||||
|
||||
1. Nainštaluj dependencies: `npm install`
|
||||
2. Nastav `.env` s DB credentials a secrets
|
||||
3. Spusti PostgreSQL: `docker-compose up -d postgres`
|
||||
4. Aplikuj migrácie: `npm run db:migrate`
|
||||
5. Seed admin: `npm run db:seed` (uložíš si temp password!)
|
||||
6. Spusti server: `npm run dev`
|
||||
7. API beží na `http://localhost:5000`
|
||||
### Prvý setup
|
||||
|
||||
1. **Nainštaluj dependencies**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **Skopíruj a nastav .env**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
- Nastav `DB_*` credentials (default sú pre docker-compose)
|
||||
- Vygeneruj silný `JWT_SECRET` a `JWT_REFRESH_SECRET`
|
||||
- Vygeneruj 32-znakový `ENCRYPTION_KEY` (pre šifrovanie JMAP hesiel)
|
||||
- Nastav `CORS_ORIGIN` pre frontend (default: http://localhost:5173)
|
||||
|
||||
3. **Spusti PostgreSQL databázu**
|
||||
```bash
|
||||
docker-compose up -d postgres
|
||||
```
|
||||
|
||||
4. **Aplikuj databázové migrácie**
|
||||
```bash
|
||||
npm run db:migrate
|
||||
```
|
||||
|
||||
5. **Vytvor admin účet**
|
||||
```bash
|
||||
npm run db:seed
|
||||
```
|
||||
⚠️ **DÔLEŽITÉ:** Skopíruj si vygenerované temporary heslo! Budeš ho potrebovať pri prvom prihlásení.
|
||||
|
||||
6. **Spusti server**
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
7. **API beží na** `http://localhost:5000`
|
||||
- Health check: `GET /health`
|
||||
- API docs: Všetky endpointy sú popísané v sekcii "API Endpointy"
|
||||
|
||||
### Každodenný development
|
||||
|
||||
```bash
|
||||
# Spusti databázu (ak nie je spustená)
|
||||
docker-compose up -d postgres
|
||||
|
||||
# Spusti dev server
|
||||
npm run dev
|
||||
|
||||
# Otvor DB GUI (voliteľné)
|
||||
npm run db:studio
|
||||
```
|
||||
|
||||
## Ako to funguje
|
||||
|
||||
### Architektúra
|
||||
|
||||
Aplikácia používa **layered architecture** so separáciou zodpovedností:
|
||||
|
||||
```
|
||||
Client Request
|
||||
↓
|
||||
Routes (routing)
|
||||
↓
|
||||
Middlewares (auth, validation, rate limiting)
|
||||
↓
|
||||
Controllers (request handling, response formatting)
|
||||
↓
|
||||
Services (business logic)
|
||||
↓
|
||||
Database (Drizzle ORM)
|
||||
```
|
||||
|
||||
### Flow autentifikácie a onboardingu
|
||||
|
||||
1. **Admin vytvorí nového usera**
|
||||
- `POST /api/admin/users` s username, firstName, lastName, role
|
||||
- Systém vygeneruje náhodné temporary heslo (16 znakov, mix uppercase/lowercase/numbers/symbols)
|
||||
- Heslo sa hashuje bcryptom (12 rounds) a uloží do `temp_password`
|
||||
- `changed_password = false`
|
||||
|
||||
2. **Prvé prihlásenie**
|
||||
- User sa prihlási s temporary heslom: `POST /api/auth/login`
|
||||
- Backend zistí že `changed_password = false` a vráti flag `mustChangePassword: true`
|
||||
- Frontend redirectne na "set password" screen
|
||||
|
||||
3. **Nastavenie vlastného hesla**
|
||||
- `POST /api/auth/set-password` s novým heslom
|
||||
- Validácia: min 8 znakov, uppercase, lowercase, number, symbol
|
||||
- Heslo sa hashuje a uloží do `password` fieldu
|
||||
- `temp_password = null`, `changed_password = true`
|
||||
|
||||
4. **Session management**
|
||||
- Pri login dostane user JWT access token (httpOnly cookie, 1h)
|
||||
- Refresh token (httpOnly cookie, 7 dní)
|
||||
- Pri expirácii access tokenu sa automaticky refreshne
|
||||
|
||||
### Email synchronizácia flow
|
||||
|
||||
1. **Pripojenie email účtu**
|
||||
- User pridá JMAP účet: `POST /api/email-accounts`
|
||||
- Backend overí JMAP credentials volaním `getJmapSession()`
|
||||
- Heslo sa zašifruje AES-256-GCM a uloží do DB
|
||||
- JMAP accountId sa uloží pre budúce requesty
|
||||
|
||||
2. **Prvý sync**
|
||||
- Automaticky sa stiahnu emaily z INBOX
|
||||
- `Email/query` + `Email/get` JMAP volania
|
||||
- Emaily sa uložia do DB s `thread_id` pre organizáciu konverzácií
|
||||
|
||||
3. **Nepretržitý sync**
|
||||
- Frontend pravidelne volá `GET /api/emails?accountId=X`
|
||||
- Backend volá JMAP a stiahne nové emaily od posledného syncu
|
||||
- Update-uje `is_read` status podľa JMAP
|
||||
|
||||
### Kontakty a auto-sync
|
||||
|
||||
1. **Discovery kontaktov**
|
||||
- `GET /api/contacts/discover?accountId=X`
|
||||
- Backend stiahne všetky emaily z JMAP
|
||||
- Extrahuje unikátne email adresy z `from` a `to` polí
|
||||
- Vráti zoznam emailov ktoré ešte nie sú v kontaktoch
|
||||
|
||||
2. **Pridanie kontaktu**
|
||||
- `POST /api/contacts` s `email` a `emailAccountId`
|
||||
- Backend uloží kontakt do DB
|
||||
- **Auto-sync:** Automaticky vyhľadá všetky emaily od/pre tento kontakt cez JMAP
|
||||
- Stiahnuť a uloží všetky nájdené emaily s linknutím na kontakt (`contact_id`)
|
||||
|
||||
3. **Email thread organizácia**
|
||||
- Emaily sa zoskupujú podľa `thread_id` (z JMAP)
|
||||
- `GET /api/emails/thread/:threadId` vráti chronologickú konverzáciu
|
||||
- Mark as read označí všetky emaily v threade
|
||||
|
||||
### Bezpečnostné mechanizmy
|
||||
|
||||
**Rate Limiting:**
|
||||
- Login: 5 pokusov / 15 min (brute force ochrana)
|
||||
- API: 100 requestov / 15 min (1000 v dev mode)
|
||||
- Sensitive operations: 3 pokusy / 15 min
|
||||
|
||||
**Password Security:**
|
||||
- Bcrypt hashing s 12 rounds (2^12 = 4096 iterations)
|
||||
- Temporary passwords: 16 znakov, vysoká entropia
|
||||
- Validácia: min 8 znakov, mix typov znakov
|
||||
|
||||
**Data Encryption:**
|
||||
- JMAP heslá: AES-256-GCM (symetrické šifrovanie)
|
||||
- Format: `iv:authTag:encryptedText`
|
||||
- Kľúč derivovaný z `ENCRYPTION_KEY` cez scrypt
|
||||
|
||||
**Session Security:**
|
||||
- JWT tokeny v httpOnly cookies (XSS ochrana)
|
||||
- SameSite=Strict (CSRF ochrana)
|
||||
- Krátka platnosť access tokenu (1h)
|
||||
- Refresh token rotation
|
||||
|
||||
**Input Validation:**
|
||||
- Zod schemas pre všetky endpointy
|
||||
- XSS sanitizácia cez xss-clean
|
||||
- SQL injection ochrana cez Drizzle ORM prepared statements
|
||||
|
||||
**Audit Logging:**
|
||||
- Všetky dôležité akcie (login, password change, user creation, email account add/remove)
|
||||
- Ukladá user_id, action, resource, old/new values, IP, user-agent
|
||||
- `GET /api/admin/audit-logs` pre adminov
|
||||
|
||||
### Database Cascade Deletes
|
||||
|
||||
**User Delete:**
|
||||
- Cascade vymaže: email_accounts → contacts → emails → audit_logs
|
||||
|
||||
**Email Account Delete:**
|
||||
- Cascade vymaže: contacts → emails (pre tento účet)
|
||||
- User si môže pridať ďalší účet
|
||||
|
||||
**Contact Delete:**
|
||||
- Emaily od tohto kontaktu zostávajú (len `contact_id = null`)
|
||||
- Možnosť filtrovať "orphaned" emaily
|
||||
|
||||
### JMAP Operácie
|
||||
|
||||
**Supported operations:**
|
||||
|
||||
1. **Email/query** - Vyhľadávanie emailov
|
||||
```javascript
|
||||
{
|
||||
filter: { inMailbox: 'inbox' },
|
||||
sort: [{ property: 'receivedAt', isAscending: false }],
|
||||
limit: 50
|
||||
}
|
||||
```
|
||||
|
||||
2. **Email/get** - Získanie detailov emailov
|
||||
```javascript
|
||||
{
|
||||
ids: ['id1', 'id2'],
|
||||
properties: ['from', 'to', 'subject', 'bodyValues', 'threadId', ...]
|
||||
}
|
||||
```
|
||||
|
||||
3. **Email/set** - Vytvorenie emailu (draft)
|
||||
```javascript
|
||||
{
|
||||
create: {
|
||||
draft1: {
|
||||
mailboxIds: { drafts: true },
|
||||
from: [...], to: [...],
|
||||
subject: '...', bodyValues: { body: { value: '...' }}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **EmailSubmission/set** - Poslanie emailu
|
||||
```javascript
|
||||
{
|
||||
create: {
|
||||
sub1: {
|
||||
emailId: 'draft1',
|
||||
identityId: 'identity1'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5. **Email/changes** - Incremental sync (delta updates)
|
||||
- Používa `sinceState` pre získanie len zmenených emailov
|
||||
- Optimalizované pre veľké mailboxy
|
||||
|
||||
### Šifrovanie JMAP hesiel
|
||||
|
||||
**Encryption flow:**
|
||||
```javascript
|
||||
// Encryption (pri pridaní účtu)
|
||||
1. Vygeneruj náhodný IV (16 bytes)
|
||||
2. Vytvor cipher s AES-256-GCM, key, IV
|
||||
3. Zašifruj heslo
|
||||
4. Získaj auth tag
|
||||
5. Ulož: `${iv}:${authTag}:${encrypted}`
|
||||
|
||||
// Decryption (pri JMAP operáciách)
|
||||
1. Split stored value na [iv, authTag, encrypted]
|
||||
2. Vytvor decipher s AES-256-GCM, key, IV
|
||||
3. Nastav auth tag
|
||||
4. Dešifruj heslo
|
||||
5. Použij pre JMAP request
|
||||
```
|
||||
|
||||
### Multi-Account Management
|
||||
|
||||
Každý user môže mať **viacero email účtov**:
|
||||
- Jeden je označený ako **primárny** (`is_primary = true`)
|
||||
- Kontakty a emaily sú **izolované per účet** (accountId filter)
|
||||
- Frontend prepína medzi účtami cez accountId parameter
|
||||
- Každý účet má vlastný JMAP session a credentials
|
||||
|
||||
### Error Handling
|
||||
|
||||
**Centrálny error handler middleware:**
|
||||
- Všetky chyby idú cez `errorHandler.js`
|
||||
- Custom error classes: `BadRequestError`, `UnauthorizedError`, `NotFoundError`, `ConflictError`
|
||||
- Production mode: Skryje stack traces
|
||||
- Development mode: Full error details + stack trace
|
||||
- Všetky errory sa logujú cez Winston logger
|
||||
|
||||
## Systém rolí
|
||||
|
||||
@@ -248,16 +715,272 @@ npm test # Spustenie testov
|
||||
|
||||
## JMAP Integrácia
|
||||
|
||||
- **Provider:** Truemail.sk
|
||||
- **Endpoint:** https://mail.truemail.sk/jmap/
|
||||
- **Auth:** Basic Auth (email + heslo)
|
||||
- **Operácie:**
|
||||
- Email/query - Vyhľadávanie emailov
|
||||
- Email/get - Získanie detailov
|
||||
- Email/set - Vytvorenie emailu
|
||||
- EmailSubmission/set - Poslanie emailu
|
||||
- Mailbox/get - Zoznam mailboxov
|
||||
- Identity/get - Email identity
|
||||
### Provider: Truemail.sk
|
||||
|
||||
**JMAP Endpoint:** `https://mail.truemail.sk/jmap/`
|
||||
|
||||
**Autentifikácia:** Basic Auth
|
||||
- Username: email adresa (napr. `user@slovensko.ai`)
|
||||
- Password: email heslo (uložené encrypted v DB)
|
||||
|
||||
### JMAP Session
|
||||
|
||||
Každý JMAP request začína získaním session:
|
||||
```javascript
|
||||
GET https://mail.truemail.sk/jmap/session/
|
||||
Authorization: Basic base64(email:password)
|
||||
|
||||
Response:
|
||||
{
|
||||
"capabilities": {
|
||||
"urn:ietf:params:jmap:core": { maxObjectsInGet: 500, ... },
|
||||
"urn:ietf:params:jmap:mail": { ... }
|
||||
},
|
||||
"accounts": {
|
||||
"account-id": {
|
||||
"name": "user@slovensko.ai",
|
||||
"isPersonal": true,
|
||||
"accountCapabilities": { ... }
|
||||
}
|
||||
},
|
||||
"primaryAccounts": {
|
||||
"urn:ietf:params:jmap:mail": "account-id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`accountId` sa ukladá do DB (`jmap_account_id`) a používa pre všetky ďalšie requesty.
|
||||
|
||||
### Supported JMAP Methods
|
||||
|
||||
**1. Email/query** - Vyhľadávanie emailov
|
||||
```javascript
|
||||
[
|
||||
"Email/query",
|
||||
{
|
||||
accountId: "account-id",
|
||||
filter: {
|
||||
inMailbox: "inbox", // alebo "sent", "drafts"
|
||||
from: "email@domain.sk" // voliteľný filter
|
||||
},
|
||||
sort: [{ property: "receivedAt", isAscending: false }],
|
||||
limit: 50,
|
||||
position: 0
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
|
||||
Response: { list: ["emailId1", "emailId2", ...], total: 156 }
|
||||
```
|
||||
|
||||
**2. Email/get** - Získanie detailov
|
||||
```javascript
|
||||
[
|
||||
"Email/get",
|
||||
{
|
||||
accountId: "account-id",
|
||||
ids: ["emailId1", "emailId2"],
|
||||
properties: [
|
||||
"id", "threadId", "mailboxIds", "keywords",
|
||||
"from", "to", "cc", "bcc",
|
||||
"subject", "receivedAt", "bodyValues",
|
||||
"textBody", "htmlBody"
|
||||
],
|
||||
bodyProperties: ["partId", "type", "value"]
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
|
||||
Response: {
|
||||
list: [{
|
||||
id: "emailId1",
|
||||
from: [{ email: "sender@domain.sk", name: "Sender Name" }],
|
||||
subject: "Email subject",
|
||||
bodyValues: { body: { value: "Email content..." } },
|
||||
...
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**3. Email/set** - Vytvorenie/Update/Delete
|
||||
```javascript
|
||||
// Vytvorenie draft emailu
|
||||
[
|
||||
"Email/set",
|
||||
{
|
||||
accountId: "account-id",
|
||||
create: {
|
||||
"draft-1": {
|
||||
mailboxIds: { "drafts-mailbox-id": true },
|
||||
from: [{ email: "me@slovensko.ai" }],
|
||||
to: [{ email: "recipient@domain.sk" }],
|
||||
subject: "Reply subject",
|
||||
bodyValues: {
|
||||
body: {
|
||||
value: "Email content",
|
||||
type: "text/plain"
|
||||
}
|
||||
},
|
||||
textBody: [{ partId: "body", type: "text/plain" }],
|
||||
keywords: { "$draft": true }
|
||||
}
|
||||
}
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
|
||||
// Update (napr. mark as read)
|
||||
[
|
||||
"Email/set",
|
||||
{
|
||||
accountId: "account-id",
|
||||
update: {
|
||||
"emailId1": {
|
||||
"keywords/$seen": true // mark as read
|
||||
}
|
||||
}
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
```
|
||||
|
||||
**4. EmailSubmission/set** - Poslanie emailu
|
||||
```javascript
|
||||
[
|
||||
"EmailSubmission/set",
|
||||
{
|
||||
accountId: "account-id",
|
||||
create: {
|
||||
"submission-1": {
|
||||
emailId: "draft-1", // ID z Email/set create
|
||||
identityId: "identity-id", // Z Identity/get
|
||||
envelope: {
|
||||
mailFrom: { email: "me@slovensko.ai" },
|
||||
rcptTo: [{ email: "recipient@domain.sk" }]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
```
|
||||
|
||||
**5. Mailbox/get** - Zoznam mailboxov (INBOX, Sent, Drafts, ...)
|
||||
```javascript
|
||||
[
|
||||
"Mailbox/get",
|
||||
{
|
||||
accountId: "account-id",
|
||||
ids: null // null = všetky
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
|
||||
Response: {
|
||||
list: [
|
||||
{ id: "inbox-id", name: "Inbox", role: "inbox", ... },
|
||||
{ id: "sent-id", name: "Sent", role: "sent", ... }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**6. Identity/get** - Email identity (pre sending)
|
||||
```javascript
|
||||
[
|
||||
"Identity/get",
|
||||
{
|
||||
accountId: "account-id",
|
||||
ids: null
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
|
||||
Response: {
|
||||
list: [{
|
||||
id: "identity-id",
|
||||
email: "me@slovensko.ai",
|
||||
name: "My Name"
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
**7. Email/changes** - Delta sync (incremental updates)
|
||||
```javascript
|
||||
[
|
||||
"Email/changes",
|
||||
{
|
||||
accountId: "account-id",
|
||||
sinceState: "previous-state-token",
|
||||
maxChanges: 100
|
||||
},
|
||||
"call-id"
|
||||
]
|
||||
|
||||
Response: {
|
||||
oldState: "previous-state",
|
||||
newState: "current-state",
|
||||
hasMoreChanges: false,
|
||||
created: ["emailId1"],
|
||||
updated: ["emailId2"],
|
||||
destroyed: ["emailId3"]
|
||||
}
|
||||
```
|
||||
|
||||
### JMAP Request Format
|
||||
|
||||
Všetky requesty používajú rovnaký formát:
|
||||
```javascript
|
||||
POST https://mail.truemail.sk/jmap/api/
|
||||
Authorization: Basic base64(email:password)
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"using": [
|
||||
"urn:ietf:params:jmap:core",
|
||||
"urn:ietf:params:jmap:mail"
|
||||
],
|
||||
"methodCalls": [
|
||||
["Method/name", { params }, "call-id"],
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
JMAP errory majú formát:
|
||||
```javascript
|
||||
{
|
||||
"methodResponses": [
|
||||
["error", {
|
||||
"type": "invalidArguments",
|
||||
"description": "Invalid filter"
|
||||
}, "call-id"]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Common error types:**
|
||||
- `invalidArguments` - Zlé parametre
|
||||
- `accountNotFound` - Neplatný accountId
|
||||
- `notFound` - Email/mailbox nenájdený
|
||||
- `forbidden` - Nedostatočné oprávnenia
|
||||
- `serverFail` - Server error
|
||||
|
||||
### Optimalizácie
|
||||
|
||||
**Batching:**
|
||||
- Jeden HTTP request môže obsahovať viacero method calls
|
||||
- Príklad: Email/query + Email/get v jednom requeste
|
||||
|
||||
**Result References:**
|
||||
- Výsledok jedného method callu môže byť použitý v ďalšom
|
||||
- Príklad: Email/query → Email/get s `#ids` referenciou
|
||||
|
||||
**Incremental Sync:**
|
||||
- Používaj `Email/changes` namiesto full Email/query
|
||||
- Ukladaj `state` token pre ďalšie syncs
|
||||
- Znižuje bandwidth a load
|
||||
|
||||
## Šifrovanie
|
||||
|
||||
|
||||
Reference in New Issue
Block a user