diff --git a/TestMasterUI/src/App.jsx b/TestMasterUI/src/App.jsx
index 66b93ec..76ddd99 100644
--- a/TestMasterUI/src/App.jsx
+++ b/TestMasterUI/src/App.jsx
@@ -32,6 +32,7 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
showTCPServerEdit: false,
showQueueEdit: false,
showUserEdit: false,
+ showPasswordChange: false,
selectedItem: null,
});
@@ -77,6 +78,9 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
case 'variables-show':
setDialogState(prev => ({ ...prev, showVariableModal: true }));
break;
+ case 'change-password':
+ setDialogState(prev => ({ ...prev, showPasswordChange: true }));
+ break;
case 'logout':
const success = await handleLogout();
if (success) {
@@ -139,6 +143,7 @@ const AppContent = ({ options, showLogin, setShowLogin }) => {
showQueueEdit={dialogState.showQueueEdit}
showUserEdit={dialogState.showUserEdit}
selectedItem={dialogState.selectedItem}
+ showPasswordChange={dialogState.showPasswordChange}
onDialogChange={handleDialogChange}
/>
diff --git a/TestMasterUI/src/components/account/PasswordChangeDialog.jsx b/TestMasterUI/src/components/account/PasswordChangeDialog.jsx
new file mode 100644
index 0000000..0c5377b
--- /dev/null
+++ b/TestMasterUI/src/components/account/PasswordChangeDialog.jsx
@@ -0,0 +1,155 @@
+import React, { useState } from 'react';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from '@/components/ui/dialog';
+import { Input } from '@/components/ui/input';
+import { Button } from '@/components/ui/button';
+import { Alert, AlertDescription } from '@/components/ui/alert';
+import { Label } from '@/components/ui/label';
+import { getCSRFToken } from '@/services/token.js';
+
+const PasswordChangeDialog = ({ open, onOpenChange }) => {
+ const [formData, setFormData] = useState({
+ oldPassword: '',
+ newPassword: '',
+ newPassword2: ''
+ });
+ const [message, setMessage] = useState({ type: '', content: '' });
+ const [isLoading, setIsLoading] = useState(false);
+
+ const resetForm = () => {
+ setFormData({
+ oldPassword: '',
+ newPassword: '',
+ newPassword2: ''
+ });
+ setMessage({ type: '', content: '' });
+ };
+
+ const handleClose = () => {
+ resetForm();
+ onOpenChange(false);
+ };
+
+ const validateForm = () => {
+ if (!formData.oldPassword) {
+ setMessage({ type: 'error', content: '현재 비밀번호를 입력해주세요.' });
+ return false;
+ }
+ if (!formData.newPassword) {
+ setMessage({ type: 'error', content: '새 비밀번호를 입력해주세요.' });
+ return false;
+ }
+ if (formData.newPassword !== formData.newPassword2) {
+ setMessage({ type: 'error', content: '새 비밀번호가 일치하지 않습니다.' });
+ return false;
+ }
+ return true;
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ if (!validateForm()) return;
+
+ setIsLoading(true);
+ setMessage({ type: '', content: '' });
+
+ try {
+ const response = await fetch('/api/account/password', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-XSRF-TOKEN': getCSRFToken()
+ },
+ body: JSON.stringify(formData)
+ });
+
+ const data = await response.json();
+
+ if (!response.ok) {
+ throw new Error(data.error || '비밀번호 변경에 실패했습니다.');
+ }
+
+ setMessage({ type: 'success', content: '비밀번호가 성공적으로 변경되었습니다.' });
+ setTimeout(() => {
+ handleClose();
+ }, 2000);
+ } catch (error) {
+ setMessage({ type: 'error', content: error.message });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default PasswordChangeDialog;
diff --git a/TestMasterUI/src/components/account/PasswordEditDialog.jsx b/TestMasterUI/src/components/account/PasswordEditDialog.jsx
deleted file mode 100644
index e69de29..0000000
diff --git a/TestMasterUI/src/components/shared/DialogManager.jsx b/TestMasterUI/src/components/shared/DialogManager.jsx
index c2282da..fe97139 100644
--- a/TestMasterUI/src/components/shared/DialogManager.jsx
+++ b/TestMasterUI/src/components/shared/DialogManager.jsx
@@ -7,6 +7,7 @@ import MockRouteEditDialog from '@/components/mock-route/MockRouteEditDialog.jsx
import TCPServerEditDialog from '@/components/tcp-servers/TCPServerEditDialog.jsx';
import QueueEditDialog from '@/components/queues/QueueEditDialog.jsx';
import UserEditDialog from '@/components/users/UserEditDialog.jsx';
+import PasswordChangeDialog from '@/components/account/PasswordChangeDialog.jsx';
const DialogManager = ({
showAddApi,
@@ -18,6 +19,7 @@ const DialogManager = ({
showQueueEdit,
showUserEdit,
selectedItem,
+ showPasswordChange,
onDialogChange
}) => {
@@ -58,6 +60,10 @@ const DialogManager = ({
onOpenChange={(isOpen) => onDialogChange('showUserEdit', isOpen)}
item={selectedItem}
/>
+
onDialogChange('showPasswordChange', isOpen)}
+ />
>;
}
diff --git a/src/main/java/com/eactive/testmaster/user/controller/AccountApiController.java b/src/main/java/com/eactive/testmaster/user/controller/AccountApiController.java
index 6db819d..7f89652 100644
--- a/src/main/java/com/eactive/testmaster/user/controller/AccountApiController.java
+++ b/src/main/java/com/eactive/testmaster/user/controller/AccountApiController.java
@@ -1,23 +1,70 @@
package com.eactive.testmaster.user.controller;
import com.eactive.testmaster.common.util.SecurityUtil;
+import com.eactive.testmaster.user.dto.PasswordChangeRequestDTO;
import com.eactive.testmaster.user.entity.User;
+import com.eactive.testmaster.user.service.UserService;
+import java.util.HashMap;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
+import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;
+import org.springframework.validation.BeanPropertyBindingResult;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.Validator;
import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
+@RequestMapping("/api/account")
public class AccountApiController {
- public AccountApiController() {
+ private final Validator validator;
+ private final UserService userService;
+
+ public AccountApiController(Validator validator, UserService userService) {
+ this.validator = validator;
+ this.userService = userService;
}
- @GetMapping("/api/account")
+ @GetMapping
@Secured("ROLE_API_TESTER")
public ResponseEntity account(ModelMap model) {
return ResponseEntity.ok().body(SecurityUtil.getCurrentUser());
}
+
+ @PostMapping("/password")
+ @Transactional
+ @Secured("ROLE_API_TESTER")
+ public ResponseEntity> updatePassword(@RequestBody PasswordChangeRequestDTO passwordChangeRequestDTO) {
+ BindingResult result = new BeanPropertyBindingResult(passwordChangeRequestDTO, "passwordChangeRequest");
+ validator.validate(passwordChangeRequestDTO, result);
+
+ if (result.hasErrors()) {
+ return ResponseEntity.badRequest().body(result.getAllErrors());
+ }
+
+ try {
+ String resultMsg = userService.updateUserPassword(
+ SecurityUtil.getCurrentUserEsntlId(),
+ passwordChangeRequestDTO.getOldPassword(),
+ passwordChangeRequestDTO.getNewPassword(),
+ passwordChangeRequestDTO.getNewPassword2()
+ );
+
+ return ResponseEntity.ok().body(new HashMap() {{
+ put("message", resultMsg);
+ }});
+ } catch (Exception e) {
+ return ResponseEntity.badRequest().body(new HashMap() {{
+ put("error", e.getMessage());
+ }});
+ }
+ }
+
}