from PyQt6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QPushButton, QProgressBar, QFrame, ) from PyQt6.QtCore import Qt, QTimer from PyQt6.QtGui import QFont from app.core import mfa as mfa_core from app.ui import theme class MFADialog(QDialog): def __init__(self, secret: str, parent=None): super().__init__(parent) self._secret = secret self._verified = False self.setWindowTitle("WGSecure — Authentification MFA") self.setFixedWidth(340) self.setModal(True) self.setStyleSheet(f""" QDialog {{ background: {theme.BG_SUNKEN}; }} QLabel {{ color: {theme.TEXT}; background: transparent; }} """) self._build_ui() self._timer = QTimer(self) self._timer.timeout.connect(self._tick) self._timer.start(500) self._tick() def _build_ui(self): layout = QVBoxLayout(self) layout.setSpacing(12) layout.setContentsMargins(20, 20, 20, 20) title = QLabel("Vérification en deux étapes") title.setAlignment(Qt.AlignmentFlag.AlignCenter) f = QFont() f.setPointSize(13) f.setBold(True) title.setFont(f) layout.addWidget(title) sub = QLabel("Entrez le code à 6 chiffres de votre application d'authentification.") sub.setWordWrap(True) sub.setAlignment(Qt.AlignmentFlag.AlignCenter) sub.setStyleSheet(f"color: {theme.TEXT_MUTED};") layout.addWidget(sub) sep = QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet(f"color: {theme.BORDER_SOFT};") layout.addWidget(sep) self._code_input = QLineEdit() self._code_input.setPlaceholderText("000 000") self._code_input.setMaxLength(7) self._code_input.setAlignment(Qt.AlignmentFlag.AlignCenter) f2 = QFont("Courier", 20) f2.setLetterSpacing(QFont.SpacingType.AbsoluteSpacing, 4) self._code_input.setFont(f2) self._code_input.setStyleSheet( f"QLineEdit {{ padding: 8px; border: 2px solid {theme.ACCENT};" f" border-radius: 6px; background: {theme.BG}; color: {theme.TEXT}; }}" ) self._code_input.returnPressed.connect(self._verify) layout.addWidget(self._code_input) self._error_label = QLabel("") self._error_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._error_label.setStyleSheet(f"color: {theme.FAIL_TEXT}; font-weight: bold;") layout.addWidget(self._error_label) self._progress = QProgressBar() self._progress.setRange(0, 30) self._progress.setTextVisible(False) self._progress.setFixedHeight(6) self._progress.setStyleSheet( f"QProgressBar {{ border-radius: 3px; background: {theme.BG}; }}" f"QProgressBar::chunk {{ background: {theme.ACCENT_LIGHT}; border-radius: 3px; }}" ) layout.addWidget(self._progress) self._timer_label = QLabel("") self._timer_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._timer_label.setStyleSheet(f"color: {theme.TEXT_FAINT}; font-size: 11px;") layout.addWidget(self._timer_label) btn_row = QHBoxLayout() btn_cancel = QPushButton("Annuler") btn_cancel.setStyleSheet( f"QPushButton {{ padding: 8px 16px; border-radius: 5px; border: none;" f" background: {theme.BG_RAISED}; color: {theme.TEXT}; }}" f"QPushButton:hover {{ background: {theme.ACCENT_HOVER}; }}" ) btn_cancel.clicked.connect(self.reject) self._btn_ok = QPushButton("Vérifier") self._btn_ok.setDefault(True) self._btn_ok.setStyleSheet( f"QPushButton {{ padding: 8px 20px; border-radius: 5px; border: none;" f" background: {theme.ACCENT}; color: {theme.TEXT}; font-weight: bold; }}" f"QPushButton:hover {{ background: {theme.ACCENT_HOVER}; }}" ) self._btn_ok.clicked.connect(self._verify) btn_row.addWidget(btn_cancel) btn_row.addStretch() btn_row.addWidget(self._btn_ok) layout.addLayout(btn_row) def _tick(self): remaining = mfa_core.time_remaining() self._progress.setValue(remaining) self._timer_label.setText(f"Code valide encore {remaining}s") def _verify(self): raw = self._code_input.text().replace(" ", "").replace("-", "") if len(raw) != 6 or not raw.isdigit(): self._error_label.setText("Entrez exactement 6 chiffres.") self._code_input.setStyleSheet( f"QLineEdit {{ padding: 8px; border: 2px solid {theme.FAIL_TEXT};" f" border-radius: 6px; background: {theme.BG}; color: {theme.TEXT}; }}" ) return if mfa_core.verify_code(self._secret, raw): self._verified = True self.accept() else: self._error_label.setText("Code incorrect. Réessayez.") self._code_input.clear() self._code_input.setStyleSheet( f"QLineEdit {{ padding: 8px; border: 2px solid {theme.FAIL_TEXT};" f" border-radius: 6px; background: {theme.BG}; color: {theme.TEXT}; }}" ) def is_verified(self) -> bool: return self._verified def closeEvent(self, event): self._timer.stop() super().closeEvent(event)