diff --git a/BEHEER.md b/BEHEER.md index 4c03ac5..8efa72b 100644 --- a/BEHEER.md +++ b/BEHEER.md @@ -333,9 +333,11 @@ De Express-app serveert zowel de API als de gebouwde frontend: | Verzoek | Wat de app doet | |---|---| -| `/api/...` | REST API (rate limited) | -| `/assets/...` | statische build uit `STATIC_DIR`, 1 jaar `immutable` gecached | -| al het overige | `index.html` — fallback voor React-routing | +| `/api/...` | REST API (rate limited, `Cache-Control: no-store`) | +| `/assets/...` | gehashte build-bestanden uit `STATIC_DIR`, 1 jaar `immutable` | +| `/favicon.svg`, `/icons/...` | vaste namen, dus `max-age=0, must-revalidate` | +| pad mét extensie zonder bestand | echte 404, niet `index.html` | +| al het overige | `index.html` — fallback voor React-routing, `no-store` | `STATIC_DIR` staat standaard op `/app/public`; daar zet de `Dockerfile` de Vite-build neer. Bestaat die map niet (bijv. in dev), dan serveert de app @@ -381,6 +383,12 @@ De backend slaat alle data op in **PostgreSQL**. > PostgreSQL 18 gebruikt `PGDATA=/var/lib/postgresql/18/docker`. Het volume is gemount op `/var/lib/postgresql` zodat data bewaard blijft na een container restart. +Daarnaast heeft de app zelf een backup onder **Instellingen**: die schrijft een +ZIP met de data van de *ingelogde* gebruiker (`data.json` + `meta.json`) en leest +zo'n ZIP ook weer in. Handig per gebruiker, maar het is geen servercopie — accounts, +wachtwoordhashes en de data van andere gebruikers zitten er niet in. Voor een +volledige backup gebruik je `pg_dump` hieronder. + **Backup maken:** ```bash @@ -407,12 +415,25 @@ docker exec -i vibefinance_postgres psql -U vibefinance -d vibefinance < backup- De eerste geregistreerde gebruiker krijgt automatisch de **Admin**-rol. Verdere accounts kunnen via de app aangemaakt worden (Gebruikersbeheer, Admin vereist) of via de API. -Rollen: -| Rol | Rechten | +### Rollen + +De rolkeuze in Gebruikersbeheer biedt drie waarden, maar de backend dwingt er op +dit moment één af: + +| Rol | Wat de code ermee doet | |---|---| -| `Admin` | Alles, inclusief gebruikersbeheer | -| `Bewerker` | Data lezen en bewerken | -| `Viewer` | Alleen lezen | +| `Admin` | Enige rol die wordt gecontroleerd: `requireAdmin` in `middleware/auth.js` bewaakt alle routes onder `/api/users` | +| `Bewerker` | Geen eigen rechten — gelijk aan `Viewer` | +| `Viewer` | Standaardrol voor een nieuw account; mag de eigen data lezen én schrijven | + +Dat `Viewer` toch mag schrijven is geen gat in de afscherming: `/api/data` werkt +per gebruiker en gaat altijd over de rij van het eigen token (`req.user.sub`). +Niemand ziet dus data van een ander. Wat ontbreekt is een read-only account — +wil je dat, dan is er een controle op `rol` nodig bij `PUT`/`DELETE /api/data`. + +> De "Gebruikers"-knop in de navigatiebalk staat bij iedereen in beeld; bij een +> niet-Admin antwoordt `/api/users` met `403` en blijft de lijst leeg. De +> afscherming zit dus in de API, niet in de navigatie. --- diff --git a/Changelog.md b/Changelog.md index a2c0ac9..9d4ae8f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,34 @@ # Changelog +## [Niet vrijgegeven] + +### Gewijzigd +- De documentatie is gelijkgetrokken met de code. `README.md`: de bestandsboom + miste `backend/src/logger.js`, `docker/check-updates.mjs`, + `frontend/public/`, `InstellingenModal.jsx`, `LoginBackground.jsx` en de + losse bestanden in `utils/`, en noemde de update-route `update-check` terwijl + het bestand `routes/update.js` heet. De schermentabel gebruikt nu de labels + zoals ze in `constants/index.js` staan ("Schuld", "Voortgang Schuld & EV") en + vermeldt **Instellingen** — thema plus backup/restore via ZIP, dat nergens in + de README stond +- De tabel "wat de app met een verzoek doet" (in beide bestanden) noemde alleen + `/assets/...` en de SPA-fallback. `server.js` kent vier gevallen: `no-store` + op de API en op HTML, een jaar `immutable` op de gehashte bundels, + `must-revalidate` op bestanden met een vaste naam (`favicon.svg`, `icons/*`) + en een echte 404 op een pad mét extensie waar geen bestand achter zit +- `BEHEER.md` beschreef `Bewerker` als "data lezen en bewerken" en `Viewer` als + "alleen lezen". Zo werkt het niet: `requireAdmin` is de enige rolcontrole in + de code en bewaakt alleen `/api/users`. Een `Viewer` mag daardoor gewoon zijn + eigen data schrijven — geen gat in de afscherming, want `/api/data` gaat altijd + over de rij van het eigen token, maar wél iets anders dan er stond. De sectie + zegt nu wat de code doet en wat er nodig is voor een echt read-only account +- `README.md` en `BEHEER.md` wijzen bij backup naar het juiste niveau: de ZIP + onder Instellingen bevat alleen de data van de ingelogde gebruiker, `pg_dump` + is de volledige servercopie +- `DATABASE_URL` staat in de variabelentabel van `README.md`. Hij wordt door + `docker-compose.yml` samengesteld uit de `POSTGRES_*` waarden, dus zelf zetten + is alleen nodig buiten Compose + ## [1.0.4] - 2026-09-10 ### Gewijzigd diff --git a/README.md b/README.md index c417226..b314a1a 100644 --- a/README.md +++ b/README.md @@ -168,14 +168,20 @@ build — anders zou de release met een placeholder als notes online komen. |---|---| | 📊 **Dashboard** | Overzicht van totaal eigen vermogen, schulden en netto vermogen | | 💰 **Eigen vermogen** | Vermogensposten per categorie beheren + geïnvesteerde fiat historiek | -| 📉 **Schulden** | Schulden bijhouden per blok met afbetalingsoverzicht | -| 📈 **Voortgang** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) | +| 📉 **Schuld** | Schulden bijhouden per blok met afbetalingsoverzicht | +| 📈 **Voortgang Schuld & EV** | Grafieken en trends van EV en schuld over tijd (apart bij te houden) | | 👥 **Gebruikers** | Accounts aanmaken, bewerken en beheren (Admin-rol vereist) | +| ⚙️ **Instellingen** | Licht/donker thema, backup downloaden en backup terugzetten | De kolommen in de tabellen op Eigen vermogen en Voortgang zijn versleepbaar: pak de rechterrand van een kolomkop. De breedtes worden per tabel in de browser bewaard; dubbelklik op de greep voor de standaardbreedte. +Backup en restore lopen via **Instellingen** in de navigatiebalk: "Backup maken" +downloadt een ZIP (`data.json` + `meta.json`) met je eigen financiële data, +"Backup terugzetten" leest zo'n ZIP weer in en schrijft hem via `/api/data` weg. +Dat is de gebruikersvariant; een volledige database-dump staat in `BEHEER.md`. + --- ## 🏗️ Architectuur @@ -193,27 +199,33 @@ vibefinance/ ├── release.ps1 / release.sh # Release (test, push, tag, Gitea-release) │ ├── docker/ -│ └── dev-entrypoint.sh # Start API + Vite samen in de dev-container +│ ├── dev-entrypoint.sh # Start API + Vite samen in de dev-container +│ ├── check-updates.mjs # Dependency-check tegen het npm-register +│ └── update-exceptions.json # Pakketten die bewust achterblijven, met reden │ ├── docs/ │ └── screenshot-dashboard.png # Screenshot bovenaan deze README │ ├── frontend/ # React / Vite SPA (build → dist) │ ├── vite.config.js # /api proxy naar de API in dezelfde container +│ ├── index.html # Vite-entry, verwijst naar /favicon.svg +│ ├── public/ # Ongebundeld: favicon.svg + icons/ │ └── src/ │ ├── App.jsx # Root – wiring + idle-logout │ ├── App.css # Reset + losse klassen (o.a. .kolom-grip) │ ├── main.jsx │ ├── version.js # Draaiende versie (__APP_VERSION__ uit de build) │ ├── constants/ # Kleuren, tabs, begindata -│ ├── utils/ # Formatters, helpers +│ ├── utils/ # format.js (bedragen/datums), storage.js │ ├── hooks/ # useTheme, useUpdateCheck, useKolomBreedtes -│ ├── context/ # AppContext (global state + API sync) +│ ├── context/ # AppContext (global state + API sync + backup) │ ├── components/ │ │ ├── ui/ # Primitieve UI (VCard, DonutChart…) │ │ ├── LoginPage.jsx +│ │ ├── LoginBackground.jsx │ │ ├── NavBar.jsx │ │ ├── ProfielPopup.jsx +│ │ ├── InstellingenModal.jsx # Thema + backup/restore │ │ └── GebruikersBeheer.jsx │ └── pages/ │ ├── DashboardTab.jsx @@ -226,10 +238,11 @@ vibefinance/ └── src/ ├── server.js # Entry point: API op /api, SPA op / ├── db.js # PostgreSQL pool + initDb() schema-migraties + ├── logger.js # pino (pino-pretty buiten productie) ├── config/ # Omgevingsvariabelen (incl. STATIC_DIR) ├── constants/ # Begindata (initialData.js) ├── middleware/ # auth.js, errorHandler.js - ├── routes/ # auth, data, users, health, update-check + ├── routes/ # auth, data, users, health, update └── stores/ # userStore, dataStore ``` @@ -245,9 +258,11 @@ frontend en API. | Verzoek | Wat de app doet | |---|---| -| `/api/...` | REST API (rate limited) | -| `/assets/...` | Statische build, 1 jaar `immutable` gecached | -| al het overige | `index.html` — fallback voor React-routing | +| `/api/...` | REST API (rate limited, `Cache-Control: no-store`) | +| `/assets/...` | Gehashte build-bestanden, 1 jaar `immutable` gecached | +| `/favicon.svg`, `/icons/...` | Vaste namen, dus `max-age=0, must-revalidate` | +| pad mét extensie, geen bestand | echte 404 (geen HTML waar een icoon wordt verwacht) | +| al het overige | `index.html` — fallback voor React-routing, niet gecached | --- @@ -295,6 +310,7 @@ Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via | `JWT_SECRET` | — | **Verplicht** — minimaal 64 tekens | | `POSTGRES_USER` | `vibefinance` | Database gebruikersnaam | | `POSTGRES_DB` | `vibefinance` | Database naam | +| `DATABASE_URL` | — | Connectiestring; `docker-compose.yml` stelt hem samen uit de drie `POSTGRES_*` waarden, dus zelf zetten hoeft alleen buiten Compose | | `JWT_EXPIRES` | `7d` | Geldigheidsduur token | | `CORS_ORIGIN` | `http://localhost` | Externe domeinnaam (één origin, dus zelden nodig) | | `APP_PORT` | `3400` | Poort op de host (container luistert op 3001) | @@ -309,6 +325,10 @@ Het schema wordt bij het starten van de app aangemaakt/bijgewerkt via - Wachtwoorden gehasht met bcrypt (cost factor 12) - JWT authenticatie met instelbare vervaltijd +- Data is per gebruiker: `/api/data` leest en schrijft alleen de rij van de + ingelogde gebruiker (`sub` uit het token) +- Van de rollen wordt alleen **Admin** afgedwongen, op `/api/users` — zie de + sectie Rollen in `BEHEER.md` - Frontend en API op één origin — geen interne proxy, geen CORS-uitzonderingen - Content-Security-Policy via Helmet, toegespitst op wat de app laadt - Database bereikbaar alleen binnen het interne Docker netwerk diff --git a/frontend/public/icons/vibefinance-logo.png b/frontend/public/icons/vibefinance-logo.png new file mode 100644 index 0000000..309d03f Binary files /dev/null and b/frontend/public/icons/vibefinance-logo.png differ