mirror of
https://github.com/MoonTechLab/LunaTV.git
synced 2026-03-06 11:57:32 +08:00
feat: disable register
This commit is contained in:
@@ -135,9 +135,6 @@ interface UserConfigProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
|
const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
|
||||||
const [userSettings, setUserSettings] = useState({
|
|
||||||
enableRegistration: false,
|
|
||||||
});
|
|
||||||
const [showAddUserForm, setShowAddUserForm] = useState(false);
|
const [showAddUserForm, setShowAddUserForm] = useState(false);
|
||||||
const [showChangePasswordForm, setShowChangePasswordForm] = useState(false);
|
const [showChangePasswordForm, setShowChangePasswordForm] = useState(false);
|
||||||
const [newUser, setNewUser] = useState({
|
const [newUser, setNewUser] = useState({
|
||||||
@@ -152,41 +149,9 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
|
|||||||
// 当前登录用户名
|
// 当前登录用户名
|
||||||
const currentUsername = getAuthInfoFromBrowserCookie()?.username || null;
|
const currentUsername = getAuthInfoFromBrowserCookie()?.username || null;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (config?.UserConfig) {
|
|
||||||
setUserSettings({
|
|
||||||
enableRegistration: config.UserConfig.AllowRegister,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
// 切换允许注册设置
|
|
||||||
const toggleAllowRegister = async (value: boolean) => {
|
|
||||||
try {
|
|
||||||
// 先更新本地 UI
|
|
||||||
setUserSettings((prev) => ({ ...prev, enableRegistration: value }));
|
|
||||||
|
|
||||||
const res = await fetch('/api/admin/user', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
action: 'setAllowRegister',
|
|
||||||
allowRegister: value,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const data = await res.json().catch(() => ({}));
|
|
||||||
throw new Error(data.error || `操作失败: ${res.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
await refreshConfig();
|
|
||||||
} catch (err) {
|
|
||||||
showError(err instanceof Error ? err.message : '操作失败');
|
|
||||||
// revert toggle UI
|
|
||||||
setUserSettings((prev) => ({ ...prev, enableRegistration: !value }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBanUser = async (uname: string) => {
|
const handleBanUser = async (uname: string) => {
|
||||||
await handleUserAction('ban', uname);
|
await handleUserAction('ban', uname);
|
||||||
@@ -305,36 +270,7 @@ const UserConfig = ({ config, role, refreshConfig }: UserConfigProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 注册设置 */}
|
|
||||||
<div>
|
|
||||||
<h4 className='text-sm font-medium text-gray-700 dark:text-gray-300 mb-3'>
|
|
||||||
注册设置
|
|
||||||
</h4>
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<label
|
|
||||||
className={`text-gray-700 dark:text-gray-300
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
允许新用户注册
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
onClick={() =>
|
|
||||||
toggleAllowRegister(!userSettings.enableRegistration)
|
|
||||||
}
|
|
||||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 ${userSettings.enableRegistration
|
|
||||||
? 'bg-green-600'
|
|
||||||
: 'bg-gray-200 dark:bg-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${userSettings.enableRegistration
|
|
||||||
? 'translate-x-6'
|
|
||||||
: 'translate-x-1'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 用户列表 */}
|
{/* 用户列表 */}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const ACTIONS = [
|
|||||||
'unban',
|
'unban',
|
||||||
'setAdmin',
|
'setAdmin',
|
||||||
'cancelAdmin',
|
'cancelAdmin',
|
||||||
'setAllowRegister',
|
|
||||||
'changePassword',
|
'changePassword',
|
||||||
'deleteUser',
|
'deleteUser',
|
||||||
] as const;
|
] as const;
|
||||||
@@ -43,12 +42,10 @@ export async function POST(request: NextRequest) {
|
|||||||
const {
|
const {
|
||||||
targetUsername, // 目标用户名
|
targetUsername, // 目标用户名
|
||||||
targetPassword, // 目标用户密码(仅在添加用户时需要)
|
targetPassword, // 目标用户密码(仅在添加用户时需要)
|
||||||
allowRegister,
|
|
||||||
action,
|
action,
|
||||||
} = body as {
|
} = body as {
|
||||||
targetUsername?: string;
|
targetUsername?: string;
|
||||||
targetPassword?: string;
|
targetPassword?: string;
|
||||||
allowRegister?: boolean;
|
|
||||||
action?: (typeof ACTIONS)[number];
|
action?: (typeof ACTIONS)[number];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,12 +53,11 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
|
return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action !== 'setAllowRegister' && !targetUsername) {
|
if (!targetUsername) {
|
||||||
return NextResponse.json({ error: '缺少目标用户名' }, { status: 400 });
|
return NextResponse.json({ error: '缺少目标用户名' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
action !== 'setAllowRegister' &&
|
|
||||||
action !== 'changePassword' &&
|
action !== 'changePassword' &&
|
||||||
action !== 'deleteUser' &&
|
action !== 'deleteUser' &&
|
||||||
username === targetUsername
|
username === targetUsername
|
||||||
@@ -105,188 +101,180 @@ export async function POST(request: NextRequest) {
|
|||||||
// 权限校验逻辑
|
// 权限校验逻辑
|
||||||
const isTargetAdmin = targetEntry?.role === 'admin';
|
const isTargetAdmin = targetEntry?.role === 'admin';
|
||||||
|
|
||||||
if (action === 'setAllowRegister') {
|
switch (action) {
|
||||||
if (typeof allowRegister !== 'boolean') {
|
case 'add': {
|
||||||
return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
|
if (targetEntry) {
|
||||||
}
|
return NextResponse.json({ error: '用户已存在' }, { status: 400 });
|
||||||
adminConfig.UserConfig.AllowRegister = allowRegister;
|
|
||||||
// 保存后直接返回成功(走后面的统一保存逻辑)
|
|
||||||
} else {
|
|
||||||
switch (action) {
|
|
||||||
case 'add': {
|
|
||||||
if (targetEntry) {
|
|
||||||
return NextResponse.json({ error: '用户已存在' }, { status: 400 });
|
|
||||||
}
|
|
||||||
if (!targetPassword) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '缺少目标用户密码' },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await db.registerUser(targetUsername!, targetPassword);
|
|
||||||
// 更新配置
|
|
||||||
adminConfig.UserConfig.Users.push({
|
|
||||||
username: targetUsername!,
|
|
||||||
role: 'user',
|
|
||||||
});
|
|
||||||
targetEntry =
|
|
||||||
adminConfig.UserConfig.Users[
|
|
||||||
adminConfig.UserConfig.Users.length - 1
|
|
||||||
];
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case 'ban': {
|
if (!targetPassword) {
|
||||||
if (!targetEntry) {
|
return NextResponse.json(
|
||||||
return NextResponse.json(
|
{ error: '缺少目标用户密码' },
|
||||||
{ error: '目标用户不存在' },
|
{ status: 400 }
|
||||||
{ status: 404 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isTargetAdmin) {
|
|
||||||
// 目标是管理员
|
|
||||||
if (operatorRole !== 'owner') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '仅站长可封禁管理员' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetEntry.banned = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'unban': {
|
|
||||||
if (!targetEntry) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '目标用户不存在' },
|
|
||||||
{ status: 404 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isTargetAdmin) {
|
|
||||||
if (operatorRole !== 'owner') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '仅站长可操作管理员' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetEntry.banned = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'setAdmin': {
|
|
||||||
if (!targetEntry) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '目标用户不存在' },
|
|
||||||
{ status: 404 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (targetEntry.role === 'admin') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '该用户已是管理员' },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (operatorRole !== 'owner') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '仅站长可设置管理员' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
targetEntry.role = 'admin';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'cancelAdmin': {
|
|
||||||
if (!targetEntry) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '目标用户不存在' },
|
|
||||||
{ status: 404 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (targetEntry.role !== 'admin') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '目标用户不是管理员' },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (operatorRole !== 'owner') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '仅站长可取消管理员' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
targetEntry.role = 'user';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'changePassword': {
|
|
||||||
if (!targetEntry) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '目标用户不存在' },
|
|
||||||
{ status: 404 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!targetPassword) {
|
|
||||||
return NextResponse.json({ error: '缺少新密码' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 权限检查:不允许修改站长密码
|
|
||||||
if (targetEntry.role === 'owner') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '无法修改站长密码' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
isTargetAdmin &&
|
|
||||||
operatorRole !== 'owner' &&
|
|
||||||
username !== targetUsername
|
|
||||||
) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '仅站长可修改其他管理员密码' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.changePassword(targetUsername!, targetPassword);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'deleteUser': {
|
|
||||||
if (!targetEntry) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '目标用户不存在' },
|
|
||||||
{ status: 404 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 权限检查:站长可删除所有用户(除了自己),管理员可删除普通用户
|
|
||||||
if (username === targetUsername) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '不能删除自己' },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTargetAdmin && operatorRole !== 'owner') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '仅站长可删除管理员' },
|
|
||||||
{ status: 401 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.deleteUser(targetUsername!);
|
|
||||||
|
|
||||||
// 从配置中移除用户
|
|
||||||
const userIndex = adminConfig.UserConfig.Users.findIndex(
|
|
||||||
(u) => u.username === targetUsername
|
|
||||||
);
|
);
|
||||||
if (userIndex > -1) {
|
|
||||||
adminConfig.UserConfig.Users.splice(userIndex, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
await db.registerUser(targetUsername!, targetPassword);
|
||||||
return NextResponse.json({ error: '未知操作' }, { status: 400 });
|
// 更新配置
|
||||||
|
adminConfig.UserConfig.Users.push({
|
||||||
|
username: targetUsername!,
|
||||||
|
role: 'user',
|
||||||
|
});
|
||||||
|
targetEntry =
|
||||||
|
adminConfig.UserConfig.Users[
|
||||||
|
adminConfig.UserConfig.Users.length - 1
|
||||||
|
];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
case 'ban': {
|
||||||
|
if (!targetEntry) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不存在' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isTargetAdmin) {
|
||||||
|
// 目标是管理员
|
||||||
|
if (operatorRole !== 'owner') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '仅站长可封禁管理员' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetEntry.banned = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'unban': {
|
||||||
|
if (!targetEntry) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不存在' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (isTargetAdmin) {
|
||||||
|
if (operatorRole !== 'owner') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '仅站长可操作管理员' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
targetEntry.banned = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'setAdmin': {
|
||||||
|
if (!targetEntry) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不存在' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (targetEntry.role === 'admin') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '该用户已是管理员' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (operatorRole !== 'owner') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '仅站长可设置管理员' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
targetEntry.role = 'admin';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'cancelAdmin': {
|
||||||
|
if (!targetEntry) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不存在' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (targetEntry.role !== 'admin') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不是管理员' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (operatorRole !== 'owner') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '仅站长可取消管理员' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
targetEntry.role = 'user';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'changePassword': {
|
||||||
|
if (!targetEntry) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不存在' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!targetPassword) {
|
||||||
|
return NextResponse.json({ error: '缺少新密码' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查:不允许修改站长密码
|
||||||
|
if (targetEntry.role === 'owner') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '无法修改站长密码' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isTargetAdmin &&
|
||||||
|
operatorRole !== 'owner' &&
|
||||||
|
username !== targetUsername
|
||||||
|
) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '仅站长可修改其他管理员密码' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.changePassword(targetUsername!, targetPassword);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'deleteUser': {
|
||||||
|
if (!targetEntry) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '目标用户不存在' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限检查:站长可删除所有用户(除了自己),管理员可删除普通用户
|
||||||
|
if (username === targetUsername) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '不能删除自己' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isTargetAdmin && operatorRole !== 'owner') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '仅站长可删除管理员' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.deleteUser(targetUsername!);
|
||||||
|
|
||||||
|
// 从配置中移除用户
|
||||||
|
const userIndex = adminConfig.UserConfig.Users.findIndex(
|
||||||
|
(u) => u.username === targetUsername
|
||||||
|
);
|
||||||
|
if (userIndex > -1) {
|
||||||
|
adminConfig.UserConfig.Users.splice(userIndex, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return NextResponse.json({ error: '未知操作' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将更新后的配置写入数据库
|
// 将更新后的配置写入数据库
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
/* eslint-disable no-console,@typescript-eslint/no-explicit-any */
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
|
|
||||||
import { getConfig } from '@/lib/config';
|
|
||||||
import { db } from '@/lib/db';
|
|
||||||
|
|
||||||
export const runtime = 'nodejs';
|
|
||||||
|
|
||||||
// 读取存储类型环境变量,默认 localstorage
|
|
||||||
const STORAGE_TYPE =
|
|
||||||
(process.env.NEXT_PUBLIC_STORAGE_TYPE as
|
|
||||||
| 'localstorage'
|
|
||||||
| 'redis'
|
|
||||||
| 'upstash'
|
|
||||||
| 'kvrocks'
|
|
||||||
| undefined) || 'localstorage';
|
|
||||||
|
|
||||||
// 生成签名
|
|
||||||
async function generateSignature(
|
|
||||||
data: string,
|
|
||||||
secret: string
|
|
||||||
): Promise<string> {
|
|
||||||
const encoder = new TextEncoder();
|
|
||||||
const keyData = encoder.encode(secret);
|
|
||||||
const messageData = encoder.encode(data);
|
|
||||||
|
|
||||||
// 导入密钥
|
|
||||||
const key = await crypto.subtle.importKey(
|
|
||||||
'raw',
|
|
||||||
keyData,
|
|
||||||
{ name: 'HMAC', hash: 'SHA-256' },
|
|
||||||
false,
|
|
||||||
['sign']
|
|
||||||
);
|
|
||||||
|
|
||||||
// 生成签名
|
|
||||||
const signature = await crypto.subtle.sign('HMAC', key, messageData);
|
|
||||||
|
|
||||||
// 转换为十六进制字符串
|
|
||||||
return Array.from(new Uint8Array(signature))
|
|
||||||
.map((b) => b.toString(16).padStart(2, '0'))
|
|
||||||
.join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成认证Cookie(带签名)
|
|
||||||
async function generateAuthCookie(username: string): Promise<string> {
|
|
||||||
const authData: any = {
|
|
||||||
role: 'user',
|
|
||||||
username,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用process.env.PASSWORD作为签名密钥,而不是用户密码
|
|
||||||
const signingKey = process.env.PASSWORD || '';
|
|
||||||
const signature = await generateSignature(username, signingKey);
|
|
||||||
authData.signature = signature;
|
|
||||||
|
|
||||||
return encodeURIComponent(JSON.stringify(authData));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
|
||||||
try {
|
|
||||||
// localstorage 模式下不支持注册
|
|
||||||
if (STORAGE_TYPE === 'localstorage') {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ error: '当前模式不支持注册' },
|
|
||||||
{ status: 400 }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const config = await getConfig();
|
|
||||||
// 校验是否开放注册
|
|
||||||
if (!config.UserConfig.AllowRegister) {
|
|
||||||
return NextResponse.json({ error: '当前未开放注册' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { username, password } = await req.json();
|
|
||||||
|
|
||||||
if (!username || typeof username !== 'string') {
|
|
||||||
return NextResponse.json({ error: '用户名不能为空' }, { status: 400 });
|
|
||||||
}
|
|
||||||
if (!password || typeof password !== 'string') {
|
|
||||||
return NextResponse.json({ error: '密码不能为空' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否和管理员重复
|
|
||||||
if (username === process.env.USERNAME) {
|
|
||||||
return NextResponse.json({ error: '用户已存在' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 检查用户是否已存在
|
|
||||||
const exist = await db.checkUserExist(username);
|
|
||||||
if (exist) {
|
|
||||||
return NextResponse.json({ error: '用户已存在' }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.registerUser(username, password);
|
|
||||||
|
|
||||||
// 添加到配置中并保存
|
|
||||||
config.UserConfig.Users.push({
|
|
||||||
username,
|
|
||||||
role: 'user',
|
|
||||||
});
|
|
||||||
await db.saveAdminConfig(config);
|
|
||||||
|
|
||||||
// 注册成功,设置认证cookie
|
|
||||||
const response = NextResponse.json({ ok: true });
|
|
||||||
const cookieValue = await generateAuthCookie(username);
|
|
||||||
const expires = new Date();
|
|
||||||
expires.setDate(expires.getDate() + 7); // 7天过期
|
|
||||||
|
|
||||||
response.cookies.set('auth', cookieValue, {
|
|
||||||
path: '/',
|
|
||||||
expires,
|
|
||||||
sameSite: 'lax', // 改为 lax 以支持 PWA
|
|
||||||
httpOnly: false, // PWA 需要客户端可访问
|
|
||||||
secure: false, // 根据协议自动设置
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('数据库注册失败', err);
|
|
||||||
return NextResponse.json({ error: '数据库错误' }, { status: 500 });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('注册接口异常', error);
|
|
||||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,7 +46,7 @@ export default async function RootLayout({
|
|||||||
let announcement =
|
let announcement =
|
||||||
process.env.ANNOUNCEMENT ||
|
process.env.ANNOUNCEMENT ||
|
||||||
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。';
|
'本网站仅提供影视信息搜索服务,所有内容均来自第三方网站。本站不存储任何视频资源,不对任何内容的准确性、合法性、完整性负责。';
|
||||||
let enableRegister = process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true';
|
|
||||||
let doubanProxyType = process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'melody-cdn-sharon';
|
let doubanProxyType = process.env.NEXT_PUBLIC_DOUBAN_PROXY_TYPE || 'melody-cdn-sharon';
|
||||||
let doubanProxy = process.env.NEXT_PUBLIC_DOUBAN_PROXY || '';
|
let doubanProxy = process.env.NEXT_PUBLIC_DOUBAN_PROXY || '';
|
||||||
let doubanImageProxyType =
|
let doubanImageProxyType =
|
||||||
@@ -64,7 +64,7 @@ export default async function RootLayout({
|
|||||||
const config = await getConfig();
|
const config = await getConfig();
|
||||||
siteName = config.SiteConfig.SiteName;
|
siteName = config.SiteConfig.SiteName;
|
||||||
announcement = config.SiteConfig.Announcement;
|
announcement = config.SiteConfig.Announcement;
|
||||||
enableRegister = config.UserConfig.AllowRegister;
|
|
||||||
doubanProxyType = config.SiteConfig.DoubanProxyType;
|
doubanProxyType = config.SiteConfig.DoubanProxyType;
|
||||||
doubanProxy = config.SiteConfig.DoubanProxy;
|
doubanProxy = config.SiteConfig.DoubanProxy;
|
||||||
doubanImageProxyType = config.SiteConfig.DoubanImageProxyType;
|
doubanImageProxyType = config.SiteConfig.DoubanImageProxyType;
|
||||||
@@ -83,7 +83,6 @@ export default async function RootLayout({
|
|||||||
// 将运行时配置注入到全局 window 对象,供客户端在运行时读取
|
// 将运行时配置注入到全局 window 对象,供客户端在运行时读取
|
||||||
const runtimeConfig = {
|
const runtimeConfig = {
|
||||||
STORAGE_TYPE: process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage',
|
STORAGE_TYPE: process.env.NEXT_PUBLIC_STORAGE_TYPE || 'localstorage',
|
||||||
ENABLE_REGISTER: enableRegister,
|
|
||||||
DOUBAN_PROXY_TYPE: doubanProxyType,
|
DOUBAN_PROXY_TYPE: doubanProxyType,
|
||||||
DOUBAN_PROXY: doubanProxy,
|
DOUBAN_PROXY: doubanProxy,
|
||||||
DOUBAN_IMAGE_PROXY_TYPE: doubanImageProxyType,
|
DOUBAN_IMAGE_PROXY_TYPE: doubanImageProxyType,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ function LoginPageClient() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [shouldAskUsername, setShouldAskUsername] = useState(false);
|
const [shouldAskUsername, setShouldAskUsername] = useState(false);
|
||||||
const [enableRegister, setEnableRegister] = useState(false);
|
|
||||||
const { siteName } = useSite();
|
const { siteName } = useSite();
|
||||||
|
|
||||||
// 在客户端挂载后设置配置
|
// 在客户端挂载后设置配置
|
||||||
@@ -83,9 +83,6 @@ function LoginPageClient() {
|
|||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
const storageType = (window as any).RUNTIME_CONFIG?.STORAGE_TYPE;
|
const storageType = (window as any).RUNTIME_CONFIG?.STORAGE_TYPE;
|
||||||
setShouldAskUsername(storageType && storageType !== 'localstorage');
|
setShouldAskUsername(storageType && storageType !== 'localstorage');
|
||||||
setEnableRegister(
|
|
||||||
Boolean((window as any).RUNTIME_CONFIG?.ENABLE_REGISTER)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -122,32 +119,7 @@ function LoginPageClient() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理注册逻辑
|
|
||||||
const handleRegister = async () => {
|
|
||||||
setError(null);
|
|
||||||
if (!password || !username) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
const res = await fetch('/api/register', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const redirect = searchParams.get('redirect') || '/';
|
|
||||||
router.replace(redirect);
|
|
||||||
} else {
|
|
||||||
const data = await res.json().catch(() => ({}));
|
|
||||||
setError(data.error ?? '服务器错误');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setError('网络错误,请稍后重试');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='relative min-h-screen flex items-center justify-center px-4 overflow-hidden'>
|
<div className='relative min-h-screen flex items-center justify-center px-4 overflow-hidden'>
|
||||||
@@ -195,38 +167,16 @@ function LoginPageClient() {
|
|||||||
<p className='text-sm text-red-600 dark:text-red-400'>{error}</p>
|
<p className='text-sm text-red-600 dark:text-red-400'>{error}</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 登录 / 注册按钮 */}
|
{/* 登录按钮 */}
|
||||||
{shouldAskUsername && enableRegister ? (
|
<button
|
||||||
<div className='flex gap-4'>
|
type='submit'
|
||||||
<button
|
disabled={
|
||||||
type='button'
|
!password || loading || (shouldAskUsername && !username)
|
||||||
onClick={handleRegister}
|
}
|
||||||
disabled={!password || !username || loading}
|
className='inline-flex w-full justify-center rounded-lg bg-green-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:from-green-600 hover:to-blue-600 disabled:cursor-not-allowed disabled:opacity-50'
|
||||||
className='flex-1 inline-flex justify-center rounded-lg bg-blue-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50'
|
>
|
||||||
>
|
{loading ? '登录中...' : '登录'}
|
||||||
{loading ? '注册中...' : '注册'}
|
</button>
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type='submit'
|
|
||||||
disabled={
|
|
||||||
!password || loading || (shouldAskUsername && !username)
|
|
||||||
}
|
|
||||||
className='flex-1 inline-flex justify-center rounded-lg bg-green-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:from-green-600 hover:to-blue-600 disabled:cursor-not-allowed disabled:opacity-50'
|
|
||||||
>
|
|
||||||
{loading ? '登录中...' : '登录'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type='submit'
|
|
||||||
disabled={
|
|
||||||
!password || loading || (shouldAskUsername && !username)
|
|
||||||
}
|
|
||||||
className='inline-flex w-full justify-center rounded-lg bg-green-600 py-3 text-base font-semibold text-white shadow-lg transition-all duration-200 hover:from-green-600 hover:to-blue-600 disabled:cursor-not-allowed disabled:opacity-50'
|
|
||||||
>
|
|
||||||
{loading ? '登录中...' : '登录'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export interface AdminConfig {
|
|||||||
FluidSearch: boolean;
|
FluidSearch: boolean;
|
||||||
};
|
};
|
||||||
UserConfig: {
|
UserConfig: {
|
||||||
AllowRegister: boolean;
|
|
||||||
Users: {
|
Users: {
|
||||||
username: string;
|
username: string;
|
||||||
role: 'user' | 'admin' | 'owner';
|
role: 'user' | 'admin' | 'owner';
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ async function getInitConfig(configFile: string, subConfig: {
|
|||||||
process.env.NEXT_PUBLIC_FLUID_SEARCH !== 'false',
|
process.env.NEXT_PUBLIC_FLUID_SEARCH !== 'false',
|
||||||
},
|
},
|
||||||
UserConfig: {
|
UserConfig: {
|
||||||
AllowRegister: process.env.NEXT_PUBLIC_ENABLE_REGISTER === 'true',
|
|
||||||
Users: [],
|
Users: [],
|
||||||
},
|
},
|
||||||
SourceConfig: [],
|
SourceConfig: [],
|
||||||
@@ -251,7 +250,7 @@ export async function getConfig(): Promise<AdminConfig> {
|
|||||||
export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
|
export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
|
||||||
// 确保必要的属性存在和初始化
|
// 确保必要的属性存在和初始化
|
||||||
if (!adminConfig.UserConfig) {
|
if (!adminConfig.UserConfig) {
|
||||||
adminConfig.UserConfig = { AllowRegister: false, Users: [] };
|
adminConfig.UserConfig = { Users: [] };
|
||||||
}
|
}
|
||||||
if (!adminConfig.UserConfig.Users || !Array.isArray(adminConfig.UserConfig.Users)) {
|
if (!adminConfig.UserConfig.Users || !Array.isArray(adminConfig.UserConfig.Users)) {
|
||||||
adminConfig.UserConfig.Users = [];
|
adminConfig.UserConfig.Users = [];
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ async function searchWithCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (apiSite.key === 'xiaomaomi') {
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
!data ||
|
!data ||
|
||||||
!data.list ||
|
!data.list ||
|
||||||
|
|||||||
Reference in New Issue
Block a user