From 0b327c8cf61c6e7cec00f73466fc98fd59fc0f38 Mon Sep 17 00:00:00 2001 From: choibk Date: Fri, 23 Jan 2026 22:09:07 +0900 Subject: [PATCH] Feat: Add password change feature and update initial account security --- client/src/App.tsx | 124 ++++++++++++++++++++++++++++++++++++++++++++- docs/deployment.md | 2 +- docs/usage.md | 5 +- server/init_db.js | 2 +- server/server.js | 22 ++++++++ 5 files changed, 150 insertions(+), 5 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 91dbe42..3c5c911 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo } from 'react'; import axios from 'axios'; -import { Key, Plus, Copy, CheckCircle, Clock, Search, Users, ChevronRight, ArrowLeft, Trash2, LogOut } from 'lucide-react'; +import { Key, Plus, Copy, CheckCircle, Clock, Search, Users, ChevronRight, ArrowLeft, Trash2, LogOut, X, Loader2, Lock } from 'lucide-react'; import Login from './components/Login'; // Axios settings for cookies @@ -24,6 +24,13 @@ const App = () => { const [user, setUser] = useState(null); const [authChecked, setAuthChecked] = useState(false); const [licenses, setLicenses] = useState([]); + const [showPasswordModal, setShowPasswordModal] = useState(false); + const [passwordForm, setPasswordForm] = useState({ + currentPassword: '', + newPassword: '', + confirmPassword: '' + }); + const [passwordChanging, setPasswordChanging] = useState(false); const [loading, setLoading] = useState(false); const [submitting, setSubmitting] = useState(false); const [copySuccess, setCopySuccess] = useState(null); @@ -108,6 +115,32 @@ const App = () => { } }; + const handleChangePassword = async (e: React.FormEvent) => { + e.preventDefault(); + if (passwordForm.newPassword !== passwordForm.confirmPassword) { + return alert('새 비밀번호가 일치하지 않습니다.'); + } + if (passwordForm.newPassword.length < 4) { + return alert('비밀번호는 4자 이상이어야 합니다.'); + } + + setPasswordChanging(true); + try { + await axios.post(`${API_BASE}/auth/change-password`, { + currentPassword: passwordForm.currentPassword, + newPassword: passwordForm.newPassword + }); + alert('비밀번호가 성공적으로 변경되었습니다. 보안을 위해 다시 로그인해 주세요.'); + handleLogout(); + setShowPasswordModal(false); + setPasswordForm({ currentPassword: '', newPassword: '', confirmPassword: '' }); + } catch (err: any) { + alert(err.response?.data?.error || '비밀번호 변경에 실패했습니다.'); + } finally { + setPasswordChanging(false); + } + }; + useEffect(() => { if (user) { fetchLicenses(); @@ -197,7 +230,10 @@ const App = () => {
{user?.name?.[0] || 'A'}
- {user?.name}님 +
+ {user?.name}님 + +
+ +
+
+ + setPasswordForm({ ...passwordForm, currentPassword: e.target.value })} + placeholder="현재 비밀번호 입력" + className="w-full px-4 py-3 bg-slate-50 border border-slate-100 rounded-2xl focus:bg-white focus:ring-4 focus:ring-indigo-50 focus:border-indigo-500 outline-none transition-all text-sm" + /> +
+
+ + setPasswordForm({ ...passwordForm, newPassword: e.target.value })} + placeholder="새 비밀번호 입력 (4자 이상)" + className="w-full px-4 py-3 bg-slate-50 border border-slate-100 rounded-2xl focus:bg-white focus:ring-4 focus:ring-indigo-50 focus:border-indigo-500 outline-none transition-all text-sm" + /> +
+
+ + setPasswordForm({ ...passwordForm, confirmPassword: e.target.value })} + placeholder="새 비밀번호 다시 입력" + className="w-full px-4 py-3 bg-slate-50 border border-slate-100 rounded-2xl focus:bg-white focus:ring-4 focus:ring-indigo-50 focus:border-indigo-500 outline-none transition-all text-sm" + /> +
+ +
+ + +
+
+ + + )} +