# 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)