From b04613784acb8824e748a3d091a219db3acd4263 Mon Sep 17 00:00:00 2001 From: ngc2207 Date: Sat, 7 Dec 2024 21:01:22 +0800 Subject: [PATCH] feat(auth): implement Gitea authentication provider with NextAuth integration --- package.json | 2 + src/app/api/auth/[...nextauth]/route.ts | 3 ++ src/auth.ts | 15 ++++++ src/lib/providers/gitea.ts | 72 +++++++++++++++++++++++++ src/middleware.ts | 1 + 5 files changed, 93 insertions(+) create mode 100644 src/app/api/auth/[...nextauth]/route.ts create mode 100644 src/auth.ts create mode 100644 src/lib/providers/gitea.ts create mode 100644 src/middleware.ts diff --git a/package.json b/package.json index ad2f657..2ae83ca 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,10 @@ "dependencies": { "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "gitea-js": "^1.22.0", "lucide-react": "^0.468.0", "next": "15.0.4", + "next-auth": "^5.0.0-beta.25", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.5.5", diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..86c9f3d --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/auth"; + +export const { GET, POST } = handlers; diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..21d7603 --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,15 @@ +import NextAuth from "next-auth"; +import Gitea from "@/lib/providers/gitea"; + +export const { handlers, signIn, signOut, auth } = NextAuth({ + providers: [ + Gitea({ + clientId: process.env.AUTH_GITEA_ID, + clientSecret: process.env.AUTH_GITEA_SECRET, + enterprise: { + baseUrl: process.env.AUTH_GITEA_URL, + }, + }), + ], + secret: process.env.AUTH_SECRET, +}); diff --git a/src/lib/providers/gitea.ts b/src/lib/providers/gitea.ts new file mode 100644 index 0000000..df50847 --- /dev/null +++ b/src/lib/providers/gitea.ts @@ -0,0 +1,72 @@ +import { Email as GiteaEmail, User } from "gitea-js"; +import { OAuthConfig, OAuthUserConfig } from "next-auth/providers"; + +export interface GiteaProfile extends User { + username: string; +} + +export default function Gitea( + config: OAuthUserConfig & { + enterprise?: { + baseUrl?: string; + }; + } +): OAuthConfig { + const baseUrl = config?.enterprise?.baseUrl ?? "https://gitea.com"; + const apiBaseUrl = config?.enterprise?.baseUrl + ? `${config?.enterprise?.baseUrl}/api/v1` + : "https://gitea.com/api/v1"; + + return { + id: "gitea", + name: "Gitea", + type: "oauth", + authorization: { + url: `${baseUrl}/login/oauth/authorize`, + params: { scope: "read:user user:email" }, + }, + token: `${baseUrl}/login/oauth/access_token`, + userinfo: { + url: `${apiBaseUrl}/user`, + async request({ + tokens, + provider, + }: { + tokens: { access_token: string }; + provider: OAuthConfig; + }) { + const profile = await fetch(provider.userinfo?.url as URL, { + headers: { + Authorization: `Bearer ${tokens.access_token}`, + "User-Agent": "authjs", + }, + }).then(async (res) => await res.json()); + + if (!profile.email) { + const res = await fetch(`${apiBaseUrl}/user/emails`, { + headers: { + Authorization: `Bearer ${tokens.access_token}`, + "User-Agent": "authjs", + }, + }); + + if (res.ok) { + const emails: GiteaEmail[] = await res.json(); + profile.email = (emails.find((e) => e.primary) ?? emails[0]).email; + } + } + + return profile; + }, + }, + profile(profile) { + return { + id: profile.id?.toString(), + name: profile.username ?? profile.login, + email: profile.email, + image: profile.avatar_url, + }; + }, + options: config, + }; +} diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..8155fe0 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1 @@ +export { auth as middleware } from "@/auth";