UNPKG

vibe-cli-tool

Version:

VibeCLI v1.9 - 模板商店生态,智能部署生成,多语言支持,交互式学习系统,企业级Web全栈应用CLI工具

389 lines (327 loc) 9.62 kB
import { User, UserSession } from '@prisma/client' import { prisma } from '@/lib/db/prisma' import { hashPassword, verifyPassword } from '@/lib/utils/password' import { generateAccessToken, generateRefreshToken, getRefreshTokenExpirationTime } from '@/lib/utils/jwt' import { AuthTokens, RegisterData, LoginCredentials } from '@/types/auth' import { randomBytes } from 'crypto' export class AuthService { async register(data: RegisterData): Promise<{ user: Omit<User, 'passwordHash'> tokens: AuthTokens }> { // 检查用户是否已存在 const existingUser = await prisma.user.findUnique({ where: { email: data.email } }) if (existingUser) { throw new Error('该邮箱已被注册') } // 创建用户 const passwordHash = await hashPassword(data.password) const user = await prisma.user.create({ data: { email: data.email, passwordHash, name: data.name, status: 'PENDING' } }) // 生成令牌 const tokens = await this.generateTokens(user) // 发送验证邮件 await this.sendVerificationEmail(user.id, user.email) return { user: this.sanitizeUser(user), tokens } } async login(credentials: LoginCredentials, deviceInfo?: { ipAddress?: string userAgent?: string deviceInfo?: string }): Promise<{ user: Omit<User, 'passwordHash'> tokens: AuthTokens }> { // 查找用户 const user = await prisma.user.findUnique({ where: { email: credentials.email } }) if (!user) { throw new Error('邮箱或密码错误') } // 验证密码 const isPasswordValid = await verifyPassword(credentials.password, user.passwordHash) if (!isPasswordValid) { throw new Error('邮箱或密码错误') } // 检查用户状态 if (user.status === 'SUSPENDED') { throw new Error('账户已被暂停,请联系管理员') } if (user.status === 'INACTIVE') { throw new Error('账户已被停用') } // 更新最后活跃时间 await prisma.user.update({ where: { id: user.id }, data: { lastActiveAt: new Date() } }) // 生成令牌 const tokens = await this.generateTokens(user, deviceInfo) return { user: this.sanitizeUser(user), tokens } } async refreshTokens(refreshToken: string): Promise<AuthTokens> { // 查找会话 const session = await prisma.userSession.findUnique({ where: { refreshToken }, include: { user: true } }) if (!session) { throw new Error('无效的刷新令牌') } // 检查令牌是否过期 if (session.expiresAt < new Date()) { // 删除过期的会话 await prisma.userSession.delete({ where: { id: session.id } }) throw new Error('刷新令牌已过期') } // 检查用户状态 if (session.user.status !== 'ACTIVE') { throw new Error('用户账户状态异常') } // 生成新的令牌 const accessToken = generateAccessToken( session.user.id, session.user.email, session.user.role ) const newRefreshToken = generateRefreshToken() const expiresAt = getRefreshTokenExpirationTime() // 更新会话 await prisma.userSession.update({ where: { id: session.id }, data: { refreshToken: newRefreshToken, expiresAt, lastUsedAt: new Date() } }) return { accessToken, refreshToken: newRefreshToken, expiresIn: 3600 // 1小时 } } async logout(refreshToken: string): Promise<void> { // 删除会话 await prisma.userSession.deleteMany({ where: { refreshToken } }) } async logoutAllDevices(userId: string): Promise<void> { // 删除用户的所有会话 await prisma.userSession.deleteMany({ where: { userId } }) } async generateTokens(user: User, deviceInfo?: { ipAddress?: string userAgent?: string deviceInfo?: string }): Promise<AuthTokens> { const accessToken = generateAccessToken(user.id, user.email, user.role) const refreshToken = generateRefreshToken() const expiresAt = getRefreshTokenExpirationTime() // 创建会话记录 await prisma.userSession.create({ data: { userId: user.id, refreshToken, expiresAt, ipAddress: deviceInfo?.ipAddress, userAgent: deviceInfo?.userAgent, deviceInfo: deviceInfo?.deviceInfo } }) return { accessToken, refreshToken, expiresIn: 3600 // 1小时 } } async sendVerificationEmail(userId: string, email: string): Promise<void> { // 生成验证令牌 const token = randomBytes(32).toString('hex') const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24小时 // 保存验证记录 await prisma.emailVerification.create({ data: { userId, token, expiresAt } }) // TODO: 发送邮件 console.log(`Email verification token for ${email}: ${token}`) } async verifyEmail(token: string): Promise<void> { // 查找验证记录 const verification = await prisma.emailVerification.findUnique({ where: { token }, include: { user: true } }) if (!verification) { throw new Error('无效的验证令牌') } if (verification.expiresAt < new Date()) { throw new Error('验证令牌已过期') } if (verification.verifiedAt) { throw new Error('邮箱已经验证过了') } // 更新用户状态 await prisma.$transaction([ prisma.user.update({ where: { id: verification.userId }, data: { status: 'ACTIVE', emailVerified: new Date() } }), prisma.emailVerification.update({ where: { id: verification.id }, data: { verifiedAt: new Date() } }) ]) } async requestPasswordReset(email: string): Promise<void> { // 查找用户 const user = await prisma.user.findUnique({ where: { email } }) if (!user) { // 为了安全,不暴露用户是否存在 return } // 生成重置令牌 const token = randomBytes(32).toString('hex') const expiresAt = new Date(Date.now() + 1 * 60 * 60 * 1000) // 1小时 // 保存重置记录 await prisma.passwordReset.create({ data: { userId: user.id, token, expiresAt } }) // TODO: 发送重置邮件 console.log(`Password reset token for ${email}: ${token}`) } async resetPassword(token: string, newPassword: string): Promise<void> { // 查找重置记录 const reset = await prisma.passwordReset.findUnique({ where: { token } }) if (!reset) { throw new Error('无效的重置令牌') } if (reset.expiresAt < new Date()) { throw new Error('重置令牌已过期') } if (reset.usedAt) { throw new Error('重置令牌已使用过') } // 更新密码 const passwordHash = await hashPassword(newPassword) await prisma.$transaction([ prisma.user.update({ where: { id: reset.userId }, data: { passwordHash } }), prisma.passwordReset.update({ where: { id: reset.id }, data: { usedAt: new Date() } }), // 删除用户的所有会话,强制重新登录 prisma.userSession.deleteMany({ where: { userId: reset.userId } }) ]) } async updateProfile(userId: string, data: { name?: string; avatar?: string }): Promise<User> { const user = await prisma.user.update({ where: { id: userId }, data }) return this.sanitizeUser(user) } async changePassword(userId: string, currentPassword: string, newPassword: string): Promise<void> { // 获取用户信息 const user = await prisma.user.findUnique({ where: { id: userId } }) if (!user) { throw new Error('用户不存在') } // 验证当前密码 const isCurrentPasswordValid = await verifyPassword(currentPassword, user.passwordHash) if (!isCurrentPasswordValid) { throw new Error('当前密码错误') } // 更新密码 const passwordHash = await hashPassword(newPassword) await prisma.user.update({ where: { id: userId }, data: { passwordHash } }) // 删除除当前会话外的所有会话 // 注意:在实际应用中,你需要传入当前的refreshToken来保留当前会话 } async getUserSessions(userId: string): Promise<UserSession[]> { return prisma.userSession.findMany({ where: { userId }, orderBy: { lastUsedAt: 'desc' } }) } async revokeSession(sessionId: string, userId: string): Promise<void> { await prisma.userSession.deleteMany({ where: { id: sessionId, userId } }) } private sanitizeUser(user: User): Omit<User, 'passwordHash'> { const { passwordHash, ...sanitizedUser } = user return sanitizedUser } async getUserById(userId: string): Promise<Omit<User, 'passwordHash'> | null> { const user = await prisma.user.findUnique({ where: { id: userId } }) if (!user) { return null } return this.sanitizeUser(user) } async getUserByEmail(email: string): Promise<Omit<User, 'passwordHash'> | null> { const user = await prisma.user.findUnique({ where: { email } }) if (!user) { return null } return this.sanitizeUser(user) } } export const authService = new AuthService()