diff --git a/prisma/migrations/20251213133000_add_github_username/migration.sql b/prisma/migrations/20251213133000_add_github_username/migration.sql new file mode 100644 index 00000000..e69de29b diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f741ce08..3b7c53b1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -20,18 +20,19 @@ enum UserRole { } model User { - id String @id @default(cuid()) - email String @unique - username String @unique - name String? - password String? // For credentials auth - avatar String? - role UserRole @default(USER) - locale String @default("en") - verified Boolean @default(false) - emailVerified DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + email String @unique + username String @unique + name String? + password String? // For credentials auth + avatar String? + role UserRole @default(USER) + locale String @default("en") + verified Boolean @default(false) + emailVerified DateTime? + githubUsername String? // Immutable GitHub username for contributor attribution + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations prompts Prompt[] @relation("PromptAuthor") diff --git a/src/app/prompts.csv/route.ts b/src/app/prompts.csv/route.ts index fba0dadc..dcfdfdb9 100644 --- a/src/app/prompts.csv/route.ts +++ b/src/app/prompts.csv/route.ts @@ -29,7 +29,8 @@ export async function GET() { }, author: { select: { - username: true, + email: true, + githubUsername: true, }, }, }, @@ -42,7 +43,9 @@ export async function GET() { const promptContent = escapeCSVField(prompt.content); const forDevs = prompt.category?.slug === "coding" ? "TRUE" : "FALSE"; const type = prompt.type; - const contributor = escapeCSVField(prompt.author.username); + // Use githubUsername for GitHub users, email for others (Google, credentials) + // This prevents impersonation since these identifiers are immutable + const contributor = escapeCSVField(prompt.author.githubUsername || prompt.author.email); return [act, promptContent, forDevs, type, contributor].join(","); }); diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index c6dc4b47..94ca4788 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -41,9 +41,11 @@ function CustomPrismaAdapter(): Adapter { return { ...prismaAdapter, - async createUser(data: AdapterUser & { username?: string }) { + async createUser(data: AdapterUser & { username?: string; githubUsername?: string }) { // Use GitHub username if provided, otherwise generate one let username = (data as any).username; + const githubUsername = (data as any).githubUsername; // Immutable GitHub username + if (!username) { username = await generateUsername(data.email, data.name); } else { @@ -64,6 +66,7 @@ function CustomPrismaAdapter(): Adapter { email: data.email, avatar: data.image, emailVerified: data.emailVerified, + githubUsername: githubUsername || undefined, // Store immutable GitHub username }, }); @@ -91,6 +94,7 @@ function CustomPrismaAdapter(): Adapter { avatar: data.image, emailVerified: data.emailVerified, username, + githubUsername: githubUsername || undefined, // Store immutable GitHub username }, }); diff --git a/src/lib/plugins/auth/github.ts b/src/lib/plugins/auth/github.ts index c209e206..ed8bb4d0 100644 --- a/src/lib/plugins/auth/github.ts +++ b/src/lib/plugins/auth/github.ts @@ -14,7 +14,8 @@ export const githubPlugin: AuthPlugin = { name: profile.name || profile.login, email: profile.email, image: profile.avatar_url, - username: profile.login, // GitHub username + username: profile.login, // GitHub username (used as display username) + githubUsername: profile.login, // Immutable GitHub username for contributor attribution }; }, }),