Skip to content

Commit 1a36353

Browse files
committed
Merge PR #257: compliance
2 parents fcf718f + 0208619 commit 1a36353

18 files changed

Lines changed: 5258 additions & 3 deletions

File tree

backend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@types/cookie-parser": "^1.4.10",
2727
"@types/multer": "^2.1.0",
2828
"@types/uuid": "^10.0.0",
29+
"archiver": "^7.0.1",
2930
"bcryptjs": "^2.4.3",
3031
"cookie-parser": "^1.4.7",
3132
"csv-parse": "^6.2.1",
@@ -48,6 +49,8 @@
4849
"zod": "^3.23.8"
4950
},
5051
"devDependencies": {
52+
"@stellar/stellar-sdk": "^14.5.0",
53+
"@types/archiver": "^7.0.0",
5154
"@types/bcryptjs": "^2.4.6",
5255
"@types/express": "^5.0.6",
5356
"@types/express-rate-limit": "^5.1.3",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
-- 018_gdpr_compliance.sql
2+
-- GDPR compliance: account_deletions table + audit_logs FK change
3+
4+
-- 1. Create account_deletions table
5+
CREATE TABLE IF NOT EXISTS account_deletions (
6+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
7+
user_id UUID NOT NULL UNIQUE REFERENCES auth.users(id),
8+
requested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
9+
scheduled_deletion_at TIMESTAMPTZ NOT NULL,
10+
cancelled_at TIMESTAMPTZ,
11+
completed_at TIMESTAMPTZ,
12+
reason TEXT,
13+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'cancelled', 'completed')),
14+
CONSTRAINT valid_scheduled_date CHECK (scheduled_deletion_at > requested_at)
15+
);
16+
17+
CREATE INDEX idx_account_deletions_status ON account_deletions(status);
18+
CREATE INDEX idx_account_deletions_scheduled ON account_deletions(scheduled_deletion_at) WHERE status = 'pending';
19+
20+
ALTER TABLE account_deletions ENABLE ROW LEVEL SECURITY;
21+
22+
CREATE POLICY "Users can view own deletion status"
23+
ON account_deletions FOR SELECT
24+
USING (auth.uid() = user_id);
25+
26+
CREATE POLICY "Users can request own deletion"
27+
ON account_deletions FOR INSERT
28+
WITH CHECK (auth.uid() = user_id);
29+
30+
CREATE POLICY "Users can cancel own deletion"
31+
ON account_deletions FOR UPDATE
32+
USING (auth.uid() = user_id);
33+
34+
-- 2. Make audit_logs.user_id nullable and change FK to SET NULL
35+
-- This ensures audit logs survive user deletion (anonymized, not deleted)
36+
ALTER TABLE audit_logs ALTER COLUMN user_id DROP NOT NULL;
37+
38+
ALTER TABLE audit_logs DROP CONSTRAINT IF EXISTS audit_logs_user_id_fkey;
39+
ALTER TABLE audit_logs
40+
ADD CONSTRAINT audit_logs_user_id_fkey
41+
FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE SET NULL;
42+
43+
-- 3. Index for efficient data export queries
44+
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);

backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import merchantRoutes from './routes/merchants';
2020
import teamRoutes from './routes/team';
2121
import auditRoutes from './routes/audit';
2222
import webhookRoutes from './routes/webhooks';
23+
import complianceRoutes from './routes/compliance';
2324
import tagsRoutes from './routes/tags';
2425
import { createExchangeRatesRouter } from './routes/exchange-rates';
2526
import { ExchangeRateService } from './services/exchange-rate/exchange-rate-service';
@@ -84,6 +85,7 @@ app.use('/api/audit', auditRoutes);
8485
app.use('/api/integrations/gmail', authenticate, gmailRouter)
8586
app.use('/api/integrations/outlook', authenticate, outlookRouter)
8687
app.use('/api/webhooks', webhookRoutes);
88+
app.use('/api/compliance', complianceRoutes);
8789
app.use('/api/tags', tagsRoutes);
8890
app.use('/api', tagsRoutes); // handles /api/subscriptions/:id/notes and /api/subscriptions/:id/tags
8991
app.use('/api/exchange-rates', createExchangeRatesRouter(exchangeRateService));

0 commit comments

Comments
 (0)