Architecture Overview
System architecture and design patterns for TMA Cloud.
System architecture and design patterns for TMA Cloud.
System Architecture
TMA Cloud uses Single-Origin Architecture:
- Production: The Express backend serves both the built frontend and all APIs from the same origin.
- Development: The browser loads the app from the Vite dev server at
http://localhost:5173, which proxies/api/*and/s/*to the backend onhttp://localhost:3000.
Production Architecture
┌─────────┐
│ Browser │
└────┬────┘
│
│ HTTP Requests
│
┌────▼─────────────────────────────────────────────┐
│ Express Backend (:3000) │
│ ┌──────────────────────────────────────────┐ │
│ │ / → Static Frontend Files │ │
│ │ /api/* → API Endpoints │ │
│ │ /s/* → Share Links │ │
│ │ /health → Health Check │ │
│ │ /metrics → Prometheus Metrics │ │
│ └──────────────────────────────────────────┘ │
└────┬───────────────────┬─────────────────────────┘
│ │
│ │
┌────▼─────┐ ┌──────▼──────┐
│PostgreSQL│ │ Redis │
│ Database │ │ (Cache) │
└──────────┘ └─────────────┘Development Architecture
┌─────────┐
│ Browser │
└────┬────┘
│
│ HTTP Requests
│
┌────▼──────────────────────────────────────┐
│ Vite Dev Server (:5173) │
│ ┌────────────────────────────────────┐ │
│ │ Frontend Development Server │ │
│ │ Proxies /api/* → Backend (:3000) │ │
│ │ Proxies /s/* → Backend (:3000) │ │
│ └────────────────────────────────────┘ │
└────┬──────────────────────────────────────┘
│
│ Proxy Requests
│
┌────▼─────────────────────────────────────┐
│ Express Backend (:3000) │
│ ┌────────────────────────────────────┐ │
│ │ API Endpoints │ │
│ │ Share Links │ │
│ └────────────────────────────────────┘ │
└────┬───────────────────┬─────────────────┘
│ │
│ │
┌────▼─────┐ ┌──────▼──────┐
│PostgreSQL│ │ Redis │
│ Database │ │ (Cache) │
└──────────┘ └─────────────┘Benefits: No CORS issues, simplified deployment, cookie-based auth, no frontend env vars needed.
Custom Share Domain (Optional)
Configure custom share base URL in Settings → Share Base URL (admin only). When configured, share links use the custom domain. Share domain middleware blocks all routes except /s/*, /health, and /metrics.
Backend Structure
backend/
├── config/ # Configuration (database, redis, logger)
├── controllers/ # Request handlers
├── middleware/ # Express middleware (auth, error handling)
├── migrations/ # Database migrations
├── models/ # Data models
├── routes/ # API routes
├── services/ # Background services
└── utils/ # UtilitiesKey Components:
- Controllers: Handle HTTP requests and business logic
- Middleware: Auth, error handling, rate limiting, share domain blocking
- Models: Database abstraction layer
- Services: pg-boss producers, worker tasks, access-time buffering, metrics, and storage operations
- Caching: Redis-based caching with short-lived prefix registries for invalidation; request handlers never scan the Redis keyspace
- Real-Time Events: Redis pub/sub + SSE for file event broadcasting; a burst for one user is published as one batch envelope and causes one list refresh
Frontend Structure
frontend/src/
├── components/ # React components
├── contexts/ # State management
├── hooks/ # Custom hooks
└── utils/ # UtilitiesKey Features:
- Responsive Design: Separate mobile (≤1024px) and desktop (>1024px) UIs
- Contexts: AuthContext, AppContext, ThemeContext
- Viewers: Image and document viewers with device-specific implementations
Electron Desktop App
Optional Windows client. Loads the same frontend from the TMA Cloud server URL; no separate frontend build.
Electron Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Main Process (Node) │
│ src/main: createWindow, getServerUrl(), IPC │
│ Server URL: embedded at build, or userData/config.json, │
│ or electron/src/config/build-config.json (dev) │
└──────────────────────────────┬──────────────────────────────────┘
│ loadURL(serverUrl)
│ IPC
┌──────────────────────────────▼──────────────────────────────────┐
│ Renderer (BrowserWindow) │
│ Same React frontend as web; origin = server URL │
│ Preload (src/preload) exposes window.electronAPI to renderer │
└──────────────────────────────┬──────────────────────────────────┘
│ fetch /api/* (same origin, cookies)
▼
┌─────────────────────────────────────────────────────────────────┐
│ TMA Cloud Backend │
└─────────────────────────────────────────────────────────────────┘Electron Folder Structure
electron/
├── src/
│ ├── main/ # Main process: window, server URL, IPC
│ ├── preload/ # contextBridge, exposes electronAPI
│ ├── config/ # build-config.json (serverUrl when run from source)
│ └── build/ # electron-builder configs + icon
├── scripts/ # prepare-client-build.js
└── dist-electron/ # Staging for packaging (generated)Main process: Creates a single BrowserWindow; loads a loading page, then the server URL. Resolves server URL from embedded constant (at build time) or src/config/build-config.json (when run from source).
Preload: contextIsolation: true, nodeIntegration: false. Exposes a small API (e.g. electronAPI.clipboard) via contextBridge and ipcRenderer.invoke.
Renderer: Same React app as web. Detects Electron via window.electronAPI and uses it where needed; all API calls go to the same backend via fetch.
Some deployments enable an optional mode where the desktop app is only allowed. In that mode, normal browsers are blocked from the main app while share links continue to work.
Cloud Drive (WinFsp)
The desktop app can mount TMA Cloud as a Windows drive so files can be opened and saved from any application's file dialogs. It has two parts:
desktop-fs— a small .NET host (TmaCloudFs.exe) built on WinFsp. It presents the drive to Windows and translates filesystem operations into requests, but holds no credentials.electron/src/main/clouddrive.cjs— the trusted half. The host forwards each operation over a local named pipe (newline-delimited JSON); the main process runs the matching authenticated call to/api/*using the app's session cookies, so the drive reuses the same auth, permissions, and audit as the rest of the app.
Any app's Save As → Z:\ TMA Cloud (WinFsp)
│ named pipe (JSON)
TmaCloudFs.exe (desktop-fs, no credentials)
│
Electron main (clouddrive.cjs, session cookies) → /api/* → backendThe main process mounts the drive when the auth cookie appears (sign-in) and unmounts on sign-out or app close. Directory listings are cached briefly and refreshed by the backend event stream. See Desktop App — Cloud Drive.
Bridge authentication. The pipe name is random per session and every request on it carries a per-session token; the main process rejects anything else, so another local process that reaches the pipe cannot issue operations against the signed-in account. The main process passes the token to the host on stdin (--token-stdin), not as a command-line argument, because a command line is readable by any process running as the same user for as long as the drive is mounted.
Drive permissions. Files and folders on the drive report a security descriptor granting the signed-in Windows user, SYSTEM and the local Administrators group but not Everyone so other accounts on a shared machine cannot read the mount.
Local staging. File bytes never travel on the pipe. Reads and writes stage through temp files under %TEMP%\tma-cloud-fs\ whose paths are exchanged over the pipe. Staging files are deleted when their handle closes, and any survivors are removed when the drive unmounts.
Data Flow
Authentication Flow
┌─────────┐ POST /api/login ┌──────────┐
│ Browser │ ────────────────────────> │ Backend │
└─────────┘ └────┬─────┘
│ │
│ │ Validate credentials
│ │ Create session
│ │ Generate JWT token
│ │
│<────────────────────────────────────┘
│ Set httpOnly cookie
│
┌────▼─────────┐
│ AuthContext │
│ Updated │
└──────────────┘File Upload Flow
┌──────────┐ POST /api/files/upload ┌──────────┐
│ Browser │ ─────────────────────────────> │ Backend │
│(FormData)│ └────┬─────┘
└──────────┘ │
│ Enforce size while streaming
│ Encrypt and write to bucket
│ Validate request metadata
│ Create database record
│ Update cache
│
┌─────────┐ Response with file info │
│ Browser │<─────────────────────────────────────┘
└─────────┘Storage: S3-compatible object storage is required. File contents stream through encryption to the bucket. Large files use multipart upload with a part size calculated to stay within the S3 and R2 10,000-part limit. If later validation rejects the request, the uploaded object is queued for deletion.
Share Link Flow
┌─────────┐ POST /api/files/share ┌──────────┐
│ Browser │ ─────────────────────────────> │ Backend │
└─────────┘ └────┬─────┘
│
│ Create share link
│ Generate token
│ Link files
│
┌─────────┐ Response with share URL │
│ Browser │<────────────────────────────────────┘
└─────────┘Database Schema
Key tables: users, files, share_links, share_link_files, sessions, mfa_backup_codes, client_heartbeats, storage_reservations, file_operation_results, app_settings, audit_log
See Database Schema for details.
Security
- JWT tokens in httpOnly cookies
- Input validation and sanitization
- SQL injection protection (parameterized queries)
- XSS protection (HTML escaping)
- Path traversal protection
- Rate limiting
- Security headers (CSP, X-Frame-Options, etc.)
Logging & Audit
- Structured Logging: Pino with automatic secret masking
- Background Work: One pg-boss worker handles batched audit writes, account-ordered file operations, object cleanup, durable maintenance schedules, admin-requested orphan work, and OnlyOffice force-save commands
- Request Logging: All requests logged with context
See Logging and Audit Logs for details.
Technology Stack
Backend: Express.js, PostgreSQL, Redis, JWT, Pino, pg-boss
Frontend: React 19, TypeScript, Vite, Tailwind CSS
Integration: OnlyOffice, Google OAuth