clerk-data-handling
Clerkを使用してユーザーデータ、プライバシー、GDPR対応を管理できます。データのエクスポート、ユーザー削除、プライバシーコンプライアンス機能の実装が必要な場合に使用します。「clerk user data」「clerk GDPR」「clerk privacy」「clerk data export」「clerk delete user」といったフレーズで実行できます。
description の原文を見る
Handle user data, privacy, and GDPR compliance with Clerk. Use when implementing data export, user deletion, or privacy compliance features. Trigger with phrases like "clerk user data", "clerk GDPR", "clerk privacy", "clerk data export", "clerk delete user".
SKILL.md 本文
Clerk データハンドリング
概要
ユーザーデータを管理し、プライバシー機能を実装し、規制への準拠を確保します。
前提条件
- Clerk統合が機能している
- GDPR/CCPA要件の理解
- ユーザー関連データを含むデータベース
手順
ステップ1: ユーザーデータエクスポート
// 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()
// Clerk ユーザーデータを取得
const clerkUser = await client.users.getUser(userId)
// アプリケーションデータを取得
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,
// 注意: privateMetadata は慎重に扱う必要があります
}
},
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
// 内部フィールドを削除
const { id, createdAt, updatedAt, ...rest } = data
return rest
}
ステップ2: ユーザー削除(忘れられる権利)
// 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: []
}
// ステップ1: アプリケーションデータベースから削除
try {
await db.$transaction([
// 関連レコードを先に削除(外部キー制約)
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
}
// ステップ2: 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
}
// ステップ3: 外部サービスから削除
try {
await deleteFromExternalServices(userId)
result.deletedFrom.push('external_services')
} catch (error: any) {
result.errors.push(`External service deletion failed: ${error.message}`)
}
// 監査用に削除イベントをログ
await logDeletionEvent(userId, result)
return result
}
async function deleteFromExternalServices(userId: string) {
// 分析サービスから削除
// メールサービスから削除
// 決済プロバイダから削除
// など
}
async function logDeletionEvent(userId: string, result: DeletionResult) {
// 削除の監査ログを保持(匿名化)
await db.deletionLog.create({
data: {
anonymizedId: hashUserId(userId),
deletedAt: new Date(),
success: result.success,
errors: result.errors
}
})
}
ステップ3: データ保持ポリシー
// lib/data-retention.ts
import { db } from './db'
import { clerkClient } from '@clerk/nextjs/server'
interface RetentionPolicy {
activityLogs: number // 日数
sessions: number // 日数
inactiveUsers: number // 日数
}
const RETENTION_POLICY: RetentionPolicy = {
activityLogs: 90,
sessions: 30,
inactiveUsers: 365
}
export async function enforceRetentionPolicy() {
const now = new Date()
// 古いアクティビティログをクリーンアップ
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`)
// 非アクティブなユーザーを通知用に特定
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
}
})
// 削除前に非アクティブなユーザーに通知
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`)
}
ステップ4: 同意管理
// 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()
}
}
})
// 監査用に同意変更をログ
await logConsentChange(userId, consent)
}
ステップ5: GDPR API エンドポイント
// app/api/user/data/route.ts
import { auth } from '@clerk/nextjs/server'
import { exportUserData } from '@/lib/data-export'
// データエクスポート(GDPR 第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'
// データ削除(GDPR 第17条)
export async function DELETE() {
const { userId } = await auth()
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
// 確認が必要
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 }
)
}
}
ステップ6: 監査ログ
// 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()
}
})
// コンプライアンスのため、外部サービスにもログを記録
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() })
})
}
}
プライバシーチェックリスト
- データエクスポート機能(GDPR 第20条)
- データ削除機能(GDPR 第17条)
- 同意管理
- データ保持ポリシー
- 監査ログ
- プライバシーポリシーの更新
- クッキー同意の実装
- データ処理契約
出力
- データエクスポート機能
- ユーザー削除機能
- 同意管理
- 監査ログ
エラーハンドリング
| シナリオ | 対応 |
|---|---|
| 部分的な削除 | 失敗したサービスを再試行し、手動レビュー用にログ |
| エクスポートタイムアウト | エクスポートをキューに入れ、完了時にメール送信 |
| 同意の同期失敗 | 指数バックオフで再試行 |
リソース
次のステップ
clerk-enterprise-rbac に進み、エンタープライズ SSO と RBAC について学習してください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- Brmbobo
- リポジトリ
- Brmbobo/Web2podcast
- ライセンス
- MIT
- 最終更新
- 2026/1/26
Source: https://github.com/Brmbobo/Web2podcast / ライセンス: MIT
関連スキル
secure-code-guardian
認証・認可の実装、ユーザー入力の保護、OWASP Top 10の脆弱性対策が必要な場合に使用します。bcrypt/argon2によるパスワードハッシング、パラメータ化ステートメントによるSQLインジェクション対策、CORS/CSPヘッダーの設定、Zodによる入力検証、JWTトークンの構築などのカスタムセキュリティ実装に対応します。認証、認可、入力検証、暗号化、OWASP Top 10対策、セッション管理、セキュリティ強化全般で活用できます。ただし、構築済みのOAuth/SSO統合や単独のセキュリティ監査が必要な場合は、より特化したスキルの検討をお勧めします。
claude-authenticity
APIエンドポイントが本物のClaudeによって支えられているか(ラッパーやプロキシ、偽装ではないか)を、claude-verifyプロジェクトを模した9つの重み付きルールベースチェックで検証できます。また、Claudeの正体を上書きしているプロバイダーから注入されたシステムプロンプトも抽出します。完全に自己完結しており、httpx以外の追加パッケージは不要です。Claude APIキーまたはエンドポイントを検証したい場合、サードパーティのClaudeサービスが本物か確認したい場合、APIプロバイダーのClaude正当性を監査したい場合、複数モデルを並行してテストしたい場合、またはプロバイダーが注入したシステムプロンプトを特定したい場合に使用できます。
anth-security-basics
Anthropic Claude APIのセキュリティベストプラクティスを適用し、キー管理、入力値の検証、プロンプトインジェクション対策を実施します。APIキーの保護、Claudeに送信する前のユーザー入力検証、コンテンツセーフティガードレールの実装が必要な場合に活用できます。「anthropic security」「claude api key security」「secure anthropic」「prompt injection defense」といったフレーズでトリガーされます。
x-ray
x-ray.mdプレ監査レポートを生成します。概要、強化された脅威モデル(プロトコルタイプのプロファイリング、Gitの重み付け攻撃面分析、時間軸リスク分析、コンポーザビリティ依存関係マッピング)、不変条件、統合、ドキュメント品質、テスト分析、開発者・Gitの履歴をカバーしています。「x-ray」「audit readiness」「readiness report」「pre-audit report」「prep this protocol」「protocol prep」「summarize this protocol」のキーワードで実行されます。
semgrep
Semgrepスタティック分析スキャンを実行し、カスタム検出ルールを作成します。Semgrepでのコードスキャン、セキュリティ脆弱性の検出、カスタムYAMLルールの作成、または特定のバグパターンの検出が必要な場合に使用します。重要:ユーザーが「バグをスキャンしたい」「コード品質を確認したい」「脆弱性を見つけたい」「スタティック分析」「セキュリティlint」「コード監査」または「コーディング標準を適用したい」と尋ねた場合も、Semgrepという名称を明記していなくても、このスキルを使用してください。Semgrepは30以上の言語に対応したパターンベースのコードスキャンに最適なツールです。
ghost-bits-cast-attack
Java「ゴーストビッツ」/キャストアタック プレイブック(Black Hat Asia 2026)。16ビット文字が8ビットバイトに暗黙的に縮小されるJavaサービスへの攻撃時に使用します。WAF/IDSを回避して、SQLインジェクション、デシリアライゼーション型RCE、ファイルアップロード(Webシェル)、パストトラバーサル、CRLF インジェクション、リクエストスマグリング、SMTPインジェクションを実行できます。Tomcat、Spring、Jetty、Undertow、Vert.x、Jackson、Fastjson、Apache Commons BCEL、Apache HttpClient、Angus Mail、JDK HttpServer、Lettuce、Jodd、XMLWriterに影響し、WAFバイパスにより多くの「パッチ済み」CVEを再度有効化します。