diff --git a/.env.example b/.env.example
index 5786fec3..2983139d 100644
--- a/.env.example
+++ b/.env.example
@@ -18,3 +18,6 @@ NEXTAUTH_SECRET="your-super-secret-key-change-in-production"
# S3_ACCESS_KEY_ID=""
# S3_SECRET_ACCESS_KEY=""
# S3_ENDPOINT="" # For S3-compatible services like MinIO
+
+# GITHUB_CLIENT_ID=your_client_id
+# GITHUB_CLIENT_SECRET=your_client_secret
\ No newline at end of file
diff --git a/messages/ar.json b/messages/ar.json
index df8a92f6..2b88d407 100644
--- a/messages/ar.json
+++ b/messages/ar.json
@@ -45,6 +45,7 @@
"auth": {
"login": "تسجيل الدخول",
"loginDescription": "أدخل بياناتك للمتابعة",
+ "loginDescriptionOAuth": "سجل الدخول بحسابك للمتابعة",
"register": "إنشاء حساب",
"registerDescription": "أنشئ حساباً للبدء",
"logout": "تسجيل الخروج",
diff --git a/messages/en.json b/messages/en.json
index b0ac502b..5342deb9 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -45,6 +45,7 @@
"auth": {
"login": "Login",
"loginDescription": "Enter your credentials to continue",
+ "loginDescriptionOAuth": "Sign in with your account to continue",
"register": "Register",
"registerDescription": "Create an account to get started",
"logout": "Logout",
diff --git a/messages/es.json b/messages/es.json
index 50483d35..dceed079 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -45,6 +45,7 @@
"auth": {
"login": "Iniciar sesión",
"loginDescription": "Ingresa tus credenciales para continuar",
+ "loginDescriptionOAuth": "Inicia sesión con tu cuenta para continuar",
"register": "Registrarse",
"registerDescription": "Crea una cuenta para comenzar",
"logout": "Cerrar sesión",
diff --git a/messages/ja.json b/messages/ja.json
index 5975f636..cb8a3b81 100644
--- a/messages/ja.json
+++ b/messages/ja.json
@@ -45,6 +45,7 @@
"auth": {
"login": "ログイン",
"loginDescription": "続行するには認証情報を入力してください",
+ "loginDescriptionOAuth": "アカウントでサインインして続行",
"register": "登録",
"registerDescription": "アカウントを作成して始めましょう",
"logout": "ログアウト",
diff --git a/messages/tr.json b/messages/tr.json
index eaae3b4d..f1727ec0 100644
--- a/messages/tr.json
+++ b/messages/tr.json
@@ -45,6 +45,7 @@
"auth": {
"login": "Giriş Yap",
"loginDescription": "Devam etmek için bilgilerinizi girin",
+ "loginDescriptionOAuth": "Devam etmek için hesabınızla giriş yapın",
"register": "Kayıt Ol",
"registerDescription": "Başlamak için bir hesap oluşturun",
"logout": "Çıkış Yap",
diff --git a/messages/zh.json b/messages/zh.json
index f9331e74..d7eec5a6 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -45,6 +45,7 @@
"auth": {
"login": "登录",
"loginDescription": "输入您的凭据以继续",
+ "loginDescriptionOAuth": "使用您的账户登录以继续",
"register": "注册",
"registerDescription": "创建账户以开始使用",
"logout": "退出登录",
diff --git a/prompts.config.ts b/prompts.config.ts
index 55ede862..ab8221e2 100644
--- a/prompts.config.ts
+++ b/prompts.config.ts
@@ -25,10 +25,10 @@ export default defineConfig({
// Authentication plugin
auth: {
- // Available: "credentials" | "google" | "azure" | custom
- provider: "credentials",
+ // Available: "credentials" | "google" | "azure" | "github" | custom
+ provider: "github",
// Allow public registration
- allowRegistration: true,
+ allowRegistration: false,
},
// Storage plugin for media uploads
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
index 5ea2a2f1..a4ef1622 100644
--- a/src/app/(auth)/login/page.tsx
+++ b/src/app/(auth)/login/page.tsx
@@ -1,7 +1,8 @@
import { Metadata } from "next";
import Link from "next/link";
import { getTranslations } from "next-intl/server";
-import { LoginForm } from "@/components/auth/login-form";
+import { getConfig } from "@/lib/config";
+import { AuthContent } from "@/components/auth/auth-content";
export const metadata: Metadata = {
title: "Login",
@@ -10,21 +11,27 @@ export const metadata: Metadata = {
export default async function LoginPage() {
const t = await getTranslations("auth");
+ const config = await getConfig();
+ const isCredentials = config.auth.provider === "credentials";
return (
{t("login")}
-
{t("loginDescription")}
+
+ {isCredentials ? t("loginDescription") : t("loginDescriptionOAuth")}
+
-
- {t("noAccount")}{" "}
- {t("register")}
-
+ {isCredentials && (
+
+ {t("noAccount")}{" "}
+ {t("register")}
+
+ )}
);
diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx
index 5e723041..9718d8da 100644
--- a/src/app/(auth)/register/page.tsx
+++ b/src/app/(auth)/register/page.tsx
@@ -1,7 +1,9 @@
import { Metadata } from "next";
import Link from "next/link";
+import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
-import { RegisterForm } from "@/components/auth/register-form";
+import { getConfig } from "@/lib/config";
+import { AuthContent } from "@/components/auth/auth-content";
export const metadata: Metadata = {
title: "Register",
@@ -10,6 +12,13 @@ export const metadata: Metadata = {
export default async function RegisterPage() {
const t = await getTranslations("auth");
+ const config = await getConfig();
+ const isCredentials = config.auth.provider === "credentials";
+
+ // Block registration if disabled or using OAuth providers
+ if (!isCredentials || !config.auth.allowRegistration) {
+ redirect("/login");
+ }
return (
@@ -19,7 +28,7 @@ export default async function RegisterPage() {
{t("registerDescription")}
{t("hasAccount")}{" "}
diff --git a/src/app/api/auth/register/route.ts b/src/app/api/auth/register/route.ts
index 4c9d6482..36327f4b 100644
--- a/src/app/api/auth/register/route.ts
+++ b/src/app/api/auth/register/route.ts
@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { z } from "zod";
import { db } from "@/lib/db";
+import { getConfig } from "@/lib/config";
const registerSchema = z.object({
name: z.string().min(2),
@@ -12,6 +13,15 @@ const registerSchema = z.object({
export async function POST(request: Request) {
try {
+ // Check if registration is allowed
+ const config = await getConfig();
+ if (!config.auth.allowRegistration) {
+ return NextResponse.json(
+ { error: "registration_disabled", message: "Registration is disabled" },
+ { status: 403 }
+ );
+ }
+
const body = await request.json();
const parsed = registerSchema.safeParse(body);
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 74897ca3..8c89a18a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -89,7 +89,7 @@ export default async function RootLayout({
-
+
{children}
diff --git a/src/components/auth/auth-content.tsx b/src/components/auth/auth-content.tsx
new file mode 100644
index 00000000..30ceb336
--- /dev/null
+++ b/src/components/auth/auth-content.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { LoginForm } from "./login-form";
+import { RegisterForm } from "./register-form";
+import { OAuthButton } from "./oauth-button";
+
+interface AuthContentProps {
+ provider: string;
+ mode: "login" | "register";
+}
+
+const providerNames: Record = {
+ github: "GitHub",
+ google: "Google",
+ azure: "Microsoft",
+ credentials: "Email",
+};
+
+export function AuthContent({ provider, mode }: AuthContentProps) {
+ // For OAuth providers, show the OAuth button
+ if (provider !== "credentials") {
+ return (
+
+ );
+ }
+
+ // For credentials, show the appropriate form
+ return mode === "login" ? : ;
+}
diff --git a/src/components/auth/oauth-button.tsx b/src/components/auth/oauth-button.tsx
new file mode 100644
index 00000000..11c9cbe0
--- /dev/null
+++ b/src/components/auth/oauth-button.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import { useState } from "react";
+import { signIn } from "next-auth/react";
+import { useTranslations } from "next-intl";
+import { Loader2 } from "lucide-react";
+import { Button } from "@/components/ui/button";
+
+interface OAuthButtonProps {
+ provider: string;
+ providerName: string;
+}
+
+const providerIcons: Record = {
+ github: (
+
+ ),
+ google: (
+
+ ),
+ azure: (
+
+ ),
+};
+
+export function OAuthButton({ provider, providerName }: OAuthButtonProps) {
+ const t = useTranslations("auth");
+ const [isLoading, setIsLoading] = useState(false);
+
+ const handleSignIn = async () => {
+ setIsLoading(true);
+ try {
+ await signIn(provider, { callbackUrl: "/" });
+ } catch (error) {
+ console.error("Sign in error:", error);
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx
index 857dcdcc..e31ca913 100644
--- a/src/components/layout/header.tsx
+++ b/src/components/layout/header.tsx
@@ -42,7 +42,11 @@ const languages = [
{ code: "ar", name: "العربية" },
];
-export function Header() {
+interface HeaderProps {
+ showRegister?: boolean;
+}
+
+export function Header({ showRegister = true }: HeaderProps) {
const { data: session } = useSession();
const t = useTranslations();
const { theme, setTheme } = useTheme();
@@ -230,9 +234,11 @@ export function Header() {
-
+ {showRegister && (
+
+ )}
)}
diff --git a/src/lib/config/index.ts b/src/lib/config/index.ts
index a5ea260b..4701b832 100644
--- a/src/lib/config/index.ts
+++ b/src/lib/config/index.ts
@@ -19,7 +19,7 @@ export interface ThemeConfig {
}
export interface AuthConfig {
- provider: "credentials" | "google" | "azure" | string;
+ provider: "credentials" | "google" | "azure" | "github" | string;
allowRegistration: boolean;
}
diff --git a/src/lib/plugins/auth/github.ts b/src/lib/plugins/auth/github.ts
new file mode 100644
index 00000000..50e4f781
--- /dev/null
+++ b/src/lib/plugins/auth/github.ts
@@ -0,0 +1,12 @@
+import GitHub from "next-auth/providers/github";
+import type { AuthPlugin } from "../types";
+
+export const githubPlugin: AuthPlugin = {
+ id: "github",
+ name: "GitHub",
+ getProvider: () =>
+ GitHub({
+ clientId: process.env.GITHUB_CLIENT_ID!,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET!,
+ }),
+};
diff --git a/src/lib/plugins/auth/index.ts b/src/lib/plugins/auth/index.ts
index facd4a4c..b3da030b 100644
--- a/src/lib/plugins/auth/index.ts
+++ b/src/lib/plugins/auth/index.ts
@@ -2,12 +2,14 @@ import { registerAuthPlugin } from "../registry";
import { credentialsPlugin } from "./credentials";
import { googlePlugin } from "./google";
import { azurePlugin } from "./azure";
+import { githubPlugin } from "./github";
// Register all built-in auth plugins
export function registerBuiltInAuthPlugins(): void {
registerAuthPlugin(credentialsPlugin);
registerAuthPlugin(googlePlugin);
registerAuthPlugin(azurePlugin);
+ registerAuthPlugin(githubPlugin);
}
-export { credentialsPlugin, googlePlugin, azurePlugin };
+export { credentialsPlugin, googlePlugin, azurePlugin, githubPlugin };
diff --git a/src/lib/plugins/index.ts b/src/lib/plugins/index.ts
index c41d2f78..0e2585c3 100644
--- a/src/lib/plugins/index.ts
+++ b/src/lib/plugins/index.ts
@@ -30,7 +30,7 @@ export async function getConfiguredAuthPlugin() {
if (!plugin) {
throw new Error(
`Auth plugin "${config.auth.provider}" not found. ` +
- `Available plugins: credentials, google, azure`
+ `Available plugins: credentials, google, azure, github`
);
}