Valgfri app-auth ved siden av proxy-modus (default uendret). Signert sesjons-cookie (scrypt-passord, HMAC, ingen avhengigheter), gating av API + WebSocket, og en selvstendig neon-login (public/login.html) for uautentiserte. Trusted bypass: en proxy-identitet (X-Remote-User / Basic Auth) regnes som innlogget uten app-passord (TRUST_PROXY_HEADER, default på), så man kan bootstrappe: logg inn via NPM Basic Auth → sett app-passord i konto-modalen → skru av Basic Auth. Konto-UI for sett/endre passord + logg ut. AUTH_MODE leses per createServer-instans (testbart). 34 integrasjonstester (8 nye for app-auth), alle grønne. README/CLAUDE.md/setup-skript oppdatert. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
10 KiB
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
npm install
npm start # → http://localhost:3000
Utvikling med auto-restart ved kodeendring:
npm run dev
Tester:
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:
X-Remote-User-header — satt av reverse proxy (oauth2-proxy, Authelia, …)Authorization: Basic— Basic Auth terminert i proxy, brukernavnet gjenbrukesnetops_user-cookie — selvvalgt navn via "Sett navn"-knappen (hjemme/dev-bruk)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.
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:
- Behold NPM Access List (Basic Auth) og deploy med
AUTH_MODE=app. - Åpne appen (du slipper inn via Basic Auth-bypass), klikk brukernavnet ditt oppe til høyre → sett et app-passord. Gjenta for hver kollega.
- 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=0etter 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)
# 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
- 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.jsserveres etter oppgradering) - Block Common Exploits: valgfritt, fungerer fint sammen med appen
- Domain Names:
- SSL-fanen: Request a new SSL Certificate (Let's Encrypt) + Force SSL.
Frontend bytter selv til
wss:når siden er https. - 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. SettDISABLE_COOKIE_AUTH=1. (Ulempe: nettleseren spør om passord på nytt ved hver ny økt — derav app-innlogging som alternativ.)
- Innebygd app-innlogging (anbefalt — slipper repeterte Basic Auth-prompts):
kjør tjenesten med
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):
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)
# /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.
- 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:
nny ·/søk ·vbytt visning ·pdagsplan ·ffeltmodus ·aaktivitet ·esclukk
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 |
| POST | /api/auth/password |
Sett/endre eget passord { password, current? } |
| 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:
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.