GDPR za softverske programere | Sto zaista trebate implementirati
Vodic o GDPR uskladenosti usmjeren na programere. Pokriva tehnicke zahtjeve, obrasce rukovanja podacima i odluke na razini koda koje trebate donijeti.
GDPR je na snazi od 2018., ali vecina vodicava za programere i dalje se fokusira na pravnu teoriju. Ovaj vodic je drugaciji. Pokriva tehnicke zahtjeve, kod koji trebate napisati i arhitektonske odluke koje cine uskladenost prakticnom umjesto bolnom.
Ako vas softver pohranjuje, obraduje ili dotice osobne podatke osoba u EU, ovo se odnosi na vas. Nije vazno gdje je vasa tvrtka smjestena.
GDPR pregled za programere
Preskocite 99 clanaka. Evo sto GDPR znaci za vasu bazu koda:
- Prikupljajte samo ono sto trebate. Ne pohranjujte podatke “za svaki slucaj.”
- Recite korisnicima sto radite s njihovim podacima. I dobijte njihov pristanak kada je potrebno.
- Dopustite korisnicima pristup, izvoz i brisanje njihovih podataka. Trebate API endpointe za ovo.
- Cuvajte podatke sigurnima. Enkripcija, kontrole pristupa, revizijski zapisi.
- Prijavite povrede brzo. Imate 72 sata za obavjestavanje vlasti nakon otkrivanja povrede.
- Dokumentirajte sve. Vase aktivnosti obrade, vase sigurnosne mjere, vase tokove podataka.
To je praktican sazetak. Ostatak ovog vodica pokazuje kako implementirati svaki zahtjev.
7 kljucnih tehnickih zahtjeva
1. Upravljanje pristankom
Pristanak mora biti slobodno dan, specifican, informiran i nedvosmislen. Unaprijed oznaceni okviri ne vrijede. Skupni pristanak (“pristanite na sve”) ne vrijedi. Povlacenje mora biti jednako lako kao i davanje pristanka.
Sto izgraditi
Sustav pristanka treba tri komponente:
- Pohrana zapisa pristanka. Za svakog korisnika, pratite na sto su pristali, kada i kako.
- Mehanizam provjere pristanka. Prije obrade podataka za odredenu svrhu, provjerite ima li korisnik aktivan pristanak.
- Mehanizam povlacenja. Dopustite korisnicima opozivanje pristanka putem vaseg sucelja i odmah prestanite s obradom.
Shema baze podataka
CREATE TABLE user_consents (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
consent_type VARCHAR(100) NOT NULL,
granted BOOLEAN NOT NULL,
granted_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_user_consents_lookup
ON user_consents (user_id, consent_type, granted);
Pohranite potpunu povijest. Nikada ne brisite ili prepisujte zapise pristanka. Kada korisnik povuce pristanak, umetnite novi redak s granted = false i postavite revoked_at. To vam daje revizijski trag.
Middleware za provjeru pristanka
Evo Express middlewarea koji provjerava pristanak prije obrade zahtjeva:
import { Request, Response, NextFunction } from "express";
import { db } from "./database";
interface ConsentRequirement {
type: string;
required: boolean;
}
function requireConsent(consentType: string) {
return async (req: Request, res: Response, next: NextFunction) => {
const userId = req.user?.id;
if (!userId) {
return res.status(401).json({ error: "Authentication required" });
}
const consent = await db.query(
`SELECT granted FROM user_consents
WHERE user_id = $1 AND consent_type = $2
ORDER BY created_at DESC
LIMIT 1`,
[userId, consentType]
);
if (!consent.rows[0]?.granted) {
return res.status(403).json({
error: "Consent required",
consentType,
message: `You must grant "${consentType}" consent to use this feature.`,
consentUrl: `/settings/privacy`,
});
}
next();
};
}
// Usage
app.post(
"/api/newsletter/subscribe",
requireConsent("marketing_emails"),
subscribeHandler
);
app.post(
"/api/analytics/track",
requireConsent("usage_analytics"),
trackHandler
);
2. Pristup podacima i izvoz (Pravo pristupa)
Korisnici imaju pravo zatraziti kopiju svih osobnih podataka koje o njima drzite. Morate ih pruziti u uobicajeno koristenom, strojno citljivom formatu. JSON ili CSV funkcionira sasvim dobro.
Sto izgraditi
Endpoint koji prikuplja sve osobne podatke za korisnika iz svake tablice i servisa, a zatim ih pakira u datoteku za preuzimanje.
interface DataExport {
exportedAt: string;
user: {
profile: Record<string, unknown>;
activity: Record<string, unknown>[];
consents: Record<string, unknown>[];
communications: Record<string, unknown>[];
};
}
app.get("/api/me/data-export", authenticate, async (req, res) => {
const userId = req.user.id;
const [profile, activity, consents, communications] = await Promise.all([
db.query("SELECT id, email, name, created_at FROM users WHERE id = $1", [
userId,
]),
db.query(
"SELECT action, metadata, created_at FROM user_activity WHERE user_id = $1 ORDER BY created_at DESC",
[userId]
),
db.query(
"SELECT consent_type, granted, granted_at, revoked_at FROM user_consents WHERE user_id = $1 ORDER BY created_at DESC",
[userId]
),
db.query(
"SELECT type, sent_at, subject FROM communications WHERE user_id = $1 ORDER BY sent_at DESC",
[userId]
),
]);
const exportData: DataExport = {
exportedAt: new Date().toISOString(),
user: {
profile: profile.rows[0],
activity: activity.rows,
consents: consents.rows,
communications: communications.rows,
},
};
res.setHeader("Content-Type", "application/json");
res.setHeader(
"Content-Disposition",
`attachment; filename="data-export-${userId}.json"`
);
res.json(exportData);
});
Ovaj endpoint mora pokriti svaku tablicu koja sadrzi korisnicke podatke. Temeljito revidirajte svoju shemu. Tablica koja nedostaje znaci nepotpun izvoz, sto je propust uskladenosti.
3. Pravo na brisanje (Pravo na zaborav)
Korisnici mogu zatraziti brisanje svih svojih osobnih podataka. Morate udovoljiti osim ako nemate zakonsku obvezu zadrzavanja (poput poreznih zapisa ili sprecavanja prijevara).
Sto izgraditi
Endpoint za brisanje koji uklanja ili anonimizira korisnicke podatke kroz sve tablice. Ovo je teze nego sto zvuci zbog ogranicenja stranih kljuceva i podataka o kojima ovise drugi sustavi.
app.delete("/api/me/account", authenticate, async (req, res) => {
const userId = req.user.id;
const client = await db.getClient();
try {
await client.query("BEGIN");
// Anonymize data that must be retained for business records
await client.query(
`UPDATE orders
SET customer_name = 'deleted', customer_email = 'deleted'
WHERE user_id = $1`,
[userId]
);
// Delete data that can be fully removed
await client.query("DELETE FROM user_activity WHERE user_id = $1", [
userId,
]);
await client.query("DELETE FROM user_consents WHERE user_id = $1", [
userId,
]);
await client.query("DELETE FROM communications WHERE user_id = $1", [
userId,
]);
await client.query("DELETE FROM sessions WHERE user_id = $1", [userId]);
// Anonymize the user record instead of deleting
// This preserves referential integrity
await client.query(
`UPDATE users SET
email = 'deleted-' || id || '@removed.invalid',
name = 'Deleted User',
phone = NULL,
address = NULL,
deleted_at = NOW()
WHERE id = $1`,
[userId]
);
await client.query("COMMIT");
// Trigger deletion in external systems
await Promise.allSettled([
emailService.deleteSubscriber(userId),
analyticsService.deleteUser(userId),
searchIndex.removeUser(userId),
]);
res.json({ message: "Account and personal data deleted" });
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
});
Kljucne odluke:
- Brisanje naspram anonimizacije. Zapisi potrebni za racunovodstvo (narudzbe, fakture) trebaju biti anonimizirani. Sve ostalo obrisite.
- Vanjski sustavi. Podaci poslani uslugama trecih strana moraju biti obrisani i tamo.
- Vrijeme. GDPR kaze “bez nepotrebnog odgadanja.” Zavrsiste brisanje unutar 30 dana. Za vecinu sustava, ucinite to odmah.
4. Minimizacija podataka
Prikupljajte i pohranjujte samo podatke koji su vam stvarno potrebni za navedenu svrhu. Ako trazite broj telefona, a nikad ne zovete korisnike, ne biste ga trebali prikupljati.
Prakticna pravila
- Revidirajte svako polje obrasca. Za svako polje, pitajte: “Koja specificna funkcionalnost prestaje raditi ako ovo uklonimo?” Ako je odgovor nista, uklonite ga.
- Postavite razdoblja zadrzavanja. Ne cuvajte podatke zauvijek. Definirajte koliko dugo je svaka vrsta podataka potrebna, a zatim ih automatski obrisite.
- Minimizirajte zapisivanje u logove. Uklonite osobne podatke iz zapisa u logovima. Zapisujte korisnicke ID-jeve, ne imena ili emailove.
-- Automatic data retention with PostgreSQL
-- Run this as a scheduled job (e.g., pg_cron)
DELETE FROM user_activity
WHERE created_at < NOW() - INTERVAL '2 years';
DELETE FROM session_logs
WHERE created_at < NOW() - INTERVAL '90 days';
DELETE FROM password_reset_tokens
WHERE created_at < NOW() - INTERVAL '24 hours';
5. Enkripcija
GDPR zahtijeva “odgovarajuce tehnicke mjere” za zastitu osobnih podataka. Enkripcija je najvaznija.
U mirovanju
- Enkriptirajte disk baze podataka. Svi glavni cloud provideri ovo podrzavaju. Omogucite i provjerite.
- Za visoko osjetljiva polja (OIB-ove, zdravstvene podatke), dodajte enkripciju na razini aplikacije povrh toga.
- Enkriptirajte sigurnosne kopije. Neenkriptirana sigurnosna kopija je povreda koja ceka da se dogodi.
U prijenosu
- TLS svugdje. Svaka veza izmedu servisa, baza podataka i korisnika. Bez iznimaka.
- Nametnite HTTPS. Preusmjerite HTTP. Postavite HSTS zaglavlja.
- Koristite TLS za veze s bazom podataka. PostgreSQL ovo nativno podrzava.
// PostgreSQL connection with TLS
import { Pool } from "pg";
const pool = new Pool({
host: process.env.DB_HOST,
port: 5432,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: {
rejectUnauthorized: true,
ca: fs.readFileSync("/path/to/server-ca.pem").toString(),
},
});
6. Obavjest o povredi
Ako su osobni podaci kompromitirani, morate obavijestiti nadlezno tijelo za zastitu podataka unutar 72 sata. Ako povreda predstavlja visok rizik za pojedince, morate obavijestiti i pogodene korisnike.
Sto izgraditi
- Revizijsko zapisivanje. Pratite svaki pristup osobnim podacima. Tko je pristupio, kada i odakle.
- Detekcija anomalija. Uzbunjujte na neobicne obrasce pristupa (masovni izvoz podataka, pristup s novih IP adresa, pristup izvan radnog vremena).
- Plan odgovora na incidente. Dokumentirajte tko sto radi kada se otkrije povreda. Ovo nije kod. To je kontrolni popis koji vas tim vjezba.
CREATE TABLE data_access_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
accessed_by UUID NOT NULL,
access_type VARCHAR(50) NOT NULL,
resource_type VARCHAR(100) NOT NULL,
resource_id UUID,
ip_address INET,
user_agent TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_data_access_log_user
ON data_access_log (user_id, created_at);
CREATE INDEX idx_data_access_log_accessor
ON data_access_log (accessed_by, created_at);
7. Privatnost po dizajnu
GDPR kaze da privatnost treba biti ugradujeni u sustave od pocetka, ne nadodana naknadno. U praksi, to znaci privatnost kao zadanu opciju.
- Zadano privatno. Nove funkcionalnosti trebaju prikupljati minimalne podatke i zahtijevati pristanak za sve izvan temeljne funkcije.
- Postavke zadano na najprivatniju opciju. Korisnici koji nikad ne diraju svoje postavke trebaju imati najvisuu zastitu privatnosti.
- Odvojite brige. Ne mijesajte analiticke podatke s funkcionalnim podacima. Ne koristite ponovo autentifikacijske tokene za pracenje.
Anonimizacija naspram pseudonimizacije
Ovo nije ista stvar, a razlika je vazna.
- Pseudonimizacija zamjenjuje identificirajuce informacije reverzibilnim tokenom. Primjer: hashiranje email adresa. Ako imate hash funkciju i originalni email, mozete ponovno identificirati osobu. GDPR se i dalje primjenjuje jer je ponovna identifikacija moguca.
- Anonimizacija trajno uklanja identificirajuce informacije. Primjer: agregatna analitika (“1.247 korisnika posjetilo je stranicu s cijenama”) bez mogucnosti identifikacije kojih korisnika. GDPR se ne primjenjuje na istinski anonimizirane podatke.
Prava anonimizacija je teska. Ako vas “anonimni” skup podataka ukljucuje vremensku oznaku, grad i tip uredaja, ta kombinacija moze jedinstveno identificirati nekoga. Budite konzervativni.
Pristanak za kolacice
Ako vasa web stranica koristi kolacice izvan onog sto je strogo potrebno, trebate pristanak prije njihovog postavljanja.
Zahtijeva pristanak: analiticiki kolacici, oglasivacki pikseli, widgeti drustvenih mreza, bilo koja skripta za pracenje trecih strana.
Ne zahtijeva pristanak: kolacici sesije, kolacici kosharice, CSRF tokeni, sam kolacic preferencija pristanka.
Vas banner pristanka trebao bi blokirati nebitne kolacice dok se ne da pristanak, ponuditi granularne izbore i uciniti “odbij sve” jednako lakim kao “prihvati sve.” Ne gradite ovo od nule. Alati poput Cookiebota rjesavaju slozenost. Kljucno pravilo: nijedna skripta za pracenje ne smije se pokrenuti prije davanja pristanka.
Obradivaci podataka trecih strana
Svaki servis trecih strana koji rukuje podacima vasih korisnika je “obradivac podataka” prema GDPR-u. Vi ste odgovorni za njihovu uskladenost.
Sto provjeriti
Prije integracije bilo kojeg servisa trecih strana koji dotice osobne podatke:
- Imaju li DPA? Ugovor o obradi podataka je obvezan. Vecina SaaS pruzatelja usluga objavljuje svoje javno.
- Gdje pohranjuju podatke? Ako izvan EU, provjerite pravnu osnovu za prijenos.
- Kojim podacima pristupaju? Minimizirajte sto saljete. Ako servis treba samo email, ne saljite cijeli profil.
- Mozete li obrisati podatke iz njihovih sustava? Zahtjevi za brisanje korisnika moraju se propagirati svugdje.
- Kako rjesavaju povrede? Njihov DPA trebao bi specificirati rokove obavjestivanja.
Uobicajeni obradivaci trecih strana za pregled
- Usluge emaila (Resend, SendGrid, Mailchimp)
- Analitika (Google Analytics, Mixpanel, Amplitude)
- Pracenje pogresaka (Sentry, Bugsnag)
- Obrada placanja (Stripe, Adyen)
- Cloud hosting (AWS, Google Cloud, Vercel)
- Alati za korisnicku podrsku (Intercom, Zendesk)
- AI API-ji (OpenAI, Anthropic, Google AI)
Odrzavajte popis svih obradivaca. Pregledavajte ga kvartalno.
Politike zadrzavanja podataka
Ne cuvajte osobne podatke duze nego sto je potrebno. Definirajte razdoblja zadrzavanja za svaku vrstu podataka.
| Vrsta podataka | Preporuceno zadrzavanje | Razlog |
|---|---|---|
| Podaci korisnickog racuna | Do zahtjeva za brisanje | Potrebno za uslugu |
| Zapisi sesija | 90 dana | Sigurnost i otklanjanje gresaka |
| Zapisi korisnicke aktivnosti | 1-2 godine | Analitika proizvoda |
| Tiketi podrske | 3 godine | Kvaliteta usluge |
| Financijski zapisi | 7 godina | Porezne/pravne obveze |
| Tokeni za resetiranje lozinke | 24 sata | Sigurnost |
| Neuspjeli pokusaji prijave | 90 dana | Sigurnosni nadzor |
Implementirajte automatizirane poslove ciscenja. Ne oslanjajte se na to da ce se netko sjetiti pokrenuti skriptu.
GDPR kontrolni popis za programere
Koristite ovo kao polaznu tocku pri izgradnji ili reviziji sustava.
Prikupljanje podataka
- Svako polje obrasca ima navedenu svrhu
- Ne prikupljaju se nepotrebni podaci
- Politika privatnosti je povezana sa svake tocke prikupljanja podataka
- Pristanak se prikuplja prije obrade (gdje je potrebno)
- Zapisi pristanka se pohranjuju s vremenskim oznakama
Pohrana podataka
- Enkripcija baze podataka u mirovanju je omogucena
- TLS je nametnut za sve veze
- Osjetljiva polja imaju enkripciju na razini aplikacije
- Sigurnosne kopije su enkriptirane
- Pristup produkcijskim podacima je ogranicen i zapisivano
Prava korisnika
- Endpoint za izvoz podataka postoji i pokriva sve tablice
- Endpoint za brisanje racuna postoji i rjesava sve podatke
- Korisnici mogu pregledati i povuci pristanak u svojim postavkama
- Brisanje se propagira na servise trecih strana
- Na sve zahtjeve za prava korisnika se odgovara unutar 30 dana
Kolacici i pracenje
- Banner pristanka za kolacice je implementiran
- Nebitni kolacici su blokirani prije pristanka
- Izbori pristanka su granularni (ne sve ili nista)
- “Odbij sve” je jednako istaknut kao “Prihvati sve”
Trece strane
- Svi obradivaci podataka su dokumentirani
- DPA-ovi su potpisani sa svakim obradivac em
- Podaci poslani trecim stranama su minimizirani
- Brisanje podataka iz trecih strana je moguce
Sigurnost
- Revizijski zapisi prate pristup osobnim podacima
- Uzbunjivanje na anomalije je konfigurirano
- Plan odgovora na incidente je dokumentiran
- Proces obavjestivanja o povredi je definiran (rok od 72 sata)
Zadrzavanje
- Razdoblja zadrzavanja su definirana za sve vrste podataka
- Automatizirani poslovi ciscenja su rasporedjeni
- Istekli podaci se zaista brisu (provjerite ovo)
Zavrsne misli
GDPR uskladenost nije jednokratni projekt. To je skup praksi utkanih u nacin na koji gradite softver. Tehnicki posao je jednostavan: pohrana pristanka, izvoz podataka, endpointi za brisanje, enkripcija, revizijsko zapisivanje. Standardni inzenjering.
Tesk dio je temeljitost. Lako je zaboraviti na onu log datoteku, onaj analiticiki dogadaj ili onu integraciju trece strane koja pohranjuje korisnicke emailove. Redovito revidirajte. Testirajte svoj endpoint za brisanje. Provjerite jesu li vasi izvozi potpuni. Ugradite privatnost u svoj proces od pocetka.
Trebate pomoc s izgradnjom softvera uskladjenog s GDPR-om ili revizijom vasiih postojecih sustava? Javite nam se. Gradimo aplikacije s privatnoscu na prvom mjestu za europske tvrtke i kompanije koje sluzze korisnike iz EU.