diff --git a/next.config.ts b/next.config.ts
index 58745f9..49e5c6a 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -7,6 +7,10 @@ const nextConfig: NextConfig = {
protocol: "https",
hostname: "avatars.githubusercontent.com",
},
+ {
+ protocol: "https",
+ hostname: "seccdn.libravatar.org",
+ },
],
},
};
diff --git a/package.json b/package.json
index c289f5c..8f238ba 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@prisma/client": "^6.1.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "gitea-js": "^1.22.0",
"lucide-react": "^0.469.0",
"next": "15.0.3",
"next-auth": "^5.0.0-beta.25",
diff --git a/public/gitea.svg b/public/gitea.svg
new file mode 100644
index 0000000..33293fa
--- /dev/null
+++ b/public/gitea.svg
@@ -0,0 +1,18 @@
+
\ No newline at end of file
diff --git a/src/auth.ts b/src/auth.ts
index 3fb5ddc..0151999 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -1,3 +1,4 @@
+import Gitea from "@/lib/gitea";
import NextAuth from "next-auth";
import { prisma } from "@/lib/prisma";
import GitHub from "next-auth/providers/github";
@@ -5,5 +6,5 @@ import { PrismaAdapter } from "@auth/prisma-adapter";
export const { handlers, signIn, signOut, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
- providers: [GitHub],
+ providers: [GitHub, Gitea],
});
diff --git a/src/lib/gitea.ts b/src/lib/gitea.ts
new file mode 100644
index 0000000..dffc59c
--- /dev/null
+++ b/src/lib/gitea.ts
@@ -0,0 +1,76 @@
+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,
+ };
+ },
+ style: {
+ logo: "/gitea.svg",
+ brandColor: "#609926",
+ },
+ options: config,
+ };
+}