AutoPercenty3/password_change_dialog.py

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)