Technische werking
Architectuur
See It Once bestaat uit drie Docker-containers die via een intern netwerk communiceren:
een Nginx-frontend die de interface serveert, een
Node.js/Express-backend die de REST API aanbiedt, en
Redis als in-memory datastore met ingebouwde TTL-ondersteuning.
Alle API-verzoeken van de browser lopen via de Nginx-proxy naar de backend —
de browser communiceert nooit rechtstreeks met Node.js.
Twee-staps verificatieflow
Het aanmaken van een secret verloopt via een bevestigde twee-staps flow:
Stap 1 — Aanmaken
De verzender vult naam, e-mailadres, naam en e-mailadres van de ontvanger,
de inhoud en een verplichte toegangscode in. De browser versleutelt het secret
lokaal met PBKDF2 + AES-256-GCM op basis van de toegangscode,
vóórdat er iets naar de server wordt gestuurd. De backend slaat het versleutelde
secret tijdelijk op als pending in Redis (TTL: 1 uur) en stuurt een
bevestigingsmail naar de verzender.
Stap 2 — Bevestiging
De verzender klikt op de bevestigingsknop in de e-mail. De backend verifieert
het eenmalige token, activeert het secret definitief in Redis en stuurt een
notificatiemail naar de ontvanger met uitsluitend het
id — zonder sleutel of toegangscode.
Stap 3 — Ophalen
De ontvanger klikt op de link in de notificatiemail, voert de toegangscode in
die de verzender separaat heeft gecommuniceerd (telefoon, chat) en de
browser ontsleutelt het secret volledig lokaal. Daarna wordt het secret permanent
van de server verwijderd.
Verplichte toegangscode (versleutelingslaag)
Een toegangscode is altijd verplicht. De browser versleutelt het secret vóór
verzending via PBKDF2 (200.000 iteraties, SHA-256) gevolgd door
AES-256-GCM. De toegangscode verlaat de browser nooit
en wordt nergens opgeslagen — ook niet door de server. De ontvanger kan het secret
alleen ontsleutelen met de toegangscode die de verzender separaat communiceert.
Zelfs de server-operator kan de inhoud niet inzien.
Verzendersverificatie via e-mail
Voordat de ontvanger een notificatie krijgt, moet de verzender de actie bevestigen
via een eenmalige bevestigingslink die naar zijn eigen e-mailadres wordt gestuurd.
Dit voorkomt misbruik: een bot of onbekende bezoeker die het formulier invult kan
de flow niet voltooien zonder toegang tot het opgegeven e-mailaccount.
De bevestigingslink bevat een willekeurig token van 32 bytes en is
1 uur geldig en eenmalig bruikbaar.
Notificatiemail naar ontvanger
De notificatiemail aan de ontvanger bevat uitsluitend het id van het
secret — geen sleutel, geen toegangscode. Onderschepping van de
e-mail geeft een aanvaller niets bruikbaars: zonder de toegangscode (die via een
apart kanaal wordt gecommuniceerd) is het secret niet te ontsleutelen.
Foutieve toegangscodepogingen
Bij een verkeerde toegangscode meldt de browser dit via
POST /api/secret/{id}/fail. Na 3 mislukte pogingen
verwijdert de backend het secret definitief, ongeacht de resterende TTL.
Automatisch verlopen & datapersistentie
Niet-geopende secrets worden automatisch verwijderd door Redis na de ingestelde
vervaltijd (15 minuten tot 7 dagen). De backend luistert via een aparte
subscriber-verbinding op Redis keyspace-events; zodra een secret verloopt,
worden de statistieken direct bijgewerkt.
Redis is geconfigureerd met AOF-persistentie
(appendfsync everysec) en aanvullende RDB-snapshots. Bij een
herstart of update blijven alle nog-niet-verlopen secrets en statistieken
bewaard — maximaal 1 seconde dataverlies bij een onverwachte crash.
Invoervalidatie
Elk API-endpoint valideert inkomende data via Zod-schema's
vóórdat enige businesslogica wordt uitgevoerd. Ontbrekende velden, verkeerde
typen of waarden buiten de toegestane grenzen worden direct afgewezen met een
duidelijke foutmelding. URL-parameters zoals id en key
worden gefilterd op toegestane tekens ([a-f0-9]) voordat ze
Redis ingaan.
Beveiliging & rate limiting
De backend zet via Helmet automatisch HTTP-beveiligingsheaders
(X-Frame-Options, X-Content-Type-Options,
Referrer-Policy e.a.). Verzoeken worden op drie niveaus
beperkt via express-rate-limit:
· Globaal: max 100 req/min per IP op alle API-routes
· Secret aanmaken: max 20 req/min per IP
· Contactformulier: max 3 req/10 min per IP
De server retourneert bij onbekende routes altijd een JSON-404 en bij
onverwachte fouten een JSON-500 — stack traces worden nooit naar de
client gestuurd.
Wat de server nooit ziet
De server ontvangt nooit de decryptiesleutel, het extra wachtwoord
of de leesbare tekst van het secret. De enige manier om een secret te lezen is
via de exacte link met de juiste key — en die link is na één gebruik permanent
ongeldig.
Dataflow samengevat
1 · Aanmaken
Browser versleutelt secret lokaal (PBKDF2 + AES-256-GCM) →
Zod valideert invoer → backend slaat pending op in Redis (1 uur TTL) →
bevestigingsmail naar verzender
2 · Bevestigen
Verzender klikt bevestigingslink → token geverifieerd & verwijderd →
secret geactiveerd in Redis met gekozen TTL →
notificatiemail naar ontvanger (alleen id, geen sleutel)
3 · Ophalen
Ontvanger opent link (alleen id) → voert toegangscode in →
browser ontsleutelt lokaal → secret getoond →
backend verwijdert secret permanent uit Redis