Geen omschrijving
  • TypeScript 97%
  • Dockerfile 1.1%
  • JavaScript 1%
  • CSS 0.5%
  • Shell 0.4%
Zoek een bestand
Jesse van Mullem 1529a01a0c feat(security+automation): API-keys + IP-allowlist + S3-backup
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.
2026-06-16 15:34:50 +02:00
prisma feat(security+automation): API-keys + IP-allowlist + S3-backup 2026-06-16 15:34:50 +02:00
public fix(build): public/ folder + /login force-dynamic 2026-06-16 12:50:43 +02:00
scripts initial: Vinculum License Hub MVP 2026-06-16 11:58:07 +02:00
src feat(security+automation): API-keys + IP-allowlist + S3-backup 2026-06-16 15:34:50 +02:00
.env.example feat(security+automation): API-keys + IP-allowlist + S3-backup 2026-06-16 15:34:50 +02:00
.gitignore initial: Vinculum License Hub MVP 2026-06-16 11:58:07 +02:00
docker-entrypoint.sh build: Dockerfile + entrypoint voor Coolify deploy 2026-06-16 12:47:10 +02:00
Dockerfile fix: run als root + try/catch + console.error op /api/{activate,heartbeat} 2026-06-16 13:00:04 +02:00
next.config.ts fix(build): public/ folder + /login force-dynamic 2026-06-16 12:50:43 +02:00
package.json feat(security+automation): API-keys + IP-allowlist + S3-backup 2026-06-16 15:34:50 +02:00
postcss.config.js initial: Vinculum License Hub MVP 2026-06-16 11:58:07 +02:00
README.md initial: Vinculum License Hub MVP 2026-06-16 11:58:07 +02:00
tailwind.config.ts initial: Vinculum License Hub MVP 2026-06-16 11:58:07 +02:00
tsconfig.json initial: Vinculum License Hub MVP 2026-06-16 11:58:07 +02:00

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_KEY env. Klanten kunnen tokens niet forge'en.
  • Binding 1:1: elke activation-code mag op maximaal één installationId worden 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)

  1. Nieuwe Coolify resource (Nextjs app, port 3100, Standalone).
  2. Env-vars (kopieer uit .env.example):
    • DATABASE_URL="file:/app/data/hub.db" (persistent volume!)
    • LICENSE_PRIVATE_KEY — multiline PEM
    • ADMIN_EMAIL, ADMIN_PASSWORD (alleen voor eerste boot — daarna password wijzigen via admin-UI of opnieuw env-set + rebuild)
    • SESSION_SECRET — random 32+ chars
    • LICENSE_JWT_TTL_HOURS=72 (default)
    • PUBLIC_URL=https://licenses.je-ma.com
  3. Persistent volume voor SQLite: mount /app/data als Coolify-volume.
  4. Build cmd: npm run build (doet prisma generate + next build).
  5. Start cmd: npm run start (Next.js standalone).
  6. Domain: pas DNS aan: A-record licenses.je-ma.com → VPS-IP.
  7. 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

  1. Admin maakt klant in Hub-UI → krijgt activation-code VIN-XXXX-XXXX-XXXX
  2. Admin geeft code aan klant (e-mail / telefoon)
  3. Klant deployd Vinculum met LICENSE_REQUIRED=true + LICENSE_PUBLIC_KEY
  4. Klant logt in → wordt geforceerd naar /settings/license
  5. Klant plakt activatie-code → app POSTs naar Hub → ontvangt token
  6. Installation-id wordt vastgezet in Hub (binding)
  7. Hourly heartbeat ververst token + pikt module-updates op
  8. 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_KEY krijgen 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.db naar S3/Spaces dagelijks via cron.
  • Restore-test: gewoon hub.db terugplaatsen + service-restart.