Teknisk dokumentasjon
Hvordan ht14.no og Badehusappen er bygget – først kort for alle, så stadig mer detaljert. Les så langt du har lyst.
Badehusappen ligger på ht14.no/badehusappen/ (kortadresse badehusappen.ht14.no). Hvert hus logger inn, og bookingene er felles for alle fire husene. Dette er HT14 versjon 0.6. Thomas har laget dette som et forslag til resten av sameiet. Ingenting er bestemt ennå, så si gjerne fra hva dere synes.
1 Kort for alle
Hva er det?
ht14.no er sameiets egen nettside med startsiden, denne dokumentasjonen og Badehusappen – en nettside som oppfører seg som en app. Der ser vi når badehuset er ledig, booker, avbestiller og sjekker inn. Den virker på mobil, nettbrett og PC, og kan legges på Hjem-skjermen.
Målet er at alle fire husene skal ha mulighet til å bruke badehuset like mye. Dagen er delt i tre perioder: formiddag 10–14, ettermiddag 14–17 og kveld 17–24, og vanlig booking er én kveld. Reglene står i Regler for bruk av badehuset.
Hvem kan gjøre hva
Hvert hus (A, B, C og D) får én felles innlogging. Med den kan dere se kalenderen, booke, avbestille egne bookinger, sjekke inn, bekrefte bruk, se statistikk, endre husets del av «Om oss» og bytte passord.
Administrator (Thomas) kan i tillegg lage og deaktivere innloggingen til hvert hus, gi et hus nytt passord, endre «Om oss» for alle hus og avbestille alle bookinger. Alt som gjøres, logges i hendelsesloggen. Unntak fra reglene og retting av historikk kommer eventuelt senere.
Om oss
«Om oss» er en del av forsiden ht14.no (ikke i appen). Før innlogging viser den bare Hus A–D og et innloggingsskjema – samme innlogging og samme økt som i Badehusappen. Etter innlogging ser dere hvem som bor i hvert hus, inntil to kontaktpersoner per hus (navn, e-post og mobil) og et fritt tekstfelt. Hvert hus fyller ut sitt eget. Opplysningene ligger bare i databasen – aldri i koden eller på de åpne sidene – og API-et gir dem bare til innloggede.
I appen er fanene Kalender, Mine, Statistikk og Mer. Kalenderen starter på i dag og viser de neste 30 dagene (eller én måned); pilene øverst blar frem og tilbake. Statistikk viser sesongen (juni–august) og hele året.
Trygghet og personvern
- Ingen registrerer seg selv – administratoren gir hvert hus e-post og passord. Mobilen kan huske passordet, og dere holder dere innlogget i omtrent ett år.
- Bookinger og kontaktinfo lagres i en database hos Cloudflare, aldri på de offentlige sidene. Passord lagres aldri i klartekst.
- Hver natt tas det en kopi til et privat lager. Ingen bookinger slettes, og alle endringer kan spores.
2 Overordnet (HLD)
Hvilke deler løsningen består av, og hvorfor. Alt kjører på gratisplanene hos Cloudflare og GitHub, så det koster 0 kr i drift.
Komponenter
| Del | Hva den gjør |
|---|---|
| ht14.no | Sameiets adresse. Kjøpt hos Domeneshop, DNS hos Cloudflare. |
| Cloudflare Worker «ht14» med statiske filer | Serverer startsiden (/), dokumentasjonen (/dokumentasjon/) og Badehusappen (/badehusappen/), og API-et på /api/: innlogging, booking, Om oss og administrasjon. |
| Cloudflare D1 «ht14» | Databasen: hus, innlogginger, bookinger, Om oss, økter, feilede innlogginger og en hendelseslogg. Forhåndsvisninger av grener bruker en egen test-database, «ht14-test». |
| badehusappen.ht14.no | Kortadresse. En Redirect Rule sender den videre (301) til ht14.no/badehusappen/. Samme sted sender www.ht14.no til ht14.no. |
| Nattlig kopi (Cron Trigger) | Hver natt skriver Workeren bookinger og brukere som CSV og JSON til det private repoet FluxReveal/ht14-data – aldri passord-hasher. Git gir full historikk, og CSV-filene åpnes i Excel. |
| GitHub | FluxReveal/ht14 (offentlig: sidene og koden, ingen hemmeligheter) og FluxReveal/ht14-data (privat: nattlig kopi). |
| Workers Builds | Publiserer automatisk når main endres. Andre grener får en egen forhåndsvisningslenke. |
Fire hus som booker badehuset, kommer aldri i nærheten av grensene på gratisplanene.
Dataflyt
flowchart LR U["Beboer
(nettleser)"] -->|1. åpner ht14.no| W["Cloudflare Worker «ht14»
sider + /api/"] W -->|2. sjekker økt og regler,
leser og skriver| D[("D1-database
ht14")] W -->|3. svar + oppdatert kalender| U C["Cron Trigger
hver natt"] -->|4. starter eksport| W W -->|5. CSV + JSON| G[("FluxReveal/ht14-data
privat repo")] classDef ext fill:#ecfeff,stroke:#0891b2,color:#0f172a; classDef wk fill:#fff7ed,stroke:#ea580c,color:#0f172a; classDef data fill:#f5f3ff,stroke:#7c3aed,color:#0f172a; classDef act fill:#f0fdf4,stroke:#16a34a,color:#0f172a; class U ext; class W wk; class D,G data; class C act;
Status («pågår», «ferdig») regnes ut fra klokka i norsk tid når noe vises. Hver periode lagres som en egen booking; en heldag er tre bookinger med felles group_id.
Innlogging og økter
Én innlogging per hus (A–D) med e-post og passord, sjekket i Workeren. Passord lagres bare som hash: PBKDF2-SHA256(HMAC-SHA256(SESSION_PEPPER, passord), salt, 10 000 runder, 256 bit), med eget tilfeldig salt (16 byte) per bruker. Salt og antall runder lagres per bruker, så tallet kan endres senere – gamle hasher oppgraderes da ved neste innlogging. Målt på ht14.no: med 10 000 runder bruker en hel innlogging 4–6 ms CPU (opptil 9 ms rett etter en ny versjon), godt innenfor gratisplanens 10 ms per forespørsel. 30 000 runder ga 13–18 ms og var for mye. Den hemmelige pepperen gjør at en lekket database alene ikke er nok til å gjette passord. Skjemaet bruker riktige autocomplete-verdier, så iPhone og Android kan lagre passordet.
Etter innlogging får mobilen cookien __Host-ht14_okt (HttpOnly, Secure, SameSite=Lax) som varer i ca. ett år. Cookien er 32 tilfeldige byte; i D1 lagres bare SHA-256 av den. Utlogging, nytt passord og deaktivering stopper øktene med en gang. Fem feil passord for samme e-post innen 15 minutter gir 15 minutters pause (20 per IP-adresse), og svaret er det samme enten e-posten finnes eller ikke. Sidene og API-et ligger på samme adresse, så cookien er en vanlig førsteparts-cookie som også Safari på iPhone godtar.
Sikkerhet
- Hemmeligheter:
SESSION_PEPPERogGH_DATA_TOKENligger bare som Worker-secrets – aldri i koden. Derfor kan repoetht14være offentlig. - Token:
GH_DATA_TOKENer et fine-grained GitHub-token som bare kan skrive tilht14-data. - Regler: sjekkes alltid i Workeren, så det hjelper ikke å fikle med JavaScript i nettleseren.
- Andre nettsider: Workeren godtar bare endringer fra ht14.no (Origin-sjekk på alle POST/PUT/DELETE), sender ingen CORS-headere og krever gyldig økt.
- Nettleseren: strenge sikkerhetshoder i
public/_headers– Content-Security-Policy (appen får bare kjøre egne skript), HSTS,X-Frame-Options: DENYognosniff. API-svar lagres aldri i hurtigbufferen. - Passord: hasher, salt, økter og innloggingsforsøk forlater aldri databasen – ikke i API-svar, logger eller den nattlige kopien.
- Dobbeltbooking: hindres i databasen – samme dato og periode kan bare ha én aktiv booking.
- Sporbarhet: hendelsesloggen i D1 viser hvem, når, fra, til og kommentar, og Git-historikken i
ht14-dataviser alle tidligere versjoner.
Tekniske valg
Én leverandør for adresse, sider, innlogging og database gjør det enkelt å drifte. Med alt på ht14.no ligger sidene og API-et på samme sted, så innloggingen virker på iPhone. D1 er en ekte database, så to hus kan ikke booke samme periode samtidig. Den nattlige kopien i GitHub gjør oss uavhengige: dataene kan alltid tas med videre. Cloudflare Access ble valgt bort fordi den gir maks 30 dagers innlogging med engangskoder på e-post.
3 Detaljert (LLD)
For den som skal bygge, drifte eller videreutvikle. Navn på filer, felt og endepunkter kan justeres underveis.
Filer
ht14/ ← offentlig (github.com/FluxReveal/ht14)
├── public/ statiske filer på ht14.no
│ ├── index.html startside med «Om oss» (/)
│ ├── om-oss.js Om oss på startsiden (henter fra /api/contacts)
│ ├── favicon.svg, ht14-logo.svg HT14-logoen (SVG)
│ ├── dokumentasjon/ disse sidene (/dokumentasjon/)
│ └── badehusappen/ Badehusappen (/badehusappen/)
│ ├── index.html, css/, manifest.webmanifest, sw.js
│ ├── ikoner/ app-ikonet (SVG)
│ ├── rules.json reglene
│ └── js/ rules-engine.js, store.js, app.js
├── src/index.js Workeren: API-et (/api/…) og regler
├── src/auth.js passord-hash og økter
├── src/export.js nattlig kopi til ht14-data
├── src/ikoner.js PNG/ICO-ikonene (base64, lages av tools/lag-ikoner.mjs)
├── wrangler.jsonc navn, D1 (ht14 og ht14-test), ht14.no
├── schema.sql databasen
└── tools/ generator, lenkesjekk og tester
ht14-data/ ← privat, bare Workeren skriver
└── data/ bookings, users (uten passord), contacts, events – CSV og JSON
store.js henter og lagrer alt via /api/…. Prøvebookinger fra før versjon 0.5 (som bare lå i nettleseren) slettes automatisk og flyttes ikke over.
Datamodell
Skjemaet står i schema.sql og er lagt inn i både «ht14» og «ht14-test». En unik indeks på dato + periode (for alle som ikke er avbestilt) gjør dobbeltbooking umulig, også hvis to hus trykker samtidig.
| Tabell | Innhold |
|---|---|
houses | A–D med navn og farge |
users | Én innlogging per hus, pluss administratorer: e-post, rolle, passord-hash |
bookings | Dato, periode (AM 10–14, MID 14–17, PM 17–24), hus, status, group_id |
house_info | Om oss: husstanden, inntil to kontaktpersoner og fritekst per hus |
sessions | Innloggede mobiler (bare hash av cookien lagres) |
login_attempts | Feil passord, for en kort pause etter flere forsøk |
events | Hendelseslogg: hvem, når, hva, fra, til, kommentar |
Utdrag av rules.json – alle regler ligger her, så de kan endres uten kodeendring:
{ "timezone": "Europe/Oslo",
"periods": { "AM": { "label": "Formiddag", "start": "10:00", "end": "14:00" },
"MID": { "label": "Ettermiddag", "start": "14:00", "end": "17:00" },
"PM": { "label": "Kveld", "start": "17:00", "end": "24:00" } },
"bookingWindow": { "months": 3 },
"defaultPeriods": ["PM"],
"summer": { "months": [6, 7, 8], "limitedDays": ["thu", "fri", "sat"],
"maxPerWeekdayPerMonth": { "thu": 1, "fri": 1, "sat": 1 },
"maxLimitedDaysPerWeek": 1 },
"liftSummerRulesWhenDaysBefore": 7 }
Regelmotor
rules-engine.js er rene funksjoner, så samme kode kjører i nettleseren (rask tilbakemelding) og i Workeren (som bestemmer).
validateBookingsjekker én periode: gyldig input → ikke passert → ikke startet → innenfor 3 måneder → ledig → 7 dager før eller senere? da OK → sommer og torsdag/fredag/lørdag? → kvote per ukedag → én av dagene per uke (mandag–søndag).- Fra 7 dager før datoen (
liftSummerRulesWhenDaysBefore= 7: kalenderdager i Oslo-tid fra i dag til datoen er 7 eller færre) gjelder ikke sommergrensene: en ledig torsdag, fredag eller lørdag kan bookes av alle hus, også om huset har brukt opp kvoten. Slike bookinger lagres utenfor kvoten (shortNoticeCountsTowardQuota= false). validateBookingMultitar flere perioder: heldag avvises etter kl. 10, og alt eller ingenting lagres.- Kvoten teller dager, ikke perioder. Kveld er forhåndsvalgt i appen; dagtid har ingen teknisk sperre og merkes ikke spesielt. Testene ligger i
tools/tests/og kjøres mednpm test.
Statuser
stateDiagram-v2 direction LR [*] --> BOOKED: booking opprettet [*] --> CHECKED_IN: spontan innsjekk BOOKED --> CHECKED_IN: sjekk inn BOOKED --> CANCELLED: avbestilt BOOKED --> NOT_CONFIRMED: perioden slutter uten innsjekk CHECKED_IN --> COMPLETED: automatisk utsjekk ved periodeslutt NOT_CONFIRMED --> COMPLETED: Vi brukte badehuset COMPLETED --> [*] CANCELLED --> [*]
CANCELLED frigjør perioden og teller aldri – verken i sommerkvoten (måned), ukeregelen eller statistikken – uansett når den ble avbestilt, også rett før (countsForQuota og stats ser bare på aktive bookinger, og Workeren bruker samme motor). Innsjekk gjelder alle perioder. I statistikken teller CHECKED_IN og COMPLETED som bruk.
API
Alle kall går til /api/ på ht14.no med JSON og økt-cookie. Uten innlogging svarer alt unntatt /api/helse og /api/login med 401.
| Endepunkt | Hva |
|---|---|
GET /api/helse | {"ok":true,"version":"0.6"} |
POST /api/login, /logout, /password, GET /api/me | Innlogging, utlogging, bytte eget passord, hvem er jeg |
GET /api/bookings?from&to, GET /api/bookings/{id}, GET /api/stats | Kalender og historikk, én booking med hendelser, statistikk |
POST /api/bookings {date, periods} | Book én eller flere perioder (alt eller ingenting). Et hus booker alltid for seg selv; admin sender med household. |
POST /api/bookings/{id}/cancel | checkin | confirm, POST /api/checkin | Avbestill, sjekk inn, «Vi brukte badehuset», spontan innsjekk |
GET /api/contacts, PUT /api/contacts/{A–D} | Om oss (bare innlogget; endre eget hus, admin alle) |
GET|POST /api/admin/users, POST /api/admin/users/{id}/password | deactivate, GET /api/admin/events | Bare admin: innlogginger, nytt passord, deaktiver, hendelseslogg |
Feilkoder
Feil returneres som { "error": KODE, "message": tekst }, og brukeren ser alltid en forståelig norsk melding. De vanligste: SLOT_TAKEN (perioden er tatt), STARTED (har startet – bruk «Sjekk inn nå»), FULLDAY_STARTED (heldag etter kl. 10), OUTSIDE_WINDOW (over 3 måneder), QUOTA_WEEKDAY og WEEK_LIMIT (sommerregelen), BAD_LOGIN (feil e-post eller passord), LOCKED (for mange forsøk – vent 15 minutter), LOGIN_REQUIRED (401), FORBIDDEN (ikke lov), BAD_ORIGIN (forespørsel fra en annen nettside) og RULES_INVALID (feil i rules.json).
4 Drift og oppsett
For Thomas, steg for steg i Cloudflare-dashbordet. Alt er på gratisplanene.
Oppsett i Cloudflare
Allerede gjort (26. oktober 2026): ht14.no er aktiv på Cloudflare og koblet til Workeren med Workers Builds. D1 «ht14» og «ht14-test» er laget, og skjemaet er lagt inn. Hemmeligheten SESSION_PEPPER er lagt inn, administratoren er laget, og badehusappen.ht14.no sender videre til appen. Repoene FluxReveal/ht14 og FluxReveal/ht14-data finnes. Gjenstår: GH_DATA_TOKEN og å slå på den nattlige kopien (steg 5–6).
- Vent til ht14.no er aktivVelg ht14.no i dashbordet og sjekk at statusen er Active (Cloudflare sender også e-post). Først da kan domenet kobles til Workeren.
- Koble repoet (Workers Builds)Workers & Pages → Create application → GitHub. Godkjenn Cloudflare-appen for FluxReveal (gjerne bare repoet
ht14), velgFluxReveal/ht14, la byggkommandoen stå tom og velg Save and Deploy. Workeren må heteht14, som iwrangler.jsonc. - Forhåndsvisning av grenerWorkeren → Settings → Builds: sjekk at produksjonsgrenen er
mainog at bygg for andre grener (preview builds) er slått på. Lenken for hver gren står under Previews og som kommentar i GitHub. - ht14.no som eget domeneStår allerede i
wrangler.jsoncog kobles på ved første publisering. Sjekk under Settings → Domains & Routes (i nyere dashbord fanen Domains). Mangler det: Add → Custom Domain →ht14.no. - Hemmeligheter
SESSION_PEPPERer lagt inn (tilfeldig, ingen kjenner den). Bytt den aldri – da slutter alle passord å virke. Gjenstår: GitHub → Settings → Developer settings → Fine-grained tokens → nytt token med tilgang bare tilFluxReveal/ht14-data, Contents: Read and write, utløp om ca. ett år. Legg det inn i Cloudflare: Workeren → Settings → Variables and Secrets → Add, type Secret, navnGH_DATA_TOKEN→ Deploy. - Nattlig kopiKoden er klar (
src/export.js). Iwrangler.jsonc: fjern//foran"triggers"-linjen og legg til komma etterobservability-linjen. Flett inn imain. Sjekk under Settings → Triggers at «15 2 * * *» står der, og neste morgen atht14-data/data/har nye filer. - Kortadressen
badehusappen.ht14.nohar en DNS-post (AAAA100::, proxied) og en regel under Rules → Redirect Rules som sender videre med 301 tilhttps://ht14.no/badehusappen/(sti og spørring beholdes). Regelen forwww.ht14.noligger i samme liste – slett den ikke. - Test på iPhoneÅpne ht14.no og Badehusappen, og
ht14.no/api/helse(skal vise{"ok":true,"version":"0.6"}). Logg inn, lagre passordet og legg appen på Hjem-skjermen.
Innlogginger
Alt gjøres i appen, også på mobilen. Bare administrator ser «Administrasjon».
- Lage innlogging for et hus: logg inn som admin → Mer → Administrasjon → «Lag innlogging for Hus A». Skriv husets e-post. Appen foreslår et tilfeldig passord – trykk «Kopier passord» eller skriv det ned – og trykk Lagre. Gi e-post og passord til huset (muntlig, på lapp eller SMS). Gjenta for B, C og D. Hvert hus kan bare ha én aktiv innlogging.
- Glemt passord eller mistet mobil: Administrasjon → «Nytt passord» ved huset → Lagre. Huset logges ut på alle telefoner og logger inn med det nye.
- Flytter noen: «Deaktiver» den gamle innloggingen og lag en ny med ny e-post. Bookingene og historikken beholdes.
- Bytte eget passord: alle kan gjøre det selv under Mer → Bytt passord. Andre telefoner logges ut.
Endre regler og gjøre unntak
Når sameiet har bestemt en endring, legges den inn i rules.json som en vanlig endring (se Slik gjøres endringer). Unntak og retting av historikk krever alltid en kommentar og vises i historikken.
Sikkerhetskopi
Når den nattlige kopien er slått på, skriver Workeren bookinger, innlogginger (uten passord-hash og salt), Om oss og hendelsesloggen til ht14-data/data/ som CSV (UTF-8, åpnes i Excel) og JSON. Økter og innloggingsforsøk tas aldri med. Git-historikken viser alle tidligere versjoner.
D1 har i tillegg Time Travel: databasen kan spoles tilbake til et hvilket som helst minutt de siste 7 dagene (gratisplanen). Finn et tidspunkt med npx wrangler d1 time-travel info ht14 --timestamp=2026-10-26T20:00:00Z og gjenopprett med npx wrangler d1 time-travel restore ht14 --timestamp=…. Gjenoppretting overskriver dagens data, så ta en eksport først: npx wrangler d1 export ht14 --remote --output=ht14.sql.
Når noe feiler
| Symptom | Hva du gjør |
|---|---|
| Siden virker ikke etter en endring | Rull tilbake: Workeren → Deployments → ⋯ ved forrige versjon → Rollback. |
| Endringen kom aldri ut | Se byggeloggen under Deployments → View build history. |
| Kopien i ht14-data oppdateres ikke | Sjekk at GH_DATA_TOKEN ikke er utløpt. Beboerne merker ingenting. |
| Alle bookinger avvises (RULES_INVALID) | Rull tilbake, eller rett rules.json. |
| Et hus får «For mange forsøk» | Vent 15 minutter, eller gi huset nytt passord i Administrasjon. |
| Ingen kommer inn («Innlogging er ikke satt opp») | Hemmeligheten SESSION_PEPPER mangler. Den må aldri slettes eller byttes. |
5 Drift og overlevering
HT14 er en egen pakke som kan overleveres til sameiet. FluxReveal (Thomas) er IT-avdeling og driftsleverandør.
Hvem eier hva
- Sameiet eier domenet ht14.no, dataene (bookinger og innlogginger), innholdet og reglene.
- FluxReveal gjør drift (følger med på at alt virker), sikkerhetskopi (den nattlige kopien) og oppdateringer (endringer, sikkerhet og fornying av token).
- Begge repoene ligger hos FluxReveal på GitHub og kan overføres til sameiet.
Slik gjøres endringer
- Thomas ber om endringen i chat.
- Endringen lages på en egen gren i
ht14. - Cloudflare lager en forhåndsvisningslenke, og Thomas tester på iPhone.
- Thomas sier «kjør», og grenen flettes inn i
main. - Workers Builds publiserer automatisk på ht14.no.
- Det kommer en linje i Hva er nytt.
- Går noe galt: ett klikk tilbake i Cloudflare (Rollback).
Slik tar sameiet over
- Overfør de to repoene (
ht14oght14-data) til sameiets GitHub-konto (repoets Settings → Transfer ownership). - Lag sameiets egen Cloudflare-konto (gratis), lag en ny D1-database, legg inn siste kopi fra
ht14-dataog koble repoet med Workers Builds. - Legg inn hemmelighetene på nytt (ny
SESSION_PEPPERog nytt token). Kopien inneholder ingen passord, så lag administratoren på nytt medtools/lag-admin-sql.mjsog gi husene nye passord i appen. - Flytt domenet: eierskifte for ht14.no hos Domeneshop til sameiet, og legg ht14.no til i sameiets Cloudflare-konto.