Hopp til innhold
Sameiet HT14Dokumentasjon Badehusappen
Teknisk

Teknisk dokumentasjon

Hvordan ht14.no og Badehusappen er bygget – først kort for alle, så stadig mer detaljert. Les så langt du har lyst.

⏱ Ca. 15 min lesetid

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

DelHva den gjør
ht14.noSameiets adresse. Kjøpt hos Domeneshop, DNS hos Cloudflare.
Cloudflare Worker «ht14» med statiske filerServerer 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.noKortadresse. 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.
GitHubFluxReveal/ht14 (offentlig: sidene og koden, ingen hemmeligheter) og FluxReveal/ht14-data (privat: nattlig kopi).
Workers BuildsPubliserer 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;
Nettleseren snakker bare med Workeren. Bare Workeren kan lese og skrive i databasen.

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_PEPPER og GH_DATA_TOKEN ligger bare som Worker-secrets – aldri i koden. Derfor kan repoet ht14 være offentlig.
  • Token: GH_DATA_TOKEN er et fine-grained GitHub-token som bare kan skrive til ht14-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: DENY og nosniff. 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-data viser 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.

TabellInnhold
housesA–D med navn og farge
usersÉn innlogging per hus, pluss administratorer: e-post, rolle, passord-hash
bookingsDato, periode (AM 10–14, MID 14–17, PM 17–24), hus, status, group_id
house_infoOm oss: husstanden, inntil to kontaktpersoner og fritekst per hus
sessionsInnloggede mobiler (bare hash av cookien lagres)
login_attemptsFeil passord, for en kort pause etter flere forsøk
eventsHendelseslogg: 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).

  • validateBooking sjekker é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).
  • validateBookingMulti tar 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 med npm 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 --> [*]
«Pågår», «ikke bekreftet» og automatisk utsjekk regnes ut fra klokka. Administrator kan avbestille alle bookinger.

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.

EndepunktHva
GET /api/helse{"ok":true,"version":"0.6"}
POST /api/login, /logout, /password, GET /api/meInnlogging, utlogging, bytte eget passord, hvem er jeg
GET /api/bookings?from&to, GET /api/bookings/{id}, GET /api/statsKalender 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/checkinAvbestill, 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/eventsBare 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).

  1. 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.
  2. Koble repoet (Workers Builds)Workers & Pages → Create application → GitHub. Godkjenn Cloudflare-appen for FluxReveal (gjerne bare repoet ht14), velg FluxReveal/ht14, la byggkommandoen stå tom og velg Save and Deploy. Workeren må hete ht14, som i wrangler.jsonc.
  3. Forhåndsvisning av grenerWorkeren → Settings → Builds: sjekk at produksjonsgrenen er main og at bygg for andre grener (preview builds) er slått på. Lenken for hver gren står under Previews og som kommentar i GitHub.
  4. ht14.no som eget domeneStår allerede i wrangler.jsonc og kobles på ved første publisering. Sjekk under Settings → Domains & Routes (i nyere dashbord fanen Domains). Mangler det: Add → Custom Domain → ht14.no.
  5. HemmeligheterSESSION_PEPPER er 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 til FluxReveal/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, navn GH_DATA_TOKEN → Deploy.
  6. Nattlig kopiKoden er klar (src/export.js). I wrangler.jsonc: fjern // foran "triggers"-linjen og legg til komma etter observability-linjen. Flett inn i main. Sjekk under Settings → Triggers at «15 2 * * *» står der, og neste morgen at ht14-data/data/ har nye filer.
  7. Kortadressenbadehusappen.ht14.no har en DNS-post (AAAA 100::, proxied) og en regel under Rules → Redirect Rules som sender videre med 301 til https://ht14.no/badehusappen/ (sti og spørring beholdes). Regelen for www.ht14.no ligger i samme liste – slett den ikke.
  8. 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».

  1. 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.
  2. Glemt passord eller mistet mobil: Administrasjon → «Nytt passord» ved huset → Lagre. Huset logges ut på alle telefoner og logger inn med det nye.
  3. Flytter noen: «Deaktiver» den gamle innloggingen og lag en ny med ny e-post. Bookingene og historikken beholdes.
  4. 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

SymptomHva du gjør
Siden virker ikke etter en endringRull tilbake: Workeren → Deployments → ⋯ ved forrige versjon → Rollback.
Endringen kom aldri utSe byggeloggen under Deployments → View build history.
Kopien i ht14-data oppdateres ikkeSjekk 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

  1. Thomas ber om endringen i chat.
  2. Endringen lages på en egen gren i ht14.
  3. Cloudflare lager en forhåndsvisningslenke, og Thomas tester på iPhone.
  4. Thomas sier «kjør», og grenen flettes inn i main.
  5. Workers Builds publiserer automatisk på ht14.no.
  6. Det kommer en linje i Hva er nytt.
  7. Går noe galt: ett klikk tilbake i Cloudflare (Rollback).

Slik tar sameiet over

  1. Overfør de to repoene (ht14 og ht14-data) til sameiets GitHub-konto (repoets Settings → Transfer ownership).
  2. Lag sameiets egen Cloudflare-konto (gratis), lag en ny D1-database, legg inn siste kopi fra ht14-data og koble repoet med Workers Builds.
  3. Legg inn hemmelighetene på nytt (ny SESSION_PEPPER og nytt token). Kopien inneholder ingen passord, så lag administratoren på nytt med tools/lag-admin-sql.mjs og gi husene nye passord i appen.
  4. Flytt domenet: eierskifte for ht14.no hos Domeneshop til sameiet, og legg ht14.no til i sameiets Cloudflare-konto.