995 lines
27 KiB
Markdown
995 lines
27 KiB
Markdown
# CRM Server API
|
|
|
|
Backend API pre email a kontaktný manažment s podporou viacerých JMAP účtov.
|
|
|
|
## Funkcie
|
|
|
|
### Autentifikácia
|
|
- **Login** - Session-based auth s JWT tokens (httpOnly cookies)
|
|
- **Onboarding** - 3-krokový flow (login → nastavenie hesla → pripojenie emailu)
|
|
- **Session management** - Automatický refresh tokenov
|
|
- **Role-based access** - Admin a Member
|
|
|
|
### Email účty (Multi-account)
|
|
- **Pridanie účtu** - Pripojenie viacerých JMAP účtov
|
|
- **Šifrovanie hesiel** - AES-256-GCM encryption pre JMAP heslá
|
|
- **Primárny účet** - Označenie hlavného účtu
|
|
- **Cascade delete** - Odstránenie účtu vymaže všetky jeho dáta
|
|
|
|
### JMAP Email integrácia
|
|
- **Sync emailov** - Stiahnutie emailov z Truemail.sk JMAP servera
|
|
- **Thread organizácia** - Emaily zoskupené do konverzácií
|
|
- **Full-text search** - Vyhľadávanie v predmete, tele, odosielateľovi
|
|
- **Posielanie odpovedí** - Odpoveď na emaily cez JMAP
|
|
- **Neprecítané správy** - Počítadlo per účet
|
|
|
|
### Kontakty
|
|
- **Objavovanie** - Automatické získanie odosielateľov z emailov
|
|
- **Auto-sync emailov** - Pri pridaní kontaktu sa stiahnu všetky jeho emaily
|
|
- **CRUD operácie** - Pridanie, úprava, odstránenie
|
|
- **Account filtering** - Kontakty izolované per email účet
|
|
|
|
### Správa používateľov (Admin)
|
|
- **Vytvorenie usera** - Auto-generovanie temporary hesla
|
|
- **Zmena rolí** - Admin/Member
|
|
- **Zoznam používateľov** - Len pre adminov
|
|
|
|
### Bezpečnosť
|
|
- **Password hashing** - Bcrypt (12 rounds)
|
|
- **Rate limiting** - Login, API, citlivé operácie
|
|
- **Helmet** - HTTP security headers (CSP, HSTS)
|
|
- **Input validation** - Zod schemas
|
|
- **SQL injection protection** - Drizzle ORM prepared statements
|
|
- **XSS protection** - Sanitizácia inputov
|
|
- **Audit logging** - Všetky dôležité akcie logované
|
|
|
|
## API Endpointy
|
|
|
|
### Autentifikácia `/api/auth`
|
|
- `POST /login` - Prihlásenie
|
|
- `POST /set-password` - Nastavenie hesla (onboarding)
|
|
- `POST /change-password` - Zmena hesla
|
|
- `POST /logout` - Odhlásenie
|
|
- `GET /session` - Získanie session info
|
|
- `GET /me` - Aktuálny používateľ
|
|
|
|
### Email účty `/api/email-accounts`
|
|
- `GET /` - Zoznam účtov
|
|
- `POST /` - Pridanie účtu
|
|
- `DELETE /:id` - Odstránenie účtu
|
|
- `PATCH /:id/primary` - Nastavenie primárneho
|
|
|
|
### Admin `/api/admin` (Admin only)
|
|
- `POST /users` - Vytvorenie používateľa
|
|
- `GET /users` - Zoznam používateľov
|
|
- `PATCH /users/:id/role` - Zmena role
|
|
- `DELETE /users/:id` - Zmazanie používateľa
|
|
|
|
### Kontakty `/api/contacts`
|
|
- `GET /` - Zoznam kontaktov (s accountId filtrom)
|
|
- `GET /discover` - Potenciálne kontakty z JMAP
|
|
- `POST /` - Pridať kontakt + auto-sync emailov
|
|
- `PATCH /:id` - Upraviť kontakt
|
|
- `DELETE /:id` - Odstrániť kontakt
|
|
|
|
### Emaily `/api/emails`
|
|
- `GET /` - Zoznam emailov (s accountId filtrom)
|
|
- `GET /search` - Vyhľadávanie v DB
|
|
- `GET /search-jmap` - JMAP full-text search
|
|
- `GET /thread/:id` - Thread konverzácie
|
|
- `POST /thread/:id/read` - Označiť thread ako prečítaný
|
|
- `POST /sync` - Manuálna synchronizácia
|
|
- `POST /reply` - Odpovedať na email
|
|
- `GET /unread-count` - Počet neprecítaných per účet
|
|
|
|
## Services
|
|
|
|
### auth.service.js
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.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ť
|
|
|
|
**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.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
|
|
|
|
### users
|
|
- id, username, role (admin/member)
|
|
- password, temp_password (bcrypt hash)
|
|
- changed_password, last_login
|
|
- created_at, updated_at
|
|
|
|
### email_accounts
|
|
- id, user_id (FK → users)
|
|
- email, email_password (AES-256 encrypted)
|
|
- jmap_account_id
|
|
- is_primary, is_active
|
|
- created_at, updated_at
|
|
|
|
### contacts
|
|
- id, user_id (FK → users)
|
|
- email_account_id (FK → email_accounts)
|
|
- email, name, notes
|
|
- added_at, created_at, updated_at
|
|
|
|
### emails
|
|
- id, user_id (FK → users)
|
|
- email_account_id (FK → email_accounts)
|
|
- contact_id (FK → contacts)
|
|
- jmap_id, message_id, thread_id
|
|
- from, to, subject, body
|
|
- is_read, date
|
|
- created_at, updated_at
|
|
|
|
### audit_logs
|
|
- id, user_id (FK → users)
|
|
- action, resource, resource_id
|
|
- old_value, new_value (JSON)
|
|
- ip_address, user_agent
|
|
- success, error_message
|
|
- created_at
|
|
|
|
## Konfigurácia
|
|
|
|
### Environment Variables (.env)
|
|
|
|
```env
|
|
# Server Configuration
|
|
PORT=5000 # Port na ktorom beží Express server
|
|
NODE_ENV=development # development | production (ovplyvňuje rate limits, error handling)
|
|
|
|
# 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 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 Configuration
|
|
JMAP_SERVER=https://mail.truemail.sk/jmap/ # JMAP endpoint pre Truemail.sk
|
|
|
|
# Encryption (pre JMAP heslá)
|
|
ENCRYPTION_KEY=your-32-char-encryption-key # PRESNE 32 znakov pre AES-256
|
|
|
|
# CORS
|
|
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
|
|
# 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
|
|
|
|
### 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í
|
|
|
|
**Admin**
|
|
- Všetko čo Member
|
|
- `/api/admin/*` endpointy
|
|
- Vytvorenie/zmazanie používateľov
|
|
- Zmena rolí
|
|
|
|
**Member**
|
|
- Vlastný profil a nastavenia
|
|
- Správa vlastných email účtov
|
|
- Vlastné kontakty a emaily
|
|
- Inbox
|
|
|
|
## JMAP Integrácia
|
|
|
|
### 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
|
|
|
|
**JMAP heslá** - AES-256-GCM
|
|
- Format: `iv:authTag:encryptedText`
|
|
- Kľúč: `ENCRYPTION_KEY` z .env (32 znakov)
|
|
- Funkcie: `encryptPassword()`, `decryptPassword()`
|
|
|
|
**User heslá** - Bcrypt
|
|
- 12 rounds (2^12 iterations)
|
|
- Funkcie: `hashPassword()`, `comparePassword()`
|