Claude-skill-registry clerk-data-handling
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/clerk-data-handling" ~/.claude/skills/majiayu000-claude-skill-registry-clerk-data-handling && rm -rf "$T"
manifest:
skills/data/clerk-data-handling/SKILL.mdsource content
Clerk Data Handling
Overview
Manage user data, implement privacy features, and ensure compliance with regulations.
Prerequisites
- Clerk integration working
- Understanding of GDPR/CCPA requirements
- Database with user-related data
Instructions
Step 1: User Data Export
// lib/data-export.ts import { clerkClient } from '@clerk/nextjs/server' import { db } from './db' interface UserDataExport { clerk: ClerkUserData application: ApplicationUserData exportedAt: string } interface ClerkUserData { id: string email: string | undefined firstName: string | null lastName: string | null createdAt: Date lastSignIn: Date | null metadata: Record<string, any> } interface ApplicationUserData { profile: any orders: any[] preferences: any activityLog: any[] } export async function exportUserData(userId: string): Promise<UserDataExport> { const client = await clerkClient() // Get Clerk user data const clerkUser = await client.users.getUser(userId) // Get application data const [profile, orders, preferences, activityLog] = await Promise.all([ db.userProfile.findUnique({ where: { clerkId: userId } }), db.order.findMany({ where: { userId }, orderBy: { createdAt: 'desc' } }), db.userPreference.findMany({ where: { userId } }), db.activityLog.findMany({ where: { userId }, orderBy: { timestamp: 'desc' }, take: 1000 }) ]) return { clerk: { id: clerkUser.id, email: clerkUser.primaryEmailAddress?.emailAddress, firstName: clerkUser.firstName, lastName: clerkUser.lastName, createdAt: new Date(clerkUser.createdAt), lastSignIn: clerkUser.lastSignInAt ? new Date(clerkUser.lastSignInAt) : null, metadata: { public: clerkUser.publicMetadata, // Note: privateMetadata should be handled carefully } }, application: { profile: sanitizeForExport(profile), orders: orders.map(sanitizeForExport), preferences: preferences.map(sanitizeForExport), activityLog: activityLog.map(sanitizeForExport) }, exportedAt: new Date().toISOString() } } function sanitizeForExport(data: any): any { if (!data) return null // Remove internal fields const { id, createdAt, updatedAt, ...rest } = data return rest }
Step 2: User Deletion (Right to be Forgotten)
// lib/user-deletion.ts import { clerkClient } from '@clerk/nextjs/server' import { db } from './db' interface DeletionResult { success: boolean deletedFrom: string[] errors: string[] } export async function deleteUserCompletely(userId: string): Promise<DeletionResult> { const result: DeletionResult = { success: true, deletedFrom: [], errors: [] } // Step 1: Delete from application database try { await db.$transaction([ // Delete related records first (foreign key constraints) db.activityLog.deleteMany({ where: { userId } }), db.order.deleteMany({ where: { userId } }), db.userPreference.deleteMany({ where: { userId } }), db.userProfile.delete({ where: { clerkId: userId } }) ]) result.deletedFrom.push('application_database') } catch (error: any) { result.errors.push(`Database deletion failed: ${error.message}`) result.success = false } // Step 2: Delete from Clerk try { const client = await clerkClient() await client.users.deleteUser(userId) result.deletedFrom.push('clerk') } catch (error: any) { result.errors.push(`Clerk deletion failed: ${error.message}`) result.success = false } // Step 3: Delete from external services try { await deleteFromExternalServices(userId) result.deletedFrom.push('external_services') } catch (error: any) { result.errors.push(`External service deletion failed: ${error.message}`) } // Log deletion for audit await logDeletionEvent(userId, result) return result } async function deleteFromExternalServices(userId: string) { // Delete from analytics // Delete from email service // Delete from payment provider // etc. } async function logDeletionEvent(userId: string, result: DeletionResult) { // Maintain audit log of deletions (anonymized) await db.deletionLog.create({ data: { anonymizedId: hashUserId(userId), deletedAt: new Date(), success: result.success, errors: result.errors } }) }
Step 3: Data Retention Policies
// lib/data-retention.ts import { db } from './db' import { clerkClient } from '@clerk/nextjs/server' interface RetentionPolicy { activityLogs: number // days sessions: number // days inactiveUsers: number // days } const RETENTION_POLICY: RetentionPolicy = { activityLogs: 90, sessions: 30, inactiveUsers: 365 } export async function enforceRetentionPolicy() { const now = new Date() // Clean up old activity logs const activityCutoff = new Date( now.getTime() - RETENTION_POLICY.activityLogs * 24 * 60 * 60 * 1000 ) const deletedLogs = await db.activityLog.deleteMany({ where: { timestamp: { lt: activityCutoff } } }) console.log(`Deleted ${deletedLogs.count} old activity logs`) // Identify inactive users for notification const inactiveCutoff = new Date( now.getTime() - RETENTION_POLICY.inactiveUsers * 24 * 60 * 60 * 1000 ) const inactiveUsers = await db.userProfile.findMany({ where: { lastActiveAt: { lt: inactiveCutoff }, notifiedAboutInactivity: false } }) // Notify inactive users before deletion for (const user of inactiveUsers) { await notifyInactiveUser(user.clerkId) await db.userProfile.update({ where: { id: user.id }, data: { notifiedAboutInactivity: true } }) } console.log(`Notified ${inactiveUsers.length} inactive users`) }
Step 4: Consent Management
// lib/consent.ts import { currentUser } from '@clerk/nextjs/server' interface ConsentRecord { marketing: boolean analytics: boolean thirdParty: boolean updatedAt: Date } export async function getConsent(userId: string): Promise<ConsentRecord | null> { const user = await currentUser() if (!user) return null return { marketing: user.publicMetadata?.consent?.marketing ?? false, analytics: user.publicMetadata?.consent?.analytics ?? false, thirdParty: user.publicMetadata?.consent?.thirdParty ?? false, updatedAt: new Date(user.publicMetadata?.consent?.updatedAt || user.createdAt) } } export async function updateConsent( userId: string, consent: Partial<ConsentRecord> ) { const client = await clerkClient() const user = await client.users.getUser(userId) const currentConsent = user.publicMetadata?.consent || {} await client.users.updateUser(userId, { publicMetadata: { ...user.publicMetadata, consent: { ...currentConsent, ...consent, updatedAt: new Date().toISOString() } } }) // Log consent change for audit await logConsentChange(userId, consent) }
Step 5: GDPR API Endpoints
// app/api/user/data/route.ts import { auth } from '@clerk/nextjs/server' import { exportUserData } from '@/lib/data-export' // Data Export (GDPR Article 20) export async function GET() { const { userId } = await auth() if (!userId) { return Response.json({ error: 'Unauthorized' }, { status: 401 }) } const userData = await exportUserData(userId) return new Response(JSON.stringify(userData, null, 2), { headers: { 'Content-Type': 'application/json', 'Content-Disposition': `attachment; filename="user-data-${userId}.json"` } }) } // app/api/user/delete/route.ts import { deleteUserCompletely } from '@/lib/user-deletion' // Data Deletion (GDPR Article 17) export async function DELETE() { const { userId } = await auth() if (!userId) { return Response.json({ error: 'Unauthorized' }, { status: 401 }) } // Require confirmation const confirmed = request.headers.get('X-Confirm-Delete') === 'true' if (!confirmed) { return Response.json( { error: 'Confirmation required', requiresHeader: 'X-Confirm-Delete: true' }, { status: 400 } ) } const result = await deleteUserCompletely(userId) if (result.success) { return Response.json({ message: 'Account deleted successfully' }) } else { return Response.json( { error: 'Partial deletion', details: result }, { status: 500 } ) } }
Step 6: Audit Logging
// lib/audit-log.ts interface AuditEvent { type: 'data_access' | 'data_export' | 'data_deletion' | 'consent_change' userId: string performedBy: string details: Record<string, any> timestamp: Date } export async function logAuditEvent(event: Omit<AuditEvent, 'timestamp'>) { await db.auditLog.create({ data: { ...event, timestamp: new Date() } }) // For compliance, also log to external service if (process.env.AUDIT_LOG_ENDPOINT) { await fetch(process.env.AUDIT_LOG_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...event, timestamp: new Date() }) }) } }
Privacy Checklist
- Data export functionality (GDPR Article 20)
- Data deletion functionality (GDPR Article 17)
- Consent management
- Data retention policies
- Audit logging
- Privacy policy updated
- Cookie consent implemented
- Data processing agreements
Output
- Data export functionality
- User deletion capability
- Consent management
- Audit logging
Error Handling
| Scenario | Action |
|---|---|
| Partial deletion | Retry failed services, log for manual review |
| Export timeout | Queue export, email when complete |
| Consent sync fail | Retry with exponential backoff |
Resources
Next Steps
Proceed to
clerk-enterprise-rbac for enterprise SSO and RBAC.