@authrim/setup 0.1.134 → 0.1.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/cloudflare.d.ts.map +1 -1
- package/dist/core/cloudflare.js +15 -0
- package/dist/core/cloudflare.js.map +1 -1
- package/dist/web/api.d.ts.map +1 -1
- package/dist/web/api.js +55 -31
- package/dist/web/api.js.map +1 -1
- package/migrations/000_fresh_schema.sql +1966 -0
- package/migrations/admin/001_admin_users.sql +189 -0
- package/migrations/admin/002_admin_rbac.sql +256 -0
- package/migrations/admin/003_admin_audit.sql +175 -0
- package/migrations/admin/004_admin_security.sql +132 -0
- package/migrations/admin/005_admin_abac_rebac.sql +345 -0
- package/migrations/admin/006_admin_setup_tokens.sql +91 -0
- package/migrations/pii/001_pii_initial.sql +466 -0
- package/migrations/pii/002_pii_log_tables.sql +139 -0
- package/migrations/pii/003_tombstone_timestamps.sql +12 -0
- package/migrations/pii/004_cleanup_admin_from_pii.sql +50 -0
- package/package.json +2 -1
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Migration: PII Database Initial Schema (D1_PII)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- Created: 2025-12-17
|
|
5
|
+
-- Description: Initial schema for PII (Personal Identifiable Information) database.
|
|
6
|
+
-- Part of PII/Non-PII database separation architecture.
|
|
7
|
+
--
|
|
8
|
+
-- IMPORTANT: This migration is for D1_PII (separate from main D1_CORE).
|
|
9
|
+
-- Apply to the PII-specific D1 database.
|
|
10
|
+
--
|
|
11
|
+
-- Tables:
|
|
12
|
+
-- - users_pii: Personal information (email, name, address, etc.)
|
|
13
|
+
-- - subject_identifiers: Pairwise Subject Identifiers
|
|
14
|
+
-- - linked_identities: External IdP linking
|
|
15
|
+
-- - audit_log_pii: PII access audit trail
|
|
16
|
+
-- - users_pii_tombstone: GDPR deletion tracking
|
|
17
|
+
--
|
|
18
|
+
-- PII Sensitivity Classes:
|
|
19
|
+
-- - IDENTITY_CORE: email, phone (required for auth)
|
|
20
|
+
-- - PROFILE: name, picture (OIDC standard claims)
|
|
21
|
+
-- - DEMOGRAPHIC: gender, birthdate (GDPR Art.9 sensitive)
|
|
22
|
+
-- - LOCATION: address claims
|
|
23
|
+
-- - HIGH_RISK: gov-id, biometrics (future)
|
|
24
|
+
-- =============================================================================
|
|
25
|
+
|
|
26
|
+
-- =============================================================================
|
|
27
|
+
-- Migration Management (same structure as D1_CORE)
|
|
28
|
+
-- =============================================================================
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
31
|
+
version INTEGER PRIMARY KEY,
|
|
32
|
+
name TEXT NOT NULL,
|
|
33
|
+
applied_at INTEGER NOT NULL,
|
|
34
|
+
checksum TEXT NOT NULL,
|
|
35
|
+
execution_time_ms INTEGER,
|
|
36
|
+
rollback_sql TEXT
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
CREATE TABLE IF NOT EXISTS migration_metadata (
|
|
40
|
+
id TEXT PRIMARY KEY DEFAULT 'global',
|
|
41
|
+
current_version INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
last_migration_at INTEGER,
|
|
43
|
+
environment TEXT DEFAULT 'development',
|
|
44
|
+
metadata_json TEXT
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
INSERT OR IGNORE INTO migration_metadata (id, current_version, environment)
|
|
48
|
+
VALUES ('global', 0, 'development');
|
|
49
|
+
|
|
50
|
+
-- =============================================================================
|
|
51
|
+
-- users_pii Table (PII Data)
|
|
52
|
+
-- =============================================================================
|
|
53
|
+
-- Personal information stored in D1_PII database.
|
|
54
|
+
-- Contains all OIDC standard claims that constitute PII.
|
|
55
|
+
--
|
|
56
|
+
-- Design decisions:
|
|
57
|
+
-- - id: Same as users_core.id (logical FK, no SQL FK since separate DB)
|
|
58
|
+
-- - email_blind_index: For searching without storing plaintext in indexes
|
|
59
|
+
-- - pii_class: Sensitivity classification for access control
|
|
60
|
+
-- - declared_residence: User-declared country (trusted for partition routing)
|
|
61
|
+
-- =============================================================================
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS users_pii (
|
|
64
|
+
-- Primary key (same as users_core.id)
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
|
|
67
|
+
-- Multi-tenant support
|
|
68
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
69
|
+
|
|
70
|
+
-- PII sensitivity classification
|
|
71
|
+
-- IDENTITY_CORE | PROFILE | DEMOGRAPHIC | LOCATION | HIGH_RISK
|
|
72
|
+
pii_class TEXT NOT NULL DEFAULT 'PROFILE',
|
|
73
|
+
|
|
74
|
+
-- Email (IDENTITY_CORE)
|
|
75
|
+
email TEXT NOT NULL,
|
|
76
|
+
|
|
77
|
+
-- Blind index for email search (HMAC-SHA256 of normalized email)
|
|
78
|
+
-- Allows searching without exposing plaintext in query logs
|
|
79
|
+
email_blind_index TEXT,
|
|
80
|
+
|
|
81
|
+
-- Phone (IDENTITY_CORE)
|
|
82
|
+
phone_number TEXT,
|
|
83
|
+
|
|
84
|
+
-- Name claims (PROFILE)
|
|
85
|
+
name TEXT,
|
|
86
|
+
given_name TEXT,
|
|
87
|
+
family_name TEXT,
|
|
88
|
+
middle_name TEXT,
|
|
89
|
+
nickname TEXT,
|
|
90
|
+
preferred_username TEXT,
|
|
91
|
+
|
|
92
|
+
-- Profile URL (PROFILE)
|
|
93
|
+
profile TEXT,
|
|
94
|
+
picture TEXT,
|
|
95
|
+
website TEXT,
|
|
96
|
+
|
|
97
|
+
-- Demographic (DEMOGRAPHIC - GDPR Art.9 sensitive)
|
|
98
|
+
gender TEXT,
|
|
99
|
+
birthdate TEXT,
|
|
100
|
+
|
|
101
|
+
-- Locale (PROFILE)
|
|
102
|
+
locale TEXT,
|
|
103
|
+
zoneinfo TEXT,
|
|
104
|
+
|
|
105
|
+
-- Address claims (LOCATION)
|
|
106
|
+
address_formatted TEXT,
|
|
107
|
+
address_street_address TEXT,
|
|
108
|
+
address_locality TEXT,
|
|
109
|
+
address_region TEXT,
|
|
110
|
+
address_postal_code TEXT,
|
|
111
|
+
address_country TEXT,
|
|
112
|
+
|
|
113
|
+
-- User-declared residence (for partition routing, HIGH TRUST)
|
|
114
|
+
declared_residence TEXT,
|
|
115
|
+
|
|
116
|
+
-- Custom attributes (JSON)
|
|
117
|
+
custom_attributes_json TEXT,
|
|
118
|
+
|
|
119
|
+
-- Timestamps
|
|
120
|
+
created_at INTEGER NOT NULL,
|
|
121
|
+
updated_at INTEGER NOT NULL
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
-- =============================================================================
|
|
125
|
+
-- Indexes for users_pii
|
|
126
|
+
-- =============================================================================
|
|
127
|
+
|
|
128
|
+
-- Email lookup via blind index (unique per tenant)
|
|
129
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_pii_email
|
|
130
|
+
ON users_pii(tenant_id, email_blind_index);
|
|
131
|
+
|
|
132
|
+
-- Tenant lookup
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_users_pii_tenant
|
|
134
|
+
ON users_pii(tenant_id);
|
|
135
|
+
|
|
136
|
+
-- PII class filter (for access control)
|
|
137
|
+
CREATE INDEX IF NOT EXISTS idx_users_pii_class
|
|
138
|
+
ON users_pii(pii_class);
|
|
139
|
+
|
|
140
|
+
-- =============================================================================
|
|
141
|
+
-- subject_identifiers Table (Pairwise Subject Identifier)
|
|
142
|
+
-- =============================================================================
|
|
143
|
+
-- OIDC Pairwise Subject Identifier storage.
|
|
144
|
+
-- Generates different `sub` claim per client/sector.
|
|
145
|
+
--
|
|
146
|
+
-- Purpose:
|
|
147
|
+
-- - Privacy protection: Prevents client-side user correlation
|
|
148
|
+
-- - OIDC compliance: RFC 8693 pairwise identifier support
|
|
149
|
+
-- =============================================================================
|
|
150
|
+
|
|
151
|
+
CREATE TABLE IF NOT EXISTS subject_identifiers (
|
|
152
|
+
-- Primary key
|
|
153
|
+
id TEXT PRIMARY KEY,
|
|
154
|
+
|
|
155
|
+
-- User reference (logical FK to users_core.id)
|
|
156
|
+
user_id TEXT NOT NULL,
|
|
157
|
+
|
|
158
|
+
-- Client ID that requested this subject
|
|
159
|
+
client_id TEXT NOT NULL,
|
|
160
|
+
|
|
161
|
+
-- Sector identifier (domain for pairwise calculation)
|
|
162
|
+
sector_identifier TEXT NOT NULL,
|
|
163
|
+
|
|
164
|
+
-- The pairwise subject value
|
|
165
|
+
subject TEXT NOT NULL,
|
|
166
|
+
|
|
167
|
+
-- Timestamp
|
|
168
|
+
created_at INTEGER NOT NULL
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
-- =============================================================================
|
|
172
|
+
-- Indexes for subject_identifiers
|
|
173
|
+
-- =============================================================================
|
|
174
|
+
|
|
175
|
+
-- Unique constraint: one subject per user per sector
|
|
176
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_subject_ids_unique
|
|
177
|
+
ON subject_identifiers(user_id, sector_identifier);
|
|
178
|
+
|
|
179
|
+
-- Lookup by subject value
|
|
180
|
+
CREATE INDEX IF NOT EXISTS idx_subject_ids_subject
|
|
181
|
+
ON subject_identifiers(subject);
|
|
182
|
+
|
|
183
|
+
-- Client lookup
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_subject_ids_client
|
|
185
|
+
ON subject_identifiers(client_id);
|
|
186
|
+
|
|
187
|
+
-- =============================================================================
|
|
188
|
+
-- linked_identities Table (External IdP Linking)
|
|
189
|
+
-- =============================================================================
|
|
190
|
+
-- Links local users to external Identity Provider accounts.
|
|
191
|
+
-- Supports federation scenarios (Google, Microsoft, SAML, etc.)
|
|
192
|
+
--
|
|
193
|
+
-- Purpose:
|
|
194
|
+
-- - Account linking: Multiple IdPs per user
|
|
195
|
+
-- - Session management: Track last used IdP
|
|
196
|
+
-- - Attribute synchronization: Store IdP-provided claims
|
|
197
|
+
-- =============================================================================
|
|
198
|
+
|
|
199
|
+
CREATE TABLE IF NOT EXISTS linked_identities (
|
|
200
|
+
-- Primary key
|
|
201
|
+
id TEXT PRIMARY KEY,
|
|
202
|
+
|
|
203
|
+
-- User reference (logical FK to users_core.id)
|
|
204
|
+
user_id TEXT NOT NULL,
|
|
205
|
+
|
|
206
|
+
-- External IdP identifier
|
|
207
|
+
provider_id TEXT NOT NULL,
|
|
208
|
+
|
|
209
|
+
-- User ID from the external IdP
|
|
210
|
+
provider_user_id TEXT NOT NULL,
|
|
211
|
+
|
|
212
|
+
-- Email from external IdP (may differ from primary email)
|
|
213
|
+
provider_email TEXT,
|
|
214
|
+
|
|
215
|
+
-- Name from external IdP
|
|
216
|
+
provider_name TEXT,
|
|
217
|
+
|
|
218
|
+
-- Raw attributes from IdP (JSON, for debugging/sync)
|
|
219
|
+
raw_attributes TEXT,
|
|
220
|
+
|
|
221
|
+
-- Timestamps
|
|
222
|
+
linked_at INTEGER NOT NULL,
|
|
223
|
+
last_used_at INTEGER
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
-- =============================================================================
|
|
227
|
+
-- Indexes for linked_identities
|
|
228
|
+
-- =============================================================================
|
|
229
|
+
|
|
230
|
+
-- Unique constraint: one link per provider per provider_user_id
|
|
231
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_linked_ids_provider
|
|
232
|
+
ON linked_identities(provider_id, provider_user_id);
|
|
233
|
+
|
|
234
|
+
-- User lookup (find all linked IdPs for a user)
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_linked_ids_user
|
|
236
|
+
ON linked_identities(user_id);
|
|
237
|
+
|
|
238
|
+
-- Provider email lookup (for account matching)
|
|
239
|
+
CREATE INDEX IF NOT EXISTS idx_linked_ids_email
|
|
240
|
+
ON linked_identities(provider_email);
|
|
241
|
+
|
|
242
|
+
-- =============================================================================
|
|
243
|
+
-- audit_log_pii Table (PII Access Audit)
|
|
244
|
+
-- =============================================================================
|
|
245
|
+
-- Tracks all PII access for compliance auditing.
|
|
246
|
+
--
|
|
247
|
+
-- IMPORTANT: Audit logs have different lifecycle than PII data:
|
|
248
|
+
-- - Retention: 1-7 years (vs PII subject to deletion)
|
|
249
|
+
-- - Volume: Grows explosively
|
|
250
|
+
-- - Export: SIEM integration, compliance reports
|
|
251
|
+
--
|
|
252
|
+
-- Design decisions:
|
|
253
|
+
-- - This table is a "recent buffer"
|
|
254
|
+
-- - Periodically export to R2/Logpush/SIEM
|
|
255
|
+
-- - exported_at tracks export status
|
|
256
|
+
-- =============================================================================
|
|
257
|
+
|
|
258
|
+
CREATE TABLE IF NOT EXISTS audit_log_pii (
|
|
259
|
+
-- Primary key
|
|
260
|
+
id TEXT PRIMARY KEY,
|
|
261
|
+
|
|
262
|
+
-- Multi-tenant support
|
|
263
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
264
|
+
|
|
265
|
+
-- Actor who accessed PII (user/admin/system)
|
|
266
|
+
user_id TEXT,
|
|
267
|
+
|
|
268
|
+
-- Action performed
|
|
269
|
+
-- pii_accessed | pii_created | pii_updated | pii_deleted | pii_exported
|
|
270
|
+
action TEXT NOT NULL,
|
|
271
|
+
|
|
272
|
+
-- Target user whose PII was accessed
|
|
273
|
+
target_user_id TEXT,
|
|
274
|
+
|
|
275
|
+
-- Action details (JSON)
|
|
276
|
+
details TEXT,
|
|
277
|
+
|
|
278
|
+
-- Request context
|
|
279
|
+
ip_address TEXT,
|
|
280
|
+
user_agent TEXT,
|
|
281
|
+
|
|
282
|
+
-- Timestamps
|
|
283
|
+
created_at INTEGER NOT NULL,
|
|
284
|
+
|
|
285
|
+
-- Export tracking (NULL = not exported yet)
|
|
286
|
+
exported_at INTEGER
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
-- =============================================================================
|
|
290
|
+
-- Indexes for audit_log_pii
|
|
291
|
+
-- =============================================================================
|
|
292
|
+
|
|
293
|
+
-- Actor lookup
|
|
294
|
+
CREATE INDEX IF NOT EXISTS idx_audit_pii_user
|
|
295
|
+
ON audit_log_pii(user_id);
|
|
296
|
+
|
|
297
|
+
-- Target user lookup
|
|
298
|
+
CREATE INDEX IF NOT EXISTS idx_audit_pii_target
|
|
299
|
+
ON audit_log_pii(target_user_id);
|
|
300
|
+
|
|
301
|
+
-- Action filter
|
|
302
|
+
CREATE INDEX IF NOT EXISTS idx_audit_pii_action
|
|
303
|
+
ON audit_log_pii(action);
|
|
304
|
+
|
|
305
|
+
-- Export status (find records to export)
|
|
306
|
+
CREATE INDEX IF NOT EXISTS idx_audit_pii_exported
|
|
307
|
+
ON audit_log_pii(exported_at);
|
|
308
|
+
|
|
309
|
+
-- Time-based queries
|
|
310
|
+
CREATE INDEX IF NOT EXISTS idx_audit_pii_created
|
|
311
|
+
ON audit_log_pii(created_at DESC);
|
|
312
|
+
|
|
313
|
+
-- =============================================================================
|
|
314
|
+
-- users_pii_tombstone Table (GDPR Deletion Tracking)
|
|
315
|
+
-- =============================================================================
|
|
316
|
+
-- Tracks PII deletions for GDPR Art.17 "Right to be Forgotten" compliance.
|
|
317
|
+
--
|
|
318
|
+
-- Purpose:
|
|
319
|
+
-- - Audit trail: "When, who, why" deleted
|
|
320
|
+
-- - Re-registration prevention: Block deleted emails during retention
|
|
321
|
+
-- - Compliance proof: Evidence of deletion
|
|
322
|
+
--
|
|
323
|
+
-- Design decisions:
|
|
324
|
+
-- - NO PII stored (email already deleted)
|
|
325
|
+
-- - email_blind_index: For duplicate prevention only
|
|
326
|
+
-- - retention_until: Auto-purge date (typically 90 days)
|
|
327
|
+
-- =============================================================================
|
|
328
|
+
|
|
329
|
+
CREATE TABLE IF NOT EXISTS users_pii_tombstone (
|
|
330
|
+
-- Primary key (same as original users_core.id)
|
|
331
|
+
id TEXT PRIMARY KEY,
|
|
332
|
+
|
|
333
|
+
-- Multi-tenant support
|
|
334
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
335
|
+
|
|
336
|
+
-- Email blind index (for preventing re-registration)
|
|
337
|
+
email_blind_index TEXT,
|
|
338
|
+
|
|
339
|
+
-- Deletion timestamp
|
|
340
|
+
deleted_at INTEGER NOT NULL,
|
|
341
|
+
|
|
342
|
+
-- Actor who initiated deletion
|
|
343
|
+
-- user: User requested (GDPR Art.17)
|
|
344
|
+
-- admin: Admin initiated
|
|
345
|
+
-- system: Automated cleanup
|
|
346
|
+
deleted_by TEXT,
|
|
347
|
+
|
|
348
|
+
-- Deletion reason
|
|
349
|
+
-- user_request | admin_action | inactivity | account_abuse | data_breach_response | other
|
|
350
|
+
deletion_reason TEXT,
|
|
351
|
+
|
|
352
|
+
-- Auto-purge date (typically deleted_at + 90 days)
|
|
353
|
+
retention_until INTEGER NOT NULL,
|
|
354
|
+
|
|
355
|
+
-- Additional metadata (JSON)
|
|
356
|
+
-- { request_id, ip_address, consent_reference, ... }
|
|
357
|
+
deletion_metadata TEXT,
|
|
358
|
+
|
|
359
|
+
-- Timestamps for BaseRepository compatibility
|
|
360
|
+
created_at INTEGER,
|
|
361
|
+
updated_at INTEGER
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
-- =============================================================================
|
|
365
|
+
-- Indexes for users_pii_tombstone
|
|
366
|
+
-- =============================================================================
|
|
367
|
+
|
|
368
|
+
-- Tenant lookup
|
|
369
|
+
CREATE INDEX IF NOT EXISTS idx_tombstone_tenant
|
|
370
|
+
ON users_pii_tombstone(tenant_id);
|
|
371
|
+
|
|
372
|
+
-- Email duplicate check
|
|
373
|
+
CREATE INDEX IF NOT EXISTS idx_tombstone_email
|
|
374
|
+
ON users_pii_tombstone(email_blind_index);
|
|
375
|
+
|
|
376
|
+
-- Cleanup job (find expired tombstones)
|
|
377
|
+
CREATE INDEX IF NOT EXISTS idx_tombstone_retention
|
|
378
|
+
ON users_pii_tombstone(retention_until);
|
|
379
|
+
|
|
380
|
+
-- =============================================================================
|
|
381
|
+
-- user_anonymization_map Table (PII ↔ Anonymous ID Mapping)
|
|
382
|
+
-- =============================================================================
|
|
383
|
+
-- Maps real user IDs to random anonymous UUIDs.
|
|
384
|
+
-- When user exercises "right to be forgotten", this mapping is deleted,
|
|
385
|
+
-- making event_log entries truly anonymous.
|
|
386
|
+
-- =============================================================================
|
|
387
|
+
|
|
388
|
+
CREATE TABLE IF NOT EXISTS user_anonymization_map (
|
|
389
|
+
id TEXT PRIMARY KEY,
|
|
390
|
+
tenant_id TEXT NOT NULL,
|
|
391
|
+
user_id TEXT NOT NULL,
|
|
392
|
+
anonymized_user_id TEXT NOT NULL,
|
|
393
|
+
created_at INTEGER NOT NULL,
|
|
394
|
+
|
|
395
|
+
UNIQUE(tenant_id, user_id)
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
CREATE INDEX IF NOT EXISTS idx_anon_map_tenant_user
|
|
399
|
+
ON user_anonymization_map(tenant_id, user_id);
|
|
400
|
+
|
|
401
|
+
CREATE INDEX IF NOT EXISTS idx_anon_map_anon_id
|
|
402
|
+
ON user_anonymization_map(anonymized_user_id);
|
|
403
|
+
|
|
404
|
+
-- =============================================================================
|
|
405
|
+
-- pii_log Table (Encrypted PII Change Audit)
|
|
406
|
+
-- =============================================================================
|
|
407
|
+
-- Stores encrypted records of PII changes for GDPR audit compliance.
|
|
408
|
+
-- Each entry records what was changed, by whom, and the legal basis.
|
|
409
|
+
-- =============================================================================
|
|
410
|
+
|
|
411
|
+
CREATE TABLE IF NOT EXISTS pii_log (
|
|
412
|
+
id TEXT PRIMARY KEY,
|
|
413
|
+
tenant_id TEXT NOT NULL,
|
|
414
|
+
user_id TEXT NOT NULL,
|
|
415
|
+
anonymized_user_id TEXT NOT NULL,
|
|
416
|
+
change_type TEXT NOT NULL,
|
|
417
|
+
affected_fields TEXT NOT NULL,
|
|
418
|
+
values_r2_key TEXT,
|
|
419
|
+
values_encrypted TEXT,
|
|
420
|
+
encryption_key_id TEXT NOT NULL,
|
|
421
|
+
encryption_iv TEXT NOT NULL,
|
|
422
|
+
actor_user_id TEXT,
|
|
423
|
+
actor_type TEXT NOT NULL,
|
|
424
|
+
request_id TEXT,
|
|
425
|
+
legal_basis TEXT,
|
|
426
|
+
consent_reference TEXT,
|
|
427
|
+
retention_until INTEGER NOT NULL,
|
|
428
|
+
created_at INTEGER NOT NULL
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_tenant_user
|
|
432
|
+
ON pii_log(tenant_id, user_id);
|
|
433
|
+
|
|
434
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_anon_user
|
|
435
|
+
ON pii_log(anonymized_user_id);
|
|
436
|
+
|
|
437
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_request_id
|
|
438
|
+
ON pii_log(request_id);
|
|
439
|
+
|
|
440
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_change_type
|
|
441
|
+
ON pii_log(change_type);
|
|
442
|
+
|
|
443
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_retention
|
|
444
|
+
ON pii_log(retention_until);
|
|
445
|
+
|
|
446
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_actor
|
|
447
|
+
ON pii_log(actor_user_id);
|
|
448
|
+
|
|
449
|
+
-- =============================================================================
|
|
450
|
+
-- Migration Complete
|
|
451
|
+
-- =============================================================================
|
|
452
|
+
-- How to apply this migration:
|
|
453
|
+
--
|
|
454
|
+
-- 1. Create the PII database:
|
|
455
|
+
-- wrangler d1 create authrim-pii
|
|
456
|
+
--
|
|
457
|
+
-- 2. Apply this migration:
|
|
458
|
+
-- wrangler d1 execute authrim-pii --file=migrations/pii/001_pii_initial.sql
|
|
459
|
+
--
|
|
460
|
+
-- 3. Add binding to wrangler.toml:
|
|
461
|
+
-- [[d1_databases]]
|
|
462
|
+
-- binding = "DB_PII"
|
|
463
|
+
-- database_name = "authrim-pii"
|
|
464
|
+
--
|
|
465
|
+
-- 4. Deploy and verify
|
|
466
|
+
-- =============================================================================
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
-- Migration: pii/002_pii_log_tables
|
|
2
|
+
-- Purpose: Create pii_log and user_anonymization_map tables for PII audit logging
|
|
3
|
+
-- Date: 2024-01-02
|
|
4
|
+
--
|
|
5
|
+
-- This migration adds:
|
|
6
|
+
-- 1. pii_log: Encrypted log of PII changes (GDPR compliance)
|
|
7
|
+
-- 2. user_anonymization_map: Mapping between real user IDs and anonymous IDs
|
|
8
|
+
--
|
|
9
|
+
-- Time units: All timestamps are epoch milliseconds.
|
|
10
|
+
-- Encryption: AES-256-GCM with per-entry IV, key rotation supported via encryption_key_id
|
|
11
|
+
|
|
12
|
+
-- =============================================================================
|
|
13
|
+
-- User Anonymization Mapping Table
|
|
14
|
+
-- =============================================================================
|
|
15
|
+
-- Maps real user IDs to random anonymous UUIDs.
|
|
16
|
+
-- When user exercises "right to be forgotten", this mapping is deleted,
|
|
17
|
+
-- making event_log entries truly anonymous.
|
|
18
|
+
|
|
19
|
+
CREATE TABLE IF NOT EXISTS user_anonymization_map (
|
|
20
|
+
-- Primary key
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
tenant_id TEXT NOT NULL,
|
|
23
|
+
|
|
24
|
+
-- Mapping
|
|
25
|
+
user_id TEXT NOT NULL, -- Real user ID
|
|
26
|
+
anonymized_user_id TEXT NOT NULL, -- Random UUID (used in event_log.anonymized_user_id)
|
|
27
|
+
|
|
28
|
+
-- Timestamps (epoch milliseconds)
|
|
29
|
+
created_at INTEGER NOT NULL,
|
|
30
|
+
|
|
31
|
+
-- Unique constraint: One mapping per user per tenant
|
|
32
|
+
UNIQUE(tenant_id, user_id)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
-- Lookup by tenant + user (primary query for getAnonymizedUserId)
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_anon_map_tenant_user
|
|
37
|
+
ON user_anonymization_map(tenant_id, user_id);
|
|
38
|
+
|
|
39
|
+
-- Reverse lookup by anonymized ID (for admin queries)
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_anon_map_anon_id
|
|
41
|
+
ON user_anonymization_map(anonymized_user_id);
|
|
42
|
+
|
|
43
|
+
-- =============================================================================
|
|
44
|
+
-- PII Log Table
|
|
45
|
+
-- =============================================================================
|
|
46
|
+
-- Stores encrypted records of PII changes for audit compliance.
|
|
47
|
+
-- Each entry records what was changed, by whom, and the legal basis.
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS pii_log (
|
|
50
|
+
-- Primary key
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
tenant_id TEXT NOT NULL,
|
|
53
|
+
|
|
54
|
+
-- User identifiers
|
|
55
|
+
user_id TEXT NOT NULL, -- Real user ID (this IS the PII)
|
|
56
|
+
anonymized_user_id TEXT NOT NULL, -- For correlation with event_log
|
|
57
|
+
|
|
58
|
+
-- Change metadata
|
|
59
|
+
change_type TEXT NOT NULL, -- 'create', 'update', 'delete', 'view', 'export'
|
|
60
|
+
affected_fields TEXT NOT NULL, -- JSON array: ["email", "name"]
|
|
61
|
+
|
|
62
|
+
-- Encrypted data storage (R2 or inline)
|
|
63
|
+
values_r2_key TEXT, -- R2 key if > 4KB: 'pii-values/{tenantId}/{date}/{entryId}.json'
|
|
64
|
+
values_encrypted TEXT, -- Inline encrypted JSON if <= 4KB
|
|
65
|
+
|
|
66
|
+
-- Encryption metadata (required for decryption)
|
|
67
|
+
encryption_key_id TEXT NOT NULL, -- Which key was used (for rotation)
|
|
68
|
+
encryption_iv TEXT NOT NULL, -- 12-byte nonce as Base64 (AES-GCM)
|
|
69
|
+
-- Note: AAD is NOT stored. Regenerate from: `${tenantId}:${sortedAffectedFields.join(',')}`
|
|
70
|
+
|
|
71
|
+
-- Actor information
|
|
72
|
+
actor_user_id TEXT, -- Who made the change (null for system)
|
|
73
|
+
actor_type TEXT NOT NULL, -- 'user', 'admin', 'system', 'api'
|
|
74
|
+
request_id TEXT, -- For correlation with event_log
|
|
75
|
+
|
|
76
|
+
-- Legal basis (GDPR Article 6)
|
|
77
|
+
legal_basis TEXT, -- 'consent', 'contract', 'legal_obligation', etc.
|
|
78
|
+
consent_reference TEXT, -- Consent record ID if applicable
|
|
79
|
+
|
|
80
|
+
-- Retention management
|
|
81
|
+
retention_until INTEGER NOT NULL, -- Expiry timestamp (epoch milliseconds)
|
|
82
|
+
|
|
83
|
+
-- Timestamps (epoch milliseconds)
|
|
84
|
+
created_at INTEGER NOT NULL
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
-- =============================================================================
|
|
88
|
+
-- PII Log Indexes
|
|
89
|
+
-- =============================================================================
|
|
90
|
+
|
|
91
|
+
-- Primary query: Find PII history for a user
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_tenant_user
|
|
93
|
+
ON pii_log(tenant_id, user_id);
|
|
94
|
+
|
|
95
|
+
-- Correlation with event_log via anonymized ID
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_anon_user
|
|
97
|
+
ON pii_log(anonymized_user_id);
|
|
98
|
+
|
|
99
|
+
-- Request correlation
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_request_id
|
|
101
|
+
ON pii_log(request_id);
|
|
102
|
+
|
|
103
|
+
-- Filter by change type (for compliance reports)
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_change_type
|
|
105
|
+
ON pii_log(change_type);
|
|
106
|
+
|
|
107
|
+
-- Cleanup: Find expired entries
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_retention
|
|
109
|
+
ON pii_log(retention_until);
|
|
110
|
+
|
|
111
|
+
-- Actor queries (who made changes)
|
|
112
|
+
CREATE INDEX IF NOT EXISTS idx_pii_log_actor
|
|
113
|
+
ON pii_log(actor_user_id);
|
|
114
|
+
|
|
115
|
+
-- =============================================================================
|
|
116
|
+
-- Comments
|
|
117
|
+
-- =============================================================================
|
|
118
|
+
|
|
119
|
+
-- Encryption Notes:
|
|
120
|
+
-- - Algorithm: AES-256-GCM
|
|
121
|
+
-- - IV: 12-byte random nonce, stored as Base64 in encryption_iv
|
|
122
|
+
-- - AAD: Regenerated as `${tenantId}:${sortedAffectedFields.join(',')}`
|
|
123
|
+
-- - Key Rotation: encryption_key_id identifies which key was used
|
|
124
|
+
-- - Old keys kept for 90 days for decryption
|
|
125
|
+
-- - New entries use current key
|
|
126
|
+
--
|
|
127
|
+
-- GDPR Compliance:
|
|
128
|
+
-- - pii_log records what was changed, when, by whom, and why
|
|
129
|
+
-- - user_anonymization_map enables "right to be forgotten"
|
|
130
|
+
-- - Delete mapping → event_log becomes truly anonymous
|
|
131
|
+
-- - Delete pii_log entries → PII is removed
|
|
132
|
+
-- - legal_basis documents lawful processing ground
|
|
133
|
+
--
|
|
134
|
+
-- Purge Workflow (2-stage logging):
|
|
135
|
+
-- 1. Log 'user.pii_purge_started' in event_log
|
|
136
|
+
-- 2. DELETE FROM pii_log WHERE tenant_id = ? AND user_id = ?
|
|
137
|
+
-- 3. DELETE FROM user_anonymization_map WHERE tenant_id = ? AND user_id = ?
|
|
138
|
+
-- 4a. On success: Log 'user.pii_purge_completed' in event_log
|
|
139
|
+
-- 4b. On failure: Log 'user.pii_purge_failed' in event_log
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Migration: Add created_at and updated_at columns to users_pii_tombstone
|
|
2
|
+
-- Purpose: Fix schema mismatch with TombstoneRepository
|
|
3
|
+
|
|
4
|
+
-- Add created_at column
|
|
5
|
+
ALTER TABLE users_pii_tombstone ADD COLUMN created_at INTEGER;
|
|
6
|
+
|
|
7
|
+
-- Add updated_at column
|
|
8
|
+
ALTER TABLE users_pii_tombstone ADD COLUMN updated_at INTEGER;
|
|
9
|
+
|
|
10
|
+
-- Set default values for existing rows
|
|
11
|
+
UPDATE users_pii_tombstone SET created_at = deleted_at WHERE created_at IS NULL;
|
|
12
|
+
UPDATE users_pii_tombstone SET updated_at = deleted_at WHERE updated_at IS NULL;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
-- =============================================================================
|
|
2
|
+
-- Migration: 004_cleanup_admin_from_pii.sql (D1_PII)
|
|
3
|
+
-- =============================================================================
|
|
4
|
+
-- Description: Remove Admin user PII data as part of Admin/EndUser separation.
|
|
5
|
+
-- This migration works in conjunction with the cleanup script.
|
|
6
|
+
--
|
|
7
|
+
-- IMPORTANT: This file contains a TEMPLATE.
|
|
8
|
+
-- Use scripts/cleanup-admin-from-core.sh to execute properly.
|
|
9
|
+
--
|
|
10
|
+
-- The cleanup script will:
|
|
11
|
+
-- 1. Query D1_CORE for admin user IDs
|
|
12
|
+
-- 2. Generate DELETE statements with those IDs
|
|
13
|
+
-- 3. Execute against D1_PII
|
|
14
|
+
--
|
|
15
|
+
-- Manual execution example:
|
|
16
|
+
-- DELETE FROM users_pii WHERE id IN ('admin-user-id-1', 'admin-user-id-2');
|
|
17
|
+
-- DELETE FROM linked_identities WHERE user_id IN ('admin-user-id-1', 'admin-user-id-2');
|
|
18
|
+
-- DELETE FROM subject_identifiers WHERE user_id IN ('admin-user-id-1', 'admin-user-id-2');
|
|
19
|
+
-- =============================================================================
|
|
20
|
+
|
|
21
|
+
-- =============================================================================
|
|
22
|
+
-- Template: Clean up orphaned PII records
|
|
23
|
+
-- =============================================================================
|
|
24
|
+
-- After D1_CORE cleanup, users_pii may have orphaned records.
|
|
25
|
+
-- These DELETE statements should be run with actual admin user IDs.
|
|
26
|
+
|
|
27
|
+
-- Placeholder: Replace {ADMIN_USER_IDS} with actual IDs from D1_CORE
|
|
28
|
+
-- DELETE FROM users_pii WHERE id IN ({ADMIN_USER_IDS});
|
|
29
|
+
-- DELETE FROM linked_identities WHERE user_id IN ({ADMIN_USER_IDS});
|
|
30
|
+
-- DELETE FROM subject_identifiers WHERE user_id IN ({ADMIN_USER_IDS});
|
|
31
|
+
|
|
32
|
+
-- =============================================================================
|
|
33
|
+
-- Alternative: Clean up orphaned records (generic approach)
|
|
34
|
+
-- =============================================================================
|
|
35
|
+
-- If you have access to both databases and can verify orphaned records,
|
|
36
|
+
-- you can use this approach to clean up any PII records that no longer
|
|
37
|
+
-- have a corresponding users_core record.
|
|
38
|
+
--
|
|
39
|
+
-- WARNING: This requires confirmation that D1_CORE cleanup has completed.
|
|
40
|
+
|
|
41
|
+
-- After cleanup is complete, verify no admin records remain:
|
|
42
|
+
-- SELECT COUNT(*) FROM users_pii WHERE id LIKE 'admin-%';
|
|
43
|
+
|
|
44
|
+
-- =============================================================================
|
|
45
|
+
-- Note on GDPR Compliance
|
|
46
|
+
-- =============================================================================
|
|
47
|
+
-- Admin users are NOT subject to GDPR user data requirements.
|
|
48
|
+
-- Admin PII can be deleted without tombstone records.
|
|
49
|
+
-- If needed for audit, create audit_log_pii entries before deletion.
|
|
50
|
+
-- =============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authrim/setup",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.136",
|
|
4
4
|
"description": "CLI tool for setting up Authrim OIDC Provider on Cloudflare Workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
|
+
"migrations",
|
|
13
14
|
"templates",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|