@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.
@@ -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.134",
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
  ],