GDPR — export, radering och samtycken
På Zkrinet: lägg till blocket under Byggblock i panelen — ett klick, din egen kod rörs inte och en ögonblicksbild tas först. Installationsstegen nedan gäller om du kör PocketBase själv, till exempel efter en export.
För appar med användare i EU. Den inloggade användaren kan själv:
- exportera sin data (rätten till dataportabilitet / tillgång) som en JSON-fil,
- radera sitt konto (rätten att bli raderad), med lösenordsbekräftelse och valfri ångerfrist,
- ge och återkalla samtycken — loggade i en lista som inte går att skriva om i efterhand.
Användarna och inloggningen är PocketBases egna (samlingen users). Pluginet lägger till rutterna och loggen ovanpå.
Testat mot PocketBase 0.40.4.
#Installation
Kopiera pb_hooks/ och pb_migrations/ till din PocketBase och starta om. Tre samlingar dyker upp: zk_gdpr_installningar, zk_gdpr_samtycken och zk_gdpr_raderingar.
Obs, en ändring av standardbeteendet: PocketBase låter som standard en användare radera sitt eget konto direkt (DELETE /api/collections/users/records/{id}, regeln id = @request.auth.id) — utan att bekräfta något. Med pluginet stängs den dörren, så att radering alltid går via lösenordsbekräftelsen nedan. Superusers kan alltid radera. Vill du ha kvar den gamla dörren: slå på tillat_direktradering i inställningarna.
#Användning från appen
import PocketBase from "pocketbase"
const pb = new PocketBase("https://din-app.se")
// 1. Exportera min data (spara som fil)
const data = await pb.send("/api/zk/gdpr/export", { method: "GET" })
const fil = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" })
// 2. Radera mitt konto — kräver lösenordet
try {
const svar = await pb.send("/api/zk/gdpr/radera", { method: "POST", body: { losenord } })
if (svar.raderad) pb.authStore.clear() // borta direkt
else visa(`Kontot raderas ${svar.verkstalls}`) // karenstid: kan ångras
} catch (err) {
// 400 = lösenord saknas · 403 = fel lösenord · 429 = för många försök · 409 = se err.data.hinder
}
// 2b. Ångra under karenstiden
await pb.send("/api/zk/gdpr/angra", { method: "POST" })
// 3. Samtycken
await pb.send("/api/zk/gdpr/samtycke", { method: "POST", body: { typ: "nyhetsbrev", version: "2026-11", givet: true } })
await pb.send("/api/zk/gdpr/samtycke", { method: "POST", body: { typ: "nyhetsbrev", givet: false } }) // återkalla
const lage = await pb.send("/api/zk/gdpr/samtycke", { method: "GET" })
// → { nyhetsbrev: { givet: false, version: "", tid: "..." } }
// Hela historiken (bara mina egna rader syns)
await pb.collection("zk_gdpr_samtycken").getFullList({ sort: "tid" })
Alla rutter kräver en inloggad användare i users. Gäster får 401; en superuser-token räknas inte som användare.
#Exporten
{
"exporterad": "2026-11-07T19:00:00.000Z",
"anvandare": { "id": "...", "email": "...", "name": "...", ... },
"samlingar": {
"anteckningar": { "falt": ["agare"], "poster": [ ... ], "avkortad": false },
"zk_gdpr_samtycken": { ... }
},
"inloggning": { "_externalAuths": [ ... ], "_authOrigins": [ ... ] }
}
- Användarens egen post — med e-posten, men utan dolda fält: lösenordshash,
tokenKeyoch alla fält du markerat som dolda finns aldrig med. - Alla poster som pekar på användaren via ett relationsfält till
users. Pluginet hittar fälten själv ur samlingarnas schema, även fleravalsfält (t.ex.medlemmar). Dolda fält tas bort även här. Relationer visas som id:n och filfält som filnamn (själva filerna följer inte med). - PocketBases egna inloggningskopplingar: kopplade konton (Google m.fl.,
_externalAuths) och kända enheter (_authOrigins).
Vad som INTE är med, och varför:
- Andra auth-samlingar, även andra användare i
userssom pekar på dig (t.ex. ett fältinbjuden_av): det är andra personers konton, inte din data. - Systemsamlingar (
_superusers,_otps,_mfas…) och vyer (deras innehåll kommer från andra samlingar). zk_gdpr_installningar(appens inställningar, inte användardata).
zk_-samlingar från andra plugins tas med när de pekar på användaren — t.ex. zk_ai_logg (frågor till AI:n är användarens data), zk_stripe_betalningar och zk_gdpr_samtycken. Regeln är alltså: har posten ett relationsfält till användaren är den användardata, oavsett vem som skapat samlingen.
#Inställningar
En rad i zk_gdpr_installningar (bara superusers ser och ändrar den; det kan bara finnas en). Den skapas vid installationen med 7 dagars ångerfrist:
| Fält | Standard | |
|---|---|---|
endast | [] = alla | Lista med samlingsnamn: bara dessa tas med i exporten |
undanta | [] | Lista med samlingsnamn som aldrig tas med |
karens_dagar | 7 (standardraden) | Ångerfrist i dagar (max 365). 0 = radera direkt. Finns ingen rad alls raderas det direkt. |
tillat_direktradering | av | På = PocketBase vanliga självradering (utan lösenord) är öppen igen |
#Raderingen
Bekräftelse: lösenordet skickas med ({ "losenord": "..." }) och kontrolleras mot PocketBases lösenordshash. Valet lösenord (inte "nyss inloggad") är medvetet: en stulen eller kvarglömd token räcker inte för att radera kontot. Efter 5 fel inom 15 minuter spärras kontots raderingsförsök i 15 minuter (429), även med rätt lösenord.
Vad som händer med andra poster — PocketBases egna regler för relationsfältet, testat:
| Relationsfältet | Händer |
|---|---|
cascadeDelete på | Posten raderas med kontot (t.ex. samtycken, AI-loggen). |
| Utan cascade, ej obligatoriskt | Posten blir kvar, kopplingen tas bort (fältet blir tomt; i fleravalsfält försvinner bara id:t). Valt för att t.ex. ordrar och betalningar ofta måste sparas enligt bokföringslagen. |
| Utan cascade, obligatoriskt | Raderingen stoppas (409) med en lista: hinder: [{ samling, falt, poster }]. Pluginet gissar inte — appens ägare får välja cascade, valfritt fält eller att ta bort posterna. |
Vi anonymiserar inte innehållet i poster som blir kvar. Står användarens namn eller e-post i ett vanligt textfält i en sådan post så står det kvar — det är appens sak att ta hand om (t.ex. med en onRecordDelete-hook för users).
Karenstid: med karens_dagar > 0 blir raderingen en begäran (202, verkstalls = datum). Användaren kan logga in och ångra (/api/zk/gdpr/angra) fram till dess. Raderingarna körs varje timme (minut 7), eller direkt med POST /api/zk/gdpr/verkstall (superuser). Har ett hinder uppstått under tiden står det i svaret och i serverloggen, och begäran ligger kvar. Användaren ser sin egen begäran i zk_gdpr_raderingar.
#Samtyckesloggen
zk_gdpr_samtycken: anvandare, typ (t.ex. nyhetsbrev, villkor; små bokstäver, siffror, _ . -), version (t.ex. villkorens version), givet (true/false), tid.
- Append-only: varje ge/återkalla blir en ny rad. Ingen kan skapa, ändra eller radera rader via samlings-API:t — inte heller superusers (adminpanelen ger 403). Rader skapas bara via rutten, alltid åt den inloggade (ett
anvandarei kroppen ignoreras). - Användaren läser bara sina egna rader.
- Gällande läge = senaste raden per typ (
GET /api/zk/gdpr/samtycke). - Raderas kontot raderas samtyckena med det (cascade). Behöver du kunna visa ett samtycke efter att kontot är borta — exportera innan, eller ta bort cascade och gör fältet valfritt (då blir raderna kvar utan koppling).
#Begränsningar — läs innan du lovar en kund något
- Bara relationsfält hittas. Data som pekar på användaren med e-post eller id i ett textfält (t.ex.
zk_granskning.vem_id,zk_mejllogg,zk_inbjudningar.epost) hittas inte och raderas inte. Gör om sådana fält till relationer, eller hantera dem själv. - Filerna följer inte med exporten, bara filnamnen.
- Max 10 000 poster per samling i exporten (
avkortad: trueom fler). Exporten byggs i minnet. - Konton utan känt lösenord (bara Google-inloggning eller engångskod) kan inte bekräfta. De behöver först sätta ett lösenord via PocketBases "glömt lösenord" (
requestPasswordReset). - Spärren mot lösenordsgissning ligger i minnet — den nollställs när servern startas om, och gäller per server (inte delad mellan flera instanser).
- Säkerhetskopior innehåller fortfarande den raderade användaren tills de roteras. Det är din driftsrutin, inte pluginets.
- Manipuleringssäkert via API:t, inte mot databasfilen. Den som har tillgång till
data.dbkan ändra samtyckesloggen. - Exporten visar data — den är inte en juridisk bedömning. Vilka samlingar som är personuppgifter, och hur länge de får sparas, avgör den som driver appen.
#Testa
./bank.py gdpr
14 tester: exporten innehåller rätt och inget annat (andra användares poster, konton och e-post aldrig; dolda fält, lösenordshash och tokenKey aldrig; fleravalsrelationer), inställningarna (endast/undanta, en rad, bara superuser), gäster och superusers nekas, radering kräver lösenord, gissningsspärr, PocketBase egen självradering stängd, obligatorisk relation stoppar (och fleraval med fler kvar gör det inte), cascade/kvarlämnade poster, karenstid med ångra och verkställ (och hinder som uppstår under karenstiden), samtycken append-only (även för superuser), användaren ser bara sina egna, och validering. Säkerhetsskydden är mutantverifierade (18 mutanter, alla fångade).