UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

444 lines (393 loc) 9.58 kB
import bcrypt from "bcryptjs"; import crypto from "crypto"; import { PrismaClient } from "@prisma/client"; // Prisma 클라이언트 인스턴스 declare global { // eslint-disable-next-line no-var var __prisma: PrismaClient | undefined; } const prisma = globalThis.__prisma || new PrismaClient(); if (process.env.NODE_ENV !== "production") globalThis.__prisma = prisma; // 타입 정의 export interface CreateUserData { email: string; password: string; name?: string; role?: string; tenantId?: string; } export interface PasswordResetData { email: string; token: string; newPassword: string; } export interface EmailVerificationData { email: string; token: string; } /** * 비밀번호 해싱 */ export async function hashPassword(password: string): Promise<string> { const saltRounds = 12; return bcrypt.hash(password, saltRounds); } /** * 비밀번호 검증 */ export async function verifyPassword( password: string, hashedPassword: string ): Promise<boolean> { return bcrypt.compare(password, hashedPassword); } /** * 보안 토큰 생성 */ export function generateSecureToken(length: number = 32): string { return crypto.randomBytes(length).toString("hex"); } /** * 새 사용자 등록 */ export async function createUser(userData: CreateUserData) { try { // 이메일 중복 확인 const existingUser = await prisma.user.findUnique({ where: { email: userData.email }, }); if (existingUser) { throw new Error("이미 존재하는 이메일입니다"); } // 비밀번호 해싱 const hashedPassword = await hashPassword(userData.password); // 이메일 검증 토큰 생성 const emailVerificationToken = generateSecureToken(); // 사용자 생성 const user = await prisma.user.create({ data: { email: userData.email, password: hashedPassword, name: userData.name, role: (userData.role as any) || "VIEWER", tenantId: userData.tenantId, isActive: false, // 이메일 검증 전까지 비활성화 }, }); // 이메일 검증 토큰 저장 await prisma.verificationToken.create({ data: { identifier: userData.email, token: emailVerificationToken, expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24시간 후 만료 }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "USER_CREATED", resource: "User", resourceId: user.id, userId: user.id, details: { email: userData.email, role: userData.role || "VIEWER", }, }, }); return { user: { id: user.id, email: user.email, name: user.name, role: user.role, }, emailVerificationToken, }; } catch (error) { console.error("Create user error:", error); throw error; } } /** * 이메일 검증 */ export async function verifyEmail(data: EmailVerificationData) { try { // 검증 토큰 확인 const verificationToken = await prisma.verificationToken.findFirst({ where: { identifier: data.email, token: data.token, expires: { gt: new Date(), }, }, }); if (!verificationToken) { throw new Error("유효하지 않거나 만료된 검증 토큰입니다"); } // 사용자 활성화 const user = await prisma.user.update({ where: { email: data.email }, data: { isActive: true, emailVerified: new Date(), }, }); // 사용된 토큰 삭제 await prisma.verificationToken.delete({ where: { identifier_token: { identifier: data.email, token: data.token, }, }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "EMAIL_VERIFIED", resource: "User", resourceId: user.id, userId: user.id, details: { email: data.email, }, }, }); return user; } catch (error) { console.error("Email verification error:", error); throw error; } } /** * 패스워드 리셋 토큰 생성 */ export async function generatePasswordResetToken(email: string) { try { // 사용자 확인 const user = await prisma.user.findUnique({ where: { email }, }); if (!user) { throw new Error("존재하지 않는 사용자입니다"); } // 기존 토큰 삭제 await prisma.passwordResetToken.deleteMany({ where: { email }, }); // 새 토큰 생성 const token = generateSecureToken(); const expires = new Date(Date.now() + 60 * 60 * 1000); // 1시간 후 만료 await prisma.passwordResetToken.create({ data: { email, token, expires, }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "PASSWORD_RESET_REQUESTED", resource: "User", resourceId: user.id, userId: user.id, details: { email, }, }, }); return token; } catch (error) { console.error("Generate password reset token error:", error); throw error; } } /** * 패스워드 리셋 */ export async function resetPassword(data: PasswordResetData) { try { // 리셋 토큰 확인 const resetToken = await prisma.passwordResetToken.findFirst({ where: { email: data.email, token: data.token, expires: { gt: new Date(), }, }, }); if (!resetToken) { throw new Error("유효하지 않거나 만료된 리셋 토큰입니다"); } // 새 비밀번호 해싱 const hashedPassword = await hashPassword(data.newPassword); // 비밀번호 업데이트 const user = await prisma.user.update({ where: { email: data.email }, data: { password: hashedPassword, failedLogins: 0, // 실패 횟수 초기화 isLocked: false, // 잠금 해제 lockedAt: null, }, }); // 사용된 토큰 삭제 await prisma.passwordResetToken.delete({ where: { id: resetToken.id, }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "PASSWORD_RESET_COMPLETED", resource: "User", resourceId: user.id, userId: user.id, details: { email: data.email, }, }, }); return user; } catch (error) { console.error("Reset password error:", error); throw error; } } /** * 사용자 계정 잠금 해제 */ export async function unlockUser(userId: string) { try { const user = await prisma.user.update({ where: { id: userId }, data: { isLocked: false, lockedAt: null, failedLogins: 0, }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "USER_UNLOCKED", resource: "User", resourceId: userId, userId: userId, details: {}, }, }); return user; } catch (error) { console.error("Unlock user error:", error); throw error; } } /** * 사용자 역할 변경 */ export async function changeUserRole( userId: string, newRole: string, changedBy: string ) { try { const oldUser = await prisma.user.findUnique({ where: { id: userId }, select: { role: true }, }); if (!oldUser) { throw new Error("사용자를 찾을 수 없습니다"); } const user = await prisma.user.update({ where: { id: userId }, data: { role: newRole as any }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "USER_ROLE_CHANGED", resource: "User", resourceId: userId, userId: changedBy, details: { oldRole: oldUser.role, newRole: newRole, targetUserId: userId, }, }, }); return user; } catch (error) { console.error("Change user role error:", error); throw error; } } /** * 사용자 프로필 업데이트 */ export async function updateUserProfile( userId: string, updateData: { name?: string; image?: string } ) { try { const user = await prisma.user.update({ where: { id: userId }, data: updateData, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: "USER_PROFILE_UPDATED", resource: "User", resourceId: userId, userId: userId, details: { updatedFields: Object.keys(updateData), }, }, }); return user; } catch (error) { console.error("Update user profile error:", error); throw error; } } /** * 사용자 활성/비활성 토글 */ export async function toggleUserStatus( userId: string, isActive: boolean, changedBy: string ) { try { const user = await prisma.user.update({ where: { id: userId }, data: { isActive }, }); // 감사 로그 기록 await prisma.auditLog.create({ data: { action: isActive ? "USER_ACTIVATED" : "USER_DEACTIVATED", resource: "User", resourceId: userId, userId: changedBy, details: { targetUserId: userId, isActive, }, }, }); return user; } catch (error) { console.error("Toggle user status error:", error); throw error; } }