For seks måneder siden redigerte eieren av PrintPoint nettstedet sitt ved å sende oss e-post. I dag redigerer han det i produksjon, fra telefonen, mellom kunder. Det som ligger imellom er et tilpasset admin-panel vi bygde på Supabase, Postgres Row-Level Security og ett enkelt Server Action-lag.
Spørsmålet vi får oftest er: hvorfor brukte dere ikke bare WordPress? Svaret ligger i sikkerhetsmodellen, vedlikeholdskostnaden de neste fem årene, og arbeidsflyten eieren faktisk har — ingen av disse løses godt av en plugin-stack. Her er bygget.
Briefen
PrintPoint er et trykkeri som har drevet i over et tiår. Eieren hadde en side som trengte tre ting:
- Kunne redigere hvert produkt, hver pris og hvert bilde selv, i produksjon.
- Slutte å motta 40+ spam-innsendinger om dagen uten å sette en CAPTCHA foran ekte kunder.
- Legge til nye sider og blogginnlegg uten et byrå i loopen.
Et større byrå ville ha spesifisert WordPress + WooCommerce + en sikkerhetsplugin + et tilpasset tema. Vi har vedlikeholdt sider som det. De er skjøre, plugin-kjeden ryker midt på natten, og sikkerheten avhenger fullstendig av den treigeste patcheren i kjeden.
Vi bygde det tilpasset i stedet. Tre uker bygging, én uke opplæring, så tok eieren det videre selv.
Auth: Supabase magic-link + OTP
Innloggingssiden er ett enkelt e-postfelt. Eieren skriver inn adressen sin; han får en engangskode i innboksen eller, på telefon, en magic-link han kan trykke på. Det er hele autentiseringsflyten. Ingen passord å rotere, ingen 2FA-app å sette opp, ingen sikkerhetsspørsmål.
Bak skjemaet håndterer Supabase Auth utstedelse av JWT, sesjonsrotasjon og rate limiting per IP. Sesjoner lever i 24 timer, refresh-tokens i 30 dager. Vi skrev ikke en eneste linje auth-kode — Supabase er bransjestandard, revidert, og betydelig mer herdet enn noe et lite byrå kunne levert internt.
Den faktiske sikkerhetsgrensen: Postgres RLS
Dette er delen som overrasker folk. Sikkerheten til admin-panelet ligger ikke i applikasjonskoden. Den ligger i databasen, i Postgres Row-Level Security-policyer.
En forenklet versjon av policyen på products-tabellen:
-- Anyone can read products (they show on the public site)
create policy "products_read_public"
on products for select
using (true);
-- Only authenticated owners can insert/update/delete
create policy "products_write_owner"
on products for all
using (
exists (
select 1 from admin_users
where admin_users.user_id = auth.uid()
and admin_users.role in ('owner', 'manager')
)
)
with check (
exists (
select 1 from admin_users
where admin_users.user_id = auth.uid()
and admin_users.role in ('owner', 'manager')
)
);
Hva det betyr i praksis: selv om frontend har en bug, selv om noen omgår hele React-laget, selv om de kaller API-et direkte med en forfalsket forespørsel — vil databasen ikke returnere eller endre noe de ikke har lov til å røre. Håndhevelsen er ett lag dypere enn applikasjonen.
Den egenskapen er i praksis umulig å oppnå i en typisk WordPress-installasjon, hvor sikkerheten avhenger av at applikasjonslaget ærlig sjekker rettigheter ved hvert inngangspunkt. Plugin-økosystemet er fullt av plugins som ikke gjør det riktig.
Hva som ligger i panelet
Eieren redigerer åtte ting, hver på sin egen dashboard-fane:
- Sider — herotekst, seksjoner, bilder, CTA-er. Endringer går live på sekunder.
- Produkter — navn, beskrivelse, pris, bilder, tilgjengelighet. Alt redigerbart.
- Blogg — Markdown-editor med bildeopplasting til Supabase Storage.
- Henvendelser — hver kontaktskjema-innsending med svar-sporing og status (ny / besvart / arkivert).
- Team — staff-kontoer med roller (owner / manager / editor). Eieren styrer tilgang uten oss.
- Audit-log — hver endring, hvem som gjorde den, når, hva som ble endret. Søkbar.
- Bot-statistikk — hvor mange spam-innsendinger fanget denne uka, brutt ned per årsak.
- Storage — bildebibliotek med opplasting, søk og erstatning.
Bot-forsvar uten CAPTCHA
Før ombygget snittet PrintPoints kontaktskjema 40+ spam-innsendinger om dagen. Etter: null. Ingen CAPTCHA foran ekte kunder. Tre teknikker, lagvis:
- Honeypot-felter — usynlige inputs som skal forbli tomme. Boter fyller dem; ekte brukere gjør det ikke. Alt som kommer inn med honeypot utfylt blir droppet stille før API-et i det hele tatt kjører.
- Rate limiting per IP — maksimalt tre innsendinger per IP per ti minutter, håndhevet på Vercel-kanten. Ekte kunder treffer aldri denne; boter blir strupet.
- Signaturbasert deteksjon — kjente spam-mønstre (kun kyrilliske navn med engelske e-postadresser, payload-maler fra vanlige spam-verktøy) blir flagget og rutet til «mistenkt»-køen, ikke eierens innboks.
Audit-loggen eieren faktisk leser
Vi forventet at audit-loggen skulle være en compliance-funksjon ingen åpnet. Eieren leser den ukentlig. Den forteller ham hva juniorstaben har endret, når, og (hvis han skal rulle tilbake) hvilken versjon han skal gå tilbake til. Den fanget en pris-feil i løpet av timer, i stedet for å vente på en kundeklage.
Audit-loggen er én Postgres-trigger per redigerbar tabell — rundt 25 linjer SQL — som fanger gammel rad, ny rad, brukeren og et tidsstempel. Billig, robust, overraskende lasttbærende.
Det dette ikke krevde
Ingen tredjepartsplugins utover Supabase. Ingen nattlige cron-jobber for å patche CMS-et. Ingen betalt sikkerhetstjeneste. Ingen byrå i loopen for innholdsredigeringer. Siden har kjørt i seks måneder uten ett eneste CMS-side-nedetid.
Eieren har admin-tilgang. Kodebasen ligger i hans GitHub. Supabase-prosjektet er på hans konto. Vercel-deploymentet er på hans konto. Hvis vi forsvant i morgen, kunne han fortsette å redigere siden sin for alltid, og en kompetent frilanser kunne plukke opp kodebasen på en ettermiddag.
Det er den delen de fleste byråer ikke vil levere — men det er det som gjør at bygget er verdt å beholde det neste tiåret.
Se det live bygget i PrintPoint case-studien, eller utforsk tjenestesiden for admin-paneler for skjelettet av samme tilnærming brukt på andre bedrifter.