summaryrefslogtreecommitdiff
path: root/lib/password-policy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/password-policy')
-rw-r--r--lib/password-policy/service.ts225
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