'use server' import { revalidatePath } from 'next/cache' import { eq } from 'drizzle-orm' import db from '@/db/db' import { securitySettings } from '@/db/schema' import { SecuritySettings } from '@/components/system/passwordPolicy' // 보안 설정 조회 export async function getSecuritySettings(): Promise { try { const settings = await db.select().from(securitySettings).limit(1) if (settings.length === 0) { // 기본 설정으로 초기화 const defaultSettings = { minPasswordLength: 8, requireUppercase: true, requireLowercase: true, requireNumbers: true, requireSymbols: true, passwordExpiryDays: 90, passwordHistoryCount: 5, maxFailedAttempts: 5, lockoutDurationMinutes: 30, requireMfaForPartners: true, smsTokenExpiryMinutes: 5, maxSmsAttemptsPerDay: 10, sessionTimeoutMinutes: 480, } const [newSettings] = await db .insert(securitySettings) .values(defaultSettings) .returning() return newSettings as SecuritySettings } return settings[0] as SecuritySettings } catch (error) { console.error('Failed to get security settings:', error) throw new Error('보안 설정을 가져오는 중 오류가 발생했습니다.') } } // 보안 설정 업데이트 export async function updateSecuritySettings( updates: Partial> ): Promise<{ success: boolean; error?: string }> { try { // 유효성 검사 const validationResult = validateSecuritySettings(updates) if (!validationResult.valid) { return { success: false, error: validationResult.error } } // 현재 설정 조회 const currentSettings = await db.select().from(securitySettings).limit(1) if (currentSettings.length === 0) { return { success: false, error: '기존 설정을 찾을 수 없습니다.' } } // 업데이트 실행 await db .update(securitySettings) .set({ ...updates, updatedAt: new Date() }) .where(eq(securitySettings.id, currentSettings[0].id)) // 캐시 무효화 revalidatePath('/admin/password-policy') return { success: true } } catch (error) { console.error('Failed to update security settings:', error) return { success: false, error: '설정 업데이트 중 오류가 발생했습니다.' } } } // 설정 유효성 검사 function validateSecuritySettings( updates: Partial> ): { valid: boolean; error?: string } { // 패스워드 길이 검사 if (updates.minPasswordLength !== undefined) { if (updates.minPasswordLength < 4 || updates.minPasswordLength > 128) { return { valid: false, error: '패스워드 최소 길이는 4자 이상 128자 이하여야 합니다.' } } } // 패스워드 만료 기간 검사 if (updates.passwordExpiryDays !== undefined && updates.passwordExpiryDays !== null) { if (updates.passwordExpiryDays < 0 || updates.passwordExpiryDays > 365) { return { valid: false, error: '패스워드 만료 기간은 0일 이상 365일 이하여야 합니다.' } } } // 패스워드 히스토리 개수 검사 if (updates.passwordHistoryCount !== undefined) { if (updates.passwordHistoryCount < 0 || updates.passwordHistoryCount > 20) { return { valid: false, error: '패스워드 히스토리 개수는 0개 이상 20개 이하여야 합니다.' } } } // 최대 실패 횟수 검사 if (updates.maxFailedAttempts !== undefined) { if (updates.maxFailedAttempts < 1 || updates.maxFailedAttempts > 20) { return { valid: false, error: '최대 실패 횟수는 1회 이상 20회 이하여야 합니다.' } } } // 잠금 시간 검사 if (updates.lockoutDurationMinutes !== undefined) { if (updates.lockoutDurationMinutes < 1 || updates.lockoutDurationMinutes > 1440) { return { valid: false, error: '잠금 시간은 1분 이상 1440분(24시간) 이하여야 합니다.' } } } // SMS 토큰 만료 시간 검사 if (updates.smsTokenExpiryMinutes !== undefined) { if (updates.smsTokenExpiryMinutes < 1 || updates.smsTokenExpiryMinutes > 30) { return { valid: false, error: 'SMS 토큰 만료 시간은 1분 이상 30분 이하여야 합니다.' } } } // SMS 일일 한도 검사 if (updates.maxSmsAttemptsPerDay !== undefined) { if (updates.maxSmsAttemptsPerDay < 1 || updates.maxSmsAttemptsPerDay > 100) { return { valid: false, error: 'SMS 일일 한도는 1회 이상 100회 이하여야 합니다.' } } } // 세션 타임아웃 검사 if (updates.sessionTimeoutMinutes !== undefined) { if (updates.sessionTimeoutMinutes < 5 || updates.sessionTimeoutMinutes > 1440) { return { valid: false, error: '세션 타임아웃은 5분 이상 1440분(24시간) 이하여야 합니다.' } } } return { valid: true } } // 설정 리셋 (기본값으로 복원) export async function resetSecuritySettings(): Promise<{ success: boolean; error?: string }> { try { const defaultSettings = { minPasswordLength: 8, requireUppercase: true, requireLowercase: true, requireNumbers: true, requireSymbols: true, passwordExpiryDays: 90, passwordHistoryCount: 5, maxFailedAttempts: 5, lockoutDurationMinutes: 30, requireMfaForPartners: true, smsTokenExpiryMinutes: 5, maxSmsAttemptsPerDay: 10, sessionTimeoutMinutes: 480, updatedAt: new Date() } // 현재 설정 조회 const currentSettings = await db.select().from(securitySettings).limit(1) if (currentSettings.length === 0) { // 새로 생성 await db.insert(securitySettings).values(defaultSettings) } else { // 업데이트 await db .update(securitySettings) .set(defaultSettings) .where(eq(securitySettings.id, currentSettings[0].id)) } // 캐시 무효화 revalidatePath('/admin/password-policy') return { success: true } } catch (error) { console.error('Failed to reset security settings:', error) return { success: false, error: '설정 초기화 중 오류가 발생했습니다.' } } }