406 lines
16 KiB
Python
406 lines
16 KiB
Python
# password_change_dialog.py
|
|
from PySide6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
|
|
QMessageBox, QCheckBox, QFrame, QProgressBar, QTextEdit, QSizePolicy
|
|
)
|
|
from PySide6.QtCore import Qt, QTimer
|
|
from PySide6.QtGui import QFont
|
|
import logging
|
|
import time
|
|
import threading
|
|
|
|
class PasswordChangeDialog(QDialog):
|
|
def __init__(self, logger, supabase_manager, parent=None):
|
|
super().__init__(parent)
|
|
self.logger = logger
|
|
self.supabase_manager = supabase_manager
|
|
|
|
self.setWindowTitle("비밀번호 변경")
|
|
self.setFixedSize(450, 550)
|
|
self.setModal(True)
|
|
|
|
self.init_ui()
|
|
|
|
def init_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
layout.setContentsMargins(30, 30, 30, 30)
|
|
layout.setSpacing(20)
|
|
|
|
# 타이틀
|
|
title_label = QLabel("비밀번호 변경")
|
|
title_label.setAlignment(Qt.AlignCenter)
|
|
title_label.setStyleSheet("font-size: 24px; font-weight: bold; color: #333; margin-bottom: 10px;")
|
|
layout.addWidget(title_label)
|
|
|
|
# 안내 문구
|
|
info_label = QLabel("보안을 위해 현재 비밀번호를 입력하고 새 비밀번호를 설정하세요.")
|
|
info_label.setAlignment(Qt.AlignCenter)
|
|
info_label.setStyleSheet("font-size: 13px; color: #666; margin-bottom: 20px;")
|
|
info_label.setWordWrap(True)
|
|
layout.addWidget(info_label)
|
|
|
|
# 현재 비밀번호 입력
|
|
current_password_label = QLabel("현재 비밀번호:")
|
|
current_password_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333;")
|
|
layout.addWidget(current_password_label)
|
|
|
|
current_password_layout = QHBoxLayout()
|
|
self.current_password_input = QLineEdit()
|
|
self.current_password_input.setPlaceholderText("현재 비밀번호를 입력하세요")
|
|
self.current_password_input.setEchoMode(QLineEdit.Password)
|
|
self.current_password_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 8px;
|
|
background-color: #fafafa;
|
|
}
|
|
QLineEdit:focus {
|
|
border-color: #1877f2;
|
|
background-color: white;
|
|
}
|
|
""")
|
|
current_password_layout.addWidget(self.current_password_input)
|
|
|
|
self.current_show_password_checkbox = QCheckBox("보기")
|
|
self.current_show_password_checkbox.stateChanged.connect(self.toggle_current_password_visibility)
|
|
current_password_layout.addWidget(self.current_show_password_checkbox)
|
|
layout.addLayout(current_password_layout)
|
|
|
|
# 새 비밀번호 입력
|
|
new_password_label = QLabel("새 비밀번호:")
|
|
new_password_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333;")
|
|
layout.addWidget(new_password_label)
|
|
|
|
new_password_layout = QHBoxLayout()
|
|
self.new_password_input = QLineEdit()
|
|
self.new_password_input.setPlaceholderText("새 비밀번호를 입력하세요")
|
|
self.new_password_input.setEchoMode(QLineEdit.Password)
|
|
self.new_password_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 8px;
|
|
background-color: #fafafa;
|
|
}
|
|
QLineEdit:focus {
|
|
border-color: #1877f2;
|
|
background-color: white;
|
|
}
|
|
""")
|
|
self.new_password_input.textChanged.connect(self.check_password_strength)
|
|
new_password_layout.addWidget(self.new_password_input)
|
|
|
|
self.new_show_password_checkbox = QCheckBox("보기")
|
|
self.new_show_password_checkbox.stateChanged.connect(self.toggle_new_password_visibility)
|
|
new_password_layout.addWidget(self.new_show_password_checkbox)
|
|
layout.addLayout(new_password_layout)
|
|
|
|
# 비밀번호 강도 표시기
|
|
strength_label = QLabel("비밀번호 강도:")
|
|
strength_label.setStyleSheet("font-size: 13px; color: #666;")
|
|
layout.addWidget(strength_label)
|
|
|
|
self.strength_bar = QProgressBar()
|
|
self.strength_bar.setMinimum(0)
|
|
self.strength_bar.setMaximum(100)
|
|
self.strength_bar.setValue(0)
|
|
self.strength_bar.setStyleSheet("""
|
|
QProgressBar {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
height: 20px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background-color: #ff4444;
|
|
border-radius: 3px;
|
|
}
|
|
""")
|
|
layout.addWidget(self.strength_bar)
|
|
|
|
# 비밀번호 요구사항 표시
|
|
self.requirements_text = QTextEdit()
|
|
self.requirements_text.setReadOnly(True)
|
|
self.requirements_text.setMaximumHeight(100)
|
|
self.requirements_text.setStyleSheet("""
|
|
QTextEdit {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: #f9f9f9;
|
|
padding: 8px;
|
|
font-size: 12px;
|
|
}
|
|
""")
|
|
self.requirements_text.setPlainText(
|
|
"비밀번호 요구사항:\n"
|
|
"• 최소 8자 이상\n"
|
|
"• 영문 대문자 포함\n"
|
|
"• 영문 소문자 포함\n"
|
|
"• 숫자 포함\n"
|
|
"• 특수문자 포함 (!@#$%^&*(),.?\":{}|<>)"
|
|
)
|
|
layout.addWidget(self.requirements_text)
|
|
|
|
# 새 비밀번호 확인
|
|
confirm_password_label = QLabel("새 비밀번호 확인:")
|
|
confirm_password_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333;")
|
|
layout.addWidget(confirm_password_label)
|
|
|
|
confirm_password_layout = QHBoxLayout()
|
|
self.confirm_password_input = QLineEdit()
|
|
self.confirm_password_input.setPlaceholderText("새 비밀번호를 다시 입력하세요")
|
|
self.confirm_password_input.setEchoMode(QLineEdit.Password)
|
|
self.confirm_password_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 8px;
|
|
background-color: #fafafa;
|
|
}
|
|
QLineEdit:focus {
|
|
border-color: #1877f2;
|
|
background-color: white;
|
|
}
|
|
""")
|
|
self.confirm_password_input.textChanged.connect(self.check_password_match)
|
|
confirm_password_layout.addWidget(self.confirm_password_input)
|
|
|
|
self.confirm_show_password_checkbox = QCheckBox("보기")
|
|
self.confirm_show_password_checkbox.stateChanged.connect(self.toggle_confirm_password_visibility)
|
|
confirm_password_layout.addWidget(self.confirm_show_password_checkbox)
|
|
layout.addLayout(confirm_password_layout)
|
|
|
|
# 비밀번호 일치 상태 표시
|
|
self.match_label = QLabel("")
|
|
self.match_label.setStyleSheet("font-size: 12px; margin-top: 5px;")
|
|
layout.addWidget(self.match_label)
|
|
|
|
# 버튼 영역
|
|
button_layout = QHBoxLayout()
|
|
button_layout.setSpacing(15)
|
|
|
|
self.cancel_button = QPushButton("취소")
|
|
self.cancel_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #f8f9fa;
|
|
color: #333;
|
|
border: 1px solid #ccc;
|
|
padding: 12px 24px;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #e9ecef;
|
|
border-color: #adb5bd;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #dee2e6;
|
|
}
|
|
""")
|
|
self.cancel_button.clicked.connect(self.reject)
|
|
|
|
self.change_button = QPushButton("변경")
|
|
self.change_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #1877f2;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 24px;
|
|
border-radius: 6px;
|
|
font-size: 14px;
|
|
font-weight: bold;
|
|
min-width: 100px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #166fe5;
|
|
}
|
|
QPushButton:pressed {
|
|
background-color: #1462d0;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #ccc;
|
|
color: #888;
|
|
}
|
|
""")
|
|
self.change_button.clicked.connect(self.handle_password_change)
|
|
self.change_button.setEnabled(False)
|
|
|
|
button_layout.addStretch()
|
|
button_layout.addWidget(self.cancel_button)
|
|
button_layout.addWidget(self.change_button)
|
|
layout.addLayout(button_layout)
|
|
|
|
self.setLayout(layout)
|
|
|
|
def toggle_current_password_visibility(self, state):
|
|
"""현재 비밀번호 보기/숨기기 토글"""
|
|
if state == 2: # Qt.Checked
|
|
self.current_password_input.setEchoMode(QLineEdit.Normal)
|
|
else:
|
|
self.current_password_input.setEchoMode(QLineEdit.Password)
|
|
|
|
def toggle_new_password_visibility(self, state):
|
|
"""새 비밀번호 보기/숨기기 토글"""
|
|
if state == 2: # Qt.Checked
|
|
self.new_password_input.setEchoMode(QLineEdit.Normal)
|
|
else:
|
|
self.new_password_input.setEchoMode(QLineEdit.Password)
|
|
|
|
def toggle_confirm_password_visibility(self, state):
|
|
"""비밀번호 확인 보기/숨기기 토글"""
|
|
if state == 2: # Qt.Checked
|
|
self.confirm_password_input.setEchoMode(QLineEdit.Normal)
|
|
else:
|
|
self.confirm_password_input.setEchoMode(QLineEdit.Password)
|
|
|
|
def check_password_strength(self):
|
|
"""비밀번호 강도 검사"""
|
|
password = self.new_password_input.text()
|
|
if not password:
|
|
self.strength_bar.setValue(0)
|
|
self.strength_bar.setStyleSheet("""
|
|
QProgressBar {
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
height: 20px;
|
|
}
|
|
QProgressBar::chunk {
|
|
background-color: #ff4444;
|
|
border-radius: 3px;
|
|
}
|
|
""")
|
|
self.update_change_button_state()
|
|
return
|
|
|
|
# 비밀번호 강도 계산
|
|
strength = 0
|
|
if len(password) >= 8:
|
|
strength += 20
|
|
if any(c.isupper() for c in password):
|
|
strength += 20
|
|
if any(c.islower() for c in password):
|
|
strength += 20
|
|
if any(c.isdigit() for c in password):
|
|
strength += 20
|
|
if any(c in "!@#$%^&*(),.?\":{}|<>" for c in password):
|
|
strength += 20
|
|
|
|
self.strength_bar.setValue(strength)
|
|
|
|
# 강도에 따른 색상 변경
|
|
if strength < 40:
|
|
color = "#ff4444" # 빨간색
|
|
elif strength < 80:
|
|
color = "#ff8c00" # 주황색
|
|
else:
|
|
color = "#00aa00" # 초록색
|
|
|
|
self.strength_bar.setStyleSheet(f"""
|
|
QProgressBar {{
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
text-align: center;
|
|
height: 20px;
|
|
}}
|
|
QProgressBar::chunk {{
|
|
background-color: {color};
|
|
border-radius: 3px;
|
|
}}
|
|
""")
|
|
|
|
self.update_change_button_state()
|
|
|
|
def check_password_match(self):
|
|
"""비밀번호 일치 확인"""
|
|
new_password = self.new_password_input.text()
|
|
confirm_password = self.confirm_password_input.text()
|
|
|
|
if not confirm_password:
|
|
self.match_label.setText("")
|
|
elif new_password == confirm_password:
|
|
self.match_label.setText("✓ 비밀번호가 일치합니다")
|
|
self.match_label.setStyleSheet("font-size: 12px; color: #00aa00; margin-top: 5px;")
|
|
else:
|
|
self.match_label.setText("✗ 비밀번호가 일치하지 않습니다")
|
|
self.match_label.setStyleSheet("font-size: 12px; color: #ff4444; margin-top: 5px;")
|
|
|
|
self.update_change_button_state()
|
|
|
|
def update_change_button_state(self):
|
|
"""변경 버튼 활성화/비활성화 상태 업데이트"""
|
|
current_password = self.current_password_input.text()
|
|
new_password = self.new_password_input.text()
|
|
confirm_password = self.confirm_password_input.text()
|
|
|
|
# 모든 필드가 채워져 있고, 비밀번호가 일치하며, 강도가 충분한지 확인
|
|
is_valid = (
|
|
current_password.strip() != "" and
|
|
new_password.strip() != "" and
|
|
confirm_password.strip() != "" and
|
|
new_password == confirm_password and
|
|
self.strength_bar.value() >= 100 # 모든 조건을 만족해야 100
|
|
)
|
|
|
|
self.change_button.setEnabled(is_valid)
|
|
|
|
def handle_password_change(self):
|
|
"""비밀번호 변경 처리"""
|
|
current_password = self.current_password_input.text().strip()
|
|
new_password = self.new_password_input.text().strip()
|
|
confirm_password = self.confirm_password_input.text().strip()
|
|
|
|
# 기본 검증
|
|
if not current_password or not new_password or not confirm_password:
|
|
QMessageBox.warning(self, "입력 오류", "모든 필드를 입력해주세요.")
|
|
return
|
|
|
|
if new_password != confirm_password:
|
|
QMessageBox.warning(self, "입력 오류", "새 비밀번호가 일치하지 않습니다.")
|
|
return
|
|
|
|
if current_password == new_password:
|
|
QMessageBox.warning(self, "입력 오류", "현재 비밀번호와 새 비밀번호가 같습니다.")
|
|
return
|
|
|
|
# 비밀번호 강도 검증
|
|
validation_result = self.supabase_manager.validate_password_strength(new_password)
|
|
if not validation_result["is_valid"]:
|
|
error_message = "비밀번호 보안 요구사항을 만족하지 않습니다:\n\n"
|
|
for error in validation_result["errors"]:
|
|
error_message += f"• {error}\n"
|
|
QMessageBox.warning(self, "비밀번호 보안 오류", error_message)
|
|
return
|
|
|
|
# 버튼 비활성화
|
|
self.change_button.setEnabled(False)
|
|
self.cancel_button.setEnabled(False)
|
|
|
|
try:
|
|
# 비밀번호 변경 시도
|
|
self.logger.log("비밀번호 변경 시도 시작", level=logging.INFO)
|
|
result = self.supabase_manager.change_password(current_password, new_password)
|
|
|
|
if result["success"]:
|
|
self.logger.log("비밀번호 변경 성공", level=logging.INFO)
|
|
QMessageBox.information(self, "변경 완료", result["message"])
|
|
self.accept()
|
|
else:
|
|
self.logger.log(f"비밀번호 변경 실패: {result['error']}", level=logging.WARNING)
|
|
QMessageBox.warning(self, "변경 실패", result["message"])
|
|
|
|
except Exception as e:
|
|
error_message = f"비밀번호 변경 중 오류가 발생했습니다: {str(e)}"
|
|
self.logger.log(error_message, level=logging.ERROR, exc_info=True)
|
|
QMessageBox.critical(self, "오류", error_message)
|
|
|
|
finally:
|
|
# 버튼 재활성화
|
|
self.change_button.setEnabled(True)
|
|
self.cancel_button.setEnabled(True) |