diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index 43001b4..faeaf89 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -297,6 +297,12 @@ interface SiteConfig {
DanmakuApiBase: string;
DanmakuApiToken: string;
EnableComments: boolean;
+ EnableRegistration?: boolean;
+ RegistrationRequireTurnstile?: boolean;
+ LoginRequireTurnstile?: boolean;
+ TurnstileSiteKey?: string;
+ TurnstileSecretKey?: string;
+ DefaultUserTags?: string[];
}
// 视频源数据类型
@@ -4577,6 +4583,12 @@ const SiteConfigComponent = ({
DanmakuApiBase: 'http://localhost:9321',
DanmakuApiToken: '87654321',
EnableComments: false,
+ EnableRegistration: false,
+ RegistrationRequireTurnstile: false,
+ LoginRequireTurnstile: false,
+ TurnstileSiteKey: '',
+ TurnstileSecretKey: '',
+ DefaultUserTags: [],
});
// 豆瓣数据源相关状态
@@ -4644,6 +4656,12 @@ const SiteConfigComponent = ({
config.SiteConfig.DanmakuApiBase || 'http://localhost:9321',
DanmakuApiToken: config.SiteConfig.DanmakuApiToken || '87654321',
EnableComments: config.SiteConfig.EnableComments || false,
+ EnableRegistration: config.SiteConfig.EnableRegistration || false,
+ RegistrationRequireTurnstile: config.SiteConfig.RegistrationRequireTurnstile || false,
+ LoginRequireTurnstile: config.SiteConfig.LoginRequireTurnstile || false,
+ TurnstileSiteKey: config.SiteConfig.TurnstileSiteKey || '',
+ TurnstileSecretKey: config.SiteConfig.TurnstileSecretKey || '',
+ DefaultUserTags: config.SiteConfig.DefaultUserTags || [],
});
}
}, [config]);
@@ -5204,6 +5222,196 @@ const SiteConfigComponent = ({
+ {/* 注册相关配置 */}
+
+
+ 注册配置
+
+
+ {/* 开启注册 */}
+
+
+
+
+
+
+ 开启后登录页面将显示注册按钮,允许用户自行注册账号。
+
+
+
+ {/* 注册启用Cloudflare Turnstile */}
+
+
+
+
+
+
+ 开启后注册时需要通过Cloudflare Turnstile人机验证。
+
+
+
+ {/* 登录启用Cloudflare Turnstile */}
+
+
+
+
+
+
+ 开启后登录时需要通过Cloudflare Turnstile人机验证。
+
+
+
+ {/* Cloudflare Turnstile Site Key */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ TurnstileSiteKey: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 在Cloudflare Dashboard中获取的Site Key(公钥)
+
+
+
+ {/* Cloudflare Turnstile Secret Key */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ TurnstileSecretKey: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 在Cloudflare Dashboard中获取的Secret Key(私钥),用于服务端验证
+
+
+
+ {/* 默认用户组 */}
+
+
+
+
+ 新注册的用户将自动分配到选中的用户组,选择"无用户组"为无限制
+
+
+
+
{/* 操作按钮 */}
+ {/* Cloudflare Turnstile */}
+ {siteConfig?.LoginRequireTurnstile && siteConfig?.TurnstileSiteKey && (
+
+ )}
+
{error && (
{error}
)}
@@ -235,12 +304,26 @@ function LoginPageClient() {
+
+ {/* 注册按钮 */}
+ {siteConfig?.EnableRegistration && shouldAskUsername && (
+
+
+
+ )}
diff --git a/src/app/register/page.tsx b/src/app/register/page.tsx
new file mode 100644
index 0000000..65410d5
--- /dev/null
+++ b/src/app/register/page.tsx
@@ -0,0 +1,341 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+'use client';
+
+import { AlertCircle, CheckCircle, Eye, EyeOff } from 'lucide-react';
+import { useRouter, useSearchParams } from 'next/navigation';
+import { Suspense, useEffect, useState } from 'react';
+
+import { CURRENT_VERSION } from '@/lib/version';
+import { checkForUpdates, UpdateStatus } from '@/lib/version_check';
+
+import { useSite } from '@/components/SiteProvider';
+import { ThemeToggle } from '@/components/ThemeToggle';
+
+// 版本显示组件
+function VersionDisplay() {
+ const [updateStatus, setUpdateStatus] = useState(null);
+ const [isChecking, setIsChecking] = useState(true);
+
+ useEffect(() => {
+ const checkUpdate = async () => {
+ try {
+ const status = await checkForUpdates();
+ setUpdateStatus(status);
+ } catch (_) {
+ // do nothing
+ } finally {
+ setIsChecking(false);
+ }
+ };
+
+ checkUpdate();
+ }, []);
+
+ return (
+
+ );
+}
+
+function RegisterPageClient() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const [username, setUsername] = useState('');
+ const [password, setPassword] = useState('');
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+ const [turnstileToken, setTurnstileToken] = useState(null);
+ const [turnstileLoaded, setTurnstileLoaded] = useState(false);
+ const [siteConfig, setSiteConfig] = useState(null);
+
+ const { siteName } = useSite();
+
+ // 获取站点配置
+ useEffect(() => {
+ const fetchConfig = async () => {
+ try {
+ const res = await fetch('/api/server-config');
+ if (res.ok) {
+ const config = await res.json();
+ setSiteConfig(config);
+
+ // 如果未开启注册,重定向到登录页
+ if (!config.EnableRegistration) {
+ router.replace('/login');
+ }
+ }
+ } catch (error) {
+ console.error('Failed to fetch config:', error);
+ }
+ };
+
+ fetchConfig();
+ }, [router]);
+
+ // 加载Cloudflare Turnstile脚本
+ useEffect(() => {
+ if (!siteConfig?.RegistrationRequireTurnstile || !siteConfig?.TurnstileSiteKey) {
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
+ script.async = true;
+ script.defer = true;
+ script.onload = () => {
+ setTurnstileLoaded(true);
+ };
+ document.body.appendChild(script);
+
+ return () => {
+ document.body.removeChild(script);
+ };
+ }, [siteConfig]);
+
+ // 渲染Turnstile组件
+ useEffect(() => {
+ if (!turnstileLoaded || !siteConfig?.TurnstileSiteKey) {
+ return;
+ }
+
+ const container = document.getElementById('turnstile-container');
+ if (container && (window as any).turnstile) {
+ (window as any).turnstile.render('#turnstile-container', {
+ sitekey: siteConfig.TurnstileSiteKey,
+ callback: (token: string) => {
+ setTurnstileToken(token);
+ },
+ });
+ }
+ }, [turnstileLoaded, siteConfig]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError(null);
+
+ if (!username || !password || !confirmPassword) {
+ setError('请填写所有字段');
+ return;
+ }
+
+ if (password !== confirmPassword) {
+ setError('两次输入的密码不一致');
+ return;
+ }
+
+ if (password.length < 6) {
+ setError('密码长度至少为6位');
+ return;
+ }
+
+ // 检查Turnstile验证
+ if (siteConfig?.RegistrationRequireTurnstile && !turnstileToken) {
+ setError('请完成人机验证');
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const res = await fetch('/api/register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ username,
+ password,
+ turnstileToken: siteConfig?.RegistrationRequireTurnstile ? turnstileToken : undefined,
+ }),
+ });
+
+ if (res.ok) {
+ // 注册成功,跳转到登录页
+ const redirect = searchParams.get('redirect') || '/login';
+ router.replace(redirect);
+ } else if (res.status === 400) {
+ const data = await res.json().catch(() => ({}));
+ setError(data.error || '注册失败');
+ } else if (res.status === 409) {
+ setError('用户名已存在');
+ } else {
+ const data = await res.json().catch(() => ({}));
+ setError(data.error ?? '服务器错误');
+ }
+ } catch (error) {
+ setError('网络错误,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 如果配置未加载或未开启注册,显示加载中
+ if (!siteConfig) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {siteName}
+
+
+ 创建新账号
+
+
+
+
+ {/* 版本信息显示 */}
+
+
+ );
+}
+
+export default function RegisterPage() {
+ return (
+ Loading...}>
+
+
+ );
+}
diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts
index 2181eb1..8d65d0d 100644
--- a/src/lib/admin.types.ts
+++ b/src/lib/admin.types.ts
@@ -24,6 +24,13 @@ export interface AdminConfig {
// 自定义去广告代码
CustomAdFilterCode?: string;
CustomAdFilterVersion?: number; // 代码版本号(时间戳)
+ // 注册相关配置
+ EnableRegistration?: boolean; // 开启注册
+ RegistrationRequireTurnstile?: boolean; // 注册启用Cloudflare Turnstile
+ LoginRequireTurnstile?: boolean; // 登录启用Cloudflare Turnstile
+ TurnstileSiteKey?: string; // Cloudflare Turnstile Site Key
+ TurnstileSecretKey?: string; // Cloudflare Turnstile Secret Key
+ DefaultUserTags?: string[]; // 新注册用户的默认用户组
};
UserConfig: {
Users: {
diff --git a/src/middleware.ts b/src/middleware.ts
index 88d6c6e..408a695 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean {
// 配置middleware匹配规则
export const config = {
matcher: [
- '/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/proxy-m3u8|api/cms-proxy|api/tvbox/subscribe|api/theme/css).*)',
+ '/((?!_next/static|_next/image|favicon.ico|login|register|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/proxy-m3u8|api/cms-proxy|api/tvbox/subscribe|api/theme/css).*)',
],
};