添加用户注册
This commit is contained in:
@@ -44,6 +44,12 @@ export async function POST(request: NextRequest) {
|
||||
EnableComments,
|
||||
CustomAdFilterCode,
|
||||
CustomAdFilterVersion,
|
||||
EnableRegistration,
|
||||
RegistrationRequireTurnstile,
|
||||
LoginRequireTurnstile,
|
||||
TurnstileSiteKey,
|
||||
TurnstileSecretKey,
|
||||
DefaultUserTags,
|
||||
} = body as {
|
||||
SiteName: string;
|
||||
Announcement: string;
|
||||
@@ -60,6 +66,12 @@ export async function POST(request: NextRequest) {
|
||||
EnableComments: boolean;
|
||||
CustomAdFilterCode?: string;
|
||||
CustomAdFilterVersion?: number;
|
||||
EnableRegistration?: boolean;
|
||||
RegistrationRequireTurnstile?: boolean;
|
||||
LoginRequireTurnstile?: boolean;
|
||||
TurnstileSiteKey?: string;
|
||||
TurnstileSecretKey?: string;
|
||||
DefaultUserTags?: string[];
|
||||
};
|
||||
|
||||
// 参数校验
|
||||
@@ -78,7 +90,13 @@ export async function POST(request: NextRequest) {
|
||||
typeof DanmakuApiToken !== 'string' ||
|
||||
typeof EnableComments !== 'boolean' ||
|
||||
(CustomAdFilterCode !== undefined && typeof CustomAdFilterCode !== 'string') ||
|
||||
(CustomAdFilterVersion !== undefined && typeof CustomAdFilterVersion !== 'number')
|
||||
(CustomAdFilterVersion !== undefined && typeof CustomAdFilterVersion !== 'number') ||
|
||||
(EnableRegistration !== undefined && typeof EnableRegistration !== 'boolean') ||
|
||||
(RegistrationRequireTurnstile !== undefined && typeof RegistrationRequireTurnstile !== 'boolean') ||
|
||||
(LoginRequireTurnstile !== undefined && typeof LoginRequireTurnstile !== 'boolean') ||
|
||||
(TurnstileSiteKey !== undefined && typeof TurnstileSiteKey !== 'string') ||
|
||||
(TurnstileSecretKey !== undefined && typeof TurnstileSecretKey !== 'string') ||
|
||||
(DefaultUserTags !== undefined && !Array.isArray(DefaultUserTags))
|
||||
) {
|
||||
return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
|
||||
}
|
||||
@@ -113,6 +131,12 @@ export async function POST(request: NextRequest) {
|
||||
EnableComments,
|
||||
CustomAdFilterCode,
|
||||
CustomAdFilterVersion,
|
||||
EnableRegistration,
|
||||
RegistrationRequireTurnstile,
|
||||
LoginRequireTurnstile,
|
||||
TurnstileSiteKey,
|
||||
TurnstileSecretKey,
|
||||
DefaultUserTags,
|
||||
};
|
||||
|
||||
// 写入数据库
|
||||
|
||||
@@ -67,8 +67,34 @@ async function generateAuthCookie(
|
||||
return encodeURIComponent(JSON.stringify(authData));
|
||||
}
|
||||
|
||||
// 验证Cloudflare Turnstile Token
|
||||
async function verifyTurnstileToken(token: string, secretKey: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
secret: secretKey,
|
||||
response: token,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return data.success === true;
|
||||
} catch (error) {
|
||||
console.error('Turnstile验证失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
// 获取站点配置
|
||||
const adminConfig = await getConfig();
|
||||
const siteConfig = adminConfig.SiteConfig;
|
||||
|
||||
// 本地 / localStorage 模式——仅校验固定密码
|
||||
if (STORAGE_TYPE === 'localstorage') {
|
||||
const envPassword = process.env.PASSWORD;
|
||||
@@ -124,7 +150,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
// 数据库 / redis 模式——校验用户名并尝试连接数据库
|
||||
const { username, password } = await req.json();
|
||||
const { username, password, turnstileToken } = await req.json();
|
||||
|
||||
if (!username || typeof username !== 'string') {
|
||||
return NextResponse.json({ error: '用户名不能为空' }, { status: 400 });
|
||||
@@ -133,6 +159,33 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: '密码不能为空' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 如果开启了Turnstile验证
|
||||
if (siteConfig.LoginRequireTurnstile) {
|
||||
if (!turnstileToken) {
|
||||
return NextResponse.json(
|
||||
{ error: '请完成人机验证' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!siteConfig.TurnstileSecretKey) {
|
||||
console.error('Turnstile Secret Key未配置');
|
||||
return NextResponse.json(
|
||||
{ error: '服务器配置错误' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// 验证Turnstile Token
|
||||
const isValid = await verifyTurnstileToken(turnstileToken, siteConfig.TurnstileSecretKey);
|
||||
if (!isValid) {
|
||||
return NextResponse.json(
|
||||
{ error: '人机验证失败,请重试' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 可能是站长,直接读环境变量
|
||||
if (
|
||||
username === process.env.USERNAME &&
|
||||
|
||||
164
src/app/api/register/route.ts
Normal file
164
src/app/api/register/route.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/* 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';
|
||||
|
||||
// 验证Cloudflare Turnstile Token
|
||||
async function verifyTurnstileToken(token: string, secretKey: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
secret: secretKey,
|
||||
response: token,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return data.success === true;
|
||||
} catch (error) {
|
||||
console.error('Turnstile验证失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
// localStorage 模式不支持注册
|
||||
if (STORAGE_TYPE === 'localstorage') {
|
||||
return NextResponse.json(
|
||||
{ error: 'localStorage模式不支持注册功能' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 获取站点配置
|
||||
const config = await getConfig();
|
||||
const siteConfig = config.SiteConfig;
|
||||
|
||||
// 检查是否开启注册
|
||||
if (!siteConfig.EnableRegistration) {
|
||||
return NextResponse.json(
|
||||
{ error: '注册功能未开启' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
const { username, password, turnstileToken } = 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 });
|
||||
}
|
||||
|
||||
// 验证用户名格式(只允许字母、数字、下划线,长度3-20)
|
||||
if (!/^[a-zA-Z0-9_]{3,20}$/.test(username)) {
|
||||
return NextResponse.json(
|
||||
{ error: '用户名只能包含字母、数字、下划线,长度3-20位' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 验证密码长度
|
||||
if (password.length < 6) {
|
||||
return NextResponse.json(
|
||||
{ error: '密码长度至少为6位' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// 检查是否与站长同名
|
||||
if (username === process.env.USERNAME) {
|
||||
return NextResponse.json(
|
||||
{ error: '该用户名不可用' },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
// 检查用户是否已存在
|
||||
const existingUser = config.UserConfig.Users.find((u) => u.username === username);
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: '用户名已存在' },
|
||||
{ status: 409 }
|
||||
);
|
||||
}
|
||||
|
||||
// 如果开启了Turnstile验证
|
||||
if (siteConfig.RegistrationRequireTurnstile) {
|
||||
if (!turnstileToken) {
|
||||
return NextResponse.json(
|
||||
{ error: '请完成人机验证' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!siteConfig.TurnstileSecretKey) {
|
||||
console.error('Turnstile Secret Key未配置');
|
||||
return NextResponse.json(
|
||||
{ error: '服务器配置错误' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
// 验证Turnstile Token
|
||||
const isValid = await verifyTurnstileToken(turnstileToken, siteConfig.TurnstileSecretKey);
|
||||
if (!isValid) {
|
||||
return NextResponse.json(
|
||||
{ error: '人机验证失败,请重试' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
try {
|
||||
// 1. 在数据库中创建用户密码
|
||||
await db.registerUser(username, password);
|
||||
|
||||
// 2. 将用户添加到管理员配置的用户列表中
|
||||
const newUser: any = {
|
||||
username: username,
|
||||
role: 'user',
|
||||
banned: false,
|
||||
};
|
||||
|
||||
// 3. 如果配置了默认用户组,分配给新用户
|
||||
if (siteConfig.DefaultUserTags && siteConfig.DefaultUserTags.length > 0) {
|
||||
newUser.tags = siteConfig.DefaultUserTags;
|
||||
}
|
||||
|
||||
config.UserConfig.Users.push(newUser);
|
||||
|
||||
// 4. 保存更新后的配置
|
||||
await db.saveAdminConfig(config);
|
||||
|
||||
// 注册成功
|
||||
return NextResponse.json({ ok: true, message: '注册成功' });
|
||||
} catch (err) {
|
||||
console.error('创建用户失败', err);
|
||||
return NextResponse.json({ error: '注册失败,请稍后重试' }, { status: 500 });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('注册接口异常', error);
|
||||
return NextResponse.json({ error: '服务器错误' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,10 @@ export async function GET(request: NextRequest) {
|
||||
StorageType: storageType,
|
||||
Version: CURRENT_VERSION,
|
||||
WatchRoom: watchRoomConfig,
|
||||
EnableRegistration: config.SiteConfig.EnableRegistration || false,
|
||||
RegistrationRequireTurnstile: config.SiteConfig.RegistrationRequireTurnstile || false,
|
||||
LoginRequireTurnstile: config.SiteConfig.LoginRequireTurnstile || false,
|
||||
TurnstileSiteKey: config.SiteConfig.TurnstileSiteKey || '',
|
||||
};
|
||||
return NextResponse.json(result);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user