diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-27 01:16:20 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-27 01:16:20 +0000 |
| commit | e9897d416b3e7327bbd4d4aef887eee37751ae82 (patch) | |
| tree | bd20ce6eadf9b21755bd7425492d2d31c7700a0e /lib/password-policy | |
| parent | 3bf1952c1dad9d479bb8b22031b06a7434d37c37 (diff) | |
(대표님) 20250627 오전 10시 작업사항
Diffstat (limited to 'lib/password-policy')
| -rw-r--r-- | lib/password-policy/service.ts | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/lib/password-policy/service.ts b/lib/password-policy/service.ts new file mode 100644 index 00000000..c4a0685c --- /dev/null +++ b/lib/password-policy/service.ts @@ -0,0 +1,225 @@ +'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<SecuritySettings> { + 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<Omit<SecuritySettings, 'id' | 'createdAt' | 'updatedAt'>> +): 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<Omit<SecuritySettings, 'id' | 'createdAt' | 'updatedAt'>> +): { 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: '설정 초기화 중 오류가 발생했습니다.' + } + } +}
\ No newline at end of file |
