Auth (app-modus):
- Roller admin/standard. Admin oppretter/sletter kontoer, resetter passord,
endrer roller (konto-modal). Eksisterende brukere → admin ved migrering;
bootstrap-bruker via proxy-bypass → admin. Siste admin vernet.
- Tilbakekalling via per-bruker epoke i tokenet: passordbytte, admin-reset,
«Logg ut alle enheter» og sletting dreper alle aktive sesjoner. Token-format
endret (3- → 4-delt) — alle logges ut én gang ved oppgradering.
Dagsplan: søkbar/velgbar planliste («📋 Planer») avledet fra planIndex —
finn plan på navn/dato/person uten å gjette dato.
GUI: login-neon trukket inn i hovedappen (header/knapper/KPI-kanter), og
login-siden fikk mer tech (HUD-hjørner, glitch-tittel, scanlines).
40 integrasjonstester (6 nye), alle grønne. README + CLAUDE.md oppdatert.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
264 lines
12 KiB
Markdown
264 lines
12 KiB
Markdown
# NetOps To-Do (Node-versjon)
|
|
|
|
Sanntids oppgavetavle for et nettverksdrift-team. Node + Express + WebSockets.
|
|
Endringer fra én bruker dukker opp hos alle andre umiddelbart — ingen polling,
|
|
ingen manuell refresh.
|
|
|
|
## Kom i gang
|
|
|
|
```bash
|
|
npm install
|
|
npm start # → http://localhost:3000
|
|
```
|
|
|
|
Utvikling med auto-restart ved kodeendring:
|
|
|
|
```bash
|
|
npm run dev
|
|
```
|
|
|
|
Tester:
|
|
|
|
```bash
|
|
npm test
|
|
```
|
|
|
|
## Miljøvariabler
|
|
|
|
| Variabel | Default | Beskrivelse |
|
|
|---|---|---|
|
|
| `PORT` | `3000` | HTTP-port |
|
|
| `HOST` | `0.0.0.0` | Lytteadresse |
|
|
| `DATA_DIR` | `./data` | Datakatalog (tasks/activity/users/plans/categories/auth) |
|
|
| `AUTH_MODE` | `proxy` | `proxy` = stol på proxy-header/Basic Auth. `app` = innebygd innlogging (sesjons-cookie + passord) |
|
|
| `TRUST_PROXY_HEADER` | `1` | I app-modus: la en proxy-identitet (X-Remote-User / Basic) slippe inn uten app-passord (for bootstrapping). Sett `0` for å slå av |
|
|
| `SESSION_TTL_DAYS` | `30` | Levetid på app-sesjons-cookien |
|
|
| `AUTH_SECRET` | (auto) | HMAC-nøkkel for sesjons-cookies. Settes ikke den, genereres og lagres i `data/auth.secret` |
|
|
| `DISABLE_COOKIE_AUTH` | (av) | Proxy-modus: `1` skrur av selvvalgt navn via cookie — da gjelder kun proxy-headere |
|
|
|
|
## Autentisering
|
|
|
|
To modi, styrt av `AUTH_MODE`.
|
|
|
|
### `proxy` (default) — auth utenfor appen
|
|
|
|
Brukernavn hentes, i prioritert rekkefølge:
|
|
|
|
1. `X-Remote-User`-header — satt av reverse proxy (oauth2-proxy, Authelia, …)
|
|
2. `Authorization: Basic` — Basic Auth terminert i proxy, brukernavnet gjenbrukes
|
|
3. `netops_user`-cookie — selvvalgt navn via "Sett navn"-knappen (hjemme/dev-bruk)
|
|
4. `anon`
|
|
|
|
For team-bruk bak proxy: sett `DISABLE_COOKIE_AUTH=1` så ingen kan velge navn selv.
|
|
|
|
### `app` — innebygd innlogging
|
|
|
|
Egen neon-login (`/login`) med passord. Sesjon er en signert, langlevd cookie
|
|
(`SESSION_TTL_DAYS`, default 30) — ingen repeterte Basic Auth-prompts, og virker
|
|
sømløst med WebSocket. Passord hashes med scrypt (Node-kjerne, ingen
|
|
avhengigheter), lagret i `data/auth.json`.
|
|
|
|
**Roller:** `admin` og `standard`. Admin kan opprette/slette kontoer, tilbakestille
|
|
passord og endre roller (brukermenyen oppe til høyre → admin-seksjon). Standard
|
|
kan endre eget passord og logge ut alle enheter. Ved oppgradering blir alle
|
|
eksisterende brukere automatisk admin, og den første som setter passord via
|
|
proxy-bypass blir admin (bootstrap). Den siste administratoren kan ikke slettes
|
|
eller degraderes.
|
|
|
|
**Tilbakekalling (herding):** hver bruker har en «epoke» bakt inn i tokenet.
|
|
Passordbytte, admin-reset og **«Logg ut alle enheter»** bumper epoken og dreper
|
|
alle eksisterende sesjoner umiddelbart (sletting av konto gjør det samme).
|
|
Sesjons-cookien er `HttpOnly` + `SameSite=Lax` + `Secure` (over HTTPS) og signert
|
|
med HMAC, så den kan ikke forfalskes. (Etter denne oppgraderingen logges alle ut
|
|
én gang, siden token-formatet endret seg.)
|
|
|
|
**Trusted bypass / migrering fra Basic Auth:** Når `TRUST_PROXY_HEADER=1`
|
|
(default) regnes en proxy-identitet (X-Remote-User eller Basic Auth) som
|
|
innlogget, *selv uten app-passord*. Det gir en trygg overgang:
|
|
|
|
1. Behold NPM Access List (Basic Auth) og deploy med `AUTH_MODE=app`.
|
|
2. Åpne appen (du slipper inn via Basic Auth-bypass), klikk brukernavnet ditt
|
|
oppe til høyre → **sett et app-passord**. Gjenta for hver kollega.
|
|
3. Fjern NPM Access List. Nå møtes alle av neon-login og logger inn med passordet.
|
|
|
|
> Bypass krever at app-porten kun er nåbar fra proxyen (samme antakelse som
|
|
> proxy-modus). Vil du være streng, sett `TRUST_PROXY_HEADER=0` etter at alle
|
|
> har satt passord — da gjelder kun app-sesjoner.
|
|
|
|
Oppsett som tjeneste: legg `Environment=AUTH_MODE=app` i systemd-enheten (se
|
|
under), eller kjør skriptet med `AUTH_MODE=app bash setup-debian13.sh`.
|
|
|
|
## Rask installasjon på Debian 13 (LXC/VM)
|
|
|
|
```bash
|
|
# Som root i containeren:
|
|
apt-get update && apt-get install -y git
|
|
git clone <din-remote> /opt/netops
|
|
bash /opt/netops/netops-todo-node/setup-debian13.sh
|
|
```
|
|
|
|
`setup-debian13.sh` installerer Node fra apt, kjører `npm ci` og setter opp
|
|
systemd-tjenesten (`DynamicUser` + `StateDirectory`, data i
|
|
`/var/lib/netops-todo`). Kjør skriptet på nytt etter `git pull` for å
|
|
oppgradere. Med proxy-auth: `DISABLE_COOKIE_AUTH=1 bash setup-debian13.sh`.
|
|
|
|
## Bak reverse proxy
|
|
|
|
WebSocket-endepunktet er `/ws` og krever upgrade-støtte i proxyen.
|
|
Appen må ligge på **rota av et (sub)domene** (f.eks. `todo.hjemme.lan`) —
|
|
frontend bruker absolutte stier, så sti-prefiks (`proxy.lan/todo/`) fungerer ikke.
|
|
|
|
### Nginx Proxy Manager
|
|
|
|
1. **Hosts → Proxy Hosts → Add Proxy Host**
|
|
- *Domain Names:* `todo.dittdomene.no`
|
|
- *Scheme:* `http` · *Forward Hostname/IP:* LXC-ens IP · *Forward Port:* `3000`
|
|
- **Websockets Support: PÅ** (påkrevd — sanntidssynken bruker `/ws`)
|
|
- *Cache Assets:* AV (ellers kan gammel `app.js` serveres etter oppgradering)
|
|
- *Block Common Exploits:* valgfritt, fungerer fint sammen med appen
|
|
2. **SSL-fanen:** *Request a new SSL Certificate* (Let's Encrypt) + *Force SSL*.
|
|
Frontend bytter selv til `wss:` når siden er https.
|
|
3. **Auth — to alternativer:**
|
|
- *Innebygd app-innlogging (anbefalt — slipper repeterte Basic Auth-prompts):*
|
|
kjør tjenesten med `AUTH_MODE=app`. Bruk gjerne NPM Access List midlertidig
|
|
for å bootstrappe (se «app»-seksjonen over), og fjern den når alle har satt
|
|
passord. Da trenger ikke NPM Access List i det hele tatt.
|
|
- *Proxy Basic Auth:* *Access Lists* → lag brukere og knytt til proxy-hosten.
|
|
NPM sender `Authorization`-headeren videre og appen bruker brukernavnet
|
|
derfra. Sett `DISABLE_COOKIE_AUTH=1`. (Ulempe: nettleseren spør om passord
|
|
på nytt ved hver ny økt — derav app-innlogging som alternativ.)
|
|
|
|
**nginx:**
|
|
|
|
```nginx
|
|
location / {
|
|
proxy_pass http://127.0.0.1:3000;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header X-Remote-User $remote_user; # ved basic auth i nginx
|
|
}
|
|
```
|
|
|
|
**Apache** (`mod_proxy_wstunnel`):
|
|
|
|
```apache
|
|
ProxyPass /ws ws://127.0.0.1:3000/ws
|
|
ProxyPassReverse /ws ws://127.0.0.1:3000/ws
|
|
ProxyPass / http://127.0.0.1:3000/
|
|
ProxyPassReverse / http://127.0.0.1:3000/
|
|
RequestHeader set X-Remote-User %{REMOTE_USER}s
|
|
```
|
|
|
|
## Kjør som tjeneste (systemd)
|
|
|
|
```ini
|
|
# /etc/systemd/system/netops-todo.service
|
|
[Unit]
|
|
Description=NetOps To-Do
|
|
After=network.target
|
|
|
|
[Service]
|
|
# DynamicUser + StateDirectory: systemd lager bruker og /var/lib/netops-todo
|
|
# med riktige rettigheter automatisk — ingen useradd/chown nødvendig.
|
|
DynamicUser=yes
|
|
StateDirectory=netops-todo
|
|
WorkingDirectory=/opt/netops-todo
|
|
ExecStart=/usr/bin/node server.js
|
|
Environment=PORT=3000
|
|
# HOST=127.0.0.1 hvis proxyen kjører på samme maskin. Står proxyen i en annen
|
|
# container/host: fjern linjen (lytt på alt) og brannmur port 3000 til kun proxyen.
|
|
Environment=HOST=127.0.0.1
|
|
Environment=DATA_DIR=/var/lib/netops-todo
|
|
# Innebygd innlogging (anbefalt). Bytt til proxy-modus ved å fjerne denne
|
|
# og evt. sette DISABLE_COOKIE_AUTH=1.
|
|
Environment=AUTH_MODE=app
|
|
Restart=on-failure
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
## Funksjoner
|
|
|
|
- **Sanntidssynk** — alle endringer pushes til alle åpne faner via WebSocket
|
|
- **Presence** — se hvem som er pålogget akkurat nå
|
|
- **Kanban-tavle** med drag & drop mellom statuskolonner, pluss klassisk tabellvisning
|
|
- **Dagsplan** — plukk oppgaver fra lista og bygg en ordnet plan for dagen
|
|
(dra for å sortere, kapasitet i timer, datovelger). Kan navngis, flyttes til
|
|
en annen dato i ett klikk, og er som hovedregel synlig for alle (med en
|
|
«Synlig for alle»-toggle for å gjøre den privat). Andres delte planer kan ses
|
|
skrivebeskyttet via «Plan for»-velgeren. **Søkbar planliste** («📋 Planer») for
|
|
å finne en plan på navn/dato/person uten å gjette datoen.
|
|
- **Brukerkontoer med roller** (i app-auth-modus) — admin kan opprette kontoer,
|
|
resette passord og styre roller; «Logg ut alle enheter» tilbakekaller økter.
|
|
- **Plan-badge på oppgaver** — hvert kort/rad viser om oppgaven er planlagt,
|
|
hvilken dag og av hvem.
|
|
- **Egendefinerte kategorier** — legg til nye kategorier fra kategori-nedtrekket
|
|
(«+ Ny kategori …»); de deles med hele teamet umiddelbart.
|
|
- **Feltmodus** — toggle (📱 / `f`) for større knapper og mer tommelvennlig
|
|
layout på mobil; slås på automatisk på små skjermer.
|
|
- **Kommentar ved statusbytte** — når du markerer ferdig eller blokkert (via
|
|
knapp, kanban-drag eller modal) tilbys du å skrive en kommentar som festes
|
|
til oppgaven og vises i aktivitetsloggen
|
|
- **Hurtig-registrering** med tokens: `Bytt SFP #l1 p2 @me !imorgen ~1h loc:DC-OSL-1`
|
|
- **Kommentarer** per oppgave
|
|
- **Aktivitetslogg** — hvem gjorde hva, når (sidepanel)
|
|
- **Konfliktdeteksjon** — versjonsnummer per oppgave; lagring fra utdatert
|
|
versjon avvises med tydelig varsel i stedet for stille overskriving
|
|
- **"Redigerer nå"-indikator** — ser om en kollega har samme oppgave åpen
|
|
- **Angre sletting** direkte fra toast
|
|
- KPI-er, grafer (kategori/prioritet/eier), filtre, sortering, søk
|
|
- Eksport/import av JSON, eksempeldata for demo
|
|
- Tastatursnarveier: `n` ny · `/` søk · `v` bytt visning · `p` dagsplan · `f` feltmodus · `a` aktivitet · `esc` lukk
|
|
|
|
## API
|
|
|
|
Alle endepunkt under `/api/`, JSON inn/ut. `v` er versjonsnummer for
|
|
optimistisk låsing — send med ved update, få 409 hvis noen andre har lagret.
|
|
|
|
| Metode | Endepunkt | Beskrivelse |
|
|
|---|---|---|
|
|
| GET | `/api/state` | Bruker, auth-status, oppgaver, aktivitet, kjente brukere, online, kategorier, dagens plan, planIndex |
|
|
| GET | `/api/auth/status` | Auth-modus, innlogget?, bruker, via, har passord |
|
|
| POST | `/api/auth/login` | App-innlogging `{ user, password }` → sesjons-cookie |
|
|
| POST | `/api/auth/logout` | Tøm sesjons-cookie (denne enheten) |
|
|
| POST | `/api/auth/logout-all` | Tilbakekall alle egne økter (bump epoke), behold denne |
|
|
| POST | `/api/auth/password` | Sett/endre eget passord `{ password, current? }` |
|
|
| GET | `/api/auth/users` | (admin) Liste over kontoer |
|
|
| POST | `/api/auth/users` | (admin) Opprett konto `{ user, password, role }` |
|
|
| POST | `/api/auth/users/:user/password` | (admin) Tilbakestill passord (logger ut brukeren) |
|
|
| PUT | `/api/auth/users/:user/role` | (admin) Sett rolle `{ role }` |
|
|
| DELETE | `/api/auth/users/:user` | (admin) Slett konto |
|
|
| POST | `/api/tasks` | Opprett oppgave (`title` påkrevd) |
|
|
| PUT | `/api/tasks/:id` | Oppdater (delvis OK; send `v` for konfliktsjekk; valgfri `comment` festes som status-kommentar) |
|
|
| DELETE | `/api/tasks/:id` | Slett |
|
|
| POST | `/api/tasks/:id/comments` | Legg til kommentar `{ text }` |
|
|
| DELETE | `/api/tasks/:id/comments/:cid` | Slett egen kommentar |
|
|
| GET | `/api/plan?date=YYYY-MM-DD&user=navn` | Hent dagsplan (egen, eller en annens hvis delt — ellers 403) |
|
|
| PUT | `/api/plan` | Lagre egen dagsplan `{ date, items: [id…], name?, shared? }` |
|
|
| PUT | `/api/plan/share` | Slå deling av/på `{ date, shared }` |
|
|
| PUT | `/api/plan/move` | Flytt hele planen `{ from, to }` (slår sammen på måldato) |
|
|
| POST | `/api/categories` | Legg til egendefinert kategori `{ name, color? }` |
|
|
| POST | `/api/replace` | Erstatt hele listen (import) |
|
|
| POST | `/api/seed` | Last eksempeldata (kun hvis tomt) |
|
|
| POST | `/api/login` | Sett navn-cookie (hvis cookie-auth er på) |
|
|
| GET | `/healthz` | Helsesjekk |
|
|
|
|
Eksempel:
|
|
|
|
```bash
|
|
curl -s -X POST localhost:3000/api/tasks \
|
|
-H 'Content-Type: application/json' \
|
|
-H 'X-Remote-User: jon' \
|
|
-d '{"title":"Sjekk BGP-sesjon mot AS2119","category":"BGP","priority":"P1"}'
|
|
```
|
|
|
|
## Lagring
|
|
|
|
JSON-filer i `DATA_DIR` (atomisk skriv via tmp-fil + rename, serialisert i
|
|
prosessen): `tasks.json`, `activity.json`, `users.json`, `plans.json`
|
|
(dagsplaner `{name, items, shared}` per bruker per dato; eldre enn 30 dager
|
|
ryddes ved oppstart) og `categories.json` (egendefinerte kategorier).
|
|
Én node-prosess per datakatalog. Holder fint til tusenvis av oppgaver; blir det
|
|
trangt er SQLite (`node:sqlite`) neste naturlige steg.
|