claude-code-cchui/src/app/api/auth/register/route.ts
gaoziman 058ea85daa feat(设置): 实现用户级别设置隔离
- 设置 API 改为基于当前登录用户
- 注册时自动创建默认用户设置
- API Key 加密后存储到数据库
- 添加默认设置常量和格式化函数
2025-12-21 14:03:55 +08:00

169 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { nanoid } from 'nanoid';
import { db } from '@/drizzle/db';
import { users, verificationCodes, userSettings } from '@/drizzle/schema';
import { eq, and, gt } from 'drizzle-orm';
import { hashPassword, validatePassword, validateEmail, validateNickname } from '@/lib/password';
import { generateToken, setAuthCookie } from '@/lib/auth';
// 请求体验证
const registerSchema = z.object({
email: z.string().email('邮箱格式不正确'),
password: z.string().min(8, '密码长度不能少于8位'),
confirmPassword: z.string(),
nickname: z.string().min(2, '昵称长度不能少于2位').max(20, '昵称长度不能超过20位'),
code: z.string().length(6, '验证码必须是6位'),
}).refine((data) => data.password === data.confirmPassword, {
message: '两次输入的密码不一致',
path: ['confirmPassword'],
});
export async function POST(request: NextRequest) {
try {
const body = await request.json();
// 验证请求体
const result = registerSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ success: false, error: result.error.issues[0].message },
{ status: 400 }
);
}
const { email, password, nickname, code } = result.data;
// 验证邮箱格式
if (!validateEmail(email)) {
return NextResponse.json(
{ success: false, error: '邮箱格式不正确' },
{ status: 400 }
);
}
// 验证密码格式
const passwordValidation = validatePassword(password);
if (!passwordValidation.valid) {
return NextResponse.json(
{ success: false, error: passwordValidation.errors[0] },
{ status: 400 }
);
}
// 验证昵称格式
const nicknameValidation = validateNickname(nickname);
if (!nicknameValidation.valid) {
return NextResponse.json(
{ success: false, error: nicknameValidation.error },
{ status: 400 }
);
}
// 检查邮箱是否已注册
const existingUser = await db
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
if (existingUser.length > 0) {
return NextResponse.json(
{ success: false, error: '该邮箱已被注册' },
{ status: 400 }
);
}
// 验证验证码
const now = new Date();
const validCode = await db
.select()
.from(verificationCodes)
.where(
and(
eq(verificationCodes.email, email),
eq(verificationCodes.code, code),
eq(verificationCodes.type, 'register'),
eq(verificationCodes.used, false),
gt(verificationCodes.expiresAt, now)
)
)
.limit(1);
if (validCode.length === 0) {
return NextResponse.json(
{ success: false, error: '验证码无效或已过期' },
{ status: 400 }
);
}
// 标记验证码为已使用
await db
.update(verificationCodes)
.set({ used: true })
.where(eq(verificationCodes.id, validCode[0].id));
// 加密密码
const hashedPassword = await hashPassword(password);
// 生成用户ID
const userId = nanoid();
// 创建用户
const [newUser] = await db
.insert(users)
.values({
userId,
email,
password: hashedPassword,
nickname,
emailVerified: true, // 已通过邮箱验证
lastLoginAt: now,
})
.returning();
// 创建默认用户设置API Key 为空,需要用户自行配置)
await db.insert(userSettings).values({
userId,
cchUrl: process.env.CCH_DEFAULT_URL || 'https://claude.leocoder.cn/',
cchApiKey: null,
cchApiKeyConfigured: false,
defaultModel: 'claude-sonnet-4-20250514',
defaultTools: ['web_search', 'code_execution', 'web_fetch'],
theme: 'light',
language: 'zh-CN',
fontSize: 15,
enableThinking: false,
saveChatHistory: true,
});
// 生成 JWT Token
const token = await generateToken({
userId: newUser.userId,
email: newUser.email,
nickname: newUser.nickname,
plan: newUser.plan || 'free',
});
// 设置认证 Cookie
await setAuthCookie(token);
return NextResponse.json({
success: true,
user: {
id: newUser.userId,
email: newUser.email,
nickname: newUser.nickname,
plan: newUser.plan,
},
message: '注册成功',
});
} catch (error) {
console.error('注册失败:', error);
return NextResponse.json(
{ success: false, error: '服务器错误' },
{ status: 500 }
);
}
}