Self-hosted KYC link + upload application with:
- Admin + manager RBAC
- Public client upload flow with consent
- Storage backend switch:
- local filesystem (demo mode)
- WebDAV (one-cloud.org / Nextcloud / NAS)
- Per-manager folder contract under
KYC_vault/managers/<manager_code>/INBOX/S_<session_uuid>
- Install dependencies:
npm install-
Create
.envfrom.env.exampleand set values. For local demo mode (default):STORAGE_BACKEND=localLOCAL_STORAGE_ROOT=local_vault- Files are saved under:
<LOCAL_STORAGE_ROOT>/<VAULT_ROOT>/managers/<manager_code>/INBOX/S_<session_uuid>/...
To switch back to one-cloud / NAS:
STORAGE_BACKEND=webdav- Nextcloud:
WEBDAV_MODE=nextcloudWEBDAV_BASE_URL=https://one-cloud.org(or full DAV root URL)- Upload path:
https://one-cloud.org/remote.php/dav/files/<username>/<VAULT_ROOT>/...
- Generic NAS (UGREEN / standard WebDAV):
WEBDAV_MODE=genericWEBDAV_BASE_URL=https://<nas-host>:5006VAULT_ROOT=<actual top-level folder exposed by NAS account>(example:KYC)- Upload path:
https://<nas-host>:5006/<VAULT_ROOT>/... - If NAS cert is self-signed (local/dev only):
WEBDAV_ALLOW_SELF_SIGNED_TLS=truePublic KYC upload UI is mobile-first by default (PUBLIC_UPLOAD_MOBILE_ONLY=true). For local phone handoff via QR code:
- Preferred: set
MOBILE_PUBLIC_BASE_URL=http://<your-lan-ip>:3000 - Or keep it empty and use
AUTO_LAN_BASE_URL=truefor auto LAN IP detection - Do not leave QR handoff on
localhostif the client scans from another device - Ensure laptop and phone are on the same Wi-Fi/hotspot network
- If multiple network adapters are active, set
MOBILE_PUBLIC_BASE_URLexplicitly - For the best in-browser camera quality (custom recorder), use HTTPS or localhost.
On plain
http://<lan-ip>:3000, mobile browsers fall back to native camera capture. - Install
ffmpegto enable automatic high-quality video compression on upload:- macOS:
brew install ffmpeg
- macOS:
-
Initialize database schema:
npm run db:init- Seed first admin:
ADMIN_EMAIL=admin@example.com ADMIN_PASSWORD='change-me' npm run db:seed-admin- Start app:
npm run dev- Open http://localhost:3000.
This repo's production path is intentionally simple:
Dockerfilefor the Node app withffmpegdeploy/docker-compose.prod.ymlforapp + Postgresdeploy/.env.prod.exampleas the production env templatedeploy/deploy-vps.shto build/start the stackdeploy/backup-db.shfor database backupsdeploy/generate-secrets.shfor strong secretsdeploy/nginx-site.example.confas an example reverse-proxy site
The app does not manage TLS or port 80/443 itself anymore. It listens only on
127.0.0.1:<APP_PORT>, and your VPS reverse proxy should forward your domain to it.
- Create a DNS
Arecord for your domain (example:taviaverify.com) to your VPS IP. - SSH into the VPS and install Docker + Docker Compose plugin.
- Make sure your existing reverse proxy can add a new site for your domain.
- Ensure the reverse proxy allows uploads of at least
300M.
On VPS:
git clone <your-repo-url> kyc
cd kyc
cp deploy/.env.prod.example deploy/.env.prodEdit deploy/.env.prod:
- Set
APP_PORTto a free localhost port, for example3100 - Set
APP_DATA_DIRto a persistent host path, for example/home/newuser/taviaverify-data - Set strong
POSTGRES_PASSWORD,JWT_SECRET,ADMIN_PASSWORD - Set
PUBLIC_BASE_URLandMOBILE_PUBLIC_BASE_URLtohttps://<your-domain> - Keep
STORAGE_BACKEND=localfor VPS-local storage, or switch towebdavfor NAS/Nextcloud
Optional helper to generate strong secrets:
./deploy/generate-secrets.shmkdir -p /home/newuser/taviaverify-data
./deploy/deploy-vps.shThe app becomes reachable only on:
http://127.0.0.1:<APP_PORT>
Health check:
curl http://127.0.0.1:<APP_PORT>/api/healthzIf your VPS uses Nginx, start with deploy/nginx-site.example.conf and forward your domain to:
127.0.0.1:<APP_PORT>
If you use another reverse proxy, the rule is the same:
- terminate HTTPS there
- forward traffic to
127.0.0.1:<APP_PORT> - preserve
HostandX-Forwarded-*headers
Then open:
https://<your-domain>https://<your-domain>/api/healthz
- View logs:
docker compose --env-file deploy/.env.prod -f deploy/docker-compose.prod.yml logs -f app- Pull/rebuild after changes:
git pull
./deploy/deploy-vps.sh- DB backup:
./deploy/backup-db.shThis repo includes deploy-production.yml for GitHub Actions.
Required GitHub Secrets:
VPS_HOSTVPS_USERVPS_APP_PATHVPS_SSH_KEY
The workflow:
- syncs the repository to your VPS path
- keeps
deploy/.env.prodon the server - runs
./deploy/deploy-vps.sh - checks
https://taviaverify.com/api/healthz
- Public upload:
/s/:token - Manager sessions:
/app/sessions - Manager settings:
/app/settings - Admin dashboard:
/admin
- Auth:
POST /api/auth/loginPOST /api/auth/logoutGET /api/auth/me
- Manager:
POST /api/manager/sessionsGET /api/manager/sessionsGET /api/manager/sessions/:idGET /api/manager/settings
- Admin:
PATCH /api/admin/account/passwordPOST /api/admin/managersGET /api/admin/managersPATCH /api/admin/managers/:id/disablePOST /api/admin/managers/:id/provision-foldersGET /api/admin/sessionsGET /api/admin/auditGET /api/admin/system-limitsPATCH /api/admin/system-limitsGET /api/admin/health
- Public:
GET /api/public/session/:tokenGET /api/public/session/:token/qr.svgPOST /api/public/session/:token/consentPOST /api/public/session/:token/upload
- Session token stored hashed in DB (
sha256). - Public endpoints are rate-limited.
- Request logs avoid token/body logging.
- Public upload endpoint can enforce mobile-only clients (
PUBLIC_UPLOAD_MOBILE_ONLY=true). - API should run on a DNS-only (grey-clouded) subdomain in production for large uploads.
- Uploaded verification videos are re-encoded server-side (when ffmpeg is available) using visually-lossless defaults and only kept when the output is smaller than the original.
- In local mode, uploaded files are written on your MacBook to
LOCAL_STORAGE_ROOT. - The app still records logical vault paths (for easy future switch to WebDAV).
- Create dedicated uploader user and app password.
- Ensure vault structure exists:
KYC_vault/managers/<manager_code>/INBOX
- Share each
KYC_vault/managers/<manager_code>/to the matching manager in Nextcloud. - Keep manager re-share disabled unless explicitly needed.
- In UGOS Pro, open File Service and enable WebDAV.
- Enable HTTPS WebDAV and confirm port (default commonly
5006; HTTP usually5005). - Create a dedicated uploader account and grant only the required shared folder permissions.
- Create or select the vault shared folder (for example
KYC_vault). - Set in
.env:STORAGE_BACKEND=webdavVAULT_ROOT=<folder exposed at WebDAV root>(for exampleKYC)WEBDAV_MODE=genericWEBDAV_BASE_URL=https://<nas-host>:5006WEBDAV_USERNAME=<uploader-user>WEBDAV_APP_PASSWORD=<uploader-password>
- If the NAS certificate is self-signed:
- Local testing only: set
WEBDAV_ALLOW_SELF_SIGNED_TLS=true - Production: use a trusted certificate and keep
WEBDAV_ALLOW_SELF_SIGNED_TLS=false
- Local testing only: set