From fa7129a5b4994ad0790127e2547c5538d0e2fea2 Mon Sep 17 00:00:00 2001 From: richardtekula Date: Thu, 4 Dec 2025 10:27:34 +0100 Subject: [PATCH] Clean up: Remove documentation files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove DOKUMENTACIA.md - Remove SECURITY_CHECK.md - Clean up README.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- DOKUMENTACIA.md | 2219 --------------------------------------------- README.md | 1133 +++-------------------- SECURITY_CHECK.md | 626 ------------- 3 files changed, 139 insertions(+), 3839 deletions(-) delete mode 100644 DOKUMENTACIA.md delete mode 100644 SECURITY_CHECK.md diff --git a/DOKUMENTACIA.md b/DOKUMENTACIA.md deleted file mode 100644 index f5b0981..0000000 --- a/DOKUMENTACIA.md +++ /dev/null @@ -1,2219 +0,0 @@ -# 📚 CRM SERVER - KOMPLETNÁ DOKUMENTÁCIA - -> Vytvorené: 2025-11-21 -> Verzia: 1.1 -> Backend: Node.js + Express + Drizzle ORM + PostgreSQL -> Frontend: React - ---- - -## 📑 OBSAH - -1. [Architektúra Systému](#architektúra-systému) -2. [Services - Biznis Logika](#services) -3. [Controllers - Request Handling](#controllers) -4. [Routes - API Endpointy](#routes) -5. [Utils - Pomocné Funkcie](#utils) -6. [Kompletný Zoznam API](#kompletný-zoznam-api) -7. [Vzťahy medzi Službami](#vzťahy-medzi-službami) - ---- - -## ARCHITEKTÚRA SYSTÉMU - -### Stack -- **Backend Framework:** Express.js -- **Database:** PostgreSQL -- **ORM:** Drizzle ORM -- **Auth:** JWT (access + refresh tokens) -- **Validation:** Zod schemas -- **Email:** JMAP protocol (Truemail.sk) -- **Encryption:** AES-256-GCM (email passwords) -- **File Upload:** Multer -- **Time Tracking:** Real-time timer s automatickým stop/start - -### Databázové Tabuľky -``` -users → userEmailAccounts ← emailAccounts - ↓ ↓ - | contacts → emails - | - └─→ projectUsers ←─┐ - ↓ | -companies → projects → todos → notes - ↓ - timesheets - -users → timeEntries → projects/todos/companies -``` - -**Vzťahy:** -- `projectUsers`: Many-to-many junction table (users ↔ projects) -- `companies` → `projects`: One-to-many (company môže mať viac projektov) -- `projects` → `todos`, `notes`, `timesheets`: One-to-many -- `users` → `todos.assignedTo`: One-to-many (user má assigned úlohy) -- `users` → `timeEntries`: One-to-many (user má záznamy času) -- `timeEntries` → `projects`, `todos`, `companies`: Optional Many-to-one - ---- - -## SERVICES - -### 1. auth.service.js -**Účel:** Autentifikácia a onboarding používateľov - -**Databáza:** `users`, `userEmailAccounts` - -**Metódy:** -```javascript -loginWithTempPassword(username, password, ipAddress, userAgent) - → Overí credentials - → Vráti JWT tokens - → Kontroluje či treba zmeniť heslo / linknúť email - -setNewPassword(userId, newPassword) - → Hashuje heslo (bcrypt) - → Nastaví changedPassword = true - → Volá: password.js (hashPassword) - -linkEmail(userId, email, emailPassword) - → Volá: email.service.js (validateJmapCredentials) - → Volá: emailAccountService.js (createEmailAccount) - → Pripojí email k userovi - -skipEmailSetup(userId) - → Umožní preskočiť email setup -``` - -**Volá:** -- `emailAccountService.createEmailAccount()` -- `email.service.validateJmapCredentials()` -- `utils/password.hashPassword()` -- `utils/jwt.generateTokenPair()` - ---- - -### 2. company.service.js -**Účel:** CRUD operácie pre firmy - -**Databáza:** `companies`, `projects`, `todos`, `notes` - -**Metódy:** -```javascript -getAllCompanies(searchTerm) - → SELECT * FROM companies - → Filter: ILIKE na name, email, city - → ORDER BY createdAt DESC - -getCompanyById(companyId) - → SELECT * FROM companies WHERE id = ? - → Throw NotFoundError ak neexistuje - -createCompany(userId, data) - → Kontrola duplicity (name) - → INSERT INTO companies - → Nastaví createdBy = userId - -updateCompany(companyId, data) - → Volá getCompanyById (check existencie) - → UPDATE companies SET ... - -deleteCompany(companyId) - → DELETE FROM companies WHERE id = ? - → CASCADE delete: projects, todos, notes - -getCompanyWithRelations(companyId) - → Volá getCompanyById() - → LEFT JOIN projects WHERE companyId - → LEFT JOIN todos WHERE companyId - → LEFT JOIN notes WHERE companyId - → Vráti všetko v jednom objekte -``` - -**Volá:** -- Priamo databázu cez Drizzle ORM -- `utils/errors.NotFoundError` -- `utils/errors.ConflictError` - ---- - -### 3. contact.service.js -**Účel:** Správa email kontaktov (per email account) - -**Databáza:** `contacts`, `emails`, `companies` - -**Metódy:** -```javascript -getContactsForEmailAccount(emailAccountId) - → SELECT * FROM contacts WHERE emailAccountId - -addContact(emailAccountId, jmapConfig, email, name, notes, addedByUserId) - → INSERT INTO contacts - → Volá: jmap.service.syncEmailsFromSender() - → Reassign: UPDATE emails SET contactId WHERE from = email - -getContactById(contactId, emailAccountId) - → Verifikuje prístup k accountu - -linkCompanyToContact(contactId, emailAccountId, companyId) - → UPDATE contacts SET companyId = ? - -createCompanyFromContact(contactId, emailAccountId, userId, companyData) - → Volá: company.service.createCompany() - → UPDATE contacts SET companyId = newCompany.id -``` - -**Volá:** -- `jmap.service.syncEmailsFromSender()` -- `company.service.createCompany()` -- Databázu (contacts, emails, companies) - ---- - -### 4. project.service.js -**Účel:** Projekty a správa tímov - -**Databáza:** `projects`, `companies`, `todos`, `notes`, `timesheets`, `projectUsers`, `users` - -**Metódy:** -```javascript -getAllProjects(searchTerm, companyId) - → SELECT * FROM projects - → Filter: search (name, desc) + companyId - -createProject(userId, data) - → Validate: company exists (ak je companyId) - → INSERT INTO projects - -getProjectWithRelations(projectId) - → SELECT project - → LEFT JOIN company - → LEFT JOIN todos - → LEFT JOIN notes - → LEFT JOIN timesheets - → LEFT JOIN projectUsers + users (tím) - → Vráti všetko naraz - -// Team Management -getProjectUsers(projectId) - → SELECT * FROM projectUsers - → LEFT JOIN users - → WHERE projectId - → Vráti: Array<{ id, userId, role, addedBy, addedAt, user: {...} }> - → DRIZZLE PATTERN: Používa .select() bez params, pristupuje cez row.table_name.field - -assignUserToProject(projectId, userId, addedByUserId, role) - → Validate: project exists (getProjectById) - → Validate: user exists - → Check: už nie je assigned (ConflictError) - → INSERT INTO projectUsers - → Vráti assignment s user details - → UNIQUE constraint: projectId + userId - -removeUserFromProject(projectId, userId) - → Validate: project exists - → Check: user je assigned (NotFoundError) - → DELETE FROM projectUsers WHERE projectId AND userId - -updateUserRoleOnProject(projectId, userId, role) - → Validate: project exists - → Check: user je assigned - → UPDATE projectUsers SET role - → Vráti updated assignment s user details -``` - -**Volá:** -- Databázu (projects, projectUsers, users) -- `utils/errors.NotFoundError` -- `utils/errors.ConflictError` - -**Dôležité - Drizzle ORM Query Pattern:** -```javascript -// ✅ SPRÁVNE - Štandardný .select() pre joins -const rawResults = await db - .select() - .from(projectUsers) - .leftJoin(users, eq(projectUsers.userId, users.id)) - .where(eq(projectUsers.projectId, projectId)) - .orderBy(desc(projectUsers.addedAt)); - -// Potom mapovať výsledky: -const assignedUsers = rawResults.map((row) => ({ - id: row.project_users.id, - userId: row.project_users.userId, - user: row.users ? { - id: row.users.id, - username: row.users.username, - } : null, -})); - -// ❌ NESPRÁVNE - Nested object syntax NEFUNGUJE v Drizzle! -const wrong = await db - .select({ - user: { - id: users.id, - username: users.username, - } - }) - .from(projectUsers) - // → TypeError: Cannot convert undefined or null to object -``` - ---- - -### 5. todo.service.js -**Účel:** Správa úloh (tasks) - -**Databáza:** `todos`, `projects`, `companies`, `users`, `notes` - -**Status Enum:** `['pending', 'in_progress', 'completed', 'cancelled']` - -**Metódy:** -```javascript -getAllTodos(filters) - → Filtre: search, projectId, companyId, assignedTo, status - → SELECT * FROM todos WHERE ... - -createTodo(userId, data) - → Validate: project/company/assignedTo exists - → INSERT INTO todos - → Default status: 'pending' - -updateTodo(todoId, data) - → Ak status = 'completed' → nastaví completedAt = NOW() - → Ak status != 'completed' → zmaže completedAt - → UPDATE todos - -toggleTodoComplete(todoId) - → Toggle: 'completed' ↔ 'pending' - → Používa sa vo frontende pre checkbox toggle - -getTodoWithRelations(todoId) - → LEFT JOIN project - → LEFT JOIN company - → LEFT JOIN assignedUser (users) - → LEFT JOIN notes -``` - -**Volá:** -- Databázu (todos, projects, companies, users) -- `utils/errors.NotFoundError` - -**⚠️ DÔLEŽITÉ - Status Field:** -- Databázové pole: `status` (enum) -- **NIE** `completed` (boolean)! -- Frontend check: `todo.status === 'completed'` ✅ -- Frontend check: `todo.completed` ❌ (toto pole neexistuje!) - ---- - -### 6. note.service.js -**Účel:** Poznámky s remindermi - -**Databáza:** `notes`, `companies`, `projects`, `todos`, `contacts` - -**Metódy:** -```javascript -// Frontend Mapping Helper -mapNoteForFrontend(note) - → Konvertuje: reminderDate → reminderAt - → Frontend používa "reminderAt", DB má "reminderDate" - -getAllNotes(filters) - → Filter: search, companyId, projectId, todoId, contactId - → Volá mapNoteForFrontend() na výsledky - -createNote(userId, data) - → Validate: company/project/todo/contact exists - → INSERT INTO notes - → reminderSent = false - → Volá mapNoteForFrontend() - -updateNote(noteId, data) - → Ak sa zmení reminderDate → reset reminderSent = false - → Volá mapNoteForFrontend() - -// Reminder Management -getPendingReminders() - → SELECT * WHERE reminderDate <= NOW() AND reminderSent = false - -markReminderAsSent(noteId) - → UPDATE notes SET reminderSent = true - -getUpcomingRemindersForUser(userId) - → WHERE createdBy = userId AND pending reminders -``` - -**Volá:** -- Databázu (notes, companies, projects, todos, contacts) -- `mapNoteForFrontend()` - internal helper - ---- - -### 7. email-account.service.js -**Účel:** Správa email účtov (many-to-many sharing) - -**Databáza:** `emailAccounts`, `userEmailAccounts`, `users` - -**Metódy:** -```javascript -getUserEmailAccounts(userId) - → SELECT emailAccounts - → JOIN userEmailAccounts - → WHERE userId - -createEmailAccount(userId, email, emailPassword) - → Volá: email.service.validateJmapCredentials() - → Encrypt password: password.encryptPassword() - → Kontrola: email už existuje? - - Áno → link existujúci account (shared = true) - - Nie → vytvor nový account - → INSERT INTO userEmailAccounts - → Ak prvý account → set isPrimary = true - → Volá: contact.service.syncContactsForAccount() - -getEmailAccountWithCredentials(accountId, userId) - → SELECT emailAccount - → Decrypt password: password.decryptPassword() - → Vráti s plaintext password (pre JMAP) - -setPrimaryEmailAccount(accountId, userId) - → UPDATE userEmailAccounts SET isPrimary = false (všetky) - → UPDATE userEmailAccounts SET isPrimary = true (tento) - -removeUserFromEmailAccount(accountId, userId) - → DELETE FROM userEmailAccounts - → Ak posledný user → CASCADE delete emailAccount + data - -shareEmailAccountWithUser(accountId, ownerId, targetUserId) - → Kontrola: owner má prístup - → INSERT INTO userEmailAccounts (link target user) -``` - -**Volá:** -- `email.service.validateJmapCredentials()` -- `password.encryptPassword()` -- `password.decryptPassword()` -- `contact.service.syncContactsForAccount()` -- Databázu (emailAccounts, userEmailAccounts) - ---- - -### 8. crm-email.service.js -**Účel:** Správa emailov (read status, search) - -**Databáza:** `emails`, `contacts` - -**Metódy:** -```javascript -getEmailsForAccount(emailAccountId) - → SELECT emails - → LEFT JOIN contacts - → WHERE emailAccountId - → Iba emaily od pridaných kontaktov - -getEmailThread(emailAccountId, threadId) - → SELECT emails WHERE threadId - → ORDER BY date ASC (konverzácia) - -searchEmails(emailAccountId, query) - → DB search: ILIKE na subject, body, from - → LIMIT 50 - -getUnreadCountForAccount(emailAccountId) - → SELECT COUNT WHERE isRead = false - → Iba od added contacts - -markThreadAsRead(emailAccountId, threadId) - → UPDATE emails SET isRead = true WHERE threadId - -markContactEmailsAsRead(contactId, emailAccountId) - → Volá: getContactByEmail() - → UPDATE emails SET isRead = true WHERE contactId -``` - -**Volá:** -- Databázu (emails, contacts) -- `getContactByEmail()` - internal - ---- - -### 9. jmap.service.js -**Účel:** JMAP protokol integrácia - -**JMAP Server:** `https://mail.truemail.sk/jmap/` - -**Metódy:** -```javascript -jmapRequest(jmapConfig, methodCalls) - → HTTP POST na JMAP server - → Authorization: Basic + password - → Vráti raw JMAP response - -getMailboxes(jmapConfig) - → JMAP call: "Mailbox/get" - → Vráti Inbox, Sent, Trash, atď. - -syncEmailsFromSender(jmapConfig, emailAccountId, contactId, email) - → JMAP query: "Email/query" WHERE from = email - → Fetch email details: "Email/get" - → INSERT INTO emails (bulk) - → Volá: markEmailAsRead() na serveri - -searchEmailsJMAP(jmapConfig, emailAccountId, query) - → JMAP full-text search - → Neukláda do DB, iba vráti results - -markEmailAsRead(jmapConfig, jmapId, isRead) - → JMAP: "Email/set" update \Seen flag - → Sync: UPDATE local DB - -sendEmail(jmapConfig, to, subject, body, inReplyTo, threadId) - → JMAP: "EmailSubmission/set" - → Pošle email cez JMAP server - → INSERT do local DB (sentByUserId) - -discoverContactsFromJMAP(jmapConfig, emailAccountId, search, limit) - → JMAP: "Email/query" group by sender - → Vráti unique senderi (potenciálne kontakty) -``` - -**Volá:** -- JMAP server API (HTTP POST) -- Databázu (emails) pre sync - ---- - -### 10. email.service.js -**Účel:** Validácia JMAP credentials - -**Metódy:** -```javascript -validateJmapCredentials(email, password) - → HTTP GET na JMAP server /.well-known/jmap - → Authorization: Basic email:password - → Ak 200 → credentials OK - → Ak 401 → invalid credentials - → Vráti accountId a session data -``` - -**Volá:** -- JMAP server (validácia) - ---- - -### 11. time-tracking.service.js -**Účel:** Sledovanie odpracovaného času - -**Databáza:** `timeEntries`, `projects`, `todos`, `companies`, `users` - -**Metódy:** -```javascript -startTimeEntry(userId, data) - → Validate: project/todo/company exists (ak sú poskytnuté) - → Check: existujúci bežiaci časovač - → Ak beží → automaticky ho zastaví - → INSERT INTO timeEntries - → isRunning = true, startTime = NOW() - -stopTimeEntry(entryId, userId, data) - → Validate: ownership, isRunning = true - → Počíta duration v minútach - → UPDATE timeEntries SET endTime, duration, isRunning = false - → Optional: update projectId, todoId, companyId, description - -getRunningTimeEntry(userId) - → SELECT * WHERE userId AND isRunning = true - → Vráti aktuálny bežiaci časovač (alebo null) - -getAllTimeEntries(userId, filters) - → Filter: projectId, todoId, companyId, startDate, endDate - → SELECT * FROM timeEntries WHERE userId - → ORDER BY startTime DESC - -getMonthlyTimeEntries(userId, year, month) - → SELECT * WHERE userId AND startTime BETWEEN month range - → Používa sa pre mesačný prehľad - -getTimeEntryById(entryId) - → SELECT * WHERE id - → Throw NotFoundError ak neexistuje - -getTimeEntryWithRelations(entryId) - → LEFT JOIN project, todo, company - → Vráti všetko v jednom objekte - -updateTimeEntry(entryId, userId, data) - → Validate: ownership, NOT isRunning (nemožno upraviť bežiaci) - → Update: startTime, endTime, projectId, todoId, companyId, description - → Prepočíta duration ak sa zmení čas - → Set isEdited = true - -deleteTimeEntry(entryId, userId) - → Validate: ownership, NOT isRunning - → DELETE FROM timeEntries - -getMonthlyStats(userId, year, month) - → Volá getMonthlyTimeEntries() - → Počíta štatistiky: - - totalMinutes / totalHours - - daysWorked (unique days) - - averagePerDay - - byProject (čas per projekt) - - byCompany (čas per firma) - -generateMonthlyTimesheet(userId, year, month) - → Generate Excel (XLSX) report z time entries - → Fetch user info: username, firstName, lastName - → Fetch completed entries for month (LEFT JOIN projects, todos, companies) - → Filter: iba entries s endTime a duration - → Počíta: totalMinutes, dailyTotals (per day) - → Vytvára Excel workbook cez ExcelJS: - - Header: Timesheet, Name, Period, Generated date - - Table: Date, Project, Todo, Company, Description, Start, End, Duration - - Summary: Daily totals + Overall total - → Save to: uploads/timesheets/{userId}/{year}/{month}/timesheet-{period}-{timestamp}.xlsx - → INSERT INTO timesheets (isGenerated = true) - → Vráti: { timesheet, filePath, entriesCount, totalMinutes, totalHours } -``` - -**Volá:** -- Databázu (timeEntries, projects, todos, companies, users, timesheets) -- ExcelJS library (workbook generation) -- File system (fs/promises) - save XLSX file -- `utils/errors.NotFoundError` -- `utils/errors.BadRequestError` - -**Dôležité poznámky:** -- **Auto-stop:** Pri štarte nového časovača sa automaticky zastaví predchádzajúci -- **Duration:** Ukladá sa v minútach (Math.round) -- **isEdited flag:** Označuje manuálne upravené záznamy -- **isRunning:** Iba jeden časovač môže byť aktívny pre usera - ---- - -### 12. audit.service.js -**Účel:** Audit logging pre compliance - -**Databáza:** `auditLogs` - -**Metódy:** -```javascript -logAuditEvent(userId, action, resourceType, resourceId, details, ip, userAgent) - → INSERT INTO auditLogs - → Ukladá všetky parametre - -// Špecifické loggery -logLoginAttempt(username, success, ip, userAgent, error) - → action = "login_attempt" - -logPasswordChange(userId, ip, userAgent) - → action = "password_change" - -logEmailLink(userId, email, ip, userAgent) - → action = "email_linked" - -logRoleChange(adminId, userId, oldRole, newRole, ip, userAgent) - → action = "role_change" - -logUserCreation(adminId, newUserId, username, role, ip, userAgent) - → action = "user_created" -``` - -**Volá:** -- Iba databázu (write-only) - ---- - -### 13. timesheet.controller.js -**Účel:** HTTP vrstva pre timesheet upload/list/download/delete (PDF/Excel) - -**Deleguje na:** `services/timesheet.service.js` - -**Toky handlerov:** -```javascript -uploadTimesheet(req, res) - → timesheetService.uploadTimesheet({ userId, year, month, file }) - → Vráti sanitized meta (bez filePath) - -getMyTimesheets(req, res) - → timesheetService.getTimesheetsForUser(userId, { year?, month? }) - -getAllTimesheets(req, res) - → timesheetService.getAllTimesheets({ userId?, year?, month? }) - -downloadTimesheet(req, res) - → timesheetService.getDownloadInfo(timesheetId, { userId, role }) - → res.download(filePath, fileName) - -deleteTimesheet(req, res) - → timesheetService.deleteTimesheet(timesheetId, { userId, role }) -``` - -**Poznámky:** -- Service vrstva rieši validáciu MIME typu (PDF/XLSX), tvorbu adresárovej štruktúry `uploads/timesheets/{userId}/{year}/{month}`, permission check (owner/admin) a bezpečné mazanie súboru. -- Response payloady obsahujú len meta údaje: `id, fileName, fileType, fileSize, year, month, isGenerated, uploadedAt`. - ---- - -## VALIDATORS - -### 1. auth.validators.js -**Účel:** Zod schemas pre autentifikáciu a user management - -**Schemas:** -```javascript -loginSchema - → username: 3-50 chars, required - → password: min 1 char, required - -setPasswordSchema - → newPassword: min 8 chars, obsahuje a-z, A-Z, 0-9, špeciálny znak - → confirmPassword: musí sa zhodovať - → .refine() custom validation pre password match - -linkEmailSchema (momentálne neexponované; route je vypnutá) - → email: valid email format, max 255 chars - → emailPassword: min 1 char - -createUserSchema (admin only) - → username: 3-50 chars, iba [a-zA-Z0-9_-] - → email: optional (ak sa zadá, môže sa linknúť JMAP) - → emailPassword: optional (pre automatické linkovanie) - → firstName, lastName: optional, max 100 chars - -updateUserSchema - → firstName, lastName, email: all optional - -changeRoleSchema - → userId: UUID - → role: enum ['admin', 'member'] -``` - -**Použitie:** -- Aktívne: `/api/auth/login`, `/api/auth/set-password`, `/api/auth/logout`, `/api/auth/session` -- Neaktivované: `/api/auth/link-email`, `/api/auth/skip-email` (ponechané schema pre prípadné obnovenie) -- Admin user management routes - ---- - -### 2. crm.validators.js -**Účel:** Zod schemas pre Company, Project, Todo, Note, Time Tracking - -**Company Schemas:** -```javascript -createCompanySchema - → name: required, max 255 chars - → description: optional, max 1000 chars - → address, city, country: optional - → phone: optional, max 50 chars - → email: optional, valid email OR empty string - → website: optional, valid URL OR empty string - -updateCompanySchema - → Všetky fields optional -``` - -**Project Schemas:** -```javascript -createProjectSchema - → name: required, max 255 chars - → companyId: optional UUID OR empty string - → status: enum ['active', 'completed', 'on_hold', 'cancelled'] - → startDate, endDate: optional strings OR empty - -updateProjectSchema - → Všetky fields optional - → NULL support: .or(z.null()) -``` - -**Todo Schemas:** -```javascript -createTodoSchema - → title: required, max 255 chars - → projectId, companyId, assignedTo: optional UUID OR empty - → status: enum ['pending', 'in_progress', 'completed', 'cancelled'] - → priority: enum ['low', 'medium', 'high', 'urgent'] - → dueDate: optional string OR empty - -updateTodoSchema - → Všetky fields optional + NULL support -``` - -**Note Schemas:** -```javascript -createNoteSchema - → content: required, max 5000 chars - → title: optional, max 255 chars - → companyId, projectId, todoId, contactId: optional UUID OR empty - → reminderDate: optional string OR empty - -updateNoteSchema - → Všetky fields optional + NULL support -``` - -**Time Tracking Schemas:** -```javascript -startTimeEntrySchema - → projectId, todoId, companyId: optional UUID (preprocessed to null if empty) - → description: optional, max 1000 chars, trimmed, null if empty - -stopTimeEntrySchema - → Same as start (používa sa pre update pri stop) - -updateTimeEntrySchema - → startTime, endTime: optional ISO strings - → projectId, todoId, companyId, description: optional with preprocessing -``` - -**Helper Functions:** -```javascript -optionalUuid(message) - → Preprocess: undefined, null, '' → null - → Validate: UUID format - → Used for optional foreign keys - -optionalDescription - → Preprocess: trim whitespace, '' → null - → Validate: max 1000 chars - → Nullable -``` - -**Pattern - Empty String Handling:** -```javascript -// Frontend môže poslať empty string namiesto null -.or(z.literal('')) // Accept empty string -.or(z.literal('').or(z.null())) // Update: accept empty OR null -``` - ---- - -### 3. email-account.validators.js -**Účel:** Zod schemas pre email account management - -**Schemas:** -```javascript -createEmailAccountSchema - → email: required, valid format, max 255 chars - → emailPassword: required, min 1 char - -updateEmailAccountSchema - → emailPassword: optional, min 1 char - → isActive: optional boolean - -setPrimaryAccountSchema - → accountId: UUID - → POZNÁMKA: NEVYUŽÍVA SA (endpoint má accountId v path params) -``` - -**Použitie:** -- `/api/email-accounts/*` routes -- JMAP credential validation flow - ---- - -## CONTROLLERS - -**Účel:** Spracovanie HTTP requestov, volanie services, vracanie responses - -### Zoznam Controllerov: -1. **admin.controller.js** - User management (admin only) -2. **auth.controller.js** - Autentifikácia a onboarding -3. **company.controller.js** - Firmy CRUD + nested notes -4. **contact.controller.js** - Email kontakty -5. **crm-email.controller.js** - Email management (read status, search) -6. **email-account.controller.js** - JMAP účty -7. **note.controller.js** - Standalone poznámky (nevyužité) -8. **project.controller.js** - Projekty CRUD + nested notes + team management -9. **todo.controller.js** - Úlohy CRUD -10. **time-tracking.controller.js** - Sledovanie času -11. **timesheet.controller.js** - Upload a download timesheetov (bez service!) - -### Štruktúra každého controllera: -```javascript -export const methodName = async (req, res) => { - try { - // 1. Extract params/body - const { param } = req.params - const data = req.body - const userId = req.userId // z authenticate middleware - - // 2. Volaj service - const result = await service.method(param, data) - - // 3. Vráť response - res.status(200).json({ - success: true, - data: result, - message: 'Success message' - }) - } catch (error) { - // 4. Error handling - const errorResponse = formatErrorResponse(error, isDev) - res.status(error.statusCode || 500).json(errorResponse) - } -} -``` - -### Response Format (štandard): -```json -{ - "success": true/false, - "data": { ... }, - "count": 10, // optional (pre lists) - "message": "Success/Error message" -} -``` - ---- - -## ROUTES - -**Účel:** Definícia endpointov, middleware, validácia - -### Zoznam Route Files: -1. **admin.routes.js** - User management (Auth + Admin role) -2. **auth.routes.js** - Login, set password (Mixed public/protected) -3. **company.routes.js** - Firmy + nested notes (Auth only) -4. **contact.routes.js** - Kontakty (Auth only) -5. **crm-email.routes.js** - Emaily (Auth only) -6. **email-account.routes.js** - JMAP účty (Auth only) -7. **project.routes.js** - Projekty + notes + team (Auth only) -8. **todo.routes.js** - Úlohy (Auth only) -9. **time-tracking.routes.js** - Time tracking (Auth only) -10. **timesheet.routes.js** - Timesheets upload/download (Auth, admin for /all) -11. **note.routes.js** - Standalone poznámky (odpojené z app.js, ponechané len ako archív) - -### Štruktúra route file: -```javascript -import express from 'express' -import * as controller from '../controllers/xxx.controller.js' -import { authenticate } from '../middlewares/auth/authMiddleware.js' -import { validateBody, validateParams } from '../middlewares/security/validateInput.js' -import { schema } from '../validators/xxx.validators.js' - -const router = express.Router() - -// Global middleware (pre všetky routes) -router.use(authenticate) - -// Route definition -router.get('/', - controller.getAll -) - -router.post('/', - validateBody(schema), // Zod validation - controller.create -) - -router.patch('/:id', - validateParams(z.object({ id: z.string().uuid() })), - validateBody(updateSchema), - controller.update -) - -export default router -``` - -### Middleware poradie: -1. `authenticate` - JWT validácia -2. `validateParams` - Path params validácia (Zod) -3. `validateBody` - Request body validácia (Zod) -4. `controller.method` - Controller handler - ---- - -## UTILS - -### Zoznam Utility Files: -1. **errors.js** - Custom error classes + formatting -2. **jwt.js** - JWT token generation and validation -3. **logger.js** - Colored console logging -4. **password.js** - Password hashing, encryption, generation - -### 1. errors.js -**Účel:** Custom error classy a formatting - -**Error Classes:** -```javascript -class AppError extends Error { - constructor(message, statusCode) { - this.statusCode = statusCode - this.isOperational = true - } -} - -class ValidationError extends AppError { statusCode = 400 } -class BadRequestError extends AppError { statusCode = 400 } -class AuthenticationError extends AppError { statusCode = 401 } -class ForbiddenError extends AppError { statusCode = 403 } -class NotFoundError extends AppError { statusCode = 404 } -class ConflictError extends AppError { statusCode = 409 } -class RateLimitError extends AppError { statusCode = 429 } -``` - -**Funkcie:** -```javascript -formatErrorResponse(error, includeStack) - → Formátuje error pre API response - → V dev mode: vracia stack trace - → V prod mode: iba message -``` - -**Použitie:** -```javascript -throw new NotFoundError('Company not found') -throw new ConflictError('Email already exists') -``` - ---- - -### 2. jwt.js -**Účel:** JWT token management - -**Funkcie:** -```javascript -generateAccessToken(payload) - → jwt.sign(payload, JWT_SECRET, { expiresIn: '1h' }) - -generateRefreshToken(payload) - → jwt.sign(payload, JWT_REFRESH_SECRET, { expiresIn: '7d' }) - -verifyAccessToken(token) - → jwt.verify(token, JWT_SECRET) - → Vráti decoded payload alebo throw error - -generateTokenPair(user) - → Vytvorí obe tokens naraz - → Payload: { userId, username, role } -``` - -**Environment Variables:** -```bash -JWT_SECRET=xxx -JWT_REFRESH_SECRET=xxx -JWT_EXPIRES_IN=1h -JWT_REFRESH_EXPIRES_IN=7d -``` - ---- - -### 3. logger.js -**Účel:** Farebné console logging - -**Metódy:** -```javascript -logger.info(message, ...args) // 🔵 Blue -logger.success(message, ...args) // 🟢 Green -logger.warn(message, ...args) // 🟡 Yellow -logger.error(message, error) // 🔴 Red -logger.debug(message, ...args) // 🔷 Cyan (dev only) -logger.audit(message, ...args) // 🟣 Magenta -``` - -**Použitie:** -```javascript -logger.info('Server started', { port: 5000 }) -logger.success('User created', { userId }) -logger.error('Database error', error) -``` - ---- - -### 4. password.js -**Účel:** Password hashing, encryption, generation - -**Funkcie:** -```javascript -// Bcrypt (pre user passwords) -hashPassword(password) - → bcrypt.hash(password, BCRYPT_ROUNDS) - -comparePassword(password, hash) - → bcrypt.compare(password, hash) - -// AES-256-GCM (pre email passwords) -encryptPassword(text) - → crypto.randomBytes(16) // IV - → crypto.scrypt(JWT_SECRET) // Key derivation - → cipher = crypto.createCipheriv('aes-256-gcm') - → Vráti: "iv:authTag:encrypted" (base64) - -decryptPassword(encryptedText) - → Split "iv:authTag:encrypted" - → decipher = crypto.createDecipheriv('aes-256-gcm') - → Vráti plaintext - -// Temp password generation -generateTempPassword(length = 12) - → Random: uppercase + lowercase + numbers + special chars - → Použitie: admin vytvára usera -``` - -**Prečo 2 typy encryption?** -- **Bcrypt (user passwords):** One-way hash, nemožné dekryptovať -- **AES-256 (email passwords):** Reversible, treba plaintext pre JMAP login - ---- - -## KOMPLETNÝ ZOZNAM API - -### 🔐 AUTHENTICATION - -#### POST /api/auth/login -``` -Účel: Prihlásenie používateľa -Body: { username, password } -Response: { user, tokens, needsPasswordChange, needsEmailSetup } -Rate Limit: Áno -``` - -#### POST /api/auth/set-password -``` -Účel: Zmena hesla pri prvom prihlásení -Body: { newPassword } -Auth: Áno -Rate Limit: Áno -``` - -#### POST /api/auth/logout -``` -Účel: Odhlásenie (clear cookies) -Auth: Áno -Response: Clear accessToken & refreshToken cookies -``` - -#### GET /api/auth/session -``` -Účel: Získať info o aktuálnej session -Auth: Áno -Response: { user, authenticated: true } -``` - -**Removed/disabled:** `/api/auth/link-email`, `/api/auth/skip-email`, `/api/auth/me` (nepoužíva ich FE). - ---- - -### 👥 ADMIN (User Management) - -#### POST /api/admin/users -``` -Účel: Vytvoriť nového usera -Body: { username, email?, emailPassword?, firstName?, lastName? } -Auth: Áno (admin only) -Response: { user with tempPassword } -Efekt: Vygeneruje temp password, user musí zmeniť pri login -``` - -#### GET /api/admin/users -``` -Účel: Zoznam všetkých userov -Auth: Áno (admin only) -Response: Array of users -``` - -#### GET /api/admin/users/:userId -``` -Účel: Detail usera -Auth: Áno (admin only) -Response: { user with emailAccounts } -``` - -#### PATCH /api/admin/users/:userId/role -``` -Účel: Zmeniť rolu usera -Body: { role: "admin" | "member" } -Auth: Áno (admin only) -``` - -#### DELETE /api/admin/users/:userId -``` -Účel: Zmazať usera -Auth: Áno (admin only) -Efekt: CASCADE delete všetkých dát usera -``` - ---- - -### 🏢 COMPANIES - -#### GET /api/companies?search=query -``` -Účel: Zoznam firiem -Query: search (optional) - hľadá v name, email, city -Auth: Áno -Response: Array of companies -``` - -#### GET /api/companies/:companyId -``` -Účel: Detail firmy -Auth: Áno -Response: Company object -``` - -> Poznámka: Endpoint `/api/companies/:companyId/details` bol odstránený (frontend používa samostatné volania). - -#### POST /api/companies -``` -Účel: Vytvoriť firmu -Body: { name*, description, address, city, country, phone, email, website } -Auth: Áno -Validation: createCompanySchema -``` - -#### PATCH /api/companies/:companyId -``` -Účel: Upraviť firmu -Body: (all optional) { name, description, ... } -Auth: Áno -Validation: updateCompanySchema -``` - -#### DELETE /api/companies/:companyId -``` -Účel: Zmazať firmu -Auth: Áno -Efekt: CASCADE delete projects, todos, notes -``` - -#### GET /api/companies/:companyId/notes -``` -Účel: Poznámky firmy -Auth: Áno -Response: Array of notes (mapped reminderDate→reminderAt) -``` - -#### POST /api/companies/:companyId/notes -``` -Účel: Pridať poznámku k firme -Body: { content*, reminderAt? } -Auth: Áno -``` - -#### PATCH /api/companies/:companyId/notes/:noteId -``` -Účel: Upraviť poznámku firmy -Body: { content?, reminderAt? } -Auth: Áno -``` - -#### DELETE /api/companies/:companyId/notes/:noteId -``` -Účel: Zmazať poznámku firmy -Auth: Áno -``` - ---- - -### 📁 PROJECTS - -#### GET /api/projects?search=query&companyId=uuid -``` -Účel: Zoznam projektov -Query: search, companyId (both optional) -Auth: Áno -``` - -#### GET /api/projects/:projectId -``` -Účel: Detail projektu -Auth: Áno -``` - -> Poznámka: Endpoint `/api/projects/:projectId/details` bol odstránený (nepoužíva ho FE). - -#### POST /api/projects -``` -Účel: Vytvoriť projekt -Body: { name*, description, companyId, status, startDate, endDate } -Auth: Áno -Validation: createProjectSchema -``` - -#### PATCH /api/projects/:projectId -``` -Účel: Upraviť projekt -Body: (all optional) -Auth: Áno -Validation: updateProjectSchema -``` - -#### DELETE /api/projects/:projectId -``` -Účel: Zmazať projekt -Auth: Áno -Efekt: CASCADE delete todos, notes, projectUsers -``` - -#### GET /api/projects/:projectId/notes -``` -Účel: Poznámky projektu -Auth: Áno -``` - -#### POST /api/projects/:projectId/notes -``` -Účel: Pridať poznámku k projektu -Body: { content*, reminderAt? } -Auth: Áno -``` - -#### PATCH /api/projects/:projectId/notes/:noteId -``` -Účel: Upraviť poznámku projektu -Auth: Áno -``` - -#### DELETE /api/projects/:projectId/notes/:noteId -``` -Účel: Zmazať poznámku projektu -Auth: Áno -``` - -#### GET /api/projects/:projectId/users -``` -Účel: Členovia tímu projektu (many-to-many relationship) -Auth: Áno -Response: Array of { - id, - userId, - role, - addedBy, - addedAt, - user: { id, username, email, role } -} -Volá: project.service.getProjectUsers() -``` - -#### POST /api/projects/:projectId/users -``` -Účel: Priradiť usera k projektu -Body: { userId*, role? } -Auth: Áno -Validation: - - userId musí byť UUID - - role je optional (text) - - User nesmie byť už assigned (UNIQUE constraint) -Response: Assignment with user details -Volá: project.service.assignUserToProject() -Errors: - - 404: Project alebo User nenájdený - - 409: User už je assigned k projektu -``` - -#### PATCH /api/projects/:projectId/users/:userId -``` -Účel: Zmeniť rolu usera na projekte -Params: projectId (UUID), userId (UUID) -Body: { role? } -Auth: Áno -Poznámka: Momentálne sa NEVYUŽÍVA vo frontende (role field removed) -Volá: project.service.updateUserRoleOnProject() -``` - -#### DELETE /api/projects/:projectId/users/:userId -``` -Účel: Odstrániť usera z projektu -Params: projectId (UUID), userId (UUID) -Auth: Áno -Response: { success: true, message: 'Používateľ bol odstránený z projektu' } -Volá: project.service.removeUserFromProject() -Errors: - - 404: User nie je assigned k projektu -``` - ---- - -### ✅ TODOS - -#### GET /api/todos?search=&projectId=&companyId=&assignedTo=&status= -``` -Účel: Zoznam úloh -Query: všetky parametre optional -Auth: Áno -``` - -#### GET /api/todos/:todoId -``` -Účel: Detail todo -Auth: Áno -``` - -> Poznámka: Endpoints `/api/todos/my` a `/api/todos/:todoId/details` boli odstránené (nepoužíva ich FE). - -#### POST /api/todos -``` -Účel: Vytvoriť todo -Body: { title*, description, projectId, companyId, assignedTo, status, priority, dueDate } -Auth: Áno -Validation: createTodoSchema -``` - -#### PATCH /api/todos/:todoId -``` -Účel: Upraviť todo -Body: (all optional) -Auth: Áno -Efekt: Ak status=completed → nastaví completedAt -``` - -#### DELETE /api/todos/:todoId -``` -Účel: Zmazať todo -Auth: Áno -``` - -#### PATCH /api/todos/:todoId/toggle -``` -Účel: Toggle completed status -Auth: Áno -Efekt: status 'completed' ↔ 'pending' -Poznámka: Používa sa vo frontende pre checkbox toggle -Response: Updated todo -``` - ---- - -### 📝 NOTES (Standalone) - -**POZNÁMKA:** Standalone note routes sú odpojené z app.js a frontend ich nepoužíva. -Poznámky sa riešia iba cez nested routes (companies/:id/notes, projects/:id/notes). - -#### GET /api/notes?search=&companyId=&projectId=&todoId=&contactId= -``` -Účel: Zoznam všetkých poznámok -Poznámka: NEVYUŽÍVA SA -``` - -#### GET /api/notes/my-reminders -``` -Účel: Moje pending reminders -Poznámka: NEVYUŽÍVA SA (mohlo by byť užitočné!) -``` - -#### GET /api/notes/:noteId -``` -Účel: Detail poznámky -Poznámka: NEVYUŽÍVA SA -``` - -#### POST /api/notes -``` -Účel: Vytvoriť standalone poznámku -Poznámka: NEVYUŽÍVA SA -``` - -#### PATCH /api/notes/:noteId -``` -Účel: Upraviť poznámku -Poznámka: NEVYUŽÍVA SA -``` - -#### DELETE /api/notes/:noteId -``` -Účel: Zmazať poznámku -Poznámka: NEVYUŽÍVA SA -``` - -#### POST /api/notes/:noteId/mark-reminder-sent -``` -Účel: Označiť reminder ako odoslaný -Poznámka: NEVYUŽÍVA SA -``` - ---- - -### 👤 CONTACTS - -#### GET /api/contacts?accountId=uuid -``` -Účel: Zoznam kontaktov pre email account -Query: accountId (required) -Auth: Áno -``` - -#### GET /api/contacts/discover?accountId=&search=&limit= -``` -Účel: Objaviť potenciálne kontakty z emailov -Query: accountId (optional, uses primary), search, limit -Auth: Áno -Volá: jmap.service.discoverContactsFromJMAP() -``` - -#### POST /api/contacts -``` -Účel: Pridať kontakt -Body: { email*, name, notes, accountId } -Auth: Áno -Efekt: Sync emails from sender, reassign existing emails -Volá: jmap.service.syncEmailsFromSender() -``` - -#### PATCH /api/contacts/:contactId?accountId=uuid -``` -Účel: Upraviť kontakt -Body: { name, notes } -Query: accountId (required) -Auth: Áno -``` - -#### DELETE /api/contacts/:contactId?accountId=uuid -``` -Účel: Zmazať kontakt -Query: accountId (required) -Auth: Áno -Efekt: CASCADE delete emails -``` - -> Poznámka: Link/unlink company a create-company routes boli odstránené (FE ich nevolá). - ---- - -### 📧 EMAIL ACCOUNTS - -#### GET /api/email-accounts -``` -Účel: Zoznam mojich email účtov -Auth: Áno -Response: Array of accounts (bez passwords!) -``` - -> Poznámka: Endpoints `/api/email-accounts/:id`, `/:id/password`, `/:id/status` boli odstránené (nepoužíva ich FE). - -#### POST /api/email-accounts -``` -Účel: Pripojiť email account -Body: { email*, emailPassword* } -Auth: Áno -Rate Limit: Áno -Efekt: - - Validate credentials (JMAP) - - Encrypt password (AES-256-GCM) - - Ak existuje → share (shared=true) - - Ak prvý → set primary - - Sync contacts -Volá: email.service, password.encryptPassword() -``` - -#### POST /api/email-accounts/:id/set-primary -``` -Účel: Nastaviť ako primárny email -Auth: Áno -Efekt: Ostatné accounts → isPrimary = false -``` - -#### DELETE /api/email-accounts/:id -``` -Účel: Odstrániť prístup k emailu -Auth: Áno -Rate Limit: Áno -Efekt: - - Ak posledný user → delete account + data - - Ak shared → iba unlink -``` - ---- - -### ✉️ EMAILS (CRM Email Management) - -#### GET /api/emails?accountId=uuid -``` -Účel: Zoznam emailov -Query: accountId (required) -Auth: Áno -Response: Iba emaily od pridaných kontaktov! -``` - -#### GET /api/emails/thread/:threadId?accountId=uuid -``` -Účel: Email thread (konverzácia) -Auth: Áno -Response: Všetky emaily v threade, sorted by date -``` - -#### GET /api/emails/search?accountId=&query= -``` -Účel: Search v uložených emailoch (DB) -Query: accountId, query (min 2 chars) -Auth: Áno -Limit: 50 results -``` - -#### GET /api/emails/search-jmap?accountId=&query= -``` -Účel: Full-text search cez JMAP server -Query: accountId, query -Auth: Áno -Volá: jmap.service.searchEmailsJMAP() -Poznámka: Hľadá vo VŠETKÝCH emailoch, nie len v DB -``` - -#### GET /api/emails/unread-count?accountId=uuid -``` -Účel: Počet neprečítaných emailov -Query: accountId (required) -Auth: Áno -Response: { totalUnread, byAccount: [...] } -``` - -#### POST /api/emails/sync?accountId=uuid -``` -Účel: Synchronizovať najnovšie emaily z JMAP -Query: accountId (required) -Auth: Áno -Efekt: Fetch latest emails, store to DB -Volá: jmap.service.syncEmails() -``` - -#### POST /api/emails/thread/:threadId/read?accountId=uuid -``` -Účel: Označiť thread ako prečítaný -Auth: Áno -Efekt: UPDATE emails SET isRead = true + sync JMAP -``` - -#### POST /api/emails/contact/:contactId/read?accountId=uuid -``` -Účel: Označiť všetky emaily kontaktu ako prečítané -Auth: Áno -``` - -> Poznámka: Endpoints `/api/emails/contact/:contactId` a `/api/emails/:jmapId/read` boli odstránené (FE ich nevolá). - -#### POST /api/emails/reply -``` -Účel: Odpovedať na email / poslať nový -Body: { to*, subject*, body*, inReplyTo?, threadId? } -Auth: Áno -Efekt: Send via JMAP, store to DB -Volá: jmap.service.sendEmail() -``` - ---- - -### ⏱️ TIME TRACKING - -#### POST /api/time-tracking/start -``` -Účel: Spustiť nový časovač -Body: { projectId?, todoId?, companyId?, description? } -Auth: Áno -Response: { entry with isRunning: true } -Efekt: - - Automaticky zastaví predchádzajúci bežiaci časovač (ak existuje) - - Vytvorí nový time entry s startTime = NOW() -``` - -#### POST /api/time-tracking/:entryId/stop -``` -Účel: Zastaviť bežiaci časovač -Params: entryId (UUID) -Body: { projectId?, todoId?, companyId?, description? } -Auth: Áno -Response: { entry with endTime, duration, isRunning: false } -Efekt: - - Nastaví endTime = NOW() - - Vypočíta duration v minútach - - Optional: update projektId/todoId/companyId/description -``` - -#### GET /api/time-tracking/running -``` -Účel: Získať aktuálny bežiaci časovač -Auth: Áno -Response: { entry } alebo null -``` - -#### GET /api/time-tracking?projectId=&todoId=&companyId=&startDate=&endDate= -``` -Účel: Zoznam všetkých time entries s filtrami -Query: projectId, todoId, companyId, startDate, endDate (all optional) -Auth: Áno -Response: Array of time entries -Order: DESC by startTime -``` - -#### GET /api/time-tracking/month/:year/:month -``` -Účel: Mesačný prehľad time entries -Params: year (YYYY), month (1-12) -Query: userId (optional, admin only – ak je zadaný, načítava sa daný používateľ) -Auth: Áno -Response: Array of entries pre daný mesiac -``` - -#### POST /api/time-tracking/month/:year/:month/generate -``` -Účel: Vygenerovať mesačný timesheet (Excel XLSX) -Params: year (YYYY), month (1-12) -Query: userId (optional, admin only - generate pre iného usera) -Auth: Áno -Body: {} (bez payloadu; posiela sa prázdny objekt) -Response: { - timesheet: { id, fileName, filePath, ... }, - filePath, - entriesCount, - totalMinutes, - totalHours -} -Efekt: - - Vytvorí Excel súbor s time entries pre daný mesiac - - Obsahuje: denné záznamy, projekty, todos, descrip, duration - - Summary: daily totals + overall total - - Uloží do: uploads/timesheets/{userId}/{year}/{month}/ - - INSERT INTO timesheets (isGenerated = true) -Volá: time-tracking.service.generateMonthlyTimesheet() -Admin feature: Admin môže generovať timesheet pre iného usera (query param userId) -``` - -#### GET /api/time-tracking/stats/monthly/:year/:month -``` -Účel: Mesačné štatistiky -Params: year (YYYY), month (1-12) -Query: userId (optional, admin only – ak je zadaný, načítava sa daný používateľ) -Auth: Áno -Response: { - totalMinutes, - totalHours, - remainingMinutes, - daysWorked, - averagePerDay, - entriesCount, - byProject: { projectId: minutes }, - byCompany: { companyId: minutes } -} -``` - -#### GET /api/time-tracking/:entryId -``` -Účel: Detail time entry -Params: entryId (UUID) -Auth: Áno -Response: Single time entry -``` - -#### GET /api/time-tracking/:entryId/details -``` -Účel: Detail time entry s reláciami -Params: entryId (UUID) -Auth: Áno -Response: { ...entry, project, todo, company } -``` - -#### PATCH /api/time-tracking/:entryId -``` -Účel: Upraviť time entry -Params: entryId (UUID) -Body: { startTime?, endTime?, projectId?, todoId?, companyId?, description? } -Auth: Áno -Validation: - - Entry nesmie byť isRunning (nemožno upraviť bežiaci časovač) - - User musí byť owner - - Pri zmene času sa prepočíta duration -Response: Updated entry with isEdited: true -``` - -#### DELETE /api/time-tracking/:entryId -``` -Účel: Zmazať time entry -Params: entryId (UUID) -Auth: Áno -Validation: - - Entry nesmie byť isRunning - - User musí byť owner -``` - ---- - -### 📊 TIMESHEETS - -#### POST /api/timesheets/upload -``` -Účel: Upload timesheet file (manuálne nahraný PDF/Excel) -Content-Type: multipart/form-data -Form: file (PDF/Excel), year (YYYY), month (1-12) -Auth: Áno -Validation: - - Max 10MB - - Allowed types: PDF, Excel (xlsx, xls) -Efekt: - - Uloží file do: uploads/timesheets/{userId}/{year}/{month}/ - - Generate unique filename: {name}-{timestamp}-{random}.ext - - INSERT INTO timesheets (isGenerated = false) -Response: { timesheet object } -``` - -#### GET /api/timesheets/my?year=YYYY&month=M -``` -Účel: Moje timesheets (uploaded + generated) -Query: year, month (both optional) -Auth: Áno -Response: { timesheets: [...], count } -Order: DESC by uploadedAt -``` - -#### GET /api/timesheets/all?userId=uuid&year=YYYY&month=M -``` -Účel: Všetky timesheets všetkých userov (admin) -Query: userId, year, month (all optional) -Auth: Áno (admin only) -Response: { timesheets: [...with user info...], count } -Includes: userId, username, firstName, lastName (LEFT JOIN users) -Order: DESC by uploadedAt -``` - -#### GET /api/timesheets/:timesheetId/download -``` -Účel: Stiahnuť timesheet file -Auth: Áno -Permissions: Owner OR admin -Response: File download (res.download) -Errors: - - 404: Timesheet nenájdený alebo súbor neexistuje - - 403: Nemáte oprávnenie (nie vlastník ani admin) -``` - -#### DELETE /api/timesheets/:timesheetId -``` -Účel: Zmazať timesheet -Auth: Áno -Permissions: Owner OR admin -Efekt: - - Delete file from filesystem (fs.unlink) - - DELETE FROM timesheets - - Continue even if file deletion fails (log error) -Errors: - - 404: Timesheet nenájdený - - 403: Nemáte oprávnenie -``` - -**POZNÁMKA:** -- Timesheets môžu byť **uploaded** (manuálne PDF/Excel) alebo **generated** (auto Excel z time entries) -- Field `isGenerated` rozlišuje typ: `true` = auto-generated, `false` = manually uploaded -- Obe typy sa ukladajú do rovnakej tabuľky `timesheets` a rovnakého adresára -- Generated timesheets sa vytvárajú cez `POST /api/time-tracking/month/:year/:month/generate` - ---- - -## VZŤAHY MEDZI SLUŽBAMI - -### Call Graph (kto volá koho) - -``` -AUTH FLOW: -auth.controller - → auth.service - → emailAccountService.createEmailAccount() - → email.service.validateJmapCredentials() - → password.encryptPassword() - → contact.service.syncContactsForAccount() - → jmap.service.discoverContactsFromJMAP() - → password.hashPassword() - → jwt.generateTokenPair() - -CONTACT CREATION: -contact.controller - → contact.service.addContact() - → jmap.service.syncEmailsFromSender() - → jmapRequest() → JMAP Server - → INSERT emails to DB - -COMPANY FROM CONTACT: -contact.controller - → contact.service.createCompanyFromContact() - → company.service.createCompany() - → UPDATE contact.companyId - -PROJECT TEAM: -project.controller - → project.service.assignUserToProject() - → Validate user exists (users table) - → INSERT projectUsers - -EMAIL SEND: -email.controller - → jmap.service.sendEmail() - → jmapRequest() → JMAP Server - → INSERT email to DB (sent) - -TIME TRACKING: -time-tracking.controller - → time-tracking.service.startTimeEntry() - → Check running entry (auto-stop if exists) - → Validate project/todo/company - → INSERT timeEntries - → time-tracking.service.stopTimeEntry() - → Calculate duration - → UPDATE timeEntries (set endTime, isRunning = false) - → time-tracking.service.getMonthlyStats() - → Aggregate statistics by project/company - -AUDIT: -Rôzne controllers - → audit.service.logAuditEvent() - → INSERT auditLogs -``` - -### Service Dependencies - -**Tier 1 (No dependencies):** -- `password.js` (util) -- `jwt.js` (util) -- `logger.js` (util) -- `errors.js` (util) - -**Tier 2 (Only utils):** -- `email.service` → (iba HTTP call) -- `audit.service` → (iba DB) - -**Tier 3 (Utils + Tier 2):** -- `jmap.service` → database, HTTP -- `company.service` → database, errors -- `todo.service` → database, errors -- `note.service` → database, errors -- `time-tracking.service` → database, errors - -**Tier 4 (Multiple services):** -- `contact.service` → company.service, jmap.service -- `emailAccountService` → email.service, password, contact.service -- `project.service` → database, errors - -**Tier 5 (Highest level):** -- `auth.service` → emailAccountService, password, jwt - ---- - -## POZNÁMKY - -### Bezpečnosť -- **User passwords:** bcrypt (12 rounds, one-way hash) -- **Email passwords:** AES-256-GCM (reversible, pre JMAP login) -- **JWT tokens:** HS256, httpOnly cookies -- **Rate limiting:** Login, password change, email operations - -### Performance -- **Database indexy:** Na všetkých foreign keys -- **Eager loading:** `getWithRelations()` metódy použiť iba ak treba všetko -- **Pagination:** Momentálne nie je, odporúčam pridať na lists - -### Maintenance -- **Error handling:** Centralizované cez `formatErrorResponse()` -- **Logging:** Štruktúrované cez `logger` -- **Audit trail:** Všetky kritické akcie logované - -### Možné zlepšenia -1. Pagination na všetkých list endpointoch -2. WebSocket pre real-time notifications (time tracking updates, email sync) -3. Background jobs pre email sync (Bull/Redis) -4. Cache layer (Redis) pre často čítané dáta -5. API versioning (/api/v1/) -6. GraphQL ako alternatíva k REST -7. Implement standalone Notes UI alebo vymazať routes -8. Time tracking export do CSV/Excel pre reporting -9. Team time tracking dashboard (admin view všetkých userov) - ---- - -## ⚠️ TECHNICKÉ POZNÁMKY A GOTCHAS - -### 1. Drizzle ORM - Join Query Pattern - -**PROBLÉM:** Drizzle ORM nepodporuje nested object syntax v `.select()` - -```javascript -// ❌ NEFUNGUJE - TypeError: Cannot convert undefined or null to object -const wrong = await db - .select({ - id: projectUsers.id, - user: { - id: users.id, - username: users.username, - } - }) - .from(projectUsers) - .leftJoin(users, eq(projectUsers.userId, users.id)); - -// ✅ SPRÁVNE RIEŠENIE -const rawResults = await db - .select() // Bez parametrov! - .from(projectUsers) - .leftJoin(users, eq(projectUsers.userId, users.id)) - .where(eq(projectUsers.projectId, projectId)) - .orderBy(desc(projectUsers.addedAt)); - -// Následne mapovať výsledky: -const assignedUsers = rawResults.map((row) => ({ - id: row.project_users.id, // snake_case table name - userId: row.project_users.userId, - role: row.project_users.role, - user: row.users ? { // null check pre LEFT JOIN - id: row.users.id, - username: row.users.username, - email: row.users.email, - } : null, -})); -``` - -**Kde sa to používa:** -- `project.service.js`: `getProjectUsers()`, `assignUserToProject()`, `updateUserRoleOnProject()`, `getProjectWithRelations()` -- Všade kde robíme LEFT JOIN s users, companies, projects, atď. - ---- - -### 2. Todo Status - Enum vs Boolean - -**PROBLÉM:** Frontend pôvodne checkoval `todo.completed` (boolean), ale databáza má `status` enum. - -**Databázová schéma:** -```javascript -status: pgEnum('todo_status', ['pending', 'in_progress', 'completed', 'cancelled']) -``` - -**SPRÁVNE použitie vo frontende:** -```javascript -// ✅ SPRÁVNE -const isCompleted = todo.status === 'completed'; - -// ❌ NESPRÁVNE - toto pole neexistuje! -const isCompleted = todo.completed; -``` - -**Toggle endpoint:** -```javascript -// Backend: /api/todos/:todoId/toggle -// Toggleuje medzi: 'completed' ↔ 'pending' -const newStatus = todo.status === 'completed' ? 'pending' : 'completed'; -``` - -**Kde sa to používa:** -- Frontend: `TodoItem.jsx`, `TodosPage.jsx`, `ProjectNotesModal.jsx` -- Backend: `todo.service.js` - `toggleTodoComplete()` - ---- - -### 3. Node.js Module Caching - -**PROBLÉM:** Po zmene kódu server niekedy beží stále starý kód, aj keď nodemon reštartoval. - -**PRÍČINY:** -- Viacero nodemon procesov naraz (v separátnych termináloch) -- ESM module caching -- Neukončené background procesy - -**RIEŠENIE:** -```bash -# Zabij všetky node/nodemon procesy -ps aux | grep -E "node|nodemon" | grep -v grep | awk '{print $2}' | xargs kill -9 - -# Alebo len na porte 5000 -lsof -i:5000 | grep LISTEN | awk '{print $2}' | xargs kill -9 - -# Clear cache a reštart -rm -rf node_modules/.cache -npm run dev -``` - -**PREVENCIA:** -- Nespúšťať server v background mode počas developmentu -- Používať iba jeden terminál pre server -- Check procesy: `ps aux | grep node` - ---- - -### 4. Frontend API Response Parsing - -**PROBLÉM:** API môže vracať rôzne formáty odpovede. - -**Admin API - Get All Users:** -```javascript -// Backend vracia: -{ users: [...], count: 10 } - -// Frontend musí extrahovať: -const usersList = Array.isArray(data) ? data : (data?.users || []) -``` - -**Project API - Get Team Members:** -```javascript -// Backend vracia priamo array: -[{ id, userId, user: {...} }] - -// Frontend očakáva array: -setTeamMembers(data || []) -``` - -**BEST PRACTICE:** -- Vždy checkuj `Array.isArray()` -- Fallback na prázdne array: `data || []` -- Console.log response v dev mode pre debug - ---- - -### 5. Database Constraints - -**projectUsers table - UNIQUE constraint:** -```javascript -unique('project_user_unique').on(table.projectId, table.userId) -``` - -**Význam:** -- User môže byť assigned k projektu **iba raz** -- Duplicate assignment → `409 ConflictError` - -**Cascade deletes:** -```javascript -onDelete: 'cascade' // Ak sa zmaže project/user → zmaže sa assignment -``` - -**Kde sa používa:** -- Projects: CASCADE delete na todos, notes, timesheets, projectUsers -- Companies: CASCADE delete na projects (a všetko pod nimi) -- Email accounts: CASCADE delete na contacts, emails - ---- - -### 6. Authentication Middleware - -**Každá route (okrem /api/auth/login) vyžaduje authentication:** - -```javascript -router.use(authenticate); // Global middleware na route file -``` - -**JWT token sa číta z:** -1. httpOnly cookie `accessToken` (preferované) -2. Authorization header `Bearer ` (fallback) - -**User info v request:** -```javascript -req.userId // UUID -req.username // String -req.userRole // 'admin' | 'member' -``` - ---- - -### 7. Time Tracking - Auto-stop Behavior - -**PROBLÉM:** User môže mať spustený iba jeden časovač naraz. - -**RIEŠENIE - Automatický stop:** -```javascript -// Pri štarte nového časovača -if (existingRunning) { - // Automaticky zastaví predchádzajúci časovač - const endTime = new Date(); - const duration = Math.round((endTime - startTime) / 60000); // minúty - await db.update(timeEntries) - .set({ endTime, duration, isRunning: false }) - .where(eq(timeEntries.id, existingRunning.id)); -} -``` - -**Validačné pravidlá:** -```javascript -// ✅ POVOLENÉ -- Spustiť nový časovač (auto-stop predchádzajúceho) -- Upraviť zastavený časovač -- Zmazať zastavený časovač - -// ❌ ZAKÁZANÉ -- Upraviť bežiaci časovač (musí sa najprv zastaviť) -- Zmazať bežiaci časovač (musí sa najprv zastaviť) -- Zastaviť časovač iného usera -``` - -**Duration calculation:** -- Ukladá sa v **minútach** (Math.round) -- Počíta sa z rozdelu: `(endTime - startTime) / 60000` -- Frontend zobrazuje: hodiny + minúty (napr. "2h 35m") - -**isEdited flag:** -- Automaticky nastavený pri manuálnej úprave -- Indikuje, že čas bol zmenený používateľom (nie auto-tracked) - -**Kde sa používa:** -- `time-tracking.service.js`: `startTimeEntry()`, `updateTimeEntry()` -- Frontend: Time tracking komponenty s real-time countdown - ---- - -## 🔍 DEBUGGING TIPS - -### 1. Server beží starý kód -```bash -# Check running processes -ps aux | grep node - -# Kill all and restart -pkill -9 node && npm run dev -``` - -### 2. Database query debugging -```javascript -// Drizzle query debugging -const result = await db.select()... -console.log('[DEBUG] Raw DB result:', JSON.stringify(result, null, 2)); -``` - -### 3. Frontend API debugging -```javascript -// V API function -console.log('[DEBUG] API Response:', data); -console.log('[DEBUG] Extracted users:', usersList); -``` - -### 4. JMAP email issues -```javascript -// Check JMAP credentials -const valid = await validateJmapCredentials(email, password); -console.log('[DEBUG] JMAP validation:', valid); -``` - ---- - -**Vytvorené:** 2025-11-21 -**Posledná aktualizácia:** 2025-11-25 -**Autor:** CRM Server Team - ---- - -## CHANGELOG - -### 2025-11-25 - Cleanup + Timesheet Service -- Presunutá biznis logika timesheetov do `services/timesheet.service.js`, controller ostáva tenký. -- Odstránené nevyužité routes (FE): auth link-email/skip-email/me, company/project/todo details, contacts link/unlink/create-company, email-account detail/password/status, emails contact listing + PATCH read, standalone notes odpojené z app.js. -- Dokumentácia zosúladená s aktuálnymi endpointmi. - -### 2025-11-24 - Additions -**Pridané sekcie:** -1. **VALIDATORS** - Kompletná dokumentácia všetkých Zod schemas - - auth.validators.js (login, password, user creation) - - crm.validators.js (company, project, todo, note, time tracking) - - email-account.validators.js (JMAP accounts) - -2. **SERVICES** - Doplnené chýbajúce metódy - - time-tracking.service.generateMonthlyTimesheet() - Excel XLSX generation - -3. **CONTROLLERS** - Pridaný chýbajúci controller - - timesheet.controller.js - File upload/download (bez service layer) - -4. **ROUTES** - Kompletný zoznam všetkých route files - - 11 route files s uvedením middleware requirements - -5. **API ROUTES** - Doplnené chýbajúce endpointy - - POST /api/time-tracking/month/:year/:month/generate - Generate Excel timesheet - - GET /api/timesheets/my - Detail s filters (year, month) - - GET /api/timesheets/all - Admin endpoint s filters - - DELETE /api/timesheets/:timesheetId - Permission checks - -6. **UTILS** - Zoznam všetkých utility files (boli už zdokumentované) - -**Upresnenia:** -- Timesheet service NEEXISTUJE - logika priamo v controlleri -- isGenerated flag rozlišuje uploaded vs generated timesheets -- Admin môže generovať timesheet pre iného usera (query param userId) -- Empty string handling vo validátoroch: `.or(z.literal(''))` pattern -- Optional UUID preprocessing v time tracking schemas diff --git a/README.md b/README.md index a23f6df..407b189 100644 --- a/README.md +++ b/README.md @@ -1,994 +1,139 @@ -# 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()` +# CRM Server – Architektúra a flow + +Tento dokument popisuje, ako backend funguje: aká je štruktúra kódu, akou cestou prechádza požiadavka, aké služby spolu komunikujú a kde sa riešia bezpečnostné a chybové scenáre. Všetky cesty v kóde sú písané v ES modules. + +## Základný stack +- **Express 4** (ESM) + **Drizzle ORM** (PostgreSQL) +- **JWT** pre prístupové/refresh tokeny (httpOnly cookies), **bcrypt** pre heslá +- **Zod** na validačné schémy, **helmet**, **cors**, **express-rate-limit** +- **JMAP** integrácia pre e-maily (Truemail) + AES-256-GCM šifrovanie hesiel k e-mail účtom + +## Štruktúra priečinkov (hlavné časti) +- `src/app.js` – skladá middleware pipeline a mountuje routes, pridáva notFound/error handler. +- `src/index.js` – spúšťací bod servera. +- `src/routes/` – deklarácie endpointov (1 súbor = 1 doména). Všetky používajú middlewares + volajú controller. +- `src/controllers/` – spracovanie requestu, volanie service vrstvy, odoslanie odpovedí. Chyby posielajú cez `next(err)`. +- `src/services/` – biznis logika a práca s DB/JMAP (bez Express závislosti). +- `src/middlewares/` – auth (JWT + role), security (rate limiting, Zod validateInput), global (validateBody pattern check, 404, error handler). +- `src/utils/` – `errors.js` (AppError a formátovanie), `jwt.js`, `password.js`, `logger.js`. +- `src/validators/` – Zod schémy pre vstupy. +- `src/db/` – Drizzle schéma a config. + +## Životný cyklus požiadavky (pipeline) +1) **Logovanie**: `morgan('dev')` (len stdout). +2) **Bezpečnostné hlavičky**: `helmet` s CSP (self + inline styles) a HSTS (preload, subdomains). +3) **CORS**: povolený pôvod z `CORS_ORIGIN` (default `http://localhost:5173`), credentials povolené. +4) **Body parsers**: `express.json` a `express.urlencoded` (limit 10 MB), `cookieParser` pre JWT v cookies. +5) **Global validateBody**: rýchly regex-detektor podozrivých payloadov (loguje a vráti 400 pri matches). +6) **Rate limiting**: `apiRateLimiter` na `/api/*` (100 req/15 min v production, 1000 v dev). Špecifické limitery na login a citlivé operácie sa aplikujú v routes. +7) **Routes**: `auth`, `admin`, `contacts`, `emails` (CRM), `email-accounts`, `timesheets`, `companies`, `projects`, `todos`, `time-tracking`, `notes`. +8) **404**: `notFound` middleware nastaví 404 a pošle ďalej ako Error. +9) **Global error handler**: `errorHandler` loguje, zvolí status (`err.statusCode` > `res.statusCode` ≥400 > 500) a formátuje pomocou `formatErrorResponse`. Ak už sú hlavičky poslané, púšťa chybu ďalej. + +## Validácia a bezpečnosť +- **Zod validácia**: `validateBody/validateQuery/validateParams` (v `middlewares/security/validateInput.js`) na úrovni routes; nahrádzajú `req.body/query/params` validovanými dátami. +- **Auth**: `authenticate` vytiahne JWT z Bearer header alebo cookie, overí cez `verifyAccessToken`, načíta usera (`auth.service.getUserById`) a uloží do `req.user` + `req.userId`. +- **Role**: `requireRole` / `requireAdmin` / `requireOwnerOrAdmin` (role middleware) na autorizáciu. +- **Rate limiting**: `loginRateLimiter` (default 5 pokusov/15 min, počíta len neúspechy), `sensitiveOperationLimiter` (10 prod / 50 dev za 15 min). +- **Šifrovanie**: `password.encryptPassword` používa AES-256-GCM (kľúč zo `JWT_SECRET` + `ENCRYPTION_SALT`). Heslá v DB sú bcrypt hashované. +- **Audit logy**: `audit.service` loguje login pokusy, zmeny hesla, role, tvorbu userov atď. do DB + konzoly. + +## Error handling (kde a ako) +- **Typy chýb**: `AppError` + podtriedy (ValidationError, AuthenticationError, ForbiddenError, NotFoundError, ConflictError, RateLimitError). +- **Formát odpovede**: `formatErrorResponse` vracia `{ success:false, error:{ message, statusCode, details?, stack? } }`. Stack iba v NODE_ENV=development. +- **Použitie**: Kontroléry nemajú lokálne try/catch formatovanie; pri chybe volajú `next(err)`. Auth middleware vracia 401 pri Auth chybách, inak púšťa ďalej do globálneho handlera. + +## Doménové moduly – kto koho volá + +### Autentifikácia (`routes/auth.routes.js`, `auth.controller.js`, `auth.service.js`) +- **/login**: `loginRateLimiter` → `validateBody(loginSchema)` → controller zavolá `authService.loginWithTempPassword` (porovná temp/permanent heslo, nastaví lastLogin, vygeneruje tokeny) → audit `logLoginAttempt` (success/fail) → nastaví httpOnly cookies + response. +- **/set-password**: `authenticate` → `sensitiveOperationLimiter` → `validateBody(setPasswordSchema)` → `authService.setNewPassword` (bcrypt, zmaže tempPassword) → audit `logPasswordChange`. +- **/logout**, **/session**: vyžadujú `authenticate`; logout vyčistí cookies, session vráti `req.user`. +- **Tokeny**: generované v `utils/jwt.js` (access + refresh); overenie hádže `AuthenticationError` pri expirácii/neplatnosti. + +### Admin (`routes/admin.routes.js`, `admin.controller.js`) +- `router.use(authenticate)` + `router.use(requireAdmin)` pre všetky admin-only akcie. +- CRUD nad používateľmi: create (generuje temp heslo + voliteľné linknutie email účtu), get/list, change role, delete. Používa `email-account.service` pri zakladaní účtu, loguje audit udalosti. + +### Email účty (`routes/email-account.routes.js`, `email-account.controller.js`, `email-account.service.js`) +- Každá akcia vyžaduje `authenticate`. +- **Create**: `sensitiveOperationLimiter` + Zod schéma → service overí JMAP credentials (`validateJmapCredentials` z `email.service.js`), šifruje heslo (AES-256-GCM), vytvorí účet a many-to-many link do `userEmailAccounts`. Ak účet existuje, vie ho len „nasdieliť“ po overení hesla. +- **Set primary**: pre konkrétneho používateľa; transakčne zruší ostatné `isPrimary` a aktivuje účet. +- **Delete**: odstráni link, a ak nikto iný účet nepoužíva, zmaže aj samotný účet. +- **Get**: vracia účty používateľa; špeciálna funkcia `getEmailAccountWithCredentials` dešifruje heslo na JMAP operácie. + +### CRM Emaily (`routes/crm-email.routes.js`, `crm-email.controller.js`, `crm-email.service.js`, `jmap.service.js`) +- Všetky endpointy za `authenticate`. +- **Listing/search**: DB filter + fulltext (subject/body/from), alebo JMAP full-text (`/search-jmap`). Filtrovanie podľa účtu, kontaktu, stavu prečítania. +- **Threads**: `/thread/:threadId` načíta konverzáciu; `/thread/:threadId/read` označí všetky maily v threade. +- **Sync**: `/sync` spustí fetch z JMAP pre daného používateľa/účet. +- **Mark contact read**: `/contact/:contactId/read` označí všetky maily od kontaktu ako prečítané. +- **Reply**: `/reply` cez JMAP; používa dešifrované heslo z email-account service. +- **Unread count**: `/unread-count` agreguje per účet. + +### Kontakty (`routes/contact.routes.js`, `contact.controller.js`, `contact.service.js`) +- Všetko za `authenticate`. +- **Get/Discover**: zoznam kontaktov (voliteľný filter `accountId`), discover číta unikátnych odosielateľov z JMAP. +- **Create**: validácia + uloženie, následne auto-sync všetkých emailov od tohto odosielateľa. +- **Update/Delete**: meno/poznámky, prípadne zmazanie; pri delete ostávajú emaily, len sa odpojí contact_id. +- **Link/Unlink company** a **create company from contact** využívajú company service. + +### Companies (`routes/company.routes.js`, `company.controller.js`, `company.service.js`, `company-email.service.js`, `company-reminder.service.js`) +- `authenticate` povinné. +- **CRUD firmy**, plus **email threads** pre firmu (agregácia emailov naprieč účtami používateľa). +- **Unread counts** per firma (agreguje emaily podľa kontaktov a účtov). +- **Notes** (vnořené /:companyId/notes) používajú `note.service`. +- **Reminders**: CRUD + summary/endpoints na prehľad (upcoming, counts, summary). Všetko cez `company-reminder.service`. + +### Projekty (`routes/project.routes.js`, `project.controller.js`, `project.service.js`) +- `authenticate` povinné. +- CRUD projektov, správa členov projektu (assign/update role/remove), projektové poznámky (vrátane `reminderAt`). + +### Todos (`routes/todo.routes.js`, `todo.controller.js`, `todo.service.js`) +- `authenticate`; CRUD + toggle completed. + +### Poznámky (`routes/note.routes.js`, `note.controller.js`, `note.service.js`) +- `authenticate`; všeobecné poznámky s filtrom na company/project/todo/contact; CRUD operácie. + +### Time Tracking (`routes/time-tracking.routes.js`, `time-tracking.controller.js`, `time-tracking.service.js`) +- `authenticate`; Zod validácia na start/stop/update. +- **Start/Stop**: vytvára/uzatvára bežiaci záznam (oprávnenie viazané na `req.userId`). +- **Bežiace položky**: `/running` (aktuálne pre usera), `/running-all` (všetkých userov – dashboard). +- **Listing/Filters**: všeobecný listing, mesačné výpisy a štatistiky, detail/relations. +- **Generate timesheet**: vytvorí XLSX výstup za mesiac (využíva `exceljs`). + +### Timesheets upload (`routes/timesheet.routes.js`, `timesheet.controller.js`, `timesheet.service.js`) +- `authenticate`; Multer s **memory storage** a limit 5 MB, whitelist MIME (PDF, XLSX, XLS). Admin môže nahrávať za iných, inak len za seba. Ukladá súbory do `uploads/timesheets`. + +### Admin (už popísané vyššie) + +### Audit (`audit.service.js`) +- Jednotné logovanie udalostí do DB + konzoly (tag `[AUDIT]`). Použité v auth flow a user managemente; možno rozšíriť na ďalšie služby. + +## Pomocné utility +- `utils/errors.js` – definícia AppError podtried + `formatErrorResponse`. +- `utils/jwt.js` – generovanie/verifikácia access/refresh tokenov. +- `utils/password.js` – bcrypt hash/compare, generovanie temp hesla, AES-256-GCM encrypt/decrypt pre email heslá. +- `utils/logger.js` – farebný stdout logger (info/success/warn/error/debug/audit). + +## Request → DB/JMAP tok (v skratke) +`Route` → `Zod middleware` (+ auth/role/rate-limit) → `Controller` → `Service` → `DB (Drizzle)` alebo `JMAP` → späť do controller → JSON response. Chyby: buď AppError (očakávané, neseká stack v prod), alebo neočakávané → global `errorHandler`. + +## Dôležité závislosti medzi modulmi +- **authMiddleware** závisí na `utils/jwt` a `auth.service` (pre user fetch). Bez prístupu k DB nie je možné overiť token. +- **crm-email.service** používa `jmap.service` + `email-account.service` (pre dešifrované heslo) + DB schémy. +- **contact.service** pri vytvorení kontaktu volá `crm-email.service` na sync emailov od odosielateľa. +- **company.controller** používa `company-email.service` (agregácia email threadov) a `note.service` / `company-reminder.service`. +- **time-tracking.service** používa Drizzle schémy `timeEntries`, `projects`, `todos`, `companies`, `users` na joiny a agregácie. +- **timesheet.controller**/routes využívajú Multer; uloženie súboru/metadata robí `timesheet.service`. + +## Ako rozširovať +- Nový endpoint: pridaj Zod schému do `validators`, zapoj `validateBody/Params/Query`, použi `authenticate`/`requireAdmin` podľa potreby, v controllery volaj service a pri chybe `next(err)`. +- Nová logika: implementuj v `services` (bez Express závislosti), AppError pri očakávaných stavoch. +- Error/response shape je centrálne daný `errorHandler` + `formatErrorResponse` – nechaj ho pracovať. + +## Environment a spustenie (stručne) +- Env vars: `PORT`, `CORS_ORIGIN`, `JWT_SECRET`, `JWT_REFRESH_SECRET`, `ENCRYPTION_SALT`, `RATE_LIMIT_*`, DB credentials atď. (pozri `.env.example` ak existuje / README bezpečnostný checklist). +- Skripty: `npm run dev` (nodemon), `npm start`, `npm test` (Jest), drizzle migrácie `db:generate/push/studio`. + +Tento README má slúžiť ako rýchla mapa: čo kde je, čo koho volá a kde hľadať validačné/bezpečnostné háky alebo biznis logiku. diff --git a/SECURITY_CHECK.md b/SECURITY_CHECK.md deleted file mode 100644 index ca4cfdd..0000000 --- a/SECURITY_CHECK.md +++ /dev/null @@ -1,626 +0,0 @@ -# 🔒 SECURITY AUDIT REPORT - CRM Server - -**Date:** 2025-12-02 -**Auditor:** Automated Security Scan -**Project Version:** 1.0.0 -**Node Version:** 20.x - ---- - -## Executive Summary - -I've completed a comprehensive security audit of your CRM server project. The project has **good security foundations** with several protective measures in place, but there are **2 critical dependency vulnerabilities** and several **medium-priority security improvements** needed. - ---- - -## ✅ STRENGTHS - -### 1. **Authentication & Authorization** -- ✅ JWT tokens stored in httpOnly cookies (XSS protection) -- ✅ SameSite=Strict cookies (CSRF protection) -- ✅ Bcrypt password hashing (12 rounds) -- ✅ Separate access & refresh tokens -- ✅ Role-based access control (admin/member) -- ✅ Temporary password system for onboarding - -### 2. **Input Validation & Sanitization** -- ✅ Zod schemas for request validation -- ✅ XSS protection via xss-clean middleware -- ✅ Custom malicious pattern detection in `validateBody.js` -- ✅ SQL injection protection via Drizzle ORM (parameterized queries) - -### 3. **Rate Limiting** -- ✅ Login rate limiter (5 attempts/15min) -- ✅ API rate limiter (100 req/15min production, 1000 dev) -- ✅ Sensitive operations limiter (3 attempts/15min) - -### 4. **Security Headers & CORS** -- ✅ Helmet middleware with CSP and HSTS -- ✅ Configured CORS with credentials support -- ✅ Body size limits (10MB) - -### 5. **Data Encryption** -- ✅ AES-256-GCM for email passwords -- ✅ Crypto.randomUUID() for tokens -- ✅ Secure password generation - -### 6. **File Upload Security** -- ✅ File type whitelist (PDF, Excel only) -- ✅ File size limit (10MB) -- ✅ Memory storage (prevents path traversal) -- ✅ Filename sanitization - -### 7. **Docker Security** -- ✅ Non-root user in Dockerfile -- ✅ Alpine Linux base image (smaller attack surface) - -### 8. **Audit Logging** -- ✅ Comprehensive audit trail -- ✅ IP address and user-agent tracking - ---- - -## 🚨 CRITICAL VULNERABILITIES - -### 1. **NPM Dependencies - 2 Low Severity Issues** ⚠️ - -**better-auth v1.3.34** (2 vulnerabilities): -- **GHSA-wmjr-v86c-m9jj**: Multi-session sign-out hook allows forged cookies to revoke arbitrary sessions (CVSS 9.6) - - Severity: Low (but high CVSS score) - - Fix available: v1.4.4 - -- **GHSA-569q-mpph-wgww**: External request basePath modification DoS - - Severity: Low - - Fix available: v1.4.4 - -**express v4.21.2**: -- **GHSA-pj86-cfqh-vqx6**: Improper control of query properties modification - - Severity: Low - - Fix available: v4.22.0 - -**Fix Available**: Yes - -**Recommended Action**: -```bash -npm audit fix -# This will update: -# - better-auth: 1.3.34 → 1.4.4 -# - express: 4.21.2 → 4.22.1 -``` - -**Additional Outdated Packages**: -- `dotenv`: 16.6.1 → 17.2.3 (latest) -- `zod`: 4.1.12 → 4.1.13 - ---- - -## ⚠️ HIGH PRIORITY ISSUES - -### 2. **Environment File Security** 🔴 - -**Issue**: `.env` file contains default/weak secrets - -**Findings**: -- ✅ `.env` is properly gitignored -- ✅ Never committed to git history -- ❌ Contains default/weak secrets that should be changed in production - -**Secrets Found**: -```env -JWT_SECRET=your-super-secret-jwt-key-change-this-in-production -JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production -BETTER_AUTH_SECRET=your-super-secret-better-auth-key-change-this-in-production -``` - -**Recommendation**: -```bash -# Generate strong secrets: -node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" - -# For each secret in .env: -JWT_SECRET= -JWT_REFRESH_SECRET= -BETTER_AUTH_SECRET= -``` - -**Best Practices**: -- Generate strong, random secrets for production -- Consider using secret management (HashiCorp Vault, AWS Secrets Manager) -- Rotate secrets regularly (every 90 days) -- Never commit secrets to version control - -### 3. **Database Credentials in docker-compose.yml** 🟡 - -**Issue**: Hardcoded database password in docker-compose.yml - -**Current Code**: -```yaml -environment: - POSTGRES_PASSWORD: heslo123 - POSTGRES_DB: crm - POSTGRES_USER: admin -``` - -**Recommendation**: -```yaml -environment: - POSTGRES_PASSWORD: ${DB_PASSWORD} - POSTGRES_DB: ${DB_NAME} - POSTGRES_USER: ${DB_USER} -``` - -### 4. **Password Encryption Salt** 🟡 - -**Location**: `src/utils/password.js:75` - -**Issue**: Hardcoded salt value 'salt' - should be unique per encryption - -**Current Code**: -```javascript -const key = crypto.scryptSync(process.env.JWT_SECRET, 'salt', 32); -``` - -**Problem**: Using a static salt means all encrypted passwords use the same key derivation, reducing security. - -**Recommendation**: -```javascript -// Generate random salt per encryption -const salt = crypto.randomBytes(16); -const key = crypto.scryptSync(process.env.JWT_SECRET, salt, 32); -// Store: `${salt.toString('hex')}:${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}` -``` - ---- - -## 🟡 MEDIUM PRIORITY IMPROVEMENTS - -### 5. **Session Security** - -**Current Issues**: -- ❌ No session invalidation on password change -- ❌ No maximum concurrent sessions per user -- ❌ No session timeout warnings -- ❌ Refresh tokens not stored in database (cannot revoke) - -**Recommendation**: -```javascript -// Add session tracking table -export const sessions = pgTable('sessions', { - id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }), - refreshToken: text('refresh_token').notNull(), - ipAddress: text('ip_address'), - userAgent: text('user_agent'), - expiresAt: timestamp('expires_at').notNull(), - createdAt: timestamp('created_at').defaultNow() -}); - -// Invalidate all sessions on password change -// Limit to 5 concurrent sessions per user -``` - -### 6. **Password Policy Enhancement** - -**Current Policy** (from code review): -- ✅ Minimum 8 characters -- ✅ Must contain uppercase, lowercase, number, symbol - -**Enhancements Needed**: -```javascript -// Add to password validation: -- Minimum 12 characters (currently 8) -- Check against common password list (e.g., have-i-been-pwned) -- Prevent password reuse (store hash of last 5 passwords) -- Add password strength meter on frontend -- Enforce password expiration (90 days for admin accounts) -``` - -### 7. **File Storage Security** - -**Current Issues**: -- ❌ Uploaded files stored locally without encryption at rest -- ❌ No virus scanning on uploads -- ❌ File paths somewhat predictable: `uploads/timesheets/{userId}/{year}/{month}/{filename}` - -**Recommendations**: -```bash -# 1. Add virus scanning -npm install clamscan - -# 2. Encrypt files at rest -# Use node's crypto to encrypt files before saving - -# 3. Use UUIDs in paths -uploads/timesheets/{uuid}/{uuid}.encrypted -``` - -**Alternative**: Migrate to cloud storage (AWS S3, Azure Blob) with server-side encryption - -### 8. **Logging Concerns** - -**Issues**: -- ❌ No check for sensitive data in logs -- ⚠️ Morgan logs all requests (could expose sensitive query params) -- ❌ Console.log statements in code (should use winston logger) - -**Recommendation**: -```javascript -// Configure morgan to skip sensitive routes -app.use(morgan('dev', { - skip: (req) => { - const sensitivePatterns = [ - /password/i, - /token/i, - /secret/i, - /api\/auth\/login/, - /api\/auth\/set-password/ - ]; - return sensitivePatterns.some(pattern => - pattern.test(req.url) || pattern.test(JSON.stringify(req.body)) - ); - } -})); - -// Replace all console.log with winston logger -import { logger } from './utils/logger.js'; -``` - ---- - -## 🔵 LOW PRIORITY / BEST PRACTICES - -### 9. **Security Headers Enhancement** - -**Current CSP** is basic. Enhance with: - -```javascript -app.use(helmet({ - contentSecurityPolicy: { - directives: { - defaultSrc: ["'self'"], - scriptSrc: ["'self'"], - styleSrc: ["'self'", "'unsafe-inline'"], - imgSrc: ["'self'", "data:", "https:"], - connectSrc: ["'self'"], - fontSrc: ["'self'"], - objectSrc: ["'none'"], - mediaSrc: ["'none'"], - frameSrc: ["'none'"], - upgradeInsecureRequests: [], - }, - }, - hsts: { - maxAge: 31536000, - includeSubDomains: true, - preload: true, - }, - referrerPolicy: { policy: "strict-origin-when-cross-origin" }, - noSniff: true, - xssFilter: true, - hidePoweredBy: true -})); -``` - -### 10. **CORS Hardening** - -**Current**: -```javascript -const corsOptions = { - origin: process.env.CORS_ORIGIN || 'http://localhost:5173', - credentials: true, - optionsSuccessStatus: 200, -}; -``` - -**Enhanced**: -```javascript -const corsOptions = { - origin: process.env.CORS_ORIGIN || 'http://localhost:5173', - credentials: true, - methods: ['GET', 'POST', 'PATCH', 'DELETE'], - allowedHeaders: ['Content-Type', 'Authorization'], - exposedHeaders: ['Content-Range', 'X-Content-Range'], - maxAge: 86400, // 24 hours - optionsSuccessStatus: 200 -}; -``` - -### 11. **Rate Limiting - IP Spoofing Protection** - -Add trust proxy setting: -```javascript -// In app.js, before rate limiters -app.set('trust proxy', 1); // Trust first proxy (nginx, cloudflare, etc.) -``` - -### 12. **Audit Log Retention** - -**Current Issues**: -- ❌ No automatic cleanup of old audit logs -- ❌ No log rotation policy -- ❌ Unlimited log growth - -**Recommendation**: -```javascript -// Add cron job to clean old logs -import cron from 'node-cron'; - -// Run daily at 2 AM -cron.schedule('0 2 * * *', async () => { - const retentionDays = 90; - await db.delete(auditLogs) - .where( - sql`created_at < NOW() - INTERVAL '${retentionDays} days'` - ); -}); -``` - -### 13. **Additional Security Measures** - -**API Security**: -```javascript -// Add request ID tracking -import { v4 as uuidv4 } from 'uuid'; -app.use((req, res, next) => { - req.id = uuidv4(); - res.setHeader('X-Request-ID', req.id); - next(); -}); - -// Add timeout middleware -import timeout from 'connect-timeout'; -app.use(timeout('30s')); -``` - -**Brute Force Protection**: -```javascript -// Add progressive delays after failed login attempts -// Implement account lockout after 10 failed attempts -// Add CAPTCHA after 3 failed attempts -``` - ---- - -## 📋 SECURITY CHECKLIST - -### 🔴 Immediate Actions (Do Now) -- [ ] Run `npm audit fix` to update dependencies -- [ ] Generate strong secrets for JWT_SECRET, JWT_REFRESH_SECRET, BETTER_AUTH_SECRET -- [ ] Move database password to environment variable in docker-compose.yml -- [ ] Fix hardcoded salt in password encryption (src/utils/password.js) - -### 🟡 Short Term (This Week) -- [ ] Implement session invalidation on password change -- [ ] Add session tracking in database -- [ ] Review and filter sensitive data from logs -- [ ] Update password policy to 12 characters minimum -- [ ] Add trust proxy setting for rate limiters - -### 🔵 Medium Term (This Month) -- [ ] Add virus scanning for file uploads -- [ ] Implement password strength requirements and breach checking -- [ ] Set up automated security scanning (Snyk, Dependabot) -- [ ] Implement audit log retention policy -- [ ] Add 2FA support -- [ ] Enhance security headers (CSP, etc.) - -### 🟢 Long Term (This Quarter) -- [ ] Migrate to managed secrets (AWS Secrets Manager, HashiCorp Vault) -- [ ] Implement file encryption at rest -- [ ] Add honeypot endpoints -- [ ] Set up SIEM/log aggregation -- [ ] Conduct penetration testing -- [ ] Implement zero-trust architecture - ---- - -## 🔧 RECOMMENDED SECURITY TOOLS - -### 1. **Dependency Scanning** -```bash -# NPM Audit (built-in) -npm audit -npm audit fix - -# Snyk (more comprehensive) -npm install -g snyk -snyk auth -snyk test -snyk monitor - -# GitHub Dependabot -# Enable in: Settings → Security & analysis → Dependabot alerts -``` - -### 2. **Static Analysis** -```bash -# ESLint security plugin -npm install --save-dev eslint-plugin-security -# Add to .eslintrc.json: "plugins": ["security"] - -# SonarQube -# Self-hosted or SonarCloud for continuous inspection -``` - -### 3. **Runtime Protection** -```bash -# Helmet (already installed) ✅ -# Express rate limit (already installed) ✅ - -# Additional recommendations: -npm install express-mongo-sanitize # NoSQL injection prevention -npm install hpp # HTTP Parameter Pollution protection -npm install csurf # CSRF token middleware -``` - -### 4. **Secret Scanning** -```bash -# TruffleHog - scan git history for secrets -docker run --rm -v "$(pwd):/repo" trufflesecurity/trufflehog:latest git file:///repo - -# GitLeaks -docker run --rm -v "$(pwd):/path" zricethezav/gitleaks:latest detect --source="/path" -``` - -### 5. **Penetration Testing** -```bash -# OWASP ZAP -docker run -t owasp/zap2docker-stable zap-baseline.py -t http://your-api - -# Burp Suite Community Edition -# Manual testing of API endpoints -``` - ---- - -## 📊 OVERALL SECURITY RATING - -### **Score: 7.5/10** 🟢 - -### Breakdown: -| Category | Score | Status | -|----------|-------|--------| -| Authentication/Authorization | 9/10 | ✅ Excellent | -| Input Validation | 8/10 | ✅ Good | -| Dependency Management | 6/10 | ⚠️ Needs Update | -| Encryption | 7/10 | 🟡 Good with improvements needed | -| Secret Management | 6/10 | ⚠️ Needs Improvement | -| Network Security | 9/10 | ✅ Excellent | -| Audit/Logging | 7/10 | 🟡 Good | -| File Upload Security | 7/10 | 🟡 Good | -| Session Management | 6/10 | 🟡 Needs Improvement | -| Error Handling | 8/10 | ✅ Good | - -### **Verdict**: -Your project has a **solid security foundation** with industry-standard practices including JWT authentication, bcrypt hashing, rate limiting, and input validation. The main concerns are: -1. Outdated dependencies with known vulnerabilities -2. Secret management (default secrets in .env) -3. Hardcoded salt in encryption -4. Session management improvements needed - -Addressing the critical and high-priority issues will bring your security posture to **production-ready** status. - ---- - -## 🎯 COMPLIANCE CONSIDERATIONS - -### GDPR (EU Data Protection) -- ✅ Audit logging for data access -- ✅ Data deletion (cascade deletes) -- ❌ Missing: Right to data portability (export user data) -- ❌ Missing: Consent management -- ❌ Missing: Data retention policies - -### SOC 2 (Security & Availability) -- ✅ Access controls -- ✅ Encryption in transit (HTTPS) -- ✅ Audit logging -- ⚠️ Missing: Encryption at rest for files -- ⚠️ Missing: Backup and disaster recovery procedures - -### OWASP Top 10 2021 -- ✅ A01: Broken Access Control - Protected ✅ -- ✅ A02: Cryptographic Failures - Mostly Protected ⚠️ -- ✅ A03: Injection - Protected ✅ -- ✅ A04: Insecure Design - Good architecture ✅ -- ⚠️ A05: Security Misconfiguration - Minor issues ⚠️ -- ✅ A06: Vulnerable Components - Needs update ⚠️ -- ✅ A07: Authentication Failures - Protected ✅ -- ✅ A08: Data Integrity Failures - Protected ✅ -- ✅ A09: Logging Failures - Good but can improve 🟡 -- ✅ A10: SSRF - Not applicable to this architecture ✅ - ---- - -## 📞 INCIDENT RESPONSE PLAN - -### If Security Breach Detected: - -1. **Immediate Actions**: - - Isolate affected systems - - Revoke all active sessions - - Rotate all secrets (JWT, database passwords) - - Enable maintenance mode - -2. **Investigation**: - - Review audit logs - - Check for unauthorized access - - Identify attack vector - - Assess data exposure - -3. **Remediation**: - - Patch vulnerabilities - - Update dependencies - - Reset affected user passwords - - Notify affected users (if required by law) - -4. **Prevention**: - - Document incident - - Update security procedures - - Implement additional monitoring - - Conduct security training - ---- - -## 📝 NEXT STEPS - -### Week 1 (Critical) -1. ✅ **Update dependencies** (5 minutes) - ```bash - npm audit fix - npm update - ``` - -2. ✅ **Rotate all secrets** (15 minutes) - ```bash - # Generate new secrets - node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" - # Update .env with new values - ``` - -3. ✅ **Fix hardcoded salt** (10 minutes) - - Update `src/utils/password.js` - - Test encryption/decryption still works - -### Week 2-4 (High Priority) -4. Implement session tracking in database -5. Add session invalidation on password change -6. Set up Dependabot/Snyk -7. Enhance logging security - -### Month 2-3 (Medium Priority) -8. Add virus scanning for files -9. Implement 2FA -10. Set up audit log retention -11. Enhance password policies - ---- - -## 📚 SECURITY RESOURCES - -- [OWASP Top 10](https://owasp.org/www-project-top-ten/) -- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/) -- [Express.js Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html) -- [JWT Security Best Practices](https://datatracker.ietf.org/doc/html/rfc8725) -- [PostgreSQL Security](https://www.postgresql.org/docs/current/security.html) - ---- - -## ✅ CONCLUSION - -Your CRM server demonstrates **good security awareness** with proper implementation of authentication, authorization, input validation, and rate limiting. The identified vulnerabilities are **manageable and fixable** within a reasonable timeframe. - -**Priority focus areas**: -1. Update dependencies immediately -2. Strengthen secret management -3. Improve session security -4. Enhance file upload security - -With these improvements, your application will achieve **production-grade security** suitable for handling sensitive customer data. - ---- - -**Report Generated**: 2025-12-02 -**Next Review Recommended**: 2025-03-02 (Quarterly) -**Security Contact**: security@your-domain.com (update this) -