From 3f5f4ed8055b9877058e56d0b29d23c977df2056 Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:35:35 +0530 Subject: [PATCH 1/9] =?UTF-8?q?docs:=20add=2000-master-architecture-index?= =?UTF-8?q?=20=E2=80=94=20sitemap=20for=2062=20feature=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/00-master-architecture-index.md | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/features/00-master-architecture-index.md diff --git a/docs/features/00-master-architecture-index.md b/docs/features/00-master-architecture-index.md new file mode 100644 index 00000000..e9f3a430 --- /dev/null +++ b/docs/features/00-master-architecture-index.md @@ -0,0 +1,220 @@ +# 00 — Zync Master Architecture Index + +**Version:** 2026.3 +**Status:** Production Verified +**Last Updated:** August 2026 + +--- + +## Overview + +Zync is an enterprise-grade, local-first collaborative workspace platform that unifies project management, real-time collaboration, AI-driven architecture generation, and developer tooling into a single high-performance web application. + +This document serves as the **master index** for all 62 feature architecture documents. Each linked file contains an exhaustive trace of the feature's frontend, backend, database, socket, and external service layers with exact file paths and line numbers. + +--- + +## Architecture at a Glance + +``` +┌─────────────────────────────────────────────────────────────────────────┐ +│ CLIENT (Browser / PWA) │ +│ React 19 + Vite + TypeScript + Tailwind CSS v4 + Radix UI + Mantine │ +│ TanStack Query (REST cache) + Jotai (UI state) + Dexie (IndexedDB) │ +│ Yjs (CRDT) + BlockNote (rich text) + Framer Motion (animations) │ +└──────────────┬──────────────────────────┬───────────────────────────────┘ + │ REST / HTTP(S) │ WebSocket (Socket.IO) + ▼ ▼ +┌──────────────────────────┐ ┌──────────────────────────────────────────┐ +│ EXPRESS 5 API GATEWAY │ │ SOCKET.IO GATEWAY │ +│ helmet + CORS + rate │ │ /notes /chat /tasks /presence │ +│ limit + load shedding │ │ Yjs binary relay + presence tracking │ +│ Firebase JWT verify │ │ Real-time event broadcasting │ +└──────────┬───────────────┘ └──────────────────┬───────────────────────┘ + │ │ + ▼ ▼ +┌─────────────────────────────────────────────────────────────────────────┐ +│ APPLICATION LOGIC (Node.js) │ +│ 23 Route files │ 14 Services │ 21 Utils │ 4 Socket Handlers │ +│ 4 Middleware │ 13 Models │ 10 Email Templates │ +└──────────┬───────────────┬───────────────┬──────────────┬────────────────┘ + │ │ │ │ + ▼ ▼ ▼ ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ +│ MongoDB │ │ Redis │ │ Firebase │ │ External APIs │ +│ Atlas │ │ Cache + │ │ Auth + │ │ GitHub, Google, │ +│ Prisma + │ │ Pub/Sub │ │ Firestore + │ │ Cloudinary, Groq, │ +│ Mongoose │ │ │ │ FCM + Store │ │ Kilo Gateway, HIBP │ +└──────────────┘ └──────────────┘ └──────────────┘ └──────────────────────┘ +``` + +--- + +## Document Index + +### Group 1: Core Infrastructure (Files 00–07) + +| # | Document | Scope | +|---|---|---| +| 00 | **[00-master-architecture-index.md](./00-master-architecture-index.md)** | This file — master sitemap | +| 01 | [01-tech-stack-overview.md](./01-tech-stack-overview.md) | Frontend, backend, database, and third-party SDK breakdown | +| 02 | [02-security-auth-architecture.md](./02-security-auth-architecture.md) | Firebase JWT, helmet, CORS, rate limiting, encryption, HIBP | +| 03 | [03-performance-caching-strategy.md](./03-performance-caching-strategy.md) | Redis tiers, TanStack persister, connection pooling, load shedding | +| 04 | [04-service-inventory.md](./04-service-inventory.md) | Every cloud service, hosting provider, and external API | +| 05 | [05-database-schema-and-models.md](./05-database-schema-and-models.md) | Complete Prisma schema + Mongoose ODM models | +| 06 | [06-middleware-stack.md](./06-middleware-stack.md) | helmet CSP, CORS, rate limit, load shedding, auth, Zod validation | +| 07 | [07-environment-variables.md](./07-environment-variables.md) | Complete env var reference with types, defaults, and descriptions | + +### Group 2: Authentication & User (Files 08–13) + +| # | Document | Scope | +|---|---|---| +| 08 | [08-firebase-auth-flow.md](./08-firebase-auth-flow.md) | Login, signup, OTP, phone verification, token refresh | +| 09 | [09-user-profile-management.md](./09-user-profile-management.md) | Profile CRUD, avatar upload, settings, departments | +| 10 | [10-account-deletion-flow.md](./10-account-deletion-flow.md) | Deletion with email confirmation code, cascade cleanup | +| 11 | [11-presence-system.md](./11-presence-system.md) | Online/offline/away states, lastSeen, Socket.IO presence | +| 12 | [12-haveibeenpwned-integration.md](./12-haveibeenpwned-integration.md) | K-anonymity SHA-256 prefix matching for breach checks | +| 13 | [13-linkedin-oauth-integration.md](./13-linkedin-oauth-integration.md) | LinkedIn OAuth flow, token storage, profile sync | + +### Group 3: Project Management (Files 14–19) + +| # | Document | Scope | +|---|---|---| +| 14 | [14-project-crud-lifecycle.md](./14-project-crud-lifecycle.md) | Create, read, update, delete projects; team assignment | +| 15 | [15-ai-project-generation.md](./15-ai-project-generation.md) | Groq SDK project scaffolding, JSON schema, Mongoose bulk insert | +| 16 | [16-project-steps-and-tasks.md](./16-project-steps-and-tasks.md) | Step/Task hierarchy, display IDs, assignment, status workflow | +| 17 | [17-ai-task-generator.md](./17-ai-task-generator.md) | Groq-powered task generation from architecture JSON | +| 18 | [18-project-architecture-agent.md](./18-project-architecture-agent.md) | Kilo Code Gateway chat → structured JSON architecture maps | +| 19 | [19-project-details-page.md](./19-project-details-page.md) | Frontend ProjectDetails page, tabs, step navigation | + +### Group 4: Task Board & GitHub Sync (Files 20–25) + +| # | Document | Scope | +|---|---|---| +| 20 | [20-kanban-task-board.md](./20-kanban-task-board.md) | Drag-and-drop Kanban, status columns, TaskBoardView | +| 21 | [21-task-socket-realtime.md](./21-task-socket-realtime.md) | Socket.IO /tasks namespace, live updates, task broadcasting | +| 22 | [22-github-oauth-integration.md](./22-github-oauth-integration.md) | GitHub App OAuth, installation flow, token encryption | +| 23 | [23-github-app-webhooks.md](./23-github-app-webhooks.md) | Webhook queue, HMAC validation, event processing worker | +| 24 | [24-github-collaborator-invites.md](./24-github-collaborator-invites.md) | Octokit invitations, OTP verification, ContributorTicket | +| 25 | [25-commit-analysis-service.md](./25-commit-analysis-service.md) | Groq commit analysis, task linkage, commit metadata | + +### Group 5: Real-Time Chat (Files 26–30) + +| # | Document | Scope | +|---|---|---| +| 26 | [26-instant-chat-system.md](./26-instant-chat-system.md) | Chat architecture, Socket.IO /chat, message model | +| 27 | [27-chat-socket-handler.md](./27-chat-socket-handler.md) | Connection management, event handlers, offline catchup | +| 28 | [28-chat-message-persistence.md](./28-chat-message-persistence.md) | MongoDB Message model, chatId derivation, seen/delivered | +| 29 | [29-chat-notifications.md](./29-chat-notifications.md) | FCM push, in-app toast, chat request notifications | +| 30 | [30-chat-request-flow.md](./30-chat-request-flow.md) | Send/accept/reject requests, connections, close friends | + +### Group 6: Collaborative Notes (Files 31–35) + +| # | Document | Scope | +|---|---|---| +| 31 | [31-realtime-notes-editor.md](./31-realtime-notes-editor.md) | Yjs CRDT, BlockNote, IndexedDB persistence, binary state | +| 32 | [32-note-socket-handler.md](./32-note-socket-handler.md) | Socket.IO /notes namespace, Yjs update relay, awareness | +| 33 | [33-notes-crud-and-folders.md](./33-notes-crud-and-folders.md) | Note CRUD, Folder hierarchy, project linking | +| 34 | [34-note-sharing-and-permissions.md](./34-note-sharing-and-permissions.md) | sharedWith array, permission checks, collaborative editing | +| 35 | [35-notes-frontend-service.md](./35-notes-frontend-service.md) | notesService.ts, useNotes hook, useNotePresence hook | + +### Group 7: Google Workspace (Files 36–40) + +| # | Document | Scope | +|---|---|---| +| 36 | [36-google-oauth-integration.md](./36-google-oauth-integration.md) | Google Integration JSON, refresh token, calendar scope | +| 37 | [37-google-calendar-integration.md](./37-google-calendar-integration.md) | Calendar list, events CRUD, timezone handling | +| 38 | [38-google-meet-integration.md](./38-google-meet-integration.md) | Meet link generation, Meeting model, participant management | +| 39 | [39-google-sheets-logger.md](./39-google-sheets-logger.md) | SheetLogger service, audit logging, Google Sheets API | +| 40 | [40-google-integration-frontend.md](./40-google-integration-frontend.md) | SettingsView Google section, connect/disconnect UI | + +### Group 8: Team Management (Files 41–44) + +| # | Document | Scope | +|---|---|---| +| 41 | [41-team-crud-and-invites.md](./41-team-crud-and-invites.md) | Team CRUD, invite codes, member management | +| 42 | [42-team-firebase-sync.md](./42-team-firebase-sync.md) | Firestore sync, real-time team updates, member presence | +| 43 | [43-team-onboarding-flow.md](./43-team-onboarding-flow.md) | CreateTeamDialog, JoinTeamDialog, TeamOnboarding | +| 44 | [44-team-settings-enhancements.md](./44-team-settings-enhancements.md) | Team settings, quick chat, type categorization | + +### Group 9: Design & Inspiration (Files 45–47) + +| # | Document | Scope | +|---|---|---| +| 45 | [45-design-inspiration-service.md](./45-design-inspiration-service.md) | Inspiration routes, Redis cache, DesignView frontend | +| 46 | [46-scraper-service.md](./46-scraper-service.md) | Puppeteer Extra, stealth plugin, request interception | +| 47 | [47-design-view-frontend.md](./47-design-view-frontend.md) | DesignView component, inspiration grid, filtering | + +### Group 10: File Upload & Media (Files 48–50) + +| # | Document | Scope | +|---|---|---| +| 48 | [48-cloudinary-upload-service.md](./48-cloudinary-upload-service.md) | Cloudinary uploads, stream-based, avatar transformations | +| 49 | [49-profile-photo-cropper.md](./49-profile-photo-cropper.md) | react-easy-crop, WebP conversion, upload pipeline | +| 50 | [50-image-optimizer.md](./50-image-optimizer.md) | Sharp-free optimization, dimension constraints, format | + +### Group 11: Notifications (Files 51–54) + +| # | Document | Scope | +|---|---|---| +| 51 | [51-fcm-backend-setup.md](./51-fcm-backend-setup.md) | Firebase Admin FCM, push notification service | +| 52 | [52-fcm-frontend-setup.md](./52-fcm-frontend-setup.md) | VAPID keys, service worker, token registration | +| 53 | [53-notification-permission-flow.md](./53-notification-permission-flow.md) | Permission prompt, PWA install wall, WakeUpService | +| 54 | [54-push-notification-service.md](./54-push-notification-service.md) | pushNotificationService.js, payload structure, delivery | + +### Group 12: UI/UX System (Files 55–59) + +| # | Document | Scope | +|---|---|---| +| 55 | [55-agentic-liquid-glass-ui.md](./55-agentic-liquid-glass-ui.md) | Design tokens, backdrop-filter, 9-state components | +| 56 | [56-loading-animation-strategy.md](./56-loading-animation-strategy.md) | Skeleton shimmers, typographic glass lifts, transitions | +| 57 | [57-pwa-install-wall.md](./57-pwa-install-wall.md) | Install prompt, beforeinstallprompt, PWA detection | +| 58 | [58-dashboard-layout-system.md](./58-dashboard-layout-system.md) | DashboardView, sidebar, responsive layout, mobile view | +| 59 | [59-activity-tracking-system.md](./59-activity-tracking-system.md) | Session model, activity tracker hook, duration calc | + +### Group 13: Support & Misc (Files 60–62) + +| # | Document | Scope | +|---|---|---| +| 60 | [60-support-ticket-system.md](./60-support-ticket-system.md) | Support routes, email templates, ticket lifecycle | +| 61 | [61-link-preview-service.md](./61-link-preview-service.md) | Link routes, metadata scraping, OG image extraction | +| 62 | [62-internal-metrics.md](./62-internal-metrics.md) | Internal API, admin secret, usage stats, health monitoring | + +--- + +## Key Source Directories + +| Directory | Purpose | Key Files | +|---|---|---| +| `backend/routes/` | Express route handlers (23 files) | `projectRoutes.js`, `chatRoutes.js`, `noteRoutes.js`, `github.js`, `meetRoutes.js` | +| `backend/services/` | Business logic services (14 files) | `firebaseAdmin.js`, `cloudinaryService.js`, `googleMeet.js`, `scraperService.js` | +| `backend/sockets/` | Socket.IO handlers (4 files) | `chatSocketHandler.js`, `noteSocketHandler.js`, `taskSocketHandler.js`, `presenceSocketHandler.js` | +| `backend/models/` | Mongoose ODM models (13 files) | `User.js`, `Project.js`, `Message.js`, `Note.js`, `Team.js` | +| `backend/middleware/` | Express middleware (4 files) | `authMiddleware.js`, `loadShedding.js`, `validation.js`, `verifyGithub.js` | +| `backend/utils/` | Utility functions (21 files) | `encryption.js`, `redisClient.js`, `githubAppAuth.js`, `cache.js` | +| `backend/prisma/` | Prisma schema + generated client | `schema.prisma` | +| `src/components/views/` | Main UI views (27+ files) | `DashboardView.tsx`, `ChatView.tsx`, `TaskBoardView.tsx`, `SettingsView.tsx` | +| `src/hooks/` | React hooks (21 files) | `useNotes.ts`, `usePresence.ts`, `useProjects.ts`, `use-task-updates.ts` | +| `src/lib/` | Frontend utilities (14 files) | `firebase.ts`, `SocketIOProvider.tsx`, `db.ts`, `query-client.ts` | +| `src/services/` | Frontend services (4 files) | `chatSocketService.ts`, `notesService.ts`, `taskSocketService.ts` | + +--- + +## Collaborators + +| Name | GitHub | Role | +|---|---|---| +| Chitkul Lakshya | `ChitkulLakshya` | Primary maintainer, architecture | +| Prem | `prem22k` | Admin, infrastructure | +| Thanmayee Reddy Kotha | `thanmayeereddykotha` | Admin, backend features | +| Eeshitha Gone | `eesha264` | Admin, frontend + integrations | + +--- + +## Related Documentation + +- [Original Architecture Docs](../architecture/) — Pre-refactor architecture documents +- [Bug Fix Logs](../bug-fixes/) — Resolved issues and their fixes +- [Guides](../guides/) — Setup and onboarding guides +- [Security](../security/) — Privacy policy and security overview +- [Plans](../plans/) — Execution plans and roadmap From 4ffb7178f869a0a125543d0fc321a7aca8483f9f Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:37:34 +0530 Subject: [PATCH 2/9] =?UTF-8?q?docs:=20add=2001-tech-stack-overview=20?= =?UTF-8?q?=E2=80=94=20complete=20frontend/backend/devops=20breakdown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/01-tech-stack-overview.md | 274 ++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 docs/features/01-tech-stack-overview.md diff --git a/docs/features/01-tech-stack-overview.md b/docs/features/01-tech-stack-overview.md new file mode 100644 index 00000000..214dea84 --- /dev/null +++ b/docs/features/01-tech-stack-overview.md @@ -0,0 +1,274 @@ +# 01 — Tech Stack Overview + +**Refactored from:** `docs/architecture/tech_stack_overview.md` +**Verified against:** `package.json` (root) + `backend/package.json` + +--- + +## Feature Summary + +Complete breakdown of every technology, framework, library, and SDK used across the Zync monorepo. This is the single source of truth for the tech stack. + +--- + +## Architecture Diagram + +``` +┌─────────────────────── FRONTEND ───────────────────────┐ +│ React 19 + Vite 8 + TypeScript 6 │ +│ Tailwind CSS v4 + Radix UI + Mantine 9 │ +│ Framer Motion + Lucide Icons │ +│ │ +│ State: TanStack Query 5 + Jotai │ +│ Cache: localStorage persister + Dexie (IndexedDB) │ +│ CRDT: Yjs 13 + y-indexeddb + y-protocols │ +│ Editor: BlockNote 0.51 + react-markdown │ +│ DnD: @dnd-kit/core + sortable │ +│ Charts: recharts 3 + chart.js 4 │ +│ Calendar: react-big-calendar + react-day-picker │ +│ Realtime: socket.io-client 4 │ +│ PWA: vite-plugin-pwa │ +│ Crop: react-easy-crop 6 │ +│ Flow: @xyflow/react (architecture diagrams) │ +│ Layout: react-resizable-panels │ +│ Forms: react-hook-form 7 │ +│ OTP: input-otp │ +│ Emoji: emoji-picker-react │ +└────────────────────────────────────────────────────────┘ + +┌─────────────────────── BACKEND ────────────────────────┐ +│ Node.js 22+ + Express 5 + CommonJS │ +│ │ +│ Security: helmet 8 + cors + express-rate-limit 8 │ +│ Validation: zod 4 │ +│ Auth: firebase-admin 14 │ +│ Crypto: crypto-js 4 (AES-256) + bcryptjs 3 │ +│ │ +│ Database: MongoDB Atlas (primary) │ +│ Prisma 5 (relational queries: Projects, Teams) │ +│ Mongoose 9 (flexible docs: Chat, Notes, AI) │ +│ Cache: redis 5 (cache + Pub/Sub) │ +│ │ +│ Realtime: socket.io 4 (4 namespaces) │ +│ Upload: multer 2 + cloudinary 2 │ +│ Image: sharp 0.35 │ +│ │ +│ AI: groq-sdk 0.36 + Kilo Code Gateway (REST) │ +│ Scrape: puppeteer 25 + puppeteer-extra-stealth │ +│ cheerio 1 + rss-parser 3 │ +│ │ +│ Integrations: │ +│ GitHub: octokit 5 │ +│ Google: googleapis 173 │ +│ Email: nodemailer 9 │ +│ PDF: pg 8 (PostgreSQL for sheet logging) │ +└────────────────────────────────────────────────────────┘ + +┌─────────────────── DEVOPS & TESTING ───────────────────┐ +│ Dev: concurrently 10 + wait-on 9 + nodemon 3 │ +│ Build: Vite 8 (frontend) + Prisma generate (backend) │ +│ Lint: eslint 9 + typescript-eslint 8 │ +│ Format: prettier 3 + husky 9 + lint-staged 17 │ +│ Test: jest 30 (backend) + vitest 4 (frontend) │ +│ playwright 1.49 (E2E) │ +│ mongodb-memory-server 11 (test DB) │ +│ msw 2 (mock service worker) │ +│ Storybook: 10.3 │ +│ Commits: commitizen + cz-conventional-changelog │ +└────────────────────────────────────────────────────────┘ +``` + +--- + +## Frontend Dependencies (from `package.json`) + +### Core Framework +| Package | Version | Purpose | +|---|---|---| +| `react` | ^19.2.7 | UI framework | +| `react-dom` | ^19.2.7 | DOM renderer | +| `react-router-dom` | ^7.18.1 | Client-side routing | +| `vite` | ^8.1.0 | Build tool + dev server | +| `typescript` | ^6.0.3 | Type safety | + +### State & Caching +| Package | Version | Purpose | +|---|---|---| +| `@tanstack/react-query` | ^5.90.21 | Server state + REST cache | +| `@tanstack/query-sync-storage-persister` | ^5.96.1 | localStorage persistence | +| `@tanstack/react-query-persist-client` | ^5.96.1 | Persist query cache | +| `dexie` | ^4.4.2 | IndexedDB wrapper | +| `dexie-react-hooks` | ^4.4.0 | React hooks for Dexie | +| `yjs` | ^13.6.31 | CRDT engine | +| `y-indexeddb` | ^9.0.12 | Yjs offline persistence | +| `y-protocols` | ^1.0.7 | Yjs awareness protocol | + +### UI & Styling +| Package | Version | Purpose | +|---|---|---| +| `tailwindcss` | ^4.1.18 | Utility CSS framework | +| `@tailwindcss/postcss` | ^4.1.18 | PostCSS integration | +| `tailwind-merge` | ^3.6.0 | Class deduplication | +| `tailwindcss-animate` | ^1.0.7 | Animation utilities | +| `framer-motion` | 12.42.2 | Spring physics animations | +| `lucide-react` | ^1.31.0 | Icon set | +| `@radix-ui/react-*` | various | 25+ accessible primitives | +| `@mantine/core` | ^9.4.0 | Advanced components | +| `@mantine/hooks` | ^9.4.0 | Mantine utilities | +| `class-variance-authority` | ^0.7.1 | Variant management | +| `clsx` | ^2.1.1 | Conditional classes | +| `sonner` | ^2.0.7 | Toast notifications | +| `vaul` | ^1.1.2 | Drawer component | +| `cmdk` | ^1.1.1 | Command palette | + +### Editor & Content +| Package | Version | Purpose | +|---|---|---| +| `@blocknote/core` | ^0.51.4 | Block editor engine | +| `@blocknote/react` | ^0.51.4 | React bindings | +| `@blocknote/mantine` | ^0.51.4 | Mantine theme | +| `react-markdown` | ^10.1.0 | Markdown rendering | +| `remark-gfm` | ^4.0.1 | GitHub-flavored markdown | +| `emoji-picker-react` | ^4.18.0 | Emoji picker | + +### Data Visualization +| Package | Version | Purpose | +|---|---|---| +| `recharts` | ^3.8.1 | Chart library | +| `chart.js` | ^4.5.1 | Canvas charts | +| `@xyflow/react` | ^12.11.2 | Architecture diagram flow | +| `elkjs` | ^0.12.0 | Layout algorithm for flow | + +### Calendar & Date +| Package | Version | Purpose | +|---|---|---| +| `react-big-calendar` | ^1.19.4 | Calendar component | +| `react-day-picker` | ^9.14.0 | Date picker | +| `date-fns` | ^4.1.0 | Date utilities | + +### Drag & Drop +| Package | Version | Purpose | +|---|---|---| +| `@dnd-kit/core` | ^6.3.1 | DnD engine | +| `@dnd-kit/sortable` | ^10.0.0 | Sortable preset | +| `@dnd-kit/utilities` | ^3.2.2 | DnD utilities | + +### Other +| Package | Version | Purpose | +|---|---|---| +| `socket.io-client` | ^4.8.3 | WebSocket client | +| `firebase` | ^12.9.0 | Client-side Firebase SDK | +| `googleapis` | 173.0.0 | Google API client | +| `input-otp` | ^1.4.2 | OTP input component | +| `react-easy-crop` | ^6.0.2 | Image cropper | +| `react-hook-form` | ^7.71.1 | Form management | +| `react-resizable-panels` | ^4.11.2 | Resizable layout panels | +| `simple-icons` | ^16.27.1 | Tech brand icons | +| `next-themes` | ^0.4.6 | Theme switching | + +--- + +## Backend Dependencies (from `backend/package.json`) + +### Core +| Package | Version | Purpose | +|---|---|---| +| `express` | ^5.2.1 | HTTP framework | +| `socket.io` | ^4.8.3 | WebSocket server | +| `helmet` | ^8.1.0 | Security headers | +| `cors` | ^2.8.5 | Cross-origin config | +| `express-rate-limit` | ^8.3.1 | Rate limiting | +| `zod` | 4.4.3 | Schema validation | +| `dotenv` | ^17.4.2 | Env var loading | + +### Database +| Package | Version | Purpose | +|---|---|---| +| `mongoose` | 9.9.2 | MongoDB ODM | +| `@prisma/client` | ^5.22.0 | MongoDB ORM | +| `redis` | ^5.10.0 | Cache + Pub/Sub | +| `pg` | ^8.16.3 | PostgreSQL (sheet logging) | + +### Auth & Security +| Package | Version | Purpose | +|---|---|---| +| `firebase-admin` | ^14.2.0 | JWT verification + FCM | +| `crypto-js` | ^4.2.0 | AES-256 encryption | +| `bcryptjs` | ^3.0.3 | Password hashing | + +### File & Media +| Package | Version | Purpose | +|---|---|---| +| `multer` | ^2.0.2 | Multipart uploads | +| `cloudinary` | 2.10.0 | Image CDN | +| `sharp` | ^0.35.3 | Image processing | +| `mime-types` | ^3.0.2 | MIME type detection | + +### AI & Scraping +| Package | Version | Purpose | +|---|---|---| +| `groq-sdk` | ^0.36.0 | Groq LPU API client | +| `puppeteer` | ^25.6.0 | Headless browser | +| `puppeteer-extra` | ^3.3.6 | Puppeteer plugins | +| `puppeteer-extra-plugin-stealth` | ^2.10.4 | Stealth evasion | +| `cheerio` | ^1.0.0-rc.12 | HTML parsing | +| `rss-parser` | ^3.13.0 | RSS feed parsing | + +### Integrations +| Package | Version | Purpose | +|---|---|---| +| `octokit` | ^5.0.4 | GitHub API SDK | +| `googleapis` | ^173.0.0 | Google API SDK | +| `nodemailer` | ^9.0.1 | Email sending | +| `axios` | 1.19.0 | HTTP client | +| `yjs` | 13.6.32 | CRDT (server-side relay) | + +--- + +## DevOps & Testing + +| Package | Version | Purpose | +|---|---|---| +| `jest` | ^30.4.2 | Backend unit tests | +| `vitest` | ^4.1.9 | Frontend unit tests | +| `@playwright/test` | ^1.49.1 | E2E tests | +| `mongodb-memory-server` | ^11.2.0 | In-memory MongoDB for tests | +| `msw` | ^2.12.10 | Mock service worker | +| `supertest` | ^7.2.2 | HTTP assertion tests | +| `eslint` | ^9.39.5 | Code linting | +| `prettier` | ^3.4.2 | Code formatting | +| `husky` | ^9.1.7 | Git hooks | +| `lint-staged` | 17.0.8 | Staged file linting | +| `concurrently` | 10.0.3 | Parallel dev servers | +| `storybook` | ^10.3.3 | Component documentation | +| `vite-plugin-pwa` | ^1.2.0 | PWA manifest + service worker | + +--- + +## NPM Scripts + +### Root (`package.json`) +| Script | Command | Purpose | +|---|---|---| +| `dev` | `node scripts/run-dev.cjs` | Start both frontend + backend | +| `start` | `node backend/index.js` | Production start (backend only) | +| `build` | `vite build` | Build frontend SPA | +| `test` | `test:frontend && test:backend` | Run all tests | +| `test:e2e` | `playwright test` | E2E tests | +| `lint` | `eslint .` | Lint everything | + +### Backend (`backend/package.json`) +| Script | Command | Purpose | +|---|---|---| +| `start` | `node index.js` | Start API server | +| `dev:watch` | `nodemon index.js` | Auto-restart on changes | +| `build` | `npx prisma generate` | Generate Prisma client | +| `test` | `jest` | Run backend tests | + +--- + +## Cross-References + +- [04-service-inventory.md](./04-service-inventory.md) — Cloud services and hosting +- [05-database-schema-and-models.md](./05-database-schema-and-models.md) — Prisma + Mongoose schema +- [06-middleware-stack.md](./06-middleware-stack.md) — Express middleware chain From 409bea8709aa599884d93dec9583939a35ec4044 Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:38:27 +0530 Subject: [PATCH 3/9] =?UTF-8?q?docs:=20add=2002-security-auth-architecture?= =?UTF-8?q?=20=E2=80=94=20JWT,=20helmet,=20CORS,=20rate=20limit,=20encrypt?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/02-security-auth-architecture.md | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 docs/features/02-security-auth-architecture.md diff --git a/docs/features/02-security-auth-architecture.md b/docs/features/02-security-auth-architecture.md new file mode 100644 index 00000000..0f9acab8 --- /dev/null +++ b/docs/features/02-security-auth-architecture.md @@ -0,0 +1,193 @@ +# 02 — Security & Auth Architecture + +**Refactored from:** `docs/architecture/security_and_auth_architecture.md` + +--- + +## Feature Summary + +Zync's security architecture spans four layers: Firebase JWT authentication, HTTP security headers (helmet + CSP), API rate limiting + load shedding, and AES-256 encryption for stored tokens. This document traces every security component end-to-end. + +--- + +## Architecture Diagram + +``` +┌─────────────────── CLIENT ───────────────────────────┐ +│ Firebase Client SDK (firebase 12) │ +│ ├─ signInWithEmailAndPassword() │ +│ ├─ signInWithPopup(GoogleAuthProvider) │ +│ ├─ createUserWithEmailAndPassword() │ +│ └─ getIdToken() → Bearer token in Authorization │ +└──────────────────────┬───────────────────────────────┘ + │ Authorization: Bearer + ▼ +┌─────────────────── EXPRESS MIDDLEWARE ───────────────┐ +│ 1. helmet() — CSP, X-Frame-Options, HSTS │ +│ 2. cors(corsOptions) — origin allowlist │ +│ 3. rateLimit — 100 req/15min (prod) / 600/min (dev) │ +│ 4. loadSheddingMiddleware — 503 if heap > 400MB │ +│ 5. verifyToken — Firebase Admin verifyIdToken() │ +│ 6. validate(schema) — Zod request validation │ +│ 7. verifyGithub — HMAC SHA-256 webhook validation │ +└──────────────────────┬───────────────────────────────┘ + │ + ▼ +┌─────────────────── ENCRYPTION LAYER ─────────────────┐ +│ crypto-js AES-256 (MASTER_ENCRYPTION_KEY) │ +│ ├─ GitHub tokens (githubIntegration JSON on User) │ +│ ├─ Google refresh tokens (googleIntegration JSON) │ +│ └─ GitHub App private key (githubAppAuth.js) │ +│ │ +│ bcryptjs — password hashing (if local auth used) │ +│ haveIBeenPwned — K-anonymity breach check │ +└──────────────────────────────────────────────────────┘ +``` + +--- + +## Backend Trace + +### 1. Helmet — HTTP Security Headers +**File:** `backend/index.js:176-234` +- Content-Security-Policy with strict directives +- `default-src: 'self'` +- `script-src: 'self' 'unsafe-inline' 'unsafe-eval' blob: https://apis.google.com` +- `connect-src: 'self' https://github.com https://api.github.com https://*.googleapis.com https://*.firebaseio.com` +- `img-src: 'self' data: https://avatars.githubusercontent.com https://res.cloudinary.com` +- `frame-src: 'self' https://github.com https://*.firebaseapp.com` +- `crossOriginEmbedderPolicy: false` (for cross-origin resources) +- `referrerPolicy: strict-origin-when-cross-origin` + +### 2. CORS — Origin Allowlist +**File:** `backend/index.js:106-135` +- Allowed origins: `localhost:5173`, `localhost:8080-8083`, `localhost:3000`, `ALLOWED_ORIGINS` env var, `FRONTEND_URL` env var +- Methods: GET, POST, PUT, DELETE, OPTIONS +- `credentials: true` for cookies +- Non-origin requests (curl, server-to-server) are allowed + +### 3. Rate Limiting +**File:** `backend/index.js:238-252` +- Applied to all `/api/` routes +- Production: 100 requests per 15 minutes per IP +- Development: 600 requests per minute +- Returns 429 with `{ message, status }` body +- `standardHeaders: true` (RateLimit-* headers) +- `legacyHeaders: false` (no X-RateLimit-* headers) + +### 4. Load Shedding +**File:** `backend/middleware/loadShedding.js:75-135` +- Monitors `process.memoryUsage().heapUsed` +- Threshold: `LOAD_SHED_HEAP_LIMIT_MB` (default 400MB) +- Only sheds "heavy" paths: `/api/github-app/webhook`, `/api/webhooks/github`, `/api/generate-project`, `/api/design`, `/api/inspiration` +- Allowlist (never shed): `/api/auth`, `/api/sessions`, `/api/chat` +- Returns 503 with `Retry-After` header + +### 5. Firebase JWT Verification +**File:** `backend/middleware/authMiddleware.js:76-119` +- Extracts `Bearer ` from `Authorization` header +- Calls `getAuth().verifyIdToken(token)` via Firebase Admin SDK +- Sets `req.user = { uid, email }` on success +- Returns 401 if no token, 403 if invalid token +- Firebase Admin initialized at module load via `firebaseAdmin.js` + +### 6. Zod Validation +**File:** `backend/middleware/validation.js:76-105` +- Higher-order function: `validate(schema)` returns middleware +- Validates `req.body`, `req.query`, `req.params` against Zod schema +- Returns 400 with array of `{ path, message }` errors + +### 7. GitHub Webhook HMAC Verification +**File:** `backend/middleware/verifyGithub.js` +- Validates `x-hub-signature-256` header +- HMAC SHA-256 with `GITHUB_WEBHOOK_SECRET` +- Used on `/api/webhooks/github` and `/api/github-app` routes + +### 8. Internal API Auth +**File:** `backend/index.js:294-304` +- `x-internal-secret` header must match `INTERNAL_API_SECRET` env var +- Protects `/internal/*` routes (metrics, health stats) + +--- + +## Encryption Layer + +### AES-256 Token Encryption +**File:** `backend/utils/encryption.js` +- Uses `crypto-js` AES-256 with `MASTER_ENCRYPTION_KEY` env var +- `encrypt(text)` → returns `{ encrypted, iv, tag }` +- `decrypt(data)` → returns plaintext +- Used for: + - GitHub OAuth tokens stored on `User.githubIntegration` + - Google refresh tokens stored on `User.googleIntegration` + - GitHub App private key in `githubAppAuth.js` + +### Password Breach Check +**File:** `backend/services/haveIBeenPwnedService.js` +- K-anonymity model: sends only first 5 chars of SHA-1 hash +- Never transmits cleartext passwords +- Returns count of breach matches + +--- + +## Frontend Trace + +### Firebase Client SDK +**File:** `src/lib/firebase.ts` +- Initializes Firebase app with `VITE_FIREBASE_*` env vars +- Exports `auth` instance for `signInWithEmailAndPassword`, `signInWithPopup`, etc. +- Google OAuth provider configured with calendar + email scopes + +### Auth Header Injection +**File:** `src/lib/auth-headers.ts` +- `getAuthHeaders()` — returns `{ Authorization: 'Bearer ', 'Content-Type': 'application/json' }` +- Gets token from `auth.currentUser.getIdToken()` +- Used by all API calls via TanStack Query + +### Auth Sign-Out +**File:** `src/lib/auth-signout.ts` +- Calls `auth.signOut()` +- Clears TanStack Query cache +- Redirects to login page + +--- + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `MASTER_ENCRYPTION_KEY` | Yes | AES-256 encryption key for token storage | +| `GITHUB_WEBHOOK_SECRET` | Yes | HMAC SHA-256 secret for webhook validation | +| `INTERNAL_API_SECRET` | Yes | Secret for `/internal/*` API routes | +| `ALLOWED_ORIGINS` | No | Comma-separated CORS origins | +| `FRONTEND_URL` | Yes | Frontend URL for CORS | +| `LOAD_SHED_HEAP_LIMIT_MB` | No | Heap threshold for load shedding (default 400) | +| `LOAD_SHED_RETRY_AFTER_SECONDS` | No | Retry-After header value (default 15) | +| `LOAD_SHED_HEAVY_PATHS` | No | Custom comma-separated heavy paths | +| `VITE_FIREBASE_API_KEY` | Yes | Firebase client API key | +| `VITE_FIREBASE_AUTH_DOMAIN` | Yes | Firebase auth domain | +| `VITE_FIREBASE_PROJECT_ID` | Yes | Firebase project ID | + +--- + +## Error Paths + +| Scenario | HTTP Status | Response | +|---|---|---| +| No Authorization header | 401 | `{ message: "Unauthorized: No token provided" }` | +| Invalid/expired JWT | 403 | `{ message: "Unauthorized: " }` | +| Rate limit exceeded | 429 | `{ message: "Too many requests...", status: 429 }` | +| Load shedding active | 503 | `{ message: "Service under memory pressure", reason: "load_shedding" }` | +| Zod validation failed | 400 | `{ message: "Validation Error", errors: [...] }` | +| Webhook HMAC mismatch | 403 | `{ message: "Forbidden" }` | +| Internal API no secret | 503 | `{ message: "Internal API not configured" }` | +| Internal API wrong secret | 403 | `{ message: "Forbidden" }` | + +--- + +## Cross-References + +- [06-middleware-stack.md](./06-middleware-stack.md) — Full middleware chain detail +- [08-firebase-auth-flow.md](./08-firebase-auth-flow.md) — Login/signup/OTP flow +- [12-haveibeenpwned-integration.md](./12-haveibeenpwned-integration.md) — Breach check deep dive +- [22-github-oauth-integration.md](./22-github-oauth-integration.md) — GitHub token encryption From 1e6f92f1306f29b9226e956fe807ad5e4544441b Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:39:58 +0530 Subject: [PATCH 4/9] =?UTF-8?q?docs:=20add=2003-performance-caching=20+=20?= =?UTF-8?q?04-service-inventory=20=E2=80=94=20Redis,=20TanStack,=20load=20?= =?UTF-8?q?shedding,=20full=20service=20inventory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../03-performance-caching-strategy.md | 140 ++++++++++++++++++ docs/features/04-service-inventory.md | 73 +++++++++ 2 files changed, 213 insertions(+) create mode 100644 docs/features/03-performance-caching-strategy.md create mode 100644 docs/features/04-service-inventory.md diff --git a/docs/features/03-performance-caching-strategy.md b/docs/features/03-performance-caching-strategy.md new file mode 100644 index 00000000..0275cbc7 --- /dev/null +++ b/docs/features/03-performance-caching-strategy.md @@ -0,0 +1,140 @@ +# 03 — Performance & Caching Strategy + +**Refactored from:** `docs/architecture/performance_and_caching_strategy.md` + +--- + +## Feature Summary + +Zync uses a multi-tier caching and performance strategy: Redis for server-side caching and Pub/Sub, TanStack Query with localStorage persistence for client-side REST caching, Dexie/IndexedDB for offline-first CRDT state, and active load shedding to stay within Render's 512MB RAM limit. + +--- + +## Architecture Diagram + +``` +┌──────────────── CLIENT CACHE TIERS ─────────────────┐ +│ │ +│ Tier 1: TanStack Query (in-memory) │ +│ ├─ Stale-while-revalidate for REST endpoints │ +│ ├─ Default staleTime: 60s │ +│ └─ Query keys: ['projects'], ['notes'], ['tasks'] │ +│ │ +│ Tier 2: localStorage Persister │ +│ ├─ @tanstack/query-sync-storage-persister │ +│ ├─ Persists query cache to localStorage │ +│ └─ Restores on page reload (instant UI) │ +│ │ +│ Tier 3: IndexedDB (Dexie) │ +│ ├─ Yjs document state (y-indexeddb) │ +│ ├─ Offline note editing │ +│ └─ Syncs on reconnect │ +└──────────────────────────────────────────────────────┘ + +┌──────────────── SERVER CACHE TIERS ─────────────────┐ +│ │ +│ Tier 1: Redis Cache │ +│ ├─ Architecture quota tokens │ +│ ├─ Rate limit counters │ +│ ├─ Socket.IO Pub/Sub adapter │ +│ ├─ Design inspiration cache (TTL: 1h) │ +│ └─ GitHub repo metadata cache │ +│ │ +│ Tier 2: In-process Memory │ +│ ├─ Puppeteer singleton (reuse browser) │ +│ ├─ Prisma client connection pool │ +│ └─ Mongoose connection pool │ +│ │ +│ Load Shedding (active memory management) │ +│ ├─ Monitors heapUsed every request │ +│ ├─ 503 on heavy paths if heap > 400MB │ +│ └─ Allowlist for critical paths │ +└──────────────────────────────────────────────────────┘ +``` + +--- + +## Backend Trace + +### Redis Client +**File:** `backend/utils/redisClient.js` +- Creates Redis client with `REDIS_URL` env var +- Fails open gracefully if Redis is offline +- Used by: + - `backend/utils/cache.js` — get/set/delete with TTL + - `backend/services/scraperService.js` — inspiration result caching + - Socket.IO adapter for multi-instance scaling + +### Cache Utility +**File:** `backend/utils/cache.js` +- `cacheGet(key)`, `cacheSet(key, value, ttl)`, `cacheDel(key)` +- JSON serialization/deserialization +- TTL in seconds (default 3600 = 1 hour) +- Returns `null` on cache miss or Redis error + +### Load Shedding Middleware +**File:** `backend/middleware/loadShedding.js:103-126` +- Checks `process.memoryUsage().heapUsed` on every heavy request +- `HEAP_LIMIT_MB` default 400 (configurable via `LOAD_SHED_HEAP_LIMIT_MB`) +- Heavy paths: `/api/github-app/webhook`, `/api/webhooks/github`, `/api/generate-project`, `/api/design`, `/api/inspiration` +- Allowlist: `/api/auth`, `/api/sessions`, `/api/chat` (always served) + +### Health Check with Memory Monitoring +**File:** `backend/index.js:313-340` +- `/health` endpoint reports heapUsed, heapTotal, rss +- Status: `healthy` (<80%), `degraded` (>80%), `critical` (>95%) +- Memory limit: 512MB (Render free tier) + +### Database Connection Pooling +- **Prisma:** Default connection pool (Prisma manages internally) +- **Mongoose:** `mongoose.connect()` with default pool size of 5 +- Both connect to same MongoDB Atlas instance via `MONGO_URI` + +--- + +## Frontend Trace + +### TanStack Query Setup +**File:** `src/lib/query-client.ts` +- Creates `QueryClient` with default config +- `staleTime: 60_000` (1 minute default) +- `gcTime: 5 * 60 * 1000` (5 minutes garbage collection) +- `retry: 1` (one retry on failure) +- `refetchOnWindowFocus: false` + +### Query Persister (localStorage) +**File:** `src/lib/query-persister.ts` +- Uses `createSyncStoragePersister` with `localStorage` +- Persists TanStack Query cache to localStorage +- On page reload, cache is restored → instant UI render +- `persister` key: `zync-query-cache` + +### Retry Helper +**File:** `src/lib/retryHelper.ts` +- Exponential backoff for failed API calls +- Max 3 retries with jitter +- Used for flaky external API calls (GitHub, Google) + +### Dexie (IndexedDB) +**File:** `src/lib/db.ts` +- Dexie database for offline-first storage +- Stores Yjs document states for collaborative notes +- Enables offline note editing with sync on reconnect + +--- + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `REDIS_URL` | No | Redis connection URL (fails open if missing) | +| `MONGO_URI` | Yes | MongoDB Atlas connection string | +| `LOAD_SHED_HEAP_LIMIT_MB` | No | Heap threshold for shedding (default 400) | + +--- + +## Cross-References + +- [06-middleware-stack.md](./06-middleware-stack.md) — Middleware chain including load shedding +- [31-realtime-notes-editor.md](./31-realtime-notes-editor.md) — Yjs + IndexedDB offline +- [45-design-inspiration-service.md](./45-design-inspiration-service.md) — Redis caching for scraper diff --git a/docs/features/04-service-inventory.md b/docs/features/04-service-inventory.md new file mode 100644 index 00000000..cc3f2658 --- /dev/null +++ b/docs/features/04-service-inventory.md @@ -0,0 +1,73 @@ +# 04 — Service Inventory + +**Refactored from:** `docs/architecture/service_inventory.md` + +--- + +## Feature Summary + +Complete inventory of every cloud service, hosting provider, third-party API, and managed dependency in Zync. This is the definitive reference for infrastructure costs, free-tier limits, and external dependencies. + +--- + +## Core Infrastructure & Hosting + +| Service | Category | Tier | Usage | Env Var / Config | +|---|---|---|---|---| +| **Vercel** | Web Hosting (CDN/Edge) | Free/Hobby | Serves Vite React SPA, handles routing, security headers, custom domain `zync-meet.vercel.app` | `vercel.json` | +| **Render** | App Hosting | Free (512MB RAM) | Node.js/Express API server + Socket.IO instance | `render.yaml` | +| **MongoDB Atlas** | Managed Database | Shared/Free | Primary DB — Mongoose + Prisma access same instance | `MONGO_URI` | +| **Firebase** | Auth/Realtime/Storage | Spark (Free) | JWT auth, Firestore backup, FCM push notifications, Firebase Storage | `VITE_FIREBASE_*` / `FIREBASE_*` | +| **Cloudinary** | Media CDN | Free Tier | Avatar uploads, image transformations via upload streams | `CLOUDINARY_*` | +| **Redis** | Cache & Pub/Sub | Self-hosted | Rate-limit counters, architecture quota, socket pub/sub, session cache | `REDIS_URL` | + +--- + +## AI Models & Gateways + +| Provider | Gateway/SDK | Role | Config | +|---|---|---|---| +| **Kilo Code Gateway** | Direct HTTP REST | Architecture Agent — natural language chat → structured JSON architecture maps | `KILO_CODE_GATEWAY_URL`, `KILO_CODE_GATEWAY_API_KEY` | +| **Groq** | `groq-sdk` | Low-latency AI for project scaffolding (`taskGenerator.js`) and commit analysis (`commitAnalysisService.js`) | `GROQ_API_KEY` | + +--- + +## Third-Party API Integrations + +| Provider | Purpose | Implementation | Env Vars | +|---|---|---|---| +| **GitHub API** | Kanban sync, OAuth, collaborator invites, webhooks | `octokit` SDK, bidirectional task↔issue sync, AES-256 encrypted tokens | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GITHUB_APP_ID`, `GITHUB_PRIVATE_KEY`, `GITHUB_WEBHOOK_SECRET` | +| **Google APIs** | OAuth, Calendar, Meet, Sheets logging | `googleapis` SDK, refresh token flow, calendar event CRUD, Sheets audit log | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI` | +| **LinkedIn** | OAuth profile import | `linkedinRoutes.js`, access token exchange, profile fetch | `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET`, `LINKEDIN_REDIRECT_URI` | +| **HaveIBeenPwned** | Password breach check | K-anonymity SHA-256 prefix matching, no cleartext transmission | None (public API) | +| **Nodemailer/SMTP** | Transactional email | Account verification, workspace invites, weekly reports | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS` | + +--- + +## Network & Security Summary + +- **Frontend Security:** `vercel.json` enforces CSP, `X-Frame-Options: DENY`, `X-Content-Type-Options: nosniff`, `Permissions-Policy` +- **Backend Security:** `helmet` CSP, rate limiting, load shedding, Firebase JWT verification, HMAC webhook validation +- **Encryption at Rest:** `crypto-js` AES-256 with `MASTER_ENCRYPTION_KEY` for stored tokens +- **Fail-Open Architecture:** Redis, Kilo Gateway, and other heavy deps fail open cleanly + +--- + +## Free Tier Limits & Risks + +| Service | Limit | Risk | Mitigation | +|---|---|---|---| +| Render | 512MB RAM, 750h/month | OOM crash | Load shedding, lazy imports, health check | +| MongoDB Atlas | 512MB storage | Data cap | Mongoose lean queries, index optimization | +| Vercel | 100GB bandwidth, 100h build | Build timeout | Vite build optimization | +| Firebase Spark | 10k auth/day, 1GB Firestore | Auth rate limit | JWT caching on client | +| Cloudinary | 25 credits/month | Upload cap | Image compression before upload | +| Redis (self-hosted) | RAM dependent | Connection failure | Fail-open pattern | + +--- + +## Cross-References + +- [01-tech-stack-overview.md](./01-tech-stack-overview.md) — Full package breakdown +- [07-environment-variables.md](./07-environment-variables.md) — Complete env var reference +- [03-performance-caching-strategy.md](./03-performance-caching-strategy.md) — Redis caching detail From 9808d5b86a0f1ea3f2ff4770185668b12b3eec98 Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:41:19 +0530 Subject: [PATCH 5/9] =?UTF-8?q?docs:=20add=2005-database-schema-and-models?= =?UTF-8?q?=20=E2=80=94=20full=20Prisma=20+=20Mongoose=20schema=20with=201?= =?UTF-8?q?1=20models,=20indexes,=20dual-ORM=20strategy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/05-database-schema-and-models.md | 471 ++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 docs/features/05-database-schema-and-models.md diff --git a/docs/features/05-database-schema-and-models.md b/docs/features/05-database-schema-and-models.md new file mode 100644 index 00000000..c2f3cd56 --- /dev/null +++ b/docs/features/05-database-schema-and-models.md @@ -0,0 +1,471 @@ +# 05 — Database Schema & Models + +**NEW document** — Complete reference for Prisma schema + Mongoose ODM models + +--- + +## Feature Summary + +Zync uses a **dual-ORM architecture** over a single MongoDB Atlas instance. **Prisma** handles relational-style queries (Projects, Teams, Users with relations and cascading deletes) while **Mongoose** handles flexible document data (Chat, Notes, AI blobs, Sessions). Both ORMs connect to the same MongoDB cluster via `MONGO_URI`. + +--- + +## Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ MongoDB Atlas (Primary) │ +│ │ +│ Collections (shared by Prisma + Mongoose): │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ users │ │ projects │ │ steps │ │projecttasks│ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ notes │ │ folders │ │ meetings │ │ sessions │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ messages │ │ teams │ │repositories│ │ activity │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ Indexes: │ +│ users: text(displayName, firstName, lastName) │ +│ projects: ownerId, ownerUid, team │ +│ messages: (chatId, createdAt), (receiverId, delivered), │ +│ (receiverId, createdAt) │ +│ projecttasks: assignedTo, status │ +│ sessions: userId, (userId, date) │ +└─────────────────────────────────────────────────────────────┘ + ▲ ▲ + │ │ +┌────────┴───────────┐ ┌────────┴──────────────┐ +│ PRISMA 5 │ │ MONGOOSE 9 │ +│ (Relational ORM) │ │ (Flexible ODM) │ +│ │ │ │ +│ schema.prisma │ │ models/*.js │ +│ Generated client │ │ 13 model files │ +│ Type-safe queries │ │ Middleware, hooks │ +│ Cascade deletes │ │ Schema validation │ +│ Relations: │ │ │ +│ User→Projects │ │ Used for: │ +│ Project→Steps │ │ Chat, Notes, AI, │ +│ Step→Tasks │ │ Sessions, Messages, │ +│ │ │ Activity, Folders │ +│ Used for: │ │ │ +│ Projects, Teams, │ │ │ +│ Users, Steps, │ │ │ +│ Tasks, Repos │ │ │ +└─────────────────────┘ └────────────────────────┘ +``` + +--- + +## Prisma Schema + +**File:** `backend/prisma/schema.prisma:1-306` + +### Generator & Datasource +```prisma +generator client { + provider = "prisma-client-js" + output = "./generated/client" +} + +datasource db { + provider = "mongodb" + url = env("MONGO_URI") +} +``` + +### Model: User (Prisma) +**Lines 12-57** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `uid` | String | @unique | Firebase UID — universal join key | +| `email` | String | @unique | User email | +| `displayName` | String | @default("User") | Display name | +| `firstName` | String? | | Optional first name | +| `lastName` | String? | | Optional last name | +| `photoURL` | String? | | Avatar URL (Cloudinary) | +| `phoneNumber` | String? | | Phone number | +| `connections` | String[] | @default([]) | Firebase UIDs of connections | +| `closeFriends` | String[] | @default([]) | Firebase UIDs of close friends | +| `chatRequests` | Json | @default("[]") | Array of request objects | +| `githubIntegration` | Json? | | `{ connected, accessToken, username, installationId, connectedAt }` | +| `googleIntegration` | Json? | | `{ connected, refreshToken, calendarId, connectedAt }` | +| `isPhoneVerified` | Boolean | @default(false) | Phone OTP status | +| `phoneVerificationCode` | String? | | OTP code | +| `phoneVerificationCodeExpired` | DateTime? | | OTP expiry | +| `deleteConfirmationCode` | String? | | Account deletion code | +| `deleteConfirmationExpires` | DateTime? | | Deletion code expiry | +| `status` | String | @default("offline") | online, offline, away | +| `lastSeen` | DateTime | @default(now()) | Last activity timestamp | +| `role` | String | @default("user") | user or admin | +| `ownedProjects` | Project[] | @relation("ProjectOwner") | Projects owned by user | +| `teamMemberships` | String[] | @default([]) | Team IDs | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `users` + +### Model: Project (Prisma) +**Lines 60-91** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `name` | String | required | Project name | +| `description` | String | required | Project description | +| `ownerId` | String | @db.ObjectId | FK to User | +| `owner` | User | @relation("ProjectOwner") | Owner relation | +| `team` | String[] | @default([]) | Firebase UIDs of team members | +| `githubRepo` | String? | | Linked repo URL | +| `githubRepoName` | String? | | Repo name | +| `githubRepoOwner` | String? | | Repo owner | +| `githubRepoIds` | String[] | @default([]) | Multiple repo IDs | +| `architecture` | Json? | | AI-generated architecture blob | +| `meetLink` | String? | | Google Meet link | +| `isTrackingActive` | Boolean | @default(false) | Activity tracking flag | +| `steps` | Step[] | @relation | Steps in this project | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `projects` +**Cascade:** `onDelete: Cascade` from User → Project + +### Model: Step (Prisma) +**Lines 94-116** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `title` | String | required | Step title | +| `description` | String? | | Optional description | +| `order` | Int | @default(0) | Sort order | +| `status` | String | @default("Pending") | Pending, Backlog, In Progress, Completed, Done | +| `assignedTo` | String? | | Firebase UID | +| `type` | String | @default("Other") | Frontend, Backend, Database, Design, Other | +| `page` | String | @default("General") | Page/category | +| `projectId` | String | @db.ObjectId | FK to Project | +| `project` | Project | @relation | Project relation | +| `tasks` | ProjectTask[] | @relation | Tasks in this step | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `steps` +**Cascade:** `onDelete: Cascade` from Project → Step + +### Model: ProjectTask (Prisma) +**Lines 119-152** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `displayId` | String? | @unique | Human-readable ID like "TASK-42" | +| `title` | String | required | Task title | +| `description` | String? | | Optional description | +| `status` | String | @default("Backlog") | Pending, Backlog, Ready, In Progress, Active, In Review, Completed, Done | +| `assignedTo` | String? | | Firebase UID | +| `assignedToName` | String? | | Display name | +| `createdBy` | String? | | Firebase UID of creator | +| `assignedBy` | String? | | Firebase UID or name | +| `commitMessage` | String? | | Linked GitHub commit message | +| `commitUrl` | String? | | Linked commit URL | +| `commitAuthor` | String? | | Commit author | +| `commitTimestamp` | DateTime? | | Commit time | +| `repoIds` | String[] | @default([]) | Linked GitHub repos | +| `stepId` | String | @db.ObjectId | FK to Step | +| `step` | Step | @relation | Step relation | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `projecttasks` +**Indexes:** `@@index([assignedTo])`, `@@index([status])` +**Cascade:** `onDelete: Cascade` from Step → ProjectTask + +### Model: Repository (Prisma) +**Lines 155-164** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `githubRepoId` | String | @unique | GitHub repo ID | +| `repoName` | String | required | Repo name | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `repositories` + +### Model: Note (Prisma) +**Lines 167-184** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `title` | String | @default("Untitled") | Note title | +| `content` | Json? | | Block-editor rich-text content | +| `ownerId` | String | required | Firebase UID | +| `folderId` | String? | | FK to Folder | +| `projectId` | String? | | Optional project link | +| `sharedWith` | String[] | @default([]) | Firebase UIDs | +| `yjsState` | Bytes? | | Yjs collaborative binary state | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `notes` + +### Model: Folder (Prisma) +**Lines 187-206** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `name` | String | required | Folder name | +| `ownerId` | String | required | Firebase UID | +| `parentId` | String? | | Self-referential (null = root) | +| `type` | String | @default("personal") | personal, team, project | +| `color` | String | @default("#FFFFFF") | Folder color | +| `projectId` | String? | | Optional project link | +| `collaborators` | String[] | @default([]) | Firebase UIDs | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `folders` +**Unique constraint:** `@@unique([ownerId, parentId, name])` — no duplicate folder names under same parent + +### Model: Meeting (Prisma) +**Lines 209-232** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `title` | String | @default("Untitled Meeting") | Meeting title | +| `description` | String? | | Optional description | +| `organizerId` | String | required | Firebase UID | +| `organizerName` | String? | | Organizer display name | +| `meetLink` | String | required | Google Meet URL | +| `projectId` | String? | | Optional project link | +| `status` | String | @default("scheduled") | scheduled, live, ended, cancelled | +| `startTime` | DateTime | @default(now()) | Meeting start | +| `endTime` | DateTime? | | Meeting end (null = ongoing) | +| `participants` | Json | @default("[]") | Array of `{ uid, email, name, status }` | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `meetings` + +### Model: Session (Prisma) +**Lines 235-254** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `userId` | String | required | Firebase UID | +| `startTime` | DateTime | @default(now()) | Session start | +| `endTime` | DateTime | @default(now()) | Session end | +| `duration` | Int | @default(0) | Total seconds | +| `activeDuration` | Int | @default(0) | Active seconds (excluding idle) | +| `lastAction` | DateTime | @default(now()) | Last user action | +| `date` | String | required | YYYY-MM-DD for grouping | +| `deviceInfo` | String? | | Device metadata | +| `createdAt` | DateTime | @default(now()) | | + +**Collection:** `sessions` +**Indexes:** `@@index([userId])`, `@@index([userId, date])` + +### Model: Message (Prisma) +**Lines 257-288** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `chatId` | String | required | Deterministic: sorted `_` | +| `text` | String? | | Message text (null if file-only) | +| `senderId` | String | required | Firebase UID | +| `senderName` | String | required | Display name | +| `senderPhotoURL` | String? | | Avatar URL | +| `receiverId` | String | required | Firebase UID | +| `type` | String | @default("text") | text, image, file, project-invite, request | +| `fileUrl` | String? | | File URL (Cloudinary) | +| `fileName` | String? | | File name | +| `fileSize` | Int? | | File size in bytes | +| `projectId` | String? | | For project-invite type | +| `projectName` | String? | | For project-invite type | +| `projectOwnerId` | String? | | For project-invite type | +| `seen` | Boolean | @default(false) | Read receipt | +| `seenAt` | DateTime? | | Read timestamp | +| `delivered` | Boolean | @default(false) | Delivery receipt | +| `deliveredAt` | DateTime? | | Delivery timestamp | +| `createdAt` | DateTime | @default(now()) | | + +**Collection:** `messages` +**Indexes:** `@@index([chatId, createdAt])`, `@@index([receiverId, delivered])`, `@@index([receiverId, createdAt])` + +### Model: Team (Prisma) +**Lines 291-305** +| Field | Type | Attributes | Notes | +|---|---|---|---| +| `id` | String | @id, @default(auto()), @db.ObjectId | Primary key | +| `name` | String | required | Team name | +| `inviteCode` | String | @unique | Invite code for joining | +| `ownerId` | String | required | Firebase UID of owner | +| `members` | String[] | @default([]) | Firebase UIDs | +| `type` | String | @default("Other") | Product, Engineering, Management, Marketing, Sales, Design, Other | +| `createdAt` | DateTime | @default(now()) | | +| `updatedAt` | DateTime | @updatedAt | | + +**Collection:** `teams` + +--- + +## Mongoose Models + +### Mongoose Model Registry +**File:** `backend/models/index.js:76-88` +Exports all 11 Mongoose models: +```js +module.exports = { + User: require('./User'), + Project: require('./Project'), + Step: require('./Step'), + ProjectTask: require('./ProjectTask'), + Repository: require('./Repository'), + Note: require('./Note'), + Folder: require('./Folder'), + Meeting: require('./Meeting'), + Session: require('./Session'), + Team: require('./Team'), + Message: require('./Message'), +}; +``` + +### Mongoose User Model (extended fields) +**File:** `backend/models/User.js:78-141` + +The Mongoose User schema mirrors Prisma but adds these extra fields not in Prisma: +| Field | Type | Default | Notes | +|---|---|---|---| +| `fcmTokens` | Array<{token, platform, updatedAt}> | [] | Push notification tokens | +| `timezone` | String | null | User timezone | +| `country` | String | null | User country | +| `countryCode` | String | null | Country code | +| `city` | String | null | User city | +| `securityPin` | String | (select: false) | Hidden from queries by default | +| `welcomeNotificationSent` | Boolean | false | Welcome notification flag | + +**Text Index:** `userSchema.index({ displayName: 'text', firstName: 'text', lastName: 'text' })` — enables full-text search on user names + +### Mongoose Project Model (extended webhook fields) +**File:** `backend/models/Project.js:78-130` + +Extra fields in Mongoose not in Prisma: +| Field | Type | Default | Notes | +|---|---|---|---| +| `architectureCacheKey` | String | null | Redis cache key for architecture | +| `architectureAnalyzedAt` | Date | null | Last architecture analysis time | +| `lastWebhookEventAt` | Date | null | Last GitHub webhook timestamp | +| `lastWebhookCommitCount` | Number | 0 | Commits in last webhook | +| `lastWebhookCommitShas` | String[] | [] | SHA list from last webhook | +| `lastWebhookChangedFiles` | String[] | [] | Changed files from last webhook | +| `lastWebhookPusher` | String | null | Who pushed | +| `lastWebhookDeliveryId` | String | null | GitHub delivery ID | +| `lastWebhookAiSummary` | String | null | AI-generated commit summary | +| `lastWebhookAiTaskMentions` | Number | 0 | Tasks mentioned in AI analysis | +| `lastWebhookAiAnalyzedCommits` | Number | 0 | Commits analyzed by AI | + +**Indexes:** `ownerId`, `ownerUid`, `team` + +### Mongoose Message Model +**File:** `backend/models/Message.js:78-117` + +Key difference from Prisma: Mongoose version has `timestamps: { createdAt: 'createdAt', updatedAt: false }` — no updatedAt field. + +**Indexes:** +- `{ chatId: 1, createdAt: 1 }` — chronological message fetch +- `{ receiverId: 1, delivered: 1 }` — offline message delivery +- `{ receiverId: 1, createdAt: -1 }` — recent messages for a user + +--- + +## Dual-ORM Strategy + +### When Prisma is Used +Prisma is used in route files that need: +- Relational queries with `include` (e.g., Project → Steps → Tasks) +- Cascade deletes (User → Projects → Steps → Tasks) +- Type-safe query results +- Complex filtering with `where` clauses + +**Files using Prisma:** +- `backend/routes/projectRoutes.js` — project CRUD with steps +- `backend/routes/teamRoutes.js` — team management +- `backend/routes/userRoutes.js` — user profile +- `backend/routes/taskRoutes.js` — task operations +- `backend/utils/projectHelper.js` — project utilities + +### When Mongoose is Used +Mongoose is used in route files that need: +- Flexible/unstructured data (chat requests, AI blobs) +- Schema middleware/hooks (pre-save validation) +- Text search (user name search) +- Lean queries for performance + +**Files using Mongoose:** +- `backend/routes/chatRoutes.js` — message persistence +- `backend/routes/noteRoutes.js` — note CRUD +- `backend/routes/sessionRoutes.js` — activity tracking +- `backend/sockets/chatSocketHandler.js` — real-time chat +- `backend/sockets/noteSocketHandler.js` — Yjs state persistence + +### Connection Management +**File:** `backend/index.js:76-87` +- Mongoose connects via `mongoose.connect(MONGO_URI)` at server startup +- Prisma client is instantiated from `backend/prisma/generated/client/` +- Both share the same `MONGO_URI` env var +- Connection pooling: Mongoose default 5, Prisma default + +--- + +## Query Patterns + +### Prisma Query Example (Project with Steps and Tasks) +```js +const project = await prisma.project.findUnique({ + where: { id: projectId }, + include: { + steps: { + orderBy: { order: 'asc' }, + include: { + tasks: { + orderBy: { createdAt: 'desc' }, + }, + }, + }, + }, +}); +``` + +### Mongoose Query Example (Chat Messages) +```js +const messages = await Message.find({ chatId }) + .sort({ createdAt: 1 }) + .limit(50) + .lean(); +``` + +### Mongoose Text Search (User Search) +```js +const users = await User.find( + { $text: { $search: query } }, + { score: { $meta: 'textScore' } } +).sort({ score: { $meta: 'textScore' } }).limit(10); +``` + +--- + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `MONGO_URI` | Yes | MongoDB Atlas connection string | + +--- + +## Cross-References + +- [01-tech-stack-overview.md](./01-tech-stack-overview.md) — Prisma + Mongoose package versions +- [14-project-crud-lifecycle.md](./14-project-crud-lifecycle.md) — Project queries with Prisma +- [26-instant-chat-system.md](./26-instant-chat-system.md) — Message model usage +- [31-realtime-notes-editor.md](./31-realtime-notes-editor.md) — Note model + Yjs state +- [59-activity-tracking-system.md](./59-activity-tracking-system.md) — Session model queries From feabbb2d987b87e3ab1b3e8798fb1e1ad4e10797 Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:42:25 +0530 Subject: [PATCH 6/9] =?UTF-8?q?docs:=20add=2006-middleware-stack=20+=2007-?= =?UTF-8?q?environment-variables=20=E2=80=94=20full=20middleware=20chain,?= =?UTF-8?q?=20complete=20env=20var=20reference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/06-middleware-stack.md | 305 ++++++++++++++++++++++ docs/features/07-environment-variables.md | 171 ++++++++++++ 2 files changed, 476 insertions(+) create mode 100644 docs/features/06-middleware-stack.md create mode 100644 docs/features/07-environment-variables.md diff --git a/docs/features/06-middleware-stack.md b/docs/features/06-middleware-stack.md new file mode 100644 index 00000000..7fe8e312 --- /dev/null +++ b/docs/features/06-middleware-stack.md @@ -0,0 +1,305 @@ +# 06 — Middleware Stack + +**NEW document** — Complete Express middleware chain with line-by-line trace + +--- + +## Feature Summary + +The Express 5 middleware stack processes every incoming request through 7 layers before it reaches route handlers. This document traces the exact order, configuration, and behavior of each middleware. + +--- + +## Architecture Diagram + +``` +Incoming HTTP Request + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 1. favicon.ico bypass (index.js:104) │ +│ GET /favicon.ico → 204 No Content │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 2. helmet() (index.js:176-234) │ +│ CSP, X-Frame-Options, HSTS, referrer-policy │ +│ 12+ security headers │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 3. cors(corsOptions) (index.js:236) │ +│ Origin allowlist check │ +│ Credentials: true │ +│ Methods: GET, POST, PUT, DELETE, OPTIONS │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 4. rateLimit on /api/ (index.js:239-252) │ +│ Prod: 100 req / 15 min │ +│ Dev: 600 req / 1 min │ +│ Returns 429 on exceed │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 5. loadShedding on /api/ (index.js:253) │ +│ Checks heapUsed > 400MB │ +│ Only sheds heavy paths │ +│ Returns 503 with Retry-After │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 6. Body parsers (index.js:256-265) │ +│ /api/webhooks + /api/github-app: rawBody preserved │ +│ All other routes: express.json() │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌───────────────────────────────────────────────────────────┐ +│ 7. Route-level middleware (per route file) │ +│ verifyToken — Firebase JWT check │ +│ validate(schema) — Zod request validation │ +│ verifyGithub — HMAC SHA-256 webhook check │ +│ internalAuth — x-internal-secret check │ +└───────────────────────┬───────────────────────────────────┘ + │ + ▼ + Route Handler +``` + +--- + +## Layer 1: Favicon Bypass +**File:** `backend/index.js:104` +```js +app.get('/favicon.ico', (req, res) => res.status(204).end()); +``` +- Short-circuits before any middleware +- Returns 204 No Content +- Prevents favicon requests from hitting rate limiter + +--- + +## Layer 2: Helmet Security Headers +**File:** `backend/index.js:176-234` + +### CSP Directives +| Directive | Value | Purpose | +|---|---|---| +| `default-src` | `['self']` | Default allowlist | +| `script-src` | `['self', 'unsafe-inline', 'unsafe-eval', 'blob:', 'https://apis.google.com', 'https://www.googleapis.com', 'https://www.gstatic.com', 'https://www.google.com']` | Script sources | +| `connect-src` | `['self', 'https://github.com', 'https://api.github.com', 'http://localhost:*', 'ws://localhost:*', 'wss://*.glitch.me', 'https://*.googleapis.com', 'https://www.google.com', 'https://www.gstatic.com', 'https://*.firebaseio.com', 'https://*.firebase.google.com']` | XHR/fetch/WebSocket targets | +| `img-src` | `['self', 'data:', 'https://avatars.githubusercontent.com', 'https://*.githubusercontent.com', 'https://*.googleusercontent.com', 'https://*.google.com', 'blob:', 'https://ui-avatars.com', 'https://res.cloudinary.com']` | Image sources | +| `style-src` | `['self', 'unsafe-inline', 'https://fonts.googleapis.com']` | CSS sources | +| `worker-src` | `['self', 'blob:']` | Service workers | +| `frame-src` | `['self', 'https://github.com', 'https://*.firebaseapp.com', 'https://*.google.com']` | Iframe sources | +| `font-src` | `['self', 'data:', 'https://fonts.gstatic.com']` | Font sources | + +### Other Helmet Options +- `crossOriginEmbedderPolicy: false` — allows cross-origin resources +- `referrerPolicy: { policy: 'strict-origin-when-cross-origin' }` — limits referrer leakage + +--- + +## Layer 3: CORS +**File:** `backend/index.js:106-135` + +### Allowed Origins +``` +http://localhost:5173 +http://localhost:8080 +http://localhost:8081 +http://localhost:8082 +http://localhost:8083 +http://127.0.0.1:8081 +http://localhost:3000 ++ ALLOWED_ORIGINS env var (comma-separated) ++ FRONTEND_URL env var +``` + +### Behavior +- No origin header (curl, server-to-server): **allowed** +- Origin in allowlist: **allowed** +- Origin not in allowlist: **rejected** with Error + +### Options +```js +{ + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + credentials: true, + optionsSuccessStatus: 200, +} +``` + +--- + +## Layer 4: Rate Limiting +**File:** `backend/index.js:238-252` + +### Configuration +| Setting | Production | Development | +|---|---|---| +| `windowMs` | 15 minutes (900,000ms) | 1 minute (60,000ms) | +| `max` | 100 requests | 600 requests | +| `standardHeaders` | true | true | +| `legacyHeaders` | false | false | + +### Applied to: `/api/*` only +### Response on exceed: `429 { message: "Too many requests...", status: 429 }` + +--- + +## Layer 5: Load Shedding +**File:** `backend/middleware/loadShedding.js:75-135` + +### Configuration +| Env Var | Default | Range | Purpose | +|---|---|---|---| +| `LOAD_SHED_HEAP_LIMIT_MB` | 400 | 128–4096 | Heap threshold | +| `LOAD_SHED_RETRY_AFTER_SECONDS` | 15 | 1–300 | Retry-After header | + +### Path Classification +| Category | Paths | Behavior | +|---|---|---| +| **Allowlist** (never shed) | `/api/auth`, `/api/sessions`, `/api/chat` | Always pass through | +| **Heavy** (shed if over limit) | `/api/github-app/webhook`, `/api/webhooks/github`, `/api/generate-project`, `/api/design`, `/api/inspiration` | 503 if heap > threshold | +| **Normal** (never shed) | Everything else | Always pass through | +| **Custom** | `LOAD_SHED_HEAVY_PATHS` env var | Override heavy paths | + +### 503 Response +```json +{ + "message": "Service under memory pressure, please retry shortly.", + "reason": "load_shedding", + "heapUsedMb": 412.35, + "heapLimitMb": 400 +} +``` +With header: `Retry-After: 15` + +--- + +## Layer 6: Body Parsers +**File:** `backend/index.js:256-265` + +### Webhook Routes (rawBody preserved) +```js +const webhookJsonParser = express.json({ + verify: (req, res, buf) => { req.rawBody = buf; }, +}); +app.use('/api/webhooks', webhookJsonParser); +app.use('/api/github-app', webhookJsonParser); +``` +- `req.rawBody` needed for HMAC SHA-256 signature verification +- Applied BEFORE `express.json()` to intercept webhook routes + +### All Other Routes +```js +app.use(express.json()); +``` +- Standard JSON body parser for all other routes + +--- + +## Layer 7: Route-Level Middleware + +### verifyToken (Firebase JWT) +**File:** `backend/middleware/authMiddleware.js:96-119` +- Applied per-route: `router.get('/profile', verifyToken, handler)` +- Extracts `Bearer ` from Authorization header +- Calls `getAuth().verifyIdToken(token)` via Firebase Admin +- Sets `req.user = { uid, email }` +- 401 if no token, 403 if invalid + +### validate (Zod Schema) +**File:** `backend/middleware/validation.js:83-103` +- Applied per-route: `router.post('/create', validate(schema), handler)` +- Validates `req.body`, `req.query`, `req.params` +- 400 with error array if validation fails + +### verifyGithub (HMAC Webhook) +**File:** `backend/middleware/verifyGithub.js` +- Applied on webhook routes +- Validates `x-hub-signature-256` header +- HMAC SHA-256 with `GITHUB_WEBHOOK_SECRET` +- 403 if signature mismatch + +### internalAuth (Admin Secret) +**File:** `backend/index.js:294-304` +- Applied on `/internal/*` routes +- Checks `x-internal-secret` header against `INTERNAL_API_SECRET` +- 503 if secret not configured, 403 if mismatch + +--- + +## Route Registration Order +**File:** `backend/index.js:271-304` + +| Order | Mount Path | Route File | Auth | +|---|---|---|---| +| 1 | `/api/projects` | `projectRoutes.js` | verifyToken (per-route) | +| 2 | `/api/generate-project` | `generateProjectRoutes.js` | verifyToken (per-route) | +| 3 | `/api/github` | `github.js` | verifyToken (per-route) | +| 4 | `/api/link` | `linkRoutes.js` | verifyToken (per-route) | +| 5 | `/api/users` | `userRoutes.js` | verifyToken (per-route) | +| 6 | `/api/sessions` | `sessionRoutes.js` | verifyToken (per-route) | +| 7 | `/api/design` | `designRoutes.js` | verifyToken (per-route) | +| 8 | `/api/inspiration` | `inspirationRoutes.js` | verifyToken (per-route) | +| 9 | `/api/notes` | `noteRoutes.js` | verifyToken (per-route) | +| 10 | `/api/chat` | `chatRoutes.js` | verifyToken (per-route) | +| 11 | `/api/architecture-agent` | `architectureAgentRoutes.js` | verifyToken (per-route) | +| 12 | `/api/tasks` | `taskRoutes.js` | verifyToken (per-route) | +| 13 | `/api/upload` | `uploadRoutes.js` | verifyToken (per-route) | +| 14 | `/api/webhooks` | `webhookRoutes.js` | verifyGithub | +| 15 | `/api/github-app` | `githubAppWebhook.js` | verifyGithub | +| 16 | `/api/meet` | `meetRoutes.js` | verifyToken (per-route) | +| 17 | `/api/linkedin` | `linkedinRoutes.js` | verifyToken (per-route) | +| 18 | `/api/teams` | `teamRoutes.js` | verifyToken (per-route) | +| 19 | `/api/google` | `googleRoutes.js` | verifyToken (per-route) | +| 20 | `/api/calendar` | `calendarRoutes.js` | verifyToken (per-route) | +| 21 | `/api/support` | `supportRoutes.js` | verifyToken (per-route) | +| 22 | `/api/collaborator` | `collaboratorRoutes.js` | verifyToken (per-route) | +| 23 | `/internal` | `internalMetrics.js` | internalAuth | + +--- + +## Static File Serving +**File:** `backend/index.js:307-310` +- Serves `dist/` directory if it exists (production build) +- Only active when frontend is built and placed in `dist/` + +--- + +## Health Check Endpoint +**File:** `backend/index.js:313-340` +- `GET /health` — no auth required +- Returns: status, timestamp, uptime, memory (heapUsed, heapTotal, rss) +- Status: `healthy` (<80% of 512MB), `degraded` (>80%), `critical` (>95%) + +--- + +## Environment Variables + +| Variable | Required | Default | Description | +|---|---|---|---| +| `ALLOWED_ORIGINS` | No | — | Comma-separated CORS origins | +| `FRONTEND_URL` | Yes | — | Frontend URL for CORS | +| `GITHUB_WEBHOOK_SECRET` | Yes | — | HMAC webhook secret | +| `INTERNAL_API_SECRET` | Yes | — | Internal API admin secret | +| `LOAD_SHED_HEAP_LIMIT_MB` | No | 400 | Heap threshold for shedding | +| `LOAD_SHED_RETRY_AFTER_SECONDS` | No | 15 | Retry-After header value | +| `LOAD_SHED_HEAVY_PATHS` | No | (built-in) | Custom heavy path list | +| `NODE_ENV` | No | — | "production" for prod rate limits | + +--- + +## Cross-References + +- [02-security-auth-architecture.md](./02-security-auth-architecture.md) — Security overview +- [23-github-app-webhooks.md](./23-github-app-webhooks.md) — Webhook HMAC verification +- [62-internal-metrics.md](./62-internal-metrics.md) — Internal API routes diff --git a/docs/features/07-environment-variables.md b/docs/features/07-environment-variables.md new file mode 100644 index 00000000..4a4361a4 --- /dev/null +++ b/docs/features/07-environment-variables.md @@ -0,0 +1,171 @@ +# 07 — Environment Variables + +**NEW document** — Complete env var reference with types, defaults, and descriptions + +--- + +## Feature Summary + +Every environment variable used across the Zync codebase, organized by subsystem. Includes which files reference each variable, whether it's required, and its default behavior. + +--- + +## Environment Variable Index + +### Firebase (Auth + FCM + Firestore) + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `VITE_FIREBASE_API_KEY` | Yes | `src/lib/firebase.ts` | Firebase client API key | +| `VITE_FIREBASE_AUTH_DOMAIN` | Yes | `src/lib/firebase.ts` | Firebase auth domain (e.g., `zync-meet.firebaseapp.com`) | +| `VITE_FIREBASE_PROJECT_ID` | Yes | `src/lib/firebase.ts` | Firebase project ID | +| `VITE_FIREBASE_APP_ID` | Yes | `src/lib/firebase.ts` | Firebase app ID | +| `VITE_FIREBASE_MESSAGING_SENDER_ID` | Yes | `src/lib/firebase.ts` | FCM sender ID | +| `VITE_FIREBASE_STORAGE_BUCKET` | No | `src/lib/firebase.ts` | Firebase storage bucket URL | +| `FIREBASE_CLIENT_EMAIL` | Yes | `backend/services/firebaseAdmin.js` | Firebase Admin service account email | +| `FIREBASE_PRIVATE_KEY` | Yes | `backend/services/firebaseAdmin.js` | Firebase Admin private key (replace `\n` with newlines) | +| `FIREBASE_PROJECT_ID` | Yes | `backend/services/firebaseAdmin.js` | Firebase Admin project ID (server-side) | +| `FIREBASE_STORAGE_BUCKET` | No | `backend/services/firebaseAdmin.js` | Storage bucket for server-side file ops | +| `VITE_FIREBASE_VAPID_KEY` | Yes | `src/hooks/use-push-notifications.ts` | VAPID public key for web push notifications | + +### Database + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `MONGO_URI` | Yes | `backend/index.js`, Prisma schema | MongoDB Atlas connection string | + +### Redis + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `REDIS_URL` | No | `backend/utils/redisClient.js` | Redis connection URL. Fails open if missing. | + +### Server Configuration + +| Variable | Required | Used By | Default | Description | +|---|---|---|---|---| +| `PORT` | No | `backend/index.js` | 5000 | Server listen port | +| `NODE_ENV` | No | `backend/index.js` | — | "production" for prod rate limits | +| `FRONTEND_URL` | Yes | `backend/index.js` (CORS) | — | Frontend URL for CORS allowlist | +| `ALLOWED_ORIGINS` | No | `backend/index.js` (CORS) | — | Comma-separated extra CORS origins | +| `INTERNAL_API_SECRET` | Yes | `backend/index.js` | — | Secret for `/internal/*` routes | + +### Security & Encryption + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `MASTER_ENCRYPTION_KEY` | Yes | `backend/utils/encryption.js` | AES-256 encryption key for token storage | +| `GITHUB_WEBHOOK_SECRET` | Yes | `backend/middleware/verifyGithub.js` | HMAC SHA-256 secret for GitHub webhooks | +| `LOAD_SHED_HEAP_LIMIT_MB` | No | `backend/middleware/loadShedding.js` | Default 400. Heap threshold for load shedding. | +| `LOAD_SHED_RETRY_AFTER_SECONDS` | No | `backend/middleware/loadShedding.js` | Default 15. Retry-After header value. | +| `LOAD_SHED_HEAVY_PATHS` | No | `backend/middleware/loadShedding.js` | Custom comma-separated heavy path prefixes | + +### GitHub Integration + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `GITHUB_CLIENT_ID` | Yes | `backend/routes/github.js` | GitHub OAuth client ID | +| `GITHUB_CLIENT_SECRET` | Yes | `backend/routes/github.js` | GitHub OAuth client secret | +| `GITHUB_APP_ID` | Yes | `backend/utils/githubAppAuth.js` | GitHub App ID | +| `GITHUB_PRIVATE_KEY` | Yes | `backend/utils/githubAppAuth.js` | GitHub App private key (PEM format) | +| `GITHUB_INSTALLATION_ID` | No | `backend/utils/githubInstallation.js` | Default installation ID | + +### Google Integration + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `GOOGLE_CLIENT_ID` | Yes | `backend/routes/googleRoutes.js` | Google OAuth client ID | +| `GOOGLE_CLIENT_SECRET` | Yes | `backend/routes/googleRoutes.js` | Google OAuth client secret | +| `GOOGLE_REDIRECT_URI` | Yes | `backend/routes/googleRoutes.js` | Google OAuth redirect URI | +| `GOOGLE_PROJECT_NUMBER` | No | `backend/services/googleMeet.js` | Google Cloud project number for Meet API | + +### LinkedIn Integration + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `LINKEDIN_CLIENT_ID` | Yes | `backend/routes/linkedinRoutes.js` | LinkedIn OAuth client ID | +| `LINKEDIN_CLIENT_SECRET` | Yes | `backend/routes/linkedinRoutes.js` | LinkedIn OAuth client secret | +| `LINKEDIN_REDIRECT_URI` | Yes | `backend/routes/linkedinRoutes.js` | LinkedIn OAuth redirect URI | + +### Cloudinary (Media) + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `CLOUDINARY_CLOUD_NAME` | Yes | `backend/services/cloudinaryService.js` | Cloudinary cloud name | +| `CLOUDINARY_API_KEY` | Yes | `backend/services/cloudinaryService.js` | Cloudinary API key | +| `CLOUDINARY_API_SECRET` | Yes | `backend/services/cloudinaryService.js` | Cloudinary API secret | + +### AI Services + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `GROQ_API_KEY` | Yes | `backend/utils/taskGenerator.js`, `backend/utils/commitAnalysisService.js` | Groq LPU API key | +| `KILO_CODE_GATEWAY_URL` | Yes | `backend/services/kiloCodeGateway.js` | Kilo Code Gateway base URL | +| `KILO_CODE_GATEWAY_API_KEY` | Yes | `backend/services/kiloCodeGateway.js` | Kilo Code Gateway API key | + +### Email (Nodemailer) + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `SMTP_HOST` | Yes | `backend/utils/emailService.js` | SMTP server host | +| `SMTP_PORT` | Yes | `backend/utils/emailService.js` | SMTP server port | +| `SMTP_USER` | Yes | `backend/utils/emailService.js` | SMTP username | +| `SMTP_PASS` | Yes | `backend/utils/emailService.js` | SMTP password | +| `SMTP_FROM` | No | `backend/utils/emailService.js` | From email address | + +### Frontend API + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `VITE_API_URL` | Yes | Frontend API calls | Backend API base URL (e.g., `http://localhost:5000` or `https://zync-backend.onrender.com`) | + +### Google Sheets Logger + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `GOOGLE_SHEETS_SPREADSHEET_ID` | No | `backend/services/sheetLogger.js` | Google Sheets ID for audit logging | +| `GOOGLE_SERVICE_ACCOUNT_EMAIL` | No | `backend/services/sheetLogger.js` | Service account email for Sheets API | +| `GOOGLE_SERVICE_ACCOUNT_PRIVATE_KEY` | No | `backend/services/sheetLogger.js` | Service account private key | + +### Usage & Quota + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `USAGE_MONTHLY_LIMIT` | No | `backend/services/usageService.js` | Monthly usage limit per user | + +--- + +## .env.example Files + +### Root `.env.example` +**File:** `.env.example` +Contains frontend env vars (VITE_* prefix) + +### Backend `.env.example` +**File:** `backend/.env.example` +Contains all backend env vars with descriptions + +### Backend `.env.kilo.example` +**File:** `backend/.env.kilo.example` +Contains Kilo Code Gateway specific env vars + +--- + +## Env Var Loading + +### Frontend (Vite) +- Vite loads `.env` files automatically +- `VITE_` prefix required for client-side exposure +- Files loaded in order: `.env`, `.env.local`, `.env.[mode]`, `.env.[mode].local` + +### Backend (Node.js) +- `dotenv` loads `.env` at startup: `require('dotenv').config()` (`backend/index.js:77`) +- `backend/utils/safeEnv.js` provides safe integer parsing: `getSafeEnvInt(name, min, max, default)` + +--- + +## Cross-References + +- [02-security-auth-architecture.md](./02-security-auth-architecture.md) — Security env vars in context +- [06-middleware-stack.md](./06-middleware-stack.md) — Middleware env vars in context +- [04-service-inventory.md](./04-service-inventory.md) — Which services need which env vars From 9ed38b9a01e480bb643f20661cc73520b732dbc6 Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:43:23 +0530 Subject: [PATCH 7/9] =?UTF-8?q?docs:=20add=2008-firebase-auth-flow=20?= =?UTF-8?q?=E2=80=94=20login,=20signup,=20OAuth=20providers,=20account=20l?= =?UTF-8?q?inking,=20token=20lifecycle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/08-firebase-auth-flow.md | 278 +++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 docs/features/08-firebase-auth-flow.md diff --git a/docs/features/08-firebase-auth-flow.md b/docs/features/08-firebase-auth-flow.md new file mode 100644 index 00000000..23b38d87 --- /dev/null +++ b/docs/features/08-firebase-auth-flow.md @@ -0,0 +1,278 @@ +# 08 — Firebase Auth Flow + +**NEW document** — Complete authentication flow: login, signup, Google/GitHub OAuth, LinkedIn, custom token, account linking, token refresh + +--- + +## Feature Summary + +Zync uses Firebase Authentication as its identity provider. Users can sign in via email/password, Google OAuth, GitHub OAuth, or LinkedIn (via custom token). Firebase issues a JWT (ID token) that the frontend sends as a Bearer token to all API calls. The backend verifies this token via Firebase Admin SDK. + +--- + +## Architecture Diagram + +``` +┌─────────────────── SIGN-IN METHODS ───────────────────────┐ +│ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Email/Pass │ │ Google OAuth│ │ GitHub OAuth│ │ +│ │ signInWith │ │ signInWith │ │ signInWith │ │ +│ │ EmailAndPwd │ │ Popup(Google)│ │ Popup(GitHub)│ │ +│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ +│ │ │ │ │ +│ ┌──────┴──────┐ ┌──────┴──────┐ ┌──────┴──────┐ │ +│ │ LinkedIn │ │ Custom Token│ │ Account │ │ +│ │ OAuth │ │ (redirect) │ │ Linking │ │ +│ │ via backend│ │ signInWith │ │ linkWith │ │ +│ │ → custom │ │ CustomToken │ │ Credential │ │ +│ └──────┬──────┘ └──────┴──────┘ └──────┴──────┘ │ +│ │ │ │ │ +└─────────┼────────────────┼────────────────┼────────────────┘ + │ │ │ + ▼ ▼ ▼ +┌───────────────────────────────────────────────────────────┐ +│ Firebase Auth (Client SDK) │ +│ onAuthStateChanged() → updates currentUser state │ +│ currentUser.getIdToken() → JWT Bearer token │ +└───────────────────────────┬───────────────────────────────┘ + │ + │ Authorization: Bearer + ▼ +┌───────────────────────────────────────────────────────────┐ +│ Backend (Express + Firebase Admin) │ +│ verifyToken middleware: │ +│ getAuth().verifyIdToken(token) │ +│ → req.user = { uid, email } │ +│ If invalid: 403 │ +│ If missing: 401 │ +└───────────────────────────────────────────────────────────┘ +``` + +--- + +## Frontend Trace + +### Firebase Initialization +**File:** `src/lib/firebase.ts:76-152` + +1. **Config object** (lines 89-102): Reads `VITE_FIREBASE_*` env vars with mock fallbacks for CI +2. **App init** (line 111): `getApps().length === 0 ? initializeApp(config) : getApp()` — prevents double-init during HMR +3. **App Check** (lines 127-135): `initializeAppCheck` with `ReCaptchaV3Provider` if valid key exists +4. **Exports** (lines 138-152): + - `auth` — `getAuth(app)` for all auth operations + - `storage` — `getStorage(app)` for file uploads + - `db` — `getFirestore(app)` for real-time sync + - `messaging` — `getMessaging(app)` for FCM (wrapped in try/catch) + +### Login Page +**File:** `src/pages/Login.tsx:119-546` + +#### State Variables (lines 122-138) +| Variable | Type | Purpose | +|---|---|---| +| `email` | string | Email input field | +| `password` | string | Password input field | +| `loading` | boolean | Disable buttons during auth | +| `currentUser` | User \| null | Current Firebase user | +| `confirmState` | { message, resolve } \| null | Custom confirm dialog | + +#### Custom Token Flow (lines 174-196) +1. Reads `customToken` from URL query params (used by LinkedIn OAuth redirect) +2. Calls `signInWithCustomToken(auth, customToken)` +3. On success: toast + `postLoginRedirect(navigate, cred.user)` +4. On error: toast with error message + +#### Auth State Listener (lines 198-207) +- `onAuthStateChanged(auth, (user) => setCurrentUser(user))` +- Shows "Continue as [user]" UI if already logged in +- Shows login form if no user + +#### Email Login (lines 233-252) +```js +const cred = await signInWithEmailAndPassword(auth, email, password); +toast({ title: 'Success', description: 'Logged in successfully' }); +await postLoginRedirect(navigate, cred.user); +``` + +#### Google OAuth Login +- `signInWithPopup(auth, new GoogleAuthProvider())` +- On success: toast + redirect +- On `auth/account-exists-with-different-credential`: triggers account linking flow + +#### GitHub OAuth Login +- `signInWithPopup(auth, new GithubAuthProvider())` +- Same error handling and linking flow as Google + +#### Account Linking Flow (lines 254-273) +1. Catches `auth/account-exists-with-different-credential` error +2. Extracts `pendingCred` from error +3. Gets `email` from `error.customData` +4. Calls `fetchSignInMethodsForEmail(email)` to find existing provider +5. Signs in with existing provider +6. Calls `linkWithCredential(user, pendingCred)` to link the new provider +7. Redirects to dashboard + +#### Continue / Switch Account (lines 209-231) +- **Continue:** `postLoginRedirect(navigate, currentUser)` — go to dashboard with existing user +- **Switch:** `signOutAndClearState(auth)` → clears state → shows login form + +### Signup Page +**File:** `src/pages/Signup.tsx` +- `createUserWithEmailAndPassword(auth, email, password)` +- Creates user in Firebase Auth +- Syncs to MongoDB via `/api/users/sync` endpoint +- Redirects to dashboard + +### Post-Login Redirect +**File:** `src/lib/postLoginRedirect.ts` +- Determines redirect target after login +- Checks for redirect URL in location state +- Falls back to dashboard (`/dashboard`) +- Handles PWA install wall redirect + +### Auth Headers +**File:** `src/lib/auth-headers.ts:80-102` + +#### `getAuthHeaders()` (lines 80-92) +```js +const user = auth.currentUser; +const token = user ? await user.getIdToken() : null; +return { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}), +}; +``` + +#### `getAuthToken()` (lines 95-102) +- Returns raw JWT string +- Throws `'Not authenticated'` if no user + +### Auth Sign-Out +**File:** `src/lib/auth-signout.ts` +- Calls `auth.signOut()` +- Clears TanStack Query cache: `queryClient.clear()` +- Redirects to `/login` + +### User Sync Hook +**File:** `src/hooks/use-user-sync.ts` +- After Firebase auth state changes, syncs user to MongoDB +- Calls `POST /api/users/sync` with Firebase UID + email +- Creates or updates MongoDB User record + +--- + +## Backend Trace + +### Firebase Admin Initialization +**File:** `backend/services/firebaseAdmin.js` +- Initializes Firebase Admin SDK with service account credentials +- Uses `FIREBASE_CLIENT_EMAIL`, `FIREBASE_PRIVATE_KEY`, `FIREBASE_PROJECT_ID` env vars +- `getFirestoreAdmin()` — lazy initialization of Firestore Admin +- `getAuth()` — returns Firebase Admin Auth instance + +### Token Verification Middleware +**File:** `backend/middleware/authMiddleware.js:96-119` + +```js +const verifyToken = async (req, res, next) => { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ message: 'Unauthorized: No token provided' }); + } + const token = authHeader.split('Bearer ')[1]; + try { + const decodedToken = await getAuth().verifyIdToken(token); + req.user = { uid: decodedToken.uid, email: decodedToken.email }; + next(); + } catch (error) { + return res.status(403).json({ message: `Unauthorized: ${error.message}` }); + } +}; +``` + +### User Sync Endpoint +**File:** `backend/routes/userRoutes.js` +- `POST /api/users/sync` — creates or updates MongoDB User from Firebase UID +- Checks if User with `uid` exists +- If not, creates new User with `uid`, `email`, `displayName` +- If yes, updates `lastSeen` and `status: 'online'` + +### LinkedIn OAuth (Custom Token Flow) +**File:** `backend/routes/linkedinRoutes.js` +1. `GET /api/linkedin/auth` — redirects to LinkedIn OAuth URL +2. `GET /api/linkedin/callback` — receives LinkedIn auth code +3. Exchanges code for LinkedIn access token +4. Fetches LinkedIn profile (name, email, photo) +5. Creates/updates Firebase user via Admin SDK +6. Generates Firebase custom token: `getAuth().createCustomToken(uid)` +7. Redirects to frontend: `FRONTEND_URL/login?customToken=` +8. Frontend calls `signInWithCustomToken(auth, customToken)` + +--- + +## Token Lifecycle + +``` +1. User signs in (email/Google/GitHub/LinkedIn) + └─ Firebase issues JWT (ID token), valid for 1 hour + +2. Frontend stores user session (Firebase handles persistence) + └─ auth.currentUser available across page reloads + +3. API calls include: Authorization: Bearer + └─ getAuthHeaders() calls currentUser.getIdToken() + └─ Firebase auto-refreshes if token is expired + +4. Backend verifies: getAuth().verifyIdToken(token) + └─ Returns decodedToken with uid, email, exp + └─ Rejects expired or invalid tokens (403) + +5. User signs out: auth.signOut() + └─ Clears session, TanStack cache, redirects to /login +``` + +--- + +## Error Paths + +| Scenario | Layer | Error Code | Handling | +|---|---|---|---| +| Invalid email/password | Frontend | `auth/wrong-password` | Toast error message | +| Email already in use | Frontend | `auth/email-already-in-use` | Toast, suggest login | +| Account exists with different credential | Frontend | `auth/account-exists-with-different-credential` | Account linking flow | +| No Authorization header | Backend | 401 | `{ message: "Unauthorized: No token provided" }` | +| Expired/invalid JWT | Backend | 403 | `{ message: "Unauthorized: " }` | +| Firebase Admin not initialized | Backend | — | Falls back to `initializeApp()` with default creds | +| LinkedIn callback error | Backend | — | Redirects to `/login?error=` | +| Custom token invalid | Frontend | `auth/invalid-custom-token` | Toast error | + +--- + +## Environment Variables + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `VITE_FIREBASE_API_KEY` | Yes | `src/lib/firebase.ts` | Client API key | +| `VITE_FIREBASE_AUTH_DOMAIN` | Yes | `src/lib/firebase.ts` | Auth domain | +| `VITE_FIREBASE_PROJECT_ID` | Yes | `src/lib/firebase.ts` | Project ID | +| `VITE_FIREBASE_APP_ID` | Yes | `src/lib/firebase.ts` | App ID | +| `VITE_FIREBASE_MESSAGING_SENDER_ID` | Yes | `src/lib/firebase.ts` | FCM sender ID | +| `VITE_RECAPTCHA_SITE_KEY` | No | `src/lib/firebase.ts` | ReCaptcha for App Check | +| `FIREBASE_CLIENT_EMAIL` | Yes | `backend/services/firebaseAdmin.js` | Admin service account email | +| `FIREBASE_PRIVATE_KEY` | Yes | `backend/services/firebaseAdmin.js` | Admin private key | +| `FIREBASE_PROJECT_ID` | Yes | `backend/services/firebaseAdmin.js` | Admin project ID | +| `LINKEDIN_CLIENT_ID` | Yes | `backend/routes/linkedinRoutes.js` | LinkedIn OAuth | +| `LINKEDIN_CLIENT_SECRET` | Yes | `backend/routes/linkedinRoutes.js` | LinkedIn OAuth | +| `LINKEDIN_REDIRECT_URI` | Yes | `backend/routes/linkedinRoutes.js` | LinkedIn callback URL | +| `FRONTEND_URL` | Yes | `backend/routes/linkedinRoutes.js` | Frontend redirect URL | + +--- + +## Cross-References + +- [02-security-auth-architecture.md](./02-security-auth-architecture.md) — Security overview +- [06-middleware-stack.md](./06-middleware-stack.md) — verifyToken middleware detail +- [09-user-profile-management.md](./09-user-profile-management.md) — Profile CRUD after auth +- [13-linkedin-oauth-integration.md](./13-linkedin-oauth-integration.md) — LinkedIn deep dive +- [22-github-oauth-integration.md](./22-github-oauth-integration.md) — GitHub OAuth deep dive From 87e3f7d7cecef998002b7acdc84d5001e7a8f85a Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:44:24 +0530 Subject: [PATCH 8/9] =?UTF-8?q?docs:=20add=2009-user-profile-management=20?= =?UTF-8?q?=E2=80=94=20profile=20CRUD,=20avatar,=20search,=20chat=20reques?= =?UTF-8?q?ts,=20location,=2014=20endpoints=20traced?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/09-user-profile-management.md | 315 ++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 docs/features/09-user-profile-management.md diff --git a/docs/features/09-user-profile-management.md b/docs/features/09-user-profile-management.md new file mode 100644 index 00000000..62045d0f --- /dev/null +++ b/docs/features/09-user-profile-management.md @@ -0,0 +1,315 @@ +# 09 — User Profile Management + +**NEW document** — Profile CRUD, avatar upload, settings, location detection, user search, chat requests + +--- + +## Feature Summary + +User profile management covers: fetching user profile (`/me`), syncing Firebase user to MongoDB (`/sync`), linking GitHub integration (`/sync-github`), location detection (`/detect-location`), user search (`/search`), chat requests (`/chat-request`), password breach check (`/check-breached-password`), profile photo cropping, and settings management. + +--- + +## Architecture Diagram + +``` +┌─────────────────── FRONTEND ───────────────────────────┐ +│ │ +│ SettingsView.tsx (94KB) │ +│ ├─ Profile section: displayName, firstName, lastName │ +│ ├─ Avatar: ProfilePhotoCropper.tsx → Cloudinary │ +│ ├─ Security: password change, breach check │ +│ ├─ Integrations: GitHub, Google, LinkedIn │ +│ ├─ Notifications: FCM token registration │ +│ └─ Account: deletion, export │ +│ │ +│ Hooks: │ +│ ├─ useMe.ts — fetches /api/users/me (TanStack Query) │ +│ ├─ use-user-sync.ts — syncs Firebase user to MongoDB │ +│ └─ use-push-notifications.ts — FCM token management │ +│ │ +│ Components: │ +│ ├─ ProfilePhotoCropper.tsx — react-easy-crop + upload │ +│ └─ PeopleView.tsx — user search + chat requests │ +└────────────────────────┬────────────────────────────────┘ + │ + ▼ +┌─────────────────── BACKEND ROUTES ──────────────────────┐ +│ │ +│ backend/routes/userRoutes.js (957 lines) │ +│ │ +│ GET /api/users/me → fetch profile │ +│ POST /api/users/sync → upsert user │ +│ POST /api/users/sync-github → link GitHub token │ +│ GET /api/users/detect-location → IP geolocation │ +│ GET /api/users/search → user search │ +│ POST /api/users/chat-request → send chat request │ +│ POST /api/users/chat-request/respond → accept/reject │ +│ POST /api/users/check-breached-password → HIBP check │ +│ PUT /api/users/profile → update profile │ +│ POST /api/users/upload-photo → avatar upload │ +│ DELETE /api/users/delete → account deletion │ +│ POST /api/users/verify-phone → phone OTP │ +│ POST /api/users/verify-phone-code → verify OTP │ +│ POST /api/users/fcm-token → register FCM token │ +│ DELETE /api/users/fcm-token → remove FCM token │ +│ POST /api/users/pin → set security PIN │ +│ PUT /api/users/pin → update PIN │ +│ │ +│ Services used: │ +│ ├─ cloudinaryService.js — avatar upload/delete │ +│ ├─ haveIBeenPwnedService.js — breach check │ +│ ├─ geoService.js — IP geolocation │ +│ ├─ mailer.js — email notifications │ +│ ├─ sheetLogger.js — Google Sheets audit log │ +│ └─ cache.js — Redis caching for /me │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## Backend Trace + +### File: `backend/routes/userRoutes.js` (957 lines) + +### Imports (lines 76-97) +```js +const express = require('express'); +const bcrypt = require('bcryptjs'); +const verifyToken = require('../middleware/authMiddleware'); +const User = require('../models/User'); +const Team = require('../models/Team'); +const Project = require('../models/Project'); +const { encrypt } = require('../utils/encryption'); +const { sendZyncEmail } = require('../services/mailer'); +const { appendRow } = require('../services/sheetLogger'); +const { normalizeDoc, normalizeDocs } = require('../utils/normalize'); +const { paginateArray, setPaginationHeaders } = require('../utils/pagination'); +const { getNewUserRegistrationTemplate, getPhoneVerificationEmailHtml, getChatRequestEmailHtml, getAccountDeletionCodeEmailHtml } = require('../utils/emailTemplates'); +const { deleteCloudinaryAsset } = require('../services/cloudinaryService'); +const { checkPassword } = require('../services/haveIBeenPwnedService'); +const { resolveIp } = require('../services/geoService'); +const cache = require('../utils/cache'); +``` + +### Helper: New User Alert Recipients (lines 99-108) +- Reads `NEW_USER_ALERT_RECIPIENTS` or `SUPPORT_RECIPIENTS` env var +- Comma-separated list of admin emails + +### Helper: Detect New User Insert (lines 110-125) +- `wasUserInsertedFromUpsertResult(result)` — checks if MongoDB upsert created a new document +- Checks `lastErrorObject.upserted` or `lastErrorObject.updatedExisting === false` + +### Helper: New User Notifications (lines 127-150) +- `dispatchNewUserNotifications({ displayName, email, uid })` +- Sends admin email via `sendZyncEmail()` with `getNewUserRegistrationTemplate` +- Logs to Google Sheets via `appendRow()` +- Both fire-and-forget (`.catch()` only logs errors) + +### Helper: Phone Verification Email (lines 152-159) +- `sendVerificationEmail(email, code)` — sends OTP code via `getPhoneVerificationEmailHtml` + +--- + +### Endpoint: POST /check-breached-password (lines 161-174) +- **Auth:** No verifyToken (called during signup before user exists) +- **Input:** `{ password: string }` +- **Logic:** Calls `checkPassword(password)` from HIBP service +- **Response:** `{ isBreached: boolean, count: number }` or 429 on rate limit + +### Endpoint: GET /me (lines 176-201) +- **Auth:** verifyToken required +- **Cache:** Redis `user:me:{uid}` with 300s TTL +- **Query:** `User.findOne({ uid }).select('-githubIntegration.accessToken -deleteConfirmationCode ...')` +- **Also:** Fetches team info via `Team.find({ members: user.uid })` +- **Response:** `{ ...userDoc, teamId: teamInfo }` +- **Cache invalidation:** On sync, profile update, GitHub link, location update + +### Endpoint: POST /sync (lines 203-316) +- **Auth:** verifyToken required +- **Input:** `{ uid?, email, displayName, photoURL, phoneNumber, firstName, lastName, timezone }` +- **Security:** UID from body must match `req.user.uid` (line 216-218) +- **Logic:** + 1. Check if user exists (`User.findOne({ uid })`) + 2. Derive `displayName` from email if not provided (line 230-232) + 3. Build `updateData` with provided fields + `status: 'online'`, `lastSeen: new Date()` + 4. `User.findOneAndUpdate({ uid }, { $set: updateData, $setOnInsert: { ... } }, { upsert: true, new: true })` + 5. If new user: `dispatchNewUserNotifications()` (admin email + Sheets log) + 6. If no country: `resolveIp(clientIp)` → async update `country`, `countryCode`, `city` + 7. Fetch team info + 8. Invalidate cache: `cache.invalidate('user:me:{uid}')` +- **Response:** `{ ...userDoc, teamId: teamInfo }` + +### Endpoint: POST /sync-github (lines 318-366) +- **Auth:** verifyToken required +- **Input:** `{ accessToken, username, firebaseUid? }` +- **Logic:** + 1. `encrypt(accessToken)` — AES-256 encrypt GitHub token + 2. `User.findOneAndUpdate({ uid }, { $set: { githubIntegration: { connected: true, accessToken: encrypted, username, connectedAt } } })` + 3. Select excludes `accessToken` from response + 4. Invalidate cache +- **Response:** `{ message: "GitHub account linked successfully", user }` + +### Endpoint: GET /detect-location (lines 369-403) +- **Auth:** verifyToken required +- **Logic:** + 1. Extract client IP from `x-forwarded-for` header or `req.ip` + 2. `resolveIp(clientIp)` — geo service lookup + 3. Update User with `country`, `countryCode`, `city` + 4. Invalidate cache +- **Response:** `{ country, countryCode, city, timezone? }` + +### Endpoint: GET /search (lines 405-437) +- **Auth:** verifyToken required +- **Input:** `?query=` +- **Logic:** + 1. Case-insensitive regex search on `displayName`, `email`, `firstName`, `lastName` + 2. Excludes current user (`uid: { $ne: currentUserUid }`) + 3. Selects: `uid, displayName, email, photoURL, status, lastSeen, teamMemberships` + 4. Pagination via `paginateArray()` + `setPaginationHeaders()` + 5. Default limit 20, max 100 +- **Response:** Array of user objects with pagination headers + +### Endpoint: POST /chat-request (lines 439-491) +- **Auth:** verifyToken required +- **Input:** `{ recipientId, message }` +- **Logic:** + 1. Find sender and recipient + 2. Check for existing pending request from same sender + 3. Create `newRequest = { senderId, senderName, senderEmail, senderPhoto, message, status: 'pending', createdAt }` + 4. Append to `recipient.chatRequests` array + 5. Send email notification via `sendZyncEmail()` with `getChatRequestEmailHtml` + 6. Invalidate recipient cache +- **Response:** `{ message: "Chat request sent successfully" }` + +### Endpoint: POST /chat-request/respond (lines 493-530+) +- **Auth:** verifyToken required +- **Input:** `{ senderId, status: 'accepted' | 'rejected' }` +- **Logic:** + 1. Find recipient and sender + 2. Update request status in `recipient.chatRequests` + 3. If accepted: add sender to `recipient.connections` and recipient to `sender.connections` + 4. Invalidate both caches +- **Response:** `{ message: "Chat request {status}" }` + +--- + +## Frontend Trace + +### useMe Hook +**File:** `src/hooks/useMe.ts` +- TanStack Query: `useQuery({ queryKey: ['user', 'me'], queryFn: () => fetch('/api/users/me', { headers: await getAuthHeaders() }) })` +- `staleTime: 60_000` (1 minute) +- Returns user profile data for the entire app + +### use-user-sync Hook +**File:** `src/hooks/use-user-sync.ts` +- Triggered on `onAuthStateChanged` in App.tsx +- Calls `POST /api/users/sync` with Firebase user data +- Ensures MongoDB User record exists for every authenticated Firebase user + +### SettingsView Component +**File:** `src/components/views/SettingsView.tsx` (94KB — largest component) +- Tabbed interface: Profile, Security, Integrations, Notifications, Account +- **Profile tab:** displayName, firstName, lastName, photoURL editing +- **Security tab:** Password breach check, security PIN +- **Integrations tab:** GitHub connect/disconnect, Google connect/disconnect, LinkedIn +- **Notifications tab:** FCM token registration, push notification preferences +- **Account tab:** Delete account with confirmation code + +### ProfilePhotoCropper Component +**File:** `src/components/ProfilePhotoCropper.tsx` (12KB) +- Uses `react-easy-crop` for client-side cropping +- Crop area: circular, zoom controls +- Output: WebP format, compressed +- Upload: `POST /api/users/upload-photo` → Cloudinary +- On success: updates `useMe` query cache + +### PeopleView Component +**File:** `src/components/views/PeopleView.tsx` (34KB) +- User search with debounced input +- Displays search results with avatar, name, status +- Send chat request button +- Pending/accepted/rejected request states + +--- + +## Database Layer + +### User Model (Mongoose) +**File:** `backend/models/User.js:78-141` + +Key fields for profile management: +| Field | Type | Select | Notes | +|---|---|---|---| +| `uid` | String | yes | Firebase UID, unique | +| `email` | String | yes | Unique | +| `displayName` | String | yes | Default "User" | +| `firstName` | String? | yes | | +| `lastName` | String? | yes | | +| `photoURL` | String? | yes | Cloudinary URL | +| `phoneNumber` | String? | yes | | +| `connections` | String[] | yes | Firebase UIDs | +| `chatRequests` | Mixed | yes | Array of request objects | +| `githubIntegration` | Mixed | yes (excludes accessToken) | | +| `googleIntegration` | Mixed | yes (excludes refreshToken) | | +| `fcmTokens` | Array | yes | Push notification tokens | +| `country` | String? | yes | From IP geolocation | +| `countryCode` | String? | yes | | +| `city` | String? | yes | | +| `timezone` | String? | yes | | +| `securityPin` | String | **no** (select: false) | Hidden from queries | +| `status` | String | yes | online/offline/away | +| `lastSeen` | Date | yes | | + +**Text Index:** `{ displayName: 'text', firstName: 'text', lastName: 'text' }` + +--- + +## Caching Strategy + +| Action | Cache Key | TTL | Invalidation | +|---|---|---|---| +| GET /me | `user:me:{uid}` | 300s | Invalidated on sync, profile update, GitHub link, location update, chat request | +| User search | None | — | Real-time query every time | + +--- + +## Error Paths + +| Scenario | HTTP Status | Response | +|---|---|---| +| No token | 401 | `{ message: "Unauthorized: No token provided" }` | +| UID mismatch in sync | 403 | `{ message: "Unauthorized: UID mismatch" }` | +| User not found | 404 | `{ message: "User not found" }` | +| Recipient not found | 404 | `{ message: "Recipient not found" }` | +| Duplicate chat request | 400 | `{ message: "Request already sent" }` | +| Invalid chat request status | 400 | `{ message: "Invalid status" }` | +| Password missing | 400 | `{ message: "Password is required" }` | +| HIBP rate limited | 429 | `{ message: error.message }` | +| Server error | 500 | `{ message: "Server error" }` | + +--- + +## Environment Variables + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `NEW_USER_ALERT_RECIPIENTS` | No | `userRoutes.js:99-108` | Admin emails for new user alerts | +| `SUPPORT_RECIPIENTS` | No | `userRoutes.js:99-108` | Fallback for admin emails | +| `CLOUDINARY_CLOUD_NAME` | Yes | `cloudinaryService.js` | Avatar upload | +| `CLOUDINARY_API_KEY` | Yes | `cloudinaryService.js` | Avatar upload | +| `CLOUDINARY_API_SECRET` | Yes | `cloudinaryService.js` | Avatar upload | +| `MASTER_ENCRYPTION_KEY` | Yes | `encryption.js` | GitHub token encryption | + +--- + +## Cross-References + +- [08-firebase-auth-flow.md](./08-firebase-auth-flow.md) — Auth flow that precedes profile management +- [10-account-deletion-flow.md](./10-account-deletion-flow.md) — Account deletion deep dive +- [12-haveibeenpwned-integration.md](./12-haveibeenpwned-integration.md) — Password breach check +- [22-github-oauth-integration.md](./22-github-oauth-integration.md) — GitHub token sync +- [30-chat-request-flow.md](./30-chat-request-flow.md) — Chat request deep dive +- [48-cloudinary-upload-service.md](./48-cloudinary-upload-service.md) — Avatar upload pipeline +- [49-profile-photo-cropper.md](./49-profile-photo-cropper.md) — Photo cropper component From 574469ed79074e7f2035e408d2ea00855aae5efc Mon Sep 17 00:00:00 2001 From: Thanmayee Reddy Kotha <190446018+thanmayeereddykotha@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:45:02 +0530 Subject: [PATCH 9/9] =?UTF-8?q?docs:=20add=2010-account-deletion-flow=20?= =?UTF-8?q?=E2=80=94=20two-step=20email=20verification,=20cascade=20cleanu?= =?UTF-8?q?p,=20sequence=20diagram?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/features/10-account-deletion-flow.md | 234 ++++++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 docs/features/10-account-deletion-flow.md diff --git a/docs/features/10-account-deletion-flow.md b/docs/features/10-account-deletion-flow.md new file mode 100644 index 00000000..8b29ff08 --- /dev/null +++ b/docs/features/10-account-deletion-flow.md @@ -0,0 +1,234 @@ +# 10 — Account Deletion Flow + +**NEW document** — Email confirmation code, cascade cleanup, Firebase Auth deletion, Cloudinary asset cleanup + +--- + +## Feature Summary + +Account deletion is a two-step process: (1) request a 6-digit verification code via email, (2) confirm deletion with the code. The backend cascades cleanup across MongoDB (User, Team memberships), Cloudinary (profile photo), and Firebase Auth (user record). + +--- + +## Architecture Diagram + +``` +┌─────────────────── FRONTEND (SettingsView) ───────────────┐ +│ │ +│ Account tab → "Delete Account" section │ +│ │ +│ Step 1: User clicks "Delete Account" │ +│ └─ POST /api/users/delete/request │ +│ └─ Backend generates 6-digit code │ +│ └─ Stores code + 10min expiry on User document │ +│ └─ Sends email via Nodemailer with code │ +│ └─ Returns: "Verification code sent to email" │ +│ │ +│ Step 2: User enters code in confirmation dialog │ +│ └─ POST /api/users/delete/confirm │ +│ └─ Verifies code matches + not expired │ +│ └─ Removes user from all Team.members arrays │ +│ └─ Deletes Cloudinary profile photo (if exists) │ +│ └─ Deletes User document from MongoDB │ +│ └─ Invalidates Redis cache │ +│ └─ Deletes user from Firebase Auth │ +│ └─ Returns: "User deleted successfully" │ +│ │ +│ Step 3: Frontend calls auth.signOut() │ +│ └─ Clears TanStack Query cache │ +│ └─ Redirects to /login │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## Sequence Diagram + +``` +User Frontend Backend Email(SMTP) MongoDB Cloudinary Firebase + │ │ │ │ │ │ │ + │ Click Delete │ │ │ │ │ │ + │───────────────►│ │ │ │ │ │ + │ │ POST /delete/request │ │ │ │ + │ │─────────────────►│ │ │ │ │ + │ │ │ Generate code │ │ │ │ + │ │ │ Save to User doc │ │ │ │ + │ │ │──────────────────────────────►│ │ │ + │ │ │ Send email │ │ │ │ + │ │ │──────────────────►│ │ │ │ + │ │ 200 OK │ │ │ │ │ + │ │◄─────────────────│ │ │ │ │ + │ Enter code │ │ │ │ │ │ + │───────────────►│ │ │ │ │ │ + │ │ POST /delete/confirm │ │ │ │ + │ │─────────────────►│ │ │ │ │ + │ │ │ Verify code+expiry │ │ │ │ + │ │ │──────────────────────────────►│ │ │ + │ │ │ Remove from Teams │ │ │ │ + │ │ │──────────────────────────────►│ │ │ + │ │ │ Delete photo │ │ │ │ + │ │ │──────────────────────────────────────────►│ │ + │ │ │ Delete User doc │ │ │ │ + │ │ │──────────────────────────────►│ │ │ + │ │ │ Invalidate cache │ │ │ │ + │ │ │ Delete from FB Auth │ │ │ + │ │ │──────────────────────────────────────────────────────►│ + │ │ 200 OK │ │ │ │ │ + │ │◄─────────────────│ │ │ │ │ + │ │ auth.signOut() │ │ │ │ │ + │ │ Redirect /login │ │ │ │ │ + │◄───────────────│ │ │ │ │ │ +``` + +--- + +## Backend Trace + +### File: `backend/routes/userRoutes.js` + +### Endpoint: POST /delete/request (lines 690-730) +- **Auth:** verifyToken required +- **Input:** `{ uid }` (from `req.user.uid`) +- **Logic:** + 1. Find user by UID + 2. Generate 6-digit code: `Math.floor(100000 + Math.random() * 900000).toString()` + 3. Store code + 10-minute expiry on User document: + ```js + await User.updateOne( + { uid }, + { $set: { + deleteConfirmationCode: code, + deleteConfirmationExpired: new Date(Date.now() + 10 * 60 * 1000), + } } + ); + ``` + 4. Send email via `sendZyncEmail()` with `getAccountDeletionCodeEmailHtml({ code })` + 5. Return 200: `{ message: 'Verification code sent to email' }` + +### Endpoint: POST /delete/confirm (lines 732-804) +- **Auth:** verifyToken required +- **Input:** `{ uid, code }` +- **Security check:** `req.user.uid !== uid` → 403 (line 736-741) +- **Verification:** + 1. Find user by UID + 2. Check `user.deleteConfirmationCode !== code` → 400 "Invalid code" + 3. Check `user.deleteConfirmationExpired < new Date()` → 400 "Code expired" +- **Cascade cleanup:** + 1. **Teams:** Find all teams where user is a member, remove UID from `members` array + ```js + const teamsWithUser = await Team.find({ members: uid }).lean(); + for (const team of teamsWithUser) { + await Team.updateOne( + { _id: team._id }, + { $set: { members: team.members.filter((m) => m !== uid) } } + ); + } + ``` + 2. **Cloudinary:** Delete profile photo if `user.photoURL` exists + ```js + if (user.photoURL) { + try { await deleteCloudinaryAsset(user.photoURL); } + catch (deleteError) { console.warn(...); } + } + ``` + - Non-blocking: failure is logged but doesn't prevent deletion + 3. **MongoDB:** Delete User document + ```js + await User.deleteOne({ uid }); + ``` + 4. **Redis:** Invalidate cache + ```js + cache.invalidate(`user:me:${uid}`); + ``` + 5. **Firebase Auth:** Delete user from Firebase (non-blocking, error logged) + ```js + const { getAuth } = require('firebase-admin/auth'); + await getAuth().deleteUser(uid); + ``` + - If Firebase deletion fails, MongoDB user is already deleted — user can no longer log in +- **Response:** 200 `{ message: 'User deleted successfully' }` + +--- + +## Frontend Trace + +### SettingsView — Account Tab +**File:** `src/components/views/SettingsView.tsx` +- "Delete Account" section in Account tab +- Step 1: Button triggers `POST /api/users/delete/request` +- Step 2: Dialog with 6-digit code input (uses `input-otp` component) +- Step 3: On confirm, calls `POST /api/users/delete/confirm` +- Step 4: On success, calls `signOutAndClearState(auth)` → redirect to `/login` + +### OTP Input Component +**File:** `src/components/ui/` (Radix-based) +- Uses `input-otp` package for 6-digit code entry +- Auto-advances between digits +- Paste support + +--- + +## Database Changes + +### User Document — Deletion Fields +| Field | Type | Set During | Cleared After | +|---|---|---|---| +| `deleteConfirmationCode` | String | `/delete/request` | User deletion | +| `deleteConfirmationExpired` | DateTime | `/delete/request` (+10 min) | User deletion | + +### Cascade Cleanup Summary +| Collection | Action | Blocking? | +|---|---|---| +| `users` | `deleteOne({ uid })` | Yes — must succeed | +| `teams` | Remove UID from `members` array | Yes — must succeed | +| Cloudinary | `deleteCloudinaryAsset(photoURL)` | No — failure logged, not fatal | +| Firebase Auth | `getAuth().deleteUser(uid)` | No — failure logged, not fatal | +| Redis cache | `cache.invalidate('user:me:{uid}')` | Yes — must succeed | + +--- + +## Error Paths + +| Scenario | HTTP Status | Response | +|---|---|---| +| No token | 401 | `{ message: "Unauthorized: No token provided" }` | +| UID mismatch (trying to delete another user) | 403 | `{ message: "Unauthorized" }` | +| User not found | 404 | `{ message: "User not found" }` | +| Wrong code | 400 | `{ message: "Invalid code" }` | +| Expired code (>10 min) | 400 | `{ message: "Code expired" }` | +| Cloudinary delete fails | — (logged) | Deletion continues | +| Firebase Auth delete fails | — (logged) | Deletion continues (MongoDB user already removed) | +| Server error | 500 | `{ message: "Server error" }` | + +--- + +## Security Considerations + +1. **UID verification:** `req.user.uid !== uid` check prevents deleting another user's account +2. **Email confirmation:** 6-digit code sent to user's email — prevents accidental deletion +3. **10-minute expiry:** Code expires after 10 minutes — prevents stale codes +4. **Non-blocking Firebase deletion:** If Firebase Auth deletion fails, MongoDB user is already gone — user cannot log in or access API +5. **Cloudinary cleanup:** Profile photo is deleted to prevent orphaned assets + +--- + +## Environment Variables + +| Variable | Required | Used By | Description | +|---|---|---|---| +| `SMTP_HOST` | Yes | `mailer.js` | Email sending for deletion code | +| `SMTP_PORT` | Yes | `mailer.js` | Email sending | +| `SMTP_USER` | Yes | `mailer.js` | Email sending | +| `SMTP_PASS` | Yes | `mailer.js` | Email sending | +| `CLOUDINARY_CLOUD_NAME` | Yes | `cloudinaryService.js` | Photo deletion | +| `CLOUDINARY_API_KEY` | Yes | `cloudinaryService.js` | Photo deletion | +| `CLOUDINARY_API_SECRET` | Yes | `cloudinaryService.js` | Photo deletion | + +--- + +## Cross-References + +- [09-user-profile-management.md](./09-user-profile-management.md) — Parent profile management +- [08-firebase-auth-flow.md](./08-firebase-auth-flow.md) — Firebase Auth user deletion +- [48-cloudinary-upload-service.md](./48-cloudinary-upload-service.md) — Cloudinary asset deletion +- [41-team-crud-and-invites.md](./41-team-crud-and-invites.md) — Team member removal on deletion