Geen omschrijving
- TypeScript 97%
- Dockerfile 1.1%
- JavaScript 1%
- CSS 0.5%
- Shell 0.4%
L2.5 API-keys voor automation: - HubApiKey schema (name, prefix, keyHash SHA-256, scope read|full). - lib/api-key.ts: generateApiKey, verifyApiKey, requireScope. - Public /api/v1/* endpoints met Bearer-auth: GET /customers, POST /customers, GET /customers/[id], PATCH /licenses/[id]. Audit-log entries 'license.api-patch' / 'customer.create' getagged met [api-key: name]. - /admin/api-keys: create-form (naam+scope), tabel met prefix + last-used + revoke-knop. Full key wordt eenmalig na create getoond. L2.6 IP-allowlist /admin: - ADMIN_IP_ALLOWLIST env (comma-CIDRs). Middleware checkt /admin en /login. /api/v1/* en /api/activate /api/heartbeat blijven public. - ADMIN_IP_ALLOWLIST_BYPASS=true voor noodgeval. - CIDR-matching via custom helper (geen extra dep). L2.7 Auto S3-backup: - lib/backup.ts: runBackup() doet 'VACUUM INTO tmp' (atomic snapshot) → uploadt via @aws-sdk/client-s3 naar bucket met key '<prefix>/hub-<ISO-ts>.db'. Fallback op fs.copyFileSync bij ouder SQLite. - /api/cron/backup POST + GET met CRON_SECRET Bearer-auth. - Plan dagelijkse Coolify Scheduled Task (curl met Bearer-header). - Retentie via S3-bucket lifecycle (niet vanuit Hub). Misc: - Nav-link 'API-keys' toegevoegd aan admin-header. - .env.example uitgebreid met L2.6+L2.7 vars + setup-tips. |
||
|---|---|---|
| prisma | ||
| public | ||
| scripts | ||
| src | ||
| .env.example | ||
| .gitignore | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| next.config.ts | ||
| package.json | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.ts | ||
| tsconfig.json | ||
Vinculum License Hub
Centrale license-server voor Vinculum CRM-installaties. Beheert klanten, activatie-codes, modules en heartbeats. Geeft Ed25519-signed tokens uit die in Vinculum-installaties worden gevalideerd.
Architectuur
+--------------------------+ POST /api/activate +-------------------+
| Vinculum-installatie A | --------------------------------> | |
| (klant 1) | {activationCode, installationId} | Vinculum License |
| | <-------------------------------- | Hub |
| /settings/license | {license: signed-token} | |
| | | (deze repo) |
| cron/hourly heartbeat | --------------------------------> | |
+--------------------------+ +-------------------+
|
|
+------v------+
| SQLite DB |
| hub.db |
+-------------+
- Ed25519 signing: Hub heeft de private key, Vinculum-installaties
hebben de public key in
LICENSE_PUBLIC_KEYenv. Klanten kunnen tokens niet forge'en. - Binding 1:1: elke activation-code mag op maximaal één
installationIdworden gebruikt. Bij rebind (server-restore): admin klikt "Rebind installation" in de Hub UI. - Token TTL: 72u (configureerbaar via
LICENSE_JWT_TTL_HOURS). Vinculum's hourly heartbeat-cron ververst lang voor TTL-expiry. Bij hub-uitval: klant blijft draaien tot huidige token expireert. - Modules-toggle: admin kan in de Hub UI per klant modules aan/uit zetten (~22 modules). Wijziging propageert binnen 1 uur naar de klant via de eerstvolgende heartbeat.
Setup
1. Lokale dev
cd VinculumLicenseHub
cp .env.example .env
# Genereer keypair (eenmalig)
node scripts/generate-keypair.mjs
# Plak LICENSE_PRIVATE_KEY in .env (multiline OK)
# Genereer SESSION_SECRET (>= 32 chars random)
openssl rand -hex 32 # plak resultaat als SESSION_SECRET
# Installeer + init DB
npm install
npx prisma generate
npx prisma db push
npm run dev
Open http://localhost:3100/login → log in met ADMIN_EMAIL /
ADMIN_PASSWORD uit .env.
2. Productie (Coolify)
- Nieuwe Coolify resource (Nextjs app, port 3100, Standalone).
- Env-vars (kopieer uit
.env.example):DATABASE_URL="file:/app/data/hub.db"(persistent volume!)LICENSE_PRIVATE_KEY— multiline PEMADMIN_EMAIL,ADMIN_PASSWORD(alleen voor eerste boot — daarna password wijzigen via admin-UI of opnieuw env-set + rebuild)SESSION_SECRET— random 32+ charsLICENSE_JWT_TTL_HOURS=72(default)PUBLIC_URL=https://licenses.je-ma.com
- Persistent volume voor SQLite: mount
/app/dataals Coolify-volume. - Build cmd:
npm run build(doetprisma generate+next build). - Start cmd:
npm run start(Next.js standalone). - Domain: pas DNS aan: A-record
licenses.je-ma.com→ VPS-IP. - HTTPS: Coolify regelt LE-certificaat automatisch.
Vinculum-installatie configureren
Per klant-deploy van Vinculum, zet in hun Coolify env:
LICENSE_REQUIRED=true
LICENSE_PUBLIC_KEY=<base64 32-byte key uit keypair-output>
LICENSE_HUB_URL=https://licenses.je-ma.com
Plan ook deze cron in Coolify "Scheduled Tasks":
Schedule: 0 * * * *
Command: curl -fsS -H "Authorization: Bearer $CRON_SECRET" \
https://crm.<klant>.example/api/cron/license-heartbeat
(Of POST naar de heartbeat-route — beide werken.)
Klant-onboarding-flow
- Admin maakt klant in Hub-UI → krijgt activation-code
VIN-XXXX-XXXX-XXXX - Admin geeft code aan klant (e-mail / telefoon)
- Klant deployd Vinculum met
LICENSE_REQUIRED=true+LICENSE_PUBLIC_KEY - Klant logt in → wordt geforceerd naar
/settings/license - Klant plakt activatie-code → app POSTs naar Hub → ontvangt token
- Installation-id wordt vastgezet in Hub (binding)
- Hourly heartbeat ververst token + pikt module-updates op
- Admin past modules aan in Hub → klant ziet wijziging binnen 1 uur
Endpoints
POST /api/activate— public, rate-limited (10/min per IP). Body:{activationCode, installationId, hostname, installationName?}Response 200:{license: "<token>"}; 4xx:{error: "..."}POST /api/heartbeat— public, rate-limited (120/min per IP). Body:{activationCode, installationId}Response 200:{license: "<token>"}; 403/410: revoked/expired
Veiligheid
- Rate-limit: in-memory sliding window. Voor multi-node scale-out: vervang door Redis.
- Bcrypt voor admin-password (12 rounds).
- Cookie: httpOnly + secure + sameSite=lax.
- CSRF: Server-Actions hebben in Next.js 16 al ingebouwde CSRF-protectie via origin-check.
- Private key: alleen in Hub-env, nooit gecommit. Verlies = alle
Vinculum-installaties moeten een nieuwe
LICENSE_PUBLIC_KEYkrijgen en alle codes opnieuw geactiveerd.
Backup
SQLite-file hub.db is alle state. Backup-strategie:
- Single-file copy:
cp hub.db hub.db.bak(lock-issue als app schrijft; beter:sqlite3 hub.db ".backup hub.db.bak"). - Off-site: rsync of S3-sync van
/app/data/hub.dbnaar S3/Spaces dagelijks via cron. - Restore-test: gewoon
hub.dbterugplaatsen + service-restart.