722 lines
29 KiB
Python
722 lines
29 KiB
Python
# password_change_dialog.py
|
|
from PySide6.QtWidgets import (
|
|
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton,
|
|
QMessageBox, QCheckBox, QFrame, QProgressBar, QTextEdit, QSizePolicy, QApplication
|
|
)
|
|
from PySide6.QtCore import Qt, QTimer, QObject, Signal, QThread, Slot
|
|
from PySide6.QtGui import QFont
|
|
import logging
|
|
import time
|
|
import threading
|
|
|
|
class PasswordChangeDialog(QDialog):
|
|
# 시그널 정의
|
|
code_send_result = Signal(dict, str) # (result, email)
|
|
code_send_error = Signal(str) # (error_message)
|
|
password_change_result = Signal(dict, str) # (result, email)
|
|
password_change_error = Signal(str) # (error_message)
|
|
|
|
def __init__(self, logger, supabase_manager, parent=None, required=False):
|
|
super().__init__(parent)
|
|
self.logger = logger
|
|
self.supabase_manager = supabase_manager
|
|
self.email_verified = False
|
|
self.required = required # 필수 변경 모드 (취소 불가능)
|
|
|
|
# 시그널 연결
|
|
self.code_send_result.connect(self._handle_send_code_result)
|
|
self.code_send_error.connect(self._handle_send_code_error)
|
|
self.password_change_result.connect(self._handle_password_change_result)
|
|
self.password_change_error.connect(self._handle_password_change_error)
|
|
|
|
self.setWindowTitle("비밀번호 변경")
|
|
|
|
# 창 크기 설정
|
|
window_width = 500
|
|
window_height = 600
|
|
self.resize(window_width, window_height)
|
|
|
|
# 화면 중앙에 위치시키기
|
|
if parent:
|
|
# 부모 창이 있으면 부모 창 중앙 (약간 위로)
|
|
parent_geometry = parent.geometry()
|
|
x = parent_geometry.x() + (parent_geometry.width() - window_width) // 2
|
|
y = parent_geometry.y() + (parent_geometry.height() - window_height) // 2 - (window_height // 3)
|
|
else:
|
|
# 부모 창이 없으면 화면 중앙 (사용 가능한 영역 기준)
|
|
screen = QApplication.primaryScreen()
|
|
if screen:
|
|
# availableGeometry를 사용하여 작업 표시줄 등을 제외한 실제 사용 가능한 영역 사용
|
|
available_geometry = screen.availableGeometry()
|
|
x = available_geometry.x() + (available_geometry.width() - window_width) // 2
|
|
# 화면 중앙보다 약간 위로 배치 (y 좌표에서 창 높이의 30% 정도 위로)
|
|
y = available_geometry.y() + (available_geometry.height() - window_height) // 2 - (window_height // 3)
|
|
else:
|
|
# 폴백: 기본 위치
|
|
x = 100
|
|
y = 10
|
|
|
|
self.move(x, y)
|
|
self.setModal(True)
|
|
|
|
# 필수 변경 모드인 경우 창 닫기 버튼 비활성화
|
|
if self.required:
|
|
self.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
|
|
|
|
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)
|
|
|
|
# 안내 문구
|
|
if self.required:
|
|
info_label = QLabel("보안을 위해 비밀번호를 반드시 변경해야 합니다.\n이메일 인증을 완료한 후 새 비밀번호를 설정하세요.")
|
|
else:
|
|
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)
|
|
|
|
# 이메일 입력 영역
|
|
email_label = QLabel("이메일:")
|
|
email_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333;")
|
|
layout.addWidget(email_label)
|
|
|
|
email_layout = QHBoxLayout()
|
|
self.email_input = QLineEdit()
|
|
self.email_input.setPlaceholderText("이메일 주소를 입력하세요")
|
|
self.email_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;
|
|
}
|
|
QLineEdit:disabled {
|
|
background-color: #e9ecef;
|
|
color: #6c757d;
|
|
}
|
|
""")
|
|
email_layout.addWidget(self.email_input)
|
|
|
|
self.send_code_button = QPushButton("인증코드 전송")
|
|
self.send_code_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #1877f2;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 20px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
min-width: 120px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #166fe5;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #ccc;
|
|
color: #888;
|
|
}
|
|
""")
|
|
self.send_code_button.clicked.connect(self.handle_send_verification_code)
|
|
email_layout.addWidget(self.send_code_button)
|
|
layout.addLayout(email_layout)
|
|
|
|
# 인증 코드 입력 영역
|
|
verification_label = QLabel("인증 코드:")
|
|
verification_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #333;")
|
|
layout.addWidget(verification_label)
|
|
|
|
verification_layout = QHBoxLayout()
|
|
self.verification_code_input = QLineEdit()
|
|
self.verification_code_input.setPlaceholderText("이메일로 받은 인증 코드를 입력하세요")
|
|
self.verification_code_input.setMaxLength(50)
|
|
self.verification_code_input.setEnabled(False)
|
|
self.verification_code_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 8px;
|
|
background-color: #e9ecef;
|
|
}
|
|
QLineEdit:focus {
|
|
border-color: #1877f2;
|
|
background-color: white;
|
|
}
|
|
QLineEdit:enabled {
|
|
background-color: #fafafa;
|
|
}
|
|
""")
|
|
verification_layout.addWidget(self.verification_code_input)
|
|
|
|
self.verify_button = QPushButton("인증 확인")
|
|
self.verify_button.setEnabled(False)
|
|
self.verify_button.setStyleSheet("""
|
|
QPushButton {
|
|
background-color: #28a745;
|
|
color: white;
|
|
border: none;
|
|
padding: 12px 20px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
font-weight: bold;
|
|
min-width: 120px;
|
|
}
|
|
QPushButton:hover {
|
|
background-color: #218838;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #ccc;
|
|
color: #888;
|
|
}
|
|
""")
|
|
self.verify_button.clicked.connect(self.handle_verify_code)
|
|
verification_layout.addWidget(self.verify_button)
|
|
layout.addLayout(verification_layout)
|
|
|
|
# 인증 상태 표시
|
|
self.verification_status_label = QLabel("")
|
|
self.verification_status_label.setStyleSheet("font-size: 12px; margin-top: 5px;")
|
|
layout.addWidget(self.verification_status_label)
|
|
|
|
# 로딩 상태 표시 (인증코드 전송 중)
|
|
self.loading_label = QLabel("")
|
|
self.loading_label.setStyleSheet("font-size: 12px; color: #1877f2; margin-top: 5px;")
|
|
self.loading_label.setAlignment(Qt.AlignCenter)
|
|
layout.addWidget(self.loading_label)
|
|
|
|
# 구분선
|
|
separator = QFrame()
|
|
separator.setFrameShape(QFrame.HLine)
|
|
separator.setStyleSheet("background-color: #ddd; max-height: 1px;")
|
|
layout.addWidget(separator)
|
|
|
|
# 새 비밀번호 입력 영역 (인증 완료 전까지 비활성화)
|
|
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.setEnabled(False)
|
|
self.new_password_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 8px;
|
|
background-color: #e9ecef;
|
|
}
|
|
QLineEdit:focus {
|
|
border-color: #1877f2;
|
|
background-color: white;
|
|
}
|
|
QLineEdit:enabled {
|
|
background-color: #fafafa;
|
|
}
|
|
""")
|
|
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.setEnabled(False)
|
|
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.setEnabled(False)
|
|
self.confirm_password_input.setStyleSheet("""
|
|
QLineEdit {
|
|
padding: 12px;
|
|
font-size: 14px;
|
|
border: 2px solid #ddd;
|
|
border-radius: 8px;
|
|
background-color: #e9ecef;
|
|
}
|
|
QLineEdit:focus {
|
|
border-color: #1877f2;
|
|
background-color: white;
|
|
}
|
|
QLineEdit:enabled {
|
|
background-color: #fafafa;
|
|
}
|
|
""")
|
|
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.setEnabled(False)
|
|
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;
|
|
}
|
|
QPushButton:disabled {
|
|
background-color: #e9ecef;
|
|
color: #6c757d;
|
|
}
|
|
""")
|
|
self.cancel_button.clicked.connect(self.reject)
|
|
|
|
# 필수 변경 모드인 경우 취소 버튼 비활성화
|
|
if self.required:
|
|
self.cancel_button.setEnabled(False)
|
|
self.cancel_button.setToolTip("비밀번호 변경이 필수입니다. 취소할 수 없습니다.")
|
|
|
|
self.change_button = QPushButton("변경")
|
|
self.change_button.setEnabled(False)
|
|
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)
|
|
|
|
button_layout.addStretch()
|
|
button_layout.addWidget(self.cancel_button)
|
|
button_layout.addWidget(self.change_button)
|
|
layout.addLayout(button_layout)
|
|
|
|
self.setLayout(layout)
|
|
|
|
def handle_send_verification_code(self):
|
|
"""인증 코드 전송 처리"""
|
|
email = self.email_input.text().strip()
|
|
|
|
if not email:
|
|
QMessageBox.warning(self, "입력 오류", "이메일을 입력해주세요.")
|
|
return
|
|
|
|
# 이메일 형식 검증
|
|
if "@" not in email or "." not in email.split("@")[1]:
|
|
QMessageBox.warning(self, "입력 오류", "올바른 이메일 형식을 입력해주세요.")
|
|
return
|
|
|
|
# 버튼 비활성화 및 로딩 상태 표시
|
|
self.send_code_button.setEnabled(False)
|
|
self.email_input.setEnabled(False)
|
|
self.loading_label.setText("인증 코드를 전송 중입니다...")
|
|
self.loading_label.setStyleSheet("font-size: 12px; color: #1877f2; margin-top: 5px; font-weight: bold;")
|
|
|
|
# UI 업데이트를 위해 즉시 처리
|
|
QTimer.singleShot(0, lambda: self._send_verification_code_threaded(email))
|
|
|
|
def _send_verification_code_threaded(self, email: str):
|
|
"""비동기로 인증 코드 전송 처리"""
|
|
def send_code():
|
|
try:
|
|
# 인증 코드 전송
|
|
result = self.supabase_manager.send_password_change_verification_email(email)
|
|
|
|
# 결과를 메인 스레드로 전달 (Signal 사용)
|
|
self.code_send_result.emit(result, email)
|
|
except Exception as e:
|
|
error_message = f"인증 코드 전송 중 오류가 발생했습니다: {str(e)}"
|
|
self.logger.log(error_message, level=logging.ERROR, exc_info=True)
|
|
# 에러를 메인 스레드로 전달 (Signal 사용)
|
|
self.code_send_error.emit(error_message)
|
|
|
|
# 백그라운드 스레드에서 실행
|
|
thread = threading.Thread(target=send_code, daemon=True)
|
|
thread.start()
|
|
|
|
@Slot(dict, str)
|
|
def _handle_send_code_result(self, result: dict, email: str):
|
|
"""인증 코드 전송 결과 처리"""
|
|
# 로딩 상태 제거 (즉시)
|
|
self.loading_label.setText("")
|
|
self.loading_label.setStyleSheet("font-size: 12px; color: #1877f2; margin-top: 5px;")
|
|
|
|
if result["success"]:
|
|
QMessageBox.information(self, "전송 완료",
|
|
f"인증 코드가 '{email}'로 전송되었습니다.\n\n"
|
|
"이메일을 확인하여 인증 코드를 입력해주세요.\n"
|
|
"이메일이 도착하지 않으면 스팸 폴더도 확인해주세요.")
|
|
self.verification_code_input.setEnabled(True)
|
|
self.verify_button.setEnabled(True)
|
|
self.verification_status_label.setText("인증 코드를 입력해주세요.")
|
|
self.verification_status_label.setStyleSheet("font-size: 12px; color: #666; margin-top: 5px;")
|
|
else:
|
|
QMessageBox.warning(self, "전송 실패", result["message"])
|
|
|
|
# 버튼 재활성화
|
|
self.send_code_button.setEnabled(True)
|
|
self.email_input.setEnabled(True)
|
|
|
|
@Slot(str)
|
|
def _handle_send_code_error(self, error_message: str):
|
|
"""인증 코드 전송 오류 처리"""
|
|
# 로딩 상태 제거
|
|
self.loading_label.setText("")
|
|
self.loading_label.setStyleSheet("font-size: 12px; color: #1877f2; margin-top: 5px;")
|
|
QMessageBox.critical(self, "오류", error_message)
|
|
|
|
# 버튼 재활성화
|
|
self.send_code_button.setEnabled(True)
|
|
self.email_input.setEnabled(True)
|
|
|
|
def handle_verify_code(self):
|
|
"""인증 코드 검증 처리"""
|
|
email = self.email_input.text().strip()
|
|
code = self.verification_code_input.text().strip()
|
|
|
|
if not code:
|
|
QMessageBox.warning(self, "입력 오류", "인증 코드를 입력해주세요.")
|
|
return
|
|
|
|
# 버튼 비활성화
|
|
self.verify_button.setEnabled(False)
|
|
|
|
try:
|
|
# 인증 코드 검증
|
|
result = self.supabase_manager.verify_password_change_token(email, code)
|
|
|
|
if result["success"]:
|
|
self.email_verified = True
|
|
self.verification_status_label.setText("✓ 이메일 인증이 완료되었습니다.")
|
|
self.verification_status_label.setStyleSheet("font-size: 12px; color: #28a745; margin-top: 5px; font-weight: bold;")
|
|
|
|
# 비밀번호 입력 필드 활성화
|
|
self.new_password_input.setEnabled(True)
|
|
self.confirm_password_input.setEnabled(True)
|
|
self.new_show_password_checkbox.setEnabled(True)
|
|
self.confirm_show_password_checkbox.setEnabled(True)
|
|
|
|
# 인증 코드 입력 필드 비활성화
|
|
self.verification_code_input.setEnabled(False)
|
|
self.verify_button.setEnabled(False)
|
|
|
|
QMessageBox.information(self, "인증 완료", "이메일 인증이 완료되었습니다.\n이제 새 비밀번호를 입력할 수 있습니다.")
|
|
else:
|
|
QMessageBox.warning(self, "인증 실패", result["message"])
|
|
self.verification_status_label.setText("✗ 인증 실패: " + result["message"])
|
|
self.verification_status_label.setStyleSheet("font-size: 12px; color: #dc3545; margin-top: 5px;")
|
|
|
|
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.verify_button.setEnabled(True)
|
|
|
|
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):
|
|
"""변경 버튼 활성화/비활성화 상태 업데이트"""
|
|
if not self.email_verified:
|
|
self.change_button.setEnabled(False)
|
|
return
|
|
|
|
new_password = self.new_password_input.text()
|
|
confirm_password = self.confirm_password_input.text()
|
|
|
|
# 모든 필드가 채워져 있고, 비밀번호가 일치하며, 강도가 충분한지 확인
|
|
is_valid = (
|
|
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):
|
|
"""비밀번호 변경 처리"""
|
|
email = self.email_input.text().strip()
|
|
new_password = self.new_password_input.text().strip()
|
|
confirm_password = self.confirm_password_input.text().strip()
|
|
|
|
# 기본 검증
|
|
if not self.email_verified:
|
|
QMessageBox.warning(self, "인증 오류", "이메일 인증을 먼저 완료해주세요.")
|
|
return
|
|
|
|
if not new_password or not confirm_password:
|
|
QMessageBox.warning(self, "입력 오류", "모든 필드를 입력해주세요.")
|
|
return
|
|
|
|
if new_password != confirm_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)
|
|
self.loading_label.setText("비밀번호를 변경하는 중입니다...")
|
|
self.loading_label.setStyleSheet("font-size: 12px; color: #1877f2; margin-top: 5px; font-weight: bold;")
|
|
|
|
# UI 업데이트를 위해 즉시 처리
|
|
QTimer.singleShot(0, lambda: self._change_password_threaded(email, new_password))
|
|
|
|
def _change_password_threaded(self, email: str, new_password: str):
|
|
"""비동기로 비밀번호 변경 처리"""
|
|
def change_password():
|
|
try:
|
|
# 비밀번호 변경 시도
|
|
result = self.supabase_manager.change_password_with_email_verification(email, new_password)
|
|
|
|
# 결과를 메인 스레드로 전달 (Signal 사용)
|
|
self.password_change_result.emit(result, email)
|
|
except Exception as e:
|
|
error_message = f"비밀번호 변경 중 오류가 발생했습니다: {str(e)}"
|
|
self.logger.log(error_message, level=logging.ERROR, exc_info=True)
|
|
# 에러를 메인 스레드로 전달 (Signal 사용)
|
|
self.password_change_error.emit(error_message)
|
|
|
|
# 백그라운드 스레드에서 실행
|
|
thread = threading.Thread(target=change_password, daemon=True)
|
|
thread.start()
|
|
|
|
@Slot(dict, str)
|
|
def _handle_password_change_result(self, result: dict, email: str):
|
|
"""비밀번호 변경 결과 처리"""
|
|
# 로딩 상태 제거
|
|
self.loading_label.setText("")
|
|
|
|
if result["success"]:
|
|
self.logger.log("비밀번호 변경 성공", level=logging.INFO)
|
|
QMessageBox.information(self, "변경 완료", result["message"])
|
|
self.accept()
|
|
else:
|
|
self.logger.log(f"비밀번호 변경 실패: {result.get('error', 'Unknown')}", level=logging.WARNING)
|
|
QMessageBox.warning(self, "변경 실패", result["message"])
|
|
|
|
# 버튼 재활성화
|
|
self.change_button.setEnabled(True)
|
|
if not self.required:
|
|
self.cancel_button.setEnabled(True)
|
|
|
|
@Slot(str)
|
|
def _handle_password_change_error(self, error_message: str):
|
|
"""비밀번호 변경 오류 처리"""
|
|
# 로딩 상태 제거
|
|
self.loading_label.setText("")
|
|
QMessageBox.critical(self, "오류", error_message)
|
|
|
|
# 버튼 재활성화
|
|
self.change_button.setEnabled(True)
|
|
if not self.required:
|
|
self.cancel_button.setEnabled(True)
|
|
|
|
def closeEvent(self, event):
|
|
"""창 닫기 이벤트 처리"""
|
|
if self.required:
|
|
# 필수 변경 모드인 경우 창 닫기 방지
|
|
QMessageBox.warning(self, "비밀번호 변경 필수",
|
|
"보안을 위해 비밀번호 변경이 필수입니다.\n비밀번호를 변경한 후에만 계속 진행할 수 있습니다.")
|
|
event.ignore()
|
|
else:
|
|
event.accept()
|