Architecture
Component model
Section titled “Component model”QSign is a containerized stack. A reverse proxy fronts a React single-page app and a Django REST backend; the backend is supported by a relational database, a cache, a search index, and an e-signature/DSC service.
┌────────────────────────────────────────────┐ │ Clients │ │ Browsers · API integrators · Signers │ └───────────────────────┬──────────────────────┘ │ HTTPS (443) ┌───────────▼───────────┐ │ nginx (reverse proxy)│ TLS termination │ / → FE │ routing │ /backend/* → API │ │ /esign* → esign │ └───┬─────────┬───────┬───┘ ┌───────────────────┘ │ └────────────────┐ ┌─────────▼─────────┐ ┌─────────────▼──────────┐ ┌─────────▼─────────┐ │ qsign-frontend │ │ qsign-backend │ │ esign / esign-api │ │ React SPA (:3000) │ │ Django + gunicorn │ │ signing + DSC / │ │ (static, served) │ │ REST API (:8000) │ │ tamper seal (TSA) │ └────────────────────┘ └───┬───────┬───────┬─────┘ └─────────┬─────────┘ │ │ │ │ ┌─────────────────┘ │ └──────────┐ │ ┌────────▼────────┐ ┌──────────▼──────┐ ┌───────▼──────┐ │ │ MySQL 8 │ │ Redis │ │ Solr 9 │ │ │ (app data) │ │ cache + throttle│ │ search index│ │ └──────────────────┘ └──────────────────┘ └──────────────┘ │ │ │ ┌────────▼─────────────────────────────────────────────────────── ▼ ──┐ │ Document storage (volume) — originals, converted, signed, stamped │ └───────────────────────────────────────────────────────────────────────┘
Optional sidecars: ClamAV (upload AV scanning) · certbot (Let's Encrypt TLS) Optional externals: Email (Zeptomail/Resend) · Aadhaar/Protean gateway · LLM · SMS · SSOContainers
Section titled “Containers”| Container | Image (genericized) | Role | Internal port |
|---|---|---|---|
nginx | nginx | TLS termination + reverse proxy + /health | 80 / 443 |
qsign-frontend | qsign-frontend:<version> | React SPA (static build, served on :3000) | 3000 |
qsign-backend | qsign-backend:<version> | Django REST API + gunicorn; document processing; cron | 8000 |
esign | esign:<version> | E-signature signing + DSC + RFC-3161 tamper-seal service | 8080 |
qsign-esign-api | esign-api:<version> | API bridge to the esign service | 8090 |
db | mysql:8.0 | Primary relational datastore | 3306 |
redis | redis:alpine | Cache + shared API-throttle counter + task broker | 6379 |
solr | solr:9.3.0 | Full-text document search index | 8983 |
clamav (optional) | clamav/clamav:stable | Antivirus scan of uploads before processing | 3310 |
certbot (optional) | certbot/certbot | Automated Let’s Encrypt certificate renewal | — |
Only the reverse proxy (
nginx) needs to be reachable from outside the host. Every other container communicates over an internal Docker network by service name. Do not publish MySQL/Redis/Solr/esign ports to untrusted networks.
Backend internals (Django)
Section titled “Backend internals (Django)”- Framework: Django 4.2 (LTS) on Python 3.11, served by gunicorn.
- Apps:
site_user(auth, accounts, subscriptions/quota),contract(documents, signing workflows, envelopes, templates, Automagic),othercompanyapi(the white-label API),dsc_signatures(PKI/PKCS#11 + tamper seal),company,dashboard,notifications, plus deprecatedlegacy_*apps retained only for historical-data URLs. - Scheduled jobs (cron, in-container): reminder emails (daily), Aadhaar-quota refunds on cancelled/rejected docs, and subscription-state housekeeping.
- Document processing: LibreOffice (Office→PDF), Ghostscript (PDF compression), Tesseract/ocrmypdf (OCR), PyMuPDF/pypdf (PDF manipulation), pyHanko (PDF CMS signing).
Data stores
Section titled “Data stores”- MySQL 8 — all application state (users, companies, documents metadata, recipients, signatures, plans/quotas, API accounts, call logs, webhook deliveries). UTF-8MB4.
- Redis — two roles: a shared rate-limit/throttle counter for the API (fails open on outage, never blocks traffic) and a task/cache backend.
- Solr 9 — full-text search index over documents; rebuildable from MySQL + storage.
- Document storage volume — the binary document files (originals, converted PDFs, signed/stamped outputs). Local filesystem by default; optionally Azure Blob Storage.
Signing pipelines (data flow)
Section titled “Signing pipelines (data flow)”e-Signature
Section titled “e-Signature”- User uploads a document → (optional) ClamAV scan → stored; Office files converted to PDF; indexed in Solr.
- Signer fields placed (manual / preset / Automagic) and signers invited (email/SMS), or per-recipient signing links returned (headless / API).
- Each signer authenticates to the signing page, records ESIGN consent (timestamp + IP + user-agent), and applies their signature image.
- When all parties have signed, the backend calls the esign service to notarize: a CMS tamper seal is applied and the final PDF’s hash + timestamp + method are recorded. Signed copies are delivered/stored.
Aadhaar eSign (India, optional)
Section titled “Aadhaar eSign (India, optional)”- The signer is redirected to the NSDL/Protean eSign gateway with a signed request.
- The signer authenticates via Aadhaar OTP; the gateway returns a signed response (PKCS#7) to the QSign callback.
- The backend validates the response signature and embeds the digital signature into the PDF. Quota is metered and refunded if the document is later cancelled/rejected. (Gateway hosts are allow-listed; the request/callback URLs are configured per ASP.)
DSC signing (optional)
Section titled “DSC signing (optional)”- Prepare — pyHanko writes the PDF with a CMS placeholder and returns the document hash to sign (a short-lived signing session).
- Sign — the signer’s certificate/private key (hardware token via PKCS#11, driven by the cross-platform agent, or a software cert) signs the hash.
- Finalize — the backend splices the signature into the CMS structure, optionally adds an RFC-3161 timestamp, and stores the signed PDF.
Network topology (per host)
Section titled “Network topology (per host)”- External:
443/tcp(HTTPS) and80/tcp(HTTP → redirect to HTTPS / ACME) on thenginxcontainer only. - Internal Docker network: services reach each other by name —
qsign-backend:8000,qsign-frontend:3000,db:3306,redis:6379,solr:8983,esign:8080,qsign-esign-api:8090. - Reverse-proxy routes:
/→ frontend;/backend/*→ backend API (prefix stripped);/esign*and/qsign-esign-api/*→ esign services;/health→ liveness probe. Solr is not exposed externally in a hardened deployment.
Deployment footprint
Section titled “Deployment footprint”A single host running Docker Compose is sufficient for small/medium volumes (see 03-system-requirements for sizing). The stack can be split across hosts (e.g., dedicated DB) or scaled horizontally behind a load balancer for HA; the backend is stateless apart from the shared MySQL/Redis/Solr/storage tiers.