From b838d0f8d64e536222b55c242237b7313878635e Mon Sep 17 00:00:00 2001 From: Taken Date: Sat, 19 Jul 2025 11:19:40 +0200 Subject: [PATCH] Added gravatar support in case of no avatar --- .env.example | 19 +++++++++++++++++++ src/lib/auth/auth.ts | 19 +++++++++++++++++++ src/lib/env/server.ts | 3 ++- src/lib/gravatar.ts | 28 ++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 .env.example create mode 100644 src/lib/gravatar.ts diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..610357a --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# General +NEXT_PUBLIC_BASE_URL=http://localhost:3000 + +# Database +PG_USER=dev +PG_PASSWORD=dev +PG_HOST=localhost +PG_PORT=5433 +PG_DATABASE=dev + +# Auth +BETTER_AUTH_SECRET= +BETTER_AUTH_URL=http://localhost:3000 +AUTHENTIK_CLIENT_ID= +AUTHENTIK_CLIENT_SECRET= +AUTHENTIK_DISCOVERY_URL= + +# other +GRAVATAR_API_KEY= \ No newline at end of file diff --git a/src/lib/auth/auth.ts b/src/lib/auth/auth.ts index 83f0baf..2c334f5 100644 --- a/src/lib/auth/auth.ts +++ b/src/lib/auth/auth.ts @@ -4,11 +4,30 @@ import { nextCookies } from "better-auth/next-js" import { genericOAuth } from "better-auth/plugins" import { db } from "../drizzle/db" import { env } from "../env/server" +import { getGravatar } from "../gravatar" export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg" }), + databaseHooks: { + user: { + create: { + before: async (user) => { + if (user.image) return + + const newImage = await getGravatar(user.email) + + return { + data: { + ...user, + image: newImage + } + } + } + } + } + }, plugins: [ nextCookies(), genericOAuth({ diff --git a/src/lib/env/server.ts b/src/lib/env/server.ts index fc4e4c8..06da7c3 100644 --- a/src/lib/env/server.ts +++ b/src/lib/env/server.ts @@ -12,7 +12,8 @@ export const env = createEnv({ BETTER_AUTH_URL: z.string().min(1), AUTHENTIK_CLIENT_ID: z.string().min(1), AUTHENTIK_CLIENT_SECRET: z.string().min(1), - AUTHENTIK_DISCOVERY_URL: z.string().min(1) + AUTHENTIK_DISCOVERY_URL: z.string().min(1), + GRAVATAR_API_KEY: z.string().min(1) }, createFinalSchema: (schema) => { return z.object(schema).transform(s => { diff --git a/src/lib/gravatar.ts b/src/lib/gravatar.ts new file mode 100644 index 0000000..3177175 --- /dev/null +++ b/src/lib/gravatar.ts @@ -0,0 +1,28 @@ +import crypto from "node:crypto" +import { env } from "./env/server" +import { z } from "zod" + +const gravatarSchema = z.object({ + avatar_url: z.string().url(), +}) + +export async function getGravatar(email: string) { + const baseUrl = "https://api.gravatar.com/v3/profiles" + const formattedEmail = email.trim().toLowerCase() + const hash = crypto.createHash('sha256').update(formattedEmail).digest('hex') + + const res = await fetch(`${baseUrl}/${hash}`, { + headers: { + Authorization: `Bearer ${env.GRAVATAR_API_KEY}` + } + }) + + if (!res.ok) return null + + const json = await res.json() + const { success, data } = gravatarSchema.safeParse(json) + + if (!success) return null + + return data.avatar_url +} \ No newline at end of file