Skip to content

Latest commit

Β 

History

History
784 lines (674 loc) Β· 38.6 KB

File metadata and controls

784 lines (674 loc) Β· 38.6 KB

Architecture Documentation β€” Code for Change Nepal

Status: Living document β€” reflects the codebase as of May 2026
Stack: React 19 + Vite + Tailwind CSS 4 (frontend) Β· Express 5 + TypeScript + MongoDB (backend)


Table of Contents

  1. System Overview
  2. Directory Structure
  3. Frontend Architecture
  4. Backend Architecture
  5. Authentication & Authorization Flow
  6. Module Pattern
  7. Role-Based Access Control (RBAC)
  8. Rate Limiting Strategy
  9. File Upload Pipeline
  10. Push Notification Architecture
  11. Deployment Topology

System Overview

The CFC website is a full-stack monorepo with a clear separation of concerns:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   DNS / CDN                          β”‚
β”‚            (Vercel Edge Network)                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                               β”‚
           β–Ό                               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Frontend (React)  β”‚     β”‚   Backend (Express)      β”‚
β”‚   codeforchangenepalβ”‚     β”‚   /api/*                 β”‚
β”‚   .com              β”‚     β”‚   deployed as Vercel     β”‚
β”‚                     │────▢│   Serverless Function     β”‚
β”‚   PWA + SW + Push   β”‚     β”‚                          β”‚
β”‚   Notifications     β”‚     β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚                     β”‚     β”‚   β”‚  MongoDB Atlas   β”‚    β”‚
β”‚   Deployed: Vercel  β”‚     β”‚   β”‚  (Mongoose ODM)  β”‚    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
                            β”‚                          β”‚
                            β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
                            β”‚   β”‚  Cloudinary      β”‚    β”‚
                            β”‚   β”‚  (Media Storage) β”‚    β”‚
                            β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
                            β”‚                          β”‚
                            β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
                            β”‚   β”‚  Redis (Optional)β”‚    β”‚
                            β”‚   β”‚  (Caching)       β”‚    β”‚
                            β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
                            β”‚                          β”‚
                            β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
                            β”‚   β”‚  SMTP (Gmail)    β”‚    β”‚
                            β”‚   β”‚  (Email/OTP)     β”‚    β”‚
                            β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Technologies

Layer Technology Version Purpose
Frontend Framework React 19.2 UI rendering
Build Tool Vite 7.2 Dev server + bundling
CSS Framework Tailwind CSS 4.1 Utility-first styling
Animation Framer Motion 12.38 Declarative animations
Routing React Router DOM 7.13 Client-side routing
HTTP Client Axios 1.13 API communication
Charts Recharts 3.8 Admin dashboard charts
WYSIWYG Editor Jodit React 5.3 Rich text editing
PDF Generation jsPDF 4.2 Certificate/resume export
QR Code React QR Code 2.18 Certificate verification
Backend Framework Express 5.2 HTTP server
Runtime TypeScript / tsx 5.9 Type safety
Database ODM Mongoose 9.0 MongoDB object modeling
Validation Zod 4.2 Schema validation
Authentication JWT + WebAuthn β€” Auth + biometrics
File Upload Multer + Cloudinary 2.0 Image/file hosting
Push Notifications web-push 3.6 Web Push API
Payment eSewa API β€” Donation processing
Email Nodemailer 7.0 OTP, notifications

Directory Structure

CFC-Official-Website/
β”‚
β”œβ”€β”€ README.md                          # Project overview (this doc)
β”œβ”€β”€ fixing.md                          # Code review findings
β”œβ”€β”€ biometrics-login.md                # WebAuthn documentation
β”œβ”€β”€ docs/                              # All documentation
β”‚   β”œβ”€β”€ ARCHITECTURE.md                # This file
β”‚   β”œβ”€β”€ API.md                         # API reference
β”‚   β”œβ”€β”€ SETUP.md                       # Setup guide
β”‚   β”œβ”€β”€ DEPLOYMENT.md                  # Deployment guide
β”‚   β”œβ”€β”€ CONTRIBUTING.md                # Contributor guide
β”‚   └── EDGE_CASES.md                  # Gotchas & edge cases
β”‚
β”œβ”€β”€ frontend-cfc/                      # React application
β”‚   β”œβ”€β”€ index.html                     # Entry HTML (PWA meta, splash screens)
β”‚   β”œβ”€β”€ vite.config.js                 # Vite config (React + Tailwind plugins)
β”‚   β”œβ”€β”€ tailwind.config.js             # Tailwind configuration
β”‚   β”œβ”€β”€ eslint.config.js               # ESLint flat config
β”‚   β”œβ”€β”€ vercel.json                    # Vercel deploy config
β”‚   β”œβ”€β”€ package.json                   # Dependencies & scripts
β”‚   β”œβ”€β”€ public/
β”‚   β”‚   β”œβ”€β”€ manifest.json              # PWA manifest (13 icon sizes)
β”‚   β”‚   β”œβ”€β”€ sw.js                      # Service worker (push notifications)
β”‚   β”‚   β”œβ”€β”€ sitemap.xml                # Static sitemap
β”‚   β”‚   β”œβ”€β”€ robots.txt                 # Robots exclusion rules
β”‚   β”‚   β”œβ”€β”€ og-image.png               # Open Graph image
β”‚   β”‚   β”œβ”€β”€ logo.png                   # CFC logo
β”‚   β”‚   β”œβ”€β”€ sajilodigital.png          # Sajilo Digital badge
β”‚   β”‚   └── Resume/                    # Sample resume files
β”‚   └── src/
β”‚       β”œβ”€β”€ main.jsx                   # App entry (Router + AuthProvider + HelmetProvider)
β”‚       β”œβ”€β”€ App.jsx                    # Route definitions (all pages)
β”‚       β”œβ”€β”€ App.css                    # Empty (Tailwind via index.css)
β”‚       β”œβ”€β”€ index.css                  # Global styles + Tailwind directives
β”‚       β”œβ”€β”€ Context/
β”‚       β”‚   └── AuthContext.jsx        # Auth state (user, login, logout, passkey, permissions)
β”‚       β”œβ”€β”€ Services/
β”‚       β”‚   └── api.jsx                # Axios instance (cookie + Bearer fallback)
β”‚       β”œβ”€β”€ Hooks/
β”‚       β”‚   β”œβ”€β”€ useScrollToTop.jsx     # Scroll reset on route change
β”‚       β”‚   β”œβ”€β”€ useFetch.jsx           # Generic data fetching hook
β”‚       β”‚   β”œβ”€β”€ useDebounce.jsx        # Debounced value hook
β”‚       β”‚   β”œβ”€β”€ usePWAInstall.js       # PWA install prompt hook
β”‚       β”‚   β”œβ”€β”€ useProvinceColors.js   # Province chart color mapping
β”‚       β”‚   β”œβ”€β”€ useResumes.js          # Resume CRUD operations hook
β”‚       β”‚   └── useImageCompressor.js  # Image compression before upload
β”‚       β”œβ”€β”€ Layout/
β”‚       β”‚   β”œβ”€β”€ MainLayout.jsx         # Header + Footer + children
β”‚       β”‚   β”œβ”€β”€ AdminLayout.jsx        # Sidebar + TopBar + content
β”‚       β”‚   └── AuthLayout.jsx         # Centered card layout
β”‚       β”œβ”€β”€ Pages/                     # Page components
β”‚       β”‚   β”œβ”€β”€ {Home,About,Events,...}.jsx
β”‚       β”‚   β”œβ”€β”€ Auth/                  # Login, Register, OTP, ResetPassword
β”‚       β”‚   β”œβ”€β”€ Admin/                 # 18 admin pages (Dashboard, Events, Blogs, Users, etc.)
β”‚       β”‚   └── ResumeBuilder/         # Resume dashboard, builder, 3 templates
β”‚       β”œβ”€β”€ Components/
β”‚       β”‚   β”œβ”€β”€ Common/                # Header, Footer, PrivateRoute, SEO, Animations, DocxViewer
β”‚       β”‚   β”œβ”€β”€ UI/                    # Card, Banner, EventCard, CustomCursor, Modal, etc.
β”‚       β”‚   β”œβ”€β”€ PageComponents/        # HeroSection, Testimonials, Supporters, EventFilter, etc.
β”‚       β”‚   └── Dashboard/             # Admin charts (Recharts components)
β”‚       β”œβ”€β”€ Data/
β”‚       β”‚   β”œβ”€β”€ navItems.js            # Navigation structure
β”‚       β”‚   β”œβ”€β”€ teamData.js            # Team member data
β”‚       β”‚   └── resumeData.js          # Resume template/mock data
β”‚       └── utils/
β”‚           β”œβ”€β”€ pushNotification.js    # Service worker registration
β”‚           └── imageCompressor.js     # Browser-image-compression wrapper
β”‚
└── backend-cfc/                       # Express + TypeScript backend
    β”œβ”€β”€ package.json                   # Dependencies & scripts
    β”œβ”€β”€ tsconfig.json                  # TypeScript config (ES2020, ESNext modules)
    β”œβ”€β”€ vercel.json                    # Vercel serverless route config
    β”œβ”€β”€ generate-vapid.cjs             # VAPID key generator
    β”œβ”€β”€ api/
    β”‚   └── index.ts                   # Vercel serverless entry point
    └── src/
        β”œβ”€β”€ server.ts                  # Standalone server entry (dev/prod)
        β”œβ”€β”€ app.ts                     # Express app setup (middleware, routes, error handler)
        β”œβ”€β”€ loaders/
        β”‚   └── database.ts            # MongoDB connection loader
        β”œβ”€β”€ shared/
        β”‚   β”œβ”€β”€ configs/
        β”‚   β”‚   β”œβ”€β”€ env.ts             # Zod-validated environment variables
        β”‚   β”‚   β”œβ”€β”€ database.ts        # Alternative DB connection (for scripts)
        β”‚   β”‚   β”œβ”€β”€ cloudinary.ts      # Cloudinary config
        β”‚   β”‚   β”œβ”€β”€ redis.ts           # Redis client (with mock fallback)
        β”‚   β”‚   └── permissions.ts     # Roles, EB positions, permissions mapping
        β”‚   β”œβ”€β”€ middlewares/
        β”‚   β”‚   β”œβ”€β”€ auth.middleware.ts  # JWT verification (cookie + Bearer)
        β”‚   β”‚   β”œβ”€β”€ role.middleware.ts  # Permission-based authorization
        β”‚   β”‚   β”œβ”€β”€ validate.middleware.ts # Zod body/params/query validation
        β”‚   β”‚   └── multer.ts          # File upload (memory storage, 5MB limit)
        β”‚   └── utils/
        β”‚       β”œβ”€β”€ appError.ts        # Operational error class
        β”‚       β”œβ”€β”€ errorHandler.ts    # Global error handler
        β”‚       β”œβ”€β”€ asyncHandler.ts    # Async route wrapper
        β”‚       β”œβ”€β”€ jwt.ts             # Token generation + verification
        β”‚       β”œβ”€β”€ hash.ts            # bcrypt password hashing
        β”‚       β”œβ”€β”€ mailer.ts          # Nodemailer transport (Gmail)
        β”‚       β”œβ”€β”€ otp.ts             # OTP generation + email sending
        β”‚       β”œβ”€β”€ response.ts        # Standardized JSON responses
        β”‚       β”œβ”€β”€ sendError.ts       # Error response helper
        β”‚       β”œβ”€β”€ cloudinary.ts      # Cloudinary upload/delete/extract
        β”‚       β”œβ”€β”€ dns.ts             # Email domain MX record verification
        β”‚       └── qr.ts              # (empty β€” QR generation in certificate model)
        └── modules/                   # 21 feature modules
            β”œβ”€β”€ auth/                  # Login, register, OTP, WebAuthn
            β”œβ”€β”€ user/                  # User CRUD, permissions, seeding
            β”œβ”€β”€ admin/                 # Dashboard, stats, activity logs
            β”œβ”€β”€ events/                # Event CRUD
            β”œβ”€β”€ blogs/                 # Blog CRUD
            β”œβ”€β”€ impact/                # Impact stories CRUD
            β”œβ”€β”€ gallery/               # Gallery CRUD
            β”œβ”€β”€ certificates/          # Certificate issue, verify, bulk
            β”œβ”€β”€ donations/             # Donations + eSewa payment
            β”œβ”€β”€ internships/           # Internship CRUD
            β”œβ”€β”€ resources/             # Resource CRUD (role-filtered)
            β”œβ”€β”€ contact/               # Contact form + admin
            β”œβ”€β”€ newsletter/            # Newsletter subscribe + admin
            β”œβ”€β”€ testimonials/          # Testimonial CRUD
            β”œβ”€β”€ supporters/            # Supporter CRUD
            β”œβ”€β”€ team/                  # Team member CRUD
            β”œβ”€β”€ periodicals/           # Periodical CRUD
            β”œβ”€β”€ walkthroughs/          # Walkthrough CRUD
            β”œβ”€β”€ resume/                # Resume builder CRUD
            β”œβ”€β”€ notifications/         # Push subscription + admin send
            └── seo/                   # Dynamic sitemap + robots.txt

Frontend Architecture

Route Structure

Routes are defined in App.jsx using React Router v7 with three layout groups:

<Routes>
  β”œβ”€β”€ <MainLayout>                     # Public routes
  β”‚   β”œβ”€β”€ /                            β†’ Home
  β”‚   β”œβ”€β”€ /about                       β†’ About
  β”‚   β”œβ”€β”€ /events                      β†’ Events
  β”‚   β”œβ”€β”€ /events/:slug                β†’ EventDetails
  β”‚   β”œβ”€β”€ /creative                    β†’ Blog listing
  β”‚   β”œβ”€β”€ /creative/:slug              β†’ BlogDetail
  β”‚   β”œβ”€β”€ /creative/walkthrough        β†’ WalkthroughList
  β”‚   β”œβ”€β”€ /creative/walkthrough/:slug  β†’ WalkthroughDetail
  β”‚   β”œβ”€β”€ /creative/periodicals        β†’ Periodicals
  β”‚   β”œβ”€β”€ /our-impact                  β†’ OurImpact
  β”‚   β”œβ”€β”€ /our-impact/:id              β†’ ImpactDetail
  β”‚   β”œβ”€β”€ /provinces                   β†’ Provinces
  β”‚   β”œβ”€β”€ /provinces/:provinceName     β†’ ProvinceDetails
  β”‚   β”œβ”€β”€ /certificate-verification/:token β†’ CertificateVerification
  β”‚   β”œβ”€β”€ /internships                 β†’ Internships
  β”‚   β”œβ”€β”€ /internship-application      β†’ InternshipApplication
  β”‚   β”œβ”€β”€ /donate-us                   β†’ DonateUs
  β”‚   β”œβ”€β”€ /donation-success            β†’ DonationSuccess
  β”‚   β”œβ”€β”€ /donation-failure            β†’ DonationFailure
  β”‚   β”œβ”€β”€ /gallery                     β†’ Gallery
  β”‚   β”œβ”€β”€ /resources                   β†’ Resources
  β”‚   β”œβ”€β”€ /contact-us                  β†’ Contact
  β”‚   β”œβ”€β”€ /faq                         β†’ FAQ
  β”‚   β”œβ”€β”€ /register                    β†’ Register
  β”‚   β”œβ”€β”€ /join-us                     β†’ JoinUs
  β”‚   └── *                            β†’ NotFound
  β”‚
  β”œβ”€β”€ <PrivateRoute> + <MainLayout>    # Authenticated user routes
  β”‚   β”œβ”€β”€ /profile                     β†’ UserProfile
  β”‚   β”œβ”€β”€ /resume-builder              β†’ ResumeDashboard
  β”‚   └── /resume-builder/:resumeId    β†’ ResumeBuilder
  β”‚
  β”œβ”€β”€ <AuthLayout>                     # Auth pages (centered card)
  β”‚   β”œβ”€β”€ /login                       β†’ Login
  β”‚   β”œβ”€β”€ /forget-password             β†’ ForgetPassword
  β”‚   β”œβ”€β”€ /verify-otp                  β†’ OTPVerify
  β”‚   └── /reset-password              β†’ ResetPassword
  β”‚
  └── <PrivateRoute adminOnly> + <AdminLayout>  # Admin routes (18 pages)
      └── /admin/{...}                 β†’ Dashboard, Events, Blogs, Users, etc.
</Routes>

State Management

The application uses React Context for auth state (AuthContext) rather than a full state management library. This is appropriate because:

  • Auth state is the only globally shared state
  • All other data is fetched per-page via custom hooks (useFetch, useResumes)
  • The admin dashboard fetches fresh data on mount (no stale cache concerns)

Admin Page Lazy Loading

All 18 admin pages and 3 resume builder pages are lazy-loaded using React.lazy() + Suspense:

const Dashboard = lazy(() => import("./Pages/Admin/Dashboard"));

This ensures the initial bundle only contains public pages, keeping the first-load bundle small. Admin pages are fetched on-demand when the user navigates to /admin/*.

PWA Architecture

  • Manifest: public/manifest.json β€” 13 icon sizes, theme color #0076B4, display: standalone
  • Service Worker: public/sw.js β€” handles push events (notification display) and notificationclick events (URL navigation)
  • iOS Support: Splash screens for 8 device sizes (iPhone 16 Pro Max, iPhone SE, iPad Pro 13" etc.)
  • Install Prompt: usePWAInstall.js hook manages the beforeinstallprompt event

Backend Architecture

Request Lifecycle

HTTP Request
    β”‚
    β–Ό
Vercel Serverless (api/index.ts)
    β”‚
    β–Ό
app.ts Middleware Stack
    β”œβ”€β”€ 1. helmet()               β€” Security headers
    β”œβ”€β”€ 2. cors()                  β€” CORS with allowed origins
    β”œβ”€β”€ 3. express.json()          β€” Body parsing (2MB limit)
    β”œβ”€β”€ 4. express.urlencoded()    β€” URL-encoded body parsing
    β”œβ”€β”€ 5. mongoSanitize()         β€” NoSQL injection prevention
    β”œβ”€β”€ 6. xssSanitize()           β€” Custom XSS stripping (script tags, event handlers)
    β”œβ”€β”€ 7. cookieParser()          β€” Cookie parsing
    β”œβ”€β”€ 8. hpp()                   β€” HTTP parameter pollution protection
    β”œβ”€β”€ 9. compression()           β€” Gzip/brotli compression
    β”œβ”€β”€ 10. morgan("dev")          β€” Request logging (dev only)
    β”œβ”€β”€ 11. Rate Limiters          β€” Auth, registration, password reset, WebAuthn
    β”‚
    β–Ό
Module Router (e.g., event.route.ts)
    β”œβ”€β”€ Optional: authenticate     β€” JWT verification
    β”œβ”€β”€ Optional: requirePermission β€” RBAC check
    β”œβ”€β”€ Optional: validate         β€” Zod schema validation
    β”œβ”€β”€ Optional: upload           β€” Multer file handling
    β”‚
    β–Ό
Controller
    β”‚   (thin layer: extracts params, calls service, sends response)
    β–Ό
Service
    β”‚   (business logic, database operations)
    β–Ό
Mongoose Model β†’ MongoDB Atlas

Middleware Execution Order (in app.ts)

Middlewares are registered in a specific order for security:

  1. CORS must come before route handlers to handle preflight
  2. Security middlewares (helmet, mongoSanitize, xssSanitize, hpp) come before body parsing to sanitize input early
  3. Rate limiters are applied after parsing but before route handlers
  4. Auth middleware is applied per-route (not globally), allowing public endpoints
  5. Global error handler is registered LAST β€” this is critical

The XSS Sanitizer (Custom Middleware)

const xssSanitize = (req, _res, next) => {
  if (req.body) {
    req.body = stripHtml(req.body);
  }
  next();
};

This recursive function strips:

  • <script> blocks (with arbitrary attributes)
  • All HTML tags via regex
  • javascript: protocol strings
  • Event handler attributes (onclick=, onerror=, etc.)

Note: This is a custom implementation. The xss-clean package is in package.json but is not used in the middleware chain.


Authentication & Authorization Flow

Password-Based Login

User                    Frontend                    Backend                    MongoDB
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚  Enter email+password  β”‚                          β”‚                         β”‚
  │───────────────────────▢│                          β”‚                         β”‚
  β”‚                        β”‚  POST /auth/login         β”‚                         β”‚
  β”‚                        │─────────────────────────▢│                         β”‚
  β”‚                        β”‚                          β”‚  Find user by email      β”‚
  β”‚                        β”‚                          │────────────────────────▢│
  β”‚                        β”‚                          │◀────────────────────────│
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚                        β”‚                          β”‚  Check:                  β”‚
  β”‚                        β”‚                          β”‚  β”œβ”€β”€ isActive?           β”‚
  β”‚                        β”‚                          β”‚  β”œβ”€β”€ isVerified?         β”‚
  β”‚                        β”‚                          β”‚  β”œβ”€β”€ lockUntil?          β”‚
  β”‚                        β”‚                          β”‚  └── password match?     β”‚
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚                        β”‚                          β”‚  Generate JWT            β”‚
  β”‚                        β”‚                          β”‚  Set HttpOnly cookie     β”‚
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚                        │◀─────────────────────────│                         β”‚
  β”‚                        β”‚  Response: { token, user }                         β”‚
  │◀───────────────────────│                          β”‚                         β”‚
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚  localStorage.setItem( β”‚                          β”‚                         β”‚
  β”‚  "token", token)        β”‚                          β”‚                         β”‚
  β”‚  localStorage.setItem( β”‚                          β”‚                         β”‚
  β”‚  "user", userJSON)      β”‚                          β”‚                         β”‚

WebAuthn / Passkey Login

User                    Frontend                    Backend                    Browser
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚  Click "Sign in with   β”‚                          β”‚                         β”‚
  β”‚  passkey"              β”‚                          β”‚                         β”‚
  │───────────────────────▢│                          β”‚                         β”‚
  β”‚                        β”‚  POST /webauthn/         β”‚                         β”‚
  β”‚                        β”‚  login-options           β”‚                         β”‚
  β”‚                        │─────────────────────────▢│                         β”‚
  β”‚                        β”‚                          β”‚  Generate challenge     β”‚
  β”‚                        β”‚                          β”‚  Store in memory (60s)  β”‚
  β”‚                        │◀─────────────────────────│                         β”‚
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚                        β”‚  startAuthentication()   β”‚                         β”‚
  β”‚                        │───────────────────────────────────────────────────▢│
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚  Native biometric      β”‚                          β”‚                         β”‚
  β”‚  prompt (Face ID /     β”‚                          β”‚                         β”‚
  β”‚  fingerprint)          β”‚                          β”‚                         β”‚
  │◀────────────────────────────────────────────────────────────────────────────│
  β”‚                        β”‚                          β”‚                         β”‚
  β”‚                        β”‚  POST /webauthn/         β”‚                         β”‚
  β”‚                        β”‚  login-verify            β”‚                         β”‚
  β”‚                        │─────────────────────────▢│                         β”‚
  β”‚                        β”‚                          β”‚  Verify signature with  β”‚
  β”‚                        β”‚                          β”‚  stored public key      β”‚
  β”‚                        β”‚                          β”‚  Same JWT as password   β”‚
  β”‚                        │◀─────────────────────────│                         β”‚
  │◀───────────────────────│                          β”‚                         β”‚

Token Handling Strategy

The system uses a dual token strategy due to cross-origin deployment constraints:

  1. HttpOnly Cookie (jwt) β€” Primary auth mechanism. Secure, XSS-proof. Works when frontend and backend are on the same domain.
  2. Bearer Token (localStorage) β€” Fallback for cross-origin deployments (e.g., Vercel frontend + separate API host). Vulnerable to XSS.

The auth middleware checks both:

const token = req.cookies?.jwt || req.headers.authorization?.split(" ")[1];

⚠️ Security Concern: See EDGE_CASES.md for details on this dual strategy.

Auth Middleware Behavior

The authenticate middleware:

  1. Extracts JWT from cookie or Authorization header
  2. Verifies token signature + expiry
  3. Decodes payload (id, email, role)
  4. Attaches decoded user to req.user
  5. Background: Updates lastActive timestamp (throttled to once per minute per user)
  6. On failure: returns 401 JSON response

Module Pattern

Every backend module follows a consistent 6-file pattern:

modules/{feature}/
β”œβ”€β”€ {feature}.interface.ts     β€” TypeScript interfaces / enums
β”œβ”€β”€ {feature}.model.ts         β€” Mongoose schema + model
β”œβ”€β”€ {feature}.validation.ts    β€” Zod validation schemas
β”œβ”€β”€ {feature}.service.ts       β€” Business logic
β”œβ”€β”€ {feature}.controller.ts    β€” Request/response handling
└── {feature}.route.ts         β€” Route definitions (wired in app.ts)

Some modules omit files when not needed (e.g., read-only modules may skip validation).

Example: Events Module

// event.interface.ts β€” defines IEvent, EventType enum, ISpeaker, IContactInfo
// event.model.ts     β€” Mongoose schema with pre-save slug generation
// event.validation.ts β€” createEventSchema, updateEventSchema (Zod)
// event.service.ts   β€” CRUD operations with Cloudinary upload
// event.controller.ts β€” Thin wrappers calling service methods
// event.route.ts     β€” 6 routes (GET 3, POST 1, PUT 1, DELETE 1)

Route Registration (in app.ts)

// All routes are registered centrally:
app.use("/api", eventRoutes);          // GET /api/events
app.use("/api", blogRoutes);           // GET /api/blogs
app.use("/api/auth", authRoutes);      // POST /api/auth/login
app.use("/api/auth/webauthn", webauthnRoutes);  // POST /api/auth/webauthn/...
app.use("/api/users", userRoutes);     // GET /api/users/list-user
app.use("/api/resumes", resumeRoutes);  // GET /api/resumes
app.use("/api/notifications", notificationRoutes); // POST /api/notifications/subscribe
// ... etc.

Role-Based Access Control (RBAC)

Roles

Role Slug Description
Super Admin superadmin Full system access
Admin admin All permissions
Executive Body eb Management team (12 positions)
College Representative cr College-level leads
General Member gm Regular members
IPPL ippl Internship/project leads
Advisor advisor Advisory board
Alumni alumni Former members
Guest guest Unregistered users (default)

EB Positions

tech-lead, project-lead, vice-project-lead, operation-lead,
admin-lead, hr-lead, pr-lead, treasurer, vice-treasurer,
executive-member, secretary, vice-secretary

Permission Model

Permissions are string-based with a resource:action convention:

PERMISSIONS = {
  MEMBER_CREATE: "member:create",
  MEMBER_UPDATE: "member:update",
  EVENT_CREATE:  "event:create",
  BLOG_CREATE:   "blog:create",
  // ... 50+ permissions total
}

Permission Resolution

// For each user, permissions are resolved as:
finalPermissions = rolePermissions βˆͺ userSpecificPermissions

// Role permissions are predefined in ROLE_PERMISSIONS
// Specific permissions can be added per-user via admin panel
// Admin/SuperAdmin bypass permission checks entirely
// EB tech-lead position also gets admin-level access

Permission Check Flow

Request β†’ authenticate β†’ requireAnyPermission(PERMISSION_X) β†’ next()

requireAnyPermission:
  1. Check if user role is admin/superadmin β†’ skip to next()
  2. Check if user is EB tech-lead β†’ skip to next()
  3. Merge role permissions + user-specific permissions
  4. Check if any required permission is in the merged set
  5. If yes β†’ next(); If no β†’ 403

Rate Limiting Strategy

The backend applies 7 distinct rate limiters:

Limiter Route Window Max Requests Purpose
Global /api/* 1 hour 1000 General API abuse
Auth /auth/login, /verify-otp 15 min 5 Brute force protection
WebAuthn /webauthn/login-* 15 min 10 Biometric brute force
Registration /auth/register 1 hour 3 Spam registration
Password Reset /auth/forget-password, /resend-otp 1 hour 5 Reset abuse
Contact Form /contacts 1 hour 3 Spam messages
Donation /donations, /donations/initiate-esewa 15 min 5 Payment abuse
Newsletter /newsletter/subscribe 1 hour 2 Subscription spam
Internship App /internships/applications 15 min 3 Application spam

All limiters use express-rate-limit with standard headers and IP-based tracking. Requires app.set("trust proxy", 1) for accurate client IP detection behind proxies.


File Upload Pipeline

User Upload
    β”‚
    β–Ό
Multer (memoryStorage)
    β”‚   - 5MB file size limit
    β”‚   - Allowed: JPEG, PNG, WEBP, PDF, DOC/DOCX
    β”‚   - Rejects everything else with clear error
    β–Ό
Controller
    β”‚   - Receives req.file.buffer
    β–Ό
Cloudinary Upload
    β”‚   - 120-second timeout
    β”‚   - Resource type: "auto"
    β”‚   - Organized into folders: cfc/{module_name}/
    β–Ό
Response
    β”œβ”€β”€ secure_url (HTTPS URL)
    β”œβ”€β”€ public_id (for deletion)
    └── folder path

Cloudinary Folder Structure

All uploads are organized under cfc/{module}/:

cfc/
β”œβ”€β”€ events/
β”œβ”€β”€ profiles/
β”œβ”€β”€ blogs/
β”œβ”€β”€ team/
β”œβ”€β”€ impact/
β”œβ”€β”€ resources/
β”œβ”€β”€ certificates/
β”œβ”€β”€ gallery/
β”œβ”€β”€ internships/
β”œβ”€β”€ testimonials/
β”œβ”€β”€ supporters/
β”œβ”€β”€ periodicals/
└── walkthroughs/

Push Notification Architecture

Admin Panel (Frontend)                    User Device
    β”‚                                        β”‚
    β”‚  "Send Notification"                    β”‚
    β”‚  (target: all users / role / province)  β”‚
    │────────────────────▢                    β”‚
    β”‚                     β”‚                    β”‚
    β”‚                     β”‚                    β”‚
    β”‚              POST /api/notifications/admin/send
    β”‚                     β”‚                    β”‚
    β”‚                     β–Ό                    β”‚
    β”‚            Backend Notification Service   β”‚
    β”‚                     β”‚                    β”‚
    β”‚              Query users matching filter  β”‚
    β”‚              (pushSubscriptions not empty)β”‚
    β”‚                     β”‚                    β”‚
    β”‚              For each user:              β”‚
    β”‚              webpush.sendNotification()  β”‚
    β”‚                     β”‚                    β”‚
    β”‚                     β”‚                    β”‚
    β”‚                     β”‚         Push Service (Browser)
    β”‚                     │◀───────────────────│
    β”‚                     β”‚                    β”‚
    β”‚                     β”‚    Service Worker  β”‚
    β”‚                     β”‚    (sw.js)         β”‚
    β”‚                     β”‚    "push" event    β”‚
    β”‚                     β”‚    showNotification β”‚
    β”‚                     β”‚                    β”‚
    β”‚                     β”‚    User taps       β”‚
    β”‚                     β”‚    "notificationclick"  β”‚
    β”‚                     β”‚    β†’ openWindow(url)    β”‚
    β”‚                     β”‚                    β”‚

Subscription Lifecycle

1. User logs in
2. Frontend subscribes via Push API (navigator.serviceWorker)
3. POST /api/notifications/subscribe β†’ saves to user.pushSubscriptions
4. Server sends welcome notification
5. On invalid subscription (410 Gone): server auto-removes from DB
6. User can manage preferences via POST /api/notifications/preferences

Deployment Topology

Production (Vercel)

β”Œβ”€ User ─────────────────────────────────────────────────┐
β”‚  https://codeforchangenepal.com                         β”‚
β”‚  https://codeforchange.sajilodigital.com.np             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                         β”‚
                    DNS (Vercel)
                         β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β–Ό                         β–Ό
   Frontend (Static)          Backend (Serverless)
   Vercel SPA                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚ api/index.ts     β”‚
   β”‚ dist/       β”‚                β”‚                  β”‚
   β”‚ index.html  │──── /api/* ───▢│ app.ts           β”‚
   β”‚ assets/*    β”‚                β”‚ MongoDB Atlas    β”‚
   β”‚ sw.js       β”‚                β”‚ Cloudinary       β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚ SMTP (Gmail)     β”‚
                                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Development (Local)

http://localhost:5173  ──── /api/* ────▢  http://localhost:5000
(Frontend dev server)                    (Backend dev server)
                                         MongoDB Atlas (remote)
                                         Cloudinary (remote)

Data Models (Summary)

The application uses MongoDB with 13+ collections:

Collection Module Key Fields
users Auth/User name, email, role, password, permissions, webauthnCredentials, pushSubscriptions
events Events title, description, date, location, type, speakers, status
blogs Blogs title, content, author, category, slug, isFeatured, seo
impacts Impact title, description, category, metrics, image
certificates Certificates certificateId, tokenHash, qrCode, recipientName, status
donations Donations donorName, amount, paymentMethod, transactionId, status
internships Internships title, companyName, category, description, status
applications Internships applicantName, email, internshipId, resume, status
contacts Contact name, email, subject, message, isRead
newsletters Newsletter email, isSubscribed, subscribedAt
gallery Gallery title, imageUrl, category, province
testimonials Testimonials name, role, content, rating, isActive
supporters Supporters name, logo, website, tier, isActive
team Team name, role, position, image, description
periodicals Periodicals title, slug, category, files, publishedAt
walkthroughs Walkthroughs title, slug, category, image, files
resources Resources title, description, fileUrl, category, allowedRoles
resumes Resumes userId, title, templateId, personalInfo, sections
logs Admin userId, action, resource, details, createdAt
tasks Admin title, description, status, priority
Counter Certificates (auto-increment counter for cert IDs)

Error Handling Strategy

Backend

  1. Operational Errors (AppError): Expected errors (validation failure, not found, auth failure). Caught by asyncHandler and forwarded to global error handler.
  2. Programmer Errors: Unexpected bugs (null reference, type errors). Also caught by global handler, returned as 500.
  3. Global Error Handler: Logs error details (message, stack, timestamp) and returns standardized JSON response.

Frontend

  1. API Interceptor: Axios response interceptor catches 401 responses and auto-clears auth state
  2. Toast Notifications: react-hot-toast for user-facing success/error messages
  3. Error Boundaries: (Not implemented β€” consider adding for admin pages)

Key Design Decisions

Decision Rationale Trade-off
MongoDB over SQL Flexible schema for user profiles, nested documents (education, membership) No joins, denormalized data
Monorepo Single repo for both frontend and backend simplifies CI/CD Larger clone, independent deploy cycles
Vercel Serverless Zero DevOps, auto-scaling, CDN Cold starts, 10s function timeout
In-memory challenge store Simple, zero dependencies for WebAuthn Lost on restart, max ~1K concurrent users
Custom XSS sanitizer Avoids npm dependency for simple use case Less robust than DOMPurify
HttpOnly cookie + Bearer token Works in both same-origin and cross-origin setups Dual attack surface
Memory storage + Cloudinary No local file management, CDN delivery Requires internet for uploads