Geen omschrijving
  • HTML 48.9%
  • JavaScript 31.1%
  • CSS 7.9%
  • Dockerfile 7.1%
  • Shell 5%
Zoek een bestand
Jesse van Mullem 187f4b213b fix: script-volgorde — app.js vóór alpine.min.js
Bug ontdekt tijdens visuele smoke-test: Alpine startte bij DOMContentLoaded
en zag `x-data="portaal()"` maar `window.portaal` was nog niet gedefinieerd
omdat alpine.min.js met defer VÓÓR app.js executeerde in document-volgorde.
Resultaat: cascade van "portaal is not defined"-errors + lege pagina.

Fix: app.js komt nu EERST in de defer-queue, alpine.min.js daarna. Alpine
ziet `window.portaal` gedefinieerd zodra 'ie start.
2026-09-15 15:30:18 +02:00
src fix: script-volgorde — app.js vóór alpine.min.js 2026-09-15 15:30:18 +02:00
.env.example Initial commit: klantportaal SPA (vanilla + Alpine + Tailwind CDN, Nginx serve) 2026-07-01 20:21:49 +02:00
.gitignore Initial commit: klantportaal SPA (vanilla + Alpine + Tailwind CDN, Nginx serve) 2026-07-01 20:21:49 +02:00
Dockerfile refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00
entrypoint.sh refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00
nginx.conf refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00
package-lock.json refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00
package.json refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00
README.md refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00
tailwind.config.js refactor: strakker UI + zelf-gehoste assets + hardening (v0.2.0) 2026-09-15 15:27:48 +02:00

Memoriaal — Klantportaal

Externe SPA voor klanten van Memoriaal Administratiebureau & Belastingadviseurs. Communiceert alleen via HTTPS/JSON met de DMS-backend (/portaal-api/v1/...). Geen eigen database, geen server-side render — puur statische bestanden geserveerd door Nginx (unprivileged).

Deploy op Coolify

  1. Coolify dashboard → + New Resource → Public / Private Git Repository
  2. Repo-URL: https://code.securityasaservice.org/MemAdmBur/memoriaal-klankportaal.git
  3. Build pack: Dockerfile
  4. Port exposed: 8080 (was 80 — nginx-unprivileged luistert op 8080)
  5. Environment variables:
    API_BASE_URL=https://testdms.memoriaal.com/portaal-api/v1
    
  6. Domains: klant.memoriaal.com (Coolify Traefik regelt TLS)
  7. DMS-kant moet PORTAAL_URL=https://klant.memoriaal.com gezet hebben — de CORS/CSP-headers gaan dan automatisch open.

Lokale test

docker build -t klantportaal .
docker run --rm -p 8080:8080 \
  -e API_BASE_URL=https://testdms.memoriaal.com/portaal-api/v1 \
  klantportaal
# open http://localhost:8080

Voor dev tegen een lokale/andere backend die niet op *.memoriaal.com staat:

docker run --rm -p 8080:8080 \
  -e API_BASE_URL=http://host.docker.internal:8000/portaal-api/v1 \
  -e PORTAAL_API_ALLOW_ANY=1 \
  klantportaal

Bouwlogica

  • Multi-stage Dockerfile:
    • Stage 1 (node:22-alpine): npm ci (integrity-check via lockfile), Tailwind compileert src/css/input.css → dist/app.css.
    • Stage 2 (nginxinc/nginx-unprivileged:alpine): kopieert de gebouwde CSS, Alpine uit node_modules, HTML, JS, images. Draait als UID 101 op poort 8080.
  • index.html is de shell (Tailwind-classes, geen inline styles/scripts).
  • src/js/app.js bevat alle Alpine-logic.
  • entrypoint.sh valideert API_BASE_URL (moet https://*.memoriaal.com/... of PORTAAL_API_ALLOW_ANY=1), substitueert __API_BASE_URL__ in app.js en ${API_HOST} in de CSP van nginx.

Beveiliging

  • Sessie: HttpOnly + Secure + SameSite=None cookie (portaal_session, 12u TTL). JS ziet het token niet — XSS kan het niet exfiltreren.
  • CSRF: elke unsafe request stuurt X-Portaal-CSRF: 1; backend eist die header wanneer de sessie via cookie komt. Cross-site forms zonder preflight-goedkeuring worden geweigerd.
  • CSP: script-src 'self' 'unsafe-eval' (unsafe-eval nodig voor Alpine's expression-evaluator), connect-src 'self' <API-host> (host wordt dynamisch geset uit API_BASE_URL). Geen externe CDN's meer.
  • HSTS: max-age=63072000; includeSubDomains.
  • Frame-Options: DENY — SPA hoort nooit in een iframe.
  • Nginx-container: draait als niet-root user (UID 101), poort 8080.
  • Dependencies: Tailwind en Alpine gepind via package-lock.json + gebundeld in de image (geen SRI-onderhoud nodig, geen CDN-compromise- risico).
  • Aanval-oppervlak: alleen de DMS /portaal-api/. Zie DMSv2-repo backend/klantportaal/ voor rate-limits, watermarking, en klant-scoping.