From bca0fdd941dcfa1c629c9f0e4faf5db885b9622d Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 22 Sep 2026 20:38:35 -0400 Subject: [PATCH] fix: login 2FA bloque sans erreur BUG-069 (challenge montait dans .login-box inexistant -> .login-card + erreur visible) --- CHANGELOG.md | 20 +++++++++++++++++++- README.fr.md | 6 +++--- README.md | 6 +++--- VERSION | 2 +- desktop/Cargo.lock | 2 +- desktop/Cargo.toml | 2 +- desktop/tauri.conf.json | 2 +- docs/ISSUES_TODOLIST.md | 2 ++ docs/ROADMAP.md | 2 +- frontend/js/auth.js | 18 ++++++++++++++++-- frontend/locales/en.json | 1 + frontend/locales/fr.json | 1 + package.json | 2 +- tests/frontend/mfa-settings.test.mjs | 27 +++++++++++++++++++++++++++ 14 files changed, 78 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67825a1..e631037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **En cours de développement** : les changements à venir sont listés dans la section -> [Unreleased](#unreleased). La dernière version livrée est **2.16.1**. +> [Unreleased](#unreleased). La dernière version livrée est **2.16.2**. --- @@ -14,6 +14,24 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.16.2] — 2026-09-22 + +### Corrigé + +- **BUG-069 - Login 2FA bloqué sans erreur** : après user+mot de passe corrects + sur un compte avec 2FA, la page de login restait affichée sans erreur et le + challenge MFA n'apparaissait jamais. Cause : `showMfaChallenge` + (`frontend/js/auth.js`) montait le challenge dans `.login-box`, inexistant + dans `index.html` (marquage réel : `#login-screen > .login-card`) → + `return` silencieux. Correctif : montage dans `.login-card` (repli + `#login-screen`) + erreur visible (`mfa.challenge_unavailable`, FR/EN) au + lieu d'un retour silencieux si le point de montage manque. Vérifié de bout + en bout au navigateur (Playwright, instance Docker) : challenge affiché, + code erroné → erreur, code valide → connecté. Tests : + `tests/frontend/mfa-settings.test.mjs` (+2 contrôles d'ancrage DOM). + +--- + ## [2.16.1] — 2026-09-22 ### Corrigé diff --git a/README.fr.md b/README.fr.md index c812b06..1540ae8 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. -[![Version](https://img.shields.io/badge/Version-2.16.1-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.16.2-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -927,8 +927,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.16.1). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.16.2). --- -*Projet : ObsiGate | Version : 2.16.1 | Dernière mise à jour : Juin 2026* +*Projet : ObsiGate | Version : 2.16.2 | Dernière mise à jour : Juin 2026* diff --git a/README.md b/README.md index 6084f45..67db440 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. -[![Version](https://img.shields.io/badge/Version-2.16.1-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.16.2-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1096,8 +1096,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.16.1). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.16.2). --- -*Project: ObsiGate | Version: 2.16.1 | Last updated: May 2026* +*Project: ObsiGate | Version: 2.16.2 | Last updated: May 2026* diff --git a/VERSION b/VERSION index 0e7079b..43c85e7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.16.1 +2.16.2 diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index 9b90451..e40e182 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.16.1" +version = "2.16.2" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index bb98302..cfa1ebd 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.16.1" +version = "2.16.2" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index b23c025..e7e6985 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.16.1", + "version": "2.16.2", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index 3f4d1bb..55d9df5 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -177,6 +177,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-066* | [🔵 MINEUR] Configuration : icônes manquantes dans la table des matières (« Fichiers cachés », « Partages publics ») | 🟢 corrigé | P3 | 📱 frontend | IA | `frontend/locales/{fr,en}.json` | Ouvrir Configuration → observer le sommaire : les entrées « Fichiers cachés » et « Partages publics » n'ont pas d'icône | `config.section_hidden` → « 🗂️ Fichiers cachés » / « 🗂️ Hidden files », `config.section_shares` → « 📤 Partages publics » (EN avait déjà l'icône). Test : `tests/frontend/unit.test.mjs` (+1 : toutes les entrées du sommaire portent une icône FR/EN) | Les libellés du sommaire utilisent des clés i18n distinctes des titres de section (`auto.f8ba6127`, `config.section_partages-publics`) qui, elles, avaient l'icône | | *BUG-067* | [🔵 MINEUR] Guide d'utilisation : l'entrée « 📱 Mobile » du sommaire ne fait rien (section absente) | 🟢 corrigé | P3 | 📱 frontend | IA | `frontend/index.html` | Ouvrir le Guide → cliquer « 📱 Mobile » dans le sommaire : rien ne se passe | L'ancre `#help-mobile-editor` était présente dans la TOC mais aucune section `id="help-mobile-editor"` n'existait (l'édition mobile n'était qu'un h3 de `help-edition`). Fix #105 : section dédiée créée avec ancre + entrée de nav cohérente. | Vérifié par test statique `tests/test_guide.py::test_nav_anchors_resolve` | | *BUG-068* | Configuration — section « 🔒 Sécurité du compte » inachevée : boutons hors thème, QR code invisible, fiabilité des fonctions à valider | 🟢 corrigé | P1 | 📱 frontend + ⚙️ backend | IA | `frontend/js/auth.js`, `frontend/style.css`, `backend/auth/router.py` | Configuration → 🔒 Sécurité du compte | `frontend/style.css` (+`config-btn-primary`/`danger` thème), `backend/auth/router.py` (`qr_data_url` segno local), `frontend/js/auth.js` (QR local + fallback, recovery WebAuthn, carte mot de passe, escapeHtml labels), locales FR/EN, `backend/requirements.txt` (+segno) ; tests `tests/test_mfa.py` (+1) + `tests/frontend/mfa-settings.test.mjs` (nouveau, 9) | pytest 1241 passed / 6 skipped, ruff 0, mypy 0, frontend unit + validate-imports verts | +| *BUG-069* | Login 2FA bloqué sans erreur : après user+pwd corrects, la page de login reste affichée et le challenge MFA n'apparaît jamais | 🟢 corrigé | P0 | 📱 frontend | IA | `frontend/js/auth.js`, `frontend/index.html` | Activer 2FA → logout → login (bon user+pwd) | `frontend/js/auth.js` (`showMfaChallenge` → `.login-card` + erreur `mfa.challenge_unavailable` si montage impossible), locales FR/EN ; tests `tests/frontend/mfa-settings.test.mjs` (+2) | Reproduit au navigateur avant correctif (challenge jamais affiché), vérifié après : challenge affiché, code erroné → erreur, code valide (200) → app ; frontend mfa-settings 11/11, unit + validate-imports verts | | | | | | | | | | | | | ### TODOs techniques (améliorations / nouvelles tâches) @@ -251,6 +252,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | 2026-09-18 | #105, BUG-067 | Documentation + correction | `frontend/index.html`, `frontend/js/config.js`, `frontend/style.css`, `frontend/locales/{fr,en}.json`, `backend/guide_export.py`, `backend/main.py`, `tests/test_guide.py`, `docs/features/guide-coverage-105.md`, `CHANGELOG.md`, `docs/ROADMAP.md`, `docs/ISSUES_TODOLIST.md` | **#105** : audit complet de couverture du Guide d'utilisation — 8 nouvelles sections (Architecture + diagramme Mermaid, API & intégrations, Diagrammes Mermaid & Excalidraw, Hors-ligne & synchronisation, Collaboration temps réel, Application desktop, Bibliothèque & signets, Multilingue) et compléments (recherche sémantique, MFA/WebAuthn, notifications push, exports HTML/ePub/ZIP, PDF, vue multi-panneaux, admin). Téléchargement du guide en Markdown et PDF (`GET /api/guide/download?format=md|pdf`, FR/EN, rendu par le moteur d'export existant). Guide plus large en desktop. **BUG-067** : ancre morte `#help-mobile-editor` → section dédiée créée. | 🟢 corrigé (en attente vérif utilisateur) | 2026-09-18 | #105 (ajustements) | Amélioration | `frontend/index.html`, `frontend/js/config.js`, `frontend/sw.js`, `frontend/locales/{fr,en}.json`, `backend/guide_export.py`, `backend/pdf_export.py`, `Dockerfile`, `scripts/build_guide_diagrams.py`, `scripts/render_guide_diagram.mjs`, `scripts/guide_content.py`, `backend/assets/guide_diagrams/df7366a40db6a5a2.png`, `tests/test_guide.py`, `docs/features/guide-coverage-105.md`, `CHANGELOG.md` | **#105 (retour utilisateur)** : 1) boutons de téléchargement du guide passés en icônes seules (tooltips i18n conservés) ; 2) le diagramme Mermaid de la section Architecture est désormais rendu en **vraie image** dans le PDF (pipeline de pré-rendu PNG Chromium+mermaid v11, PNG commité sous `backend/assets/guide_diagrams/.png`, résolu par `diagram_png_for()` ; le Markdown garde le fenced mermaid) ; 3) emoji du PDF rendus **en couleur** au lieu de rectangles : `fonts-noto-color-emoji` ajouté au Dockerfile + `"Noto Color Emoji"` en fin de pile de polices PDF. Vérifié : pytest 1218 (test_guide ×13), ruff/mypy 0, validate-imports 38, unit 10/10 ; PDF live conteneur 2020 : 24 pages, 0 glyphes tofu, diagramme 3568x1174 embarqué. | 🟢 livré | 2026-09-22 | BUG-068 | Correction | `backend/auth/router.py`, `backend/requirements.txt`, `frontend/js/auth.js`, `frontend/style.css`, `frontend/locales/{fr,en}.json`, `tests/test_mfa.py`, `tests/frontend/mfa-settings.test.mjs` (nouveau), `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-068** : section « 🔒 Sécurité du compte » finalisée. (1) Boutons hors thème : `config-btn-primary`/`config-btn-danger` n'existaient pas en CSS → définis depuis les variables du thème (+ états disabled). (2) QR invisible : l'image tierce était bloquée par la CSP (`img-src 'self' data: blob:`) et exposait le secret TOTP → QR SVG `data:` généré en local par le backend (`qr_data_url`, segno) avec repli saisie manuelle. (3) Codes de récupération perdus à la 1re activation WebAuthn → `_showRecoveryCodes(codes, targetId)` avec repli `webauthn-flow-area`. (4) Carte « Mot de passe » ajoutée (endpoint `change-password` existant, jusque-là sans UI) + échappement des libellés de clés WebAuthn. Vérifié : pytest 1241 passed / 6 skipped, ruff 0, mypy 0 (78 fichiers), `mfa-settings.test.mjs` 9/9, unit 10/10, validate-imports 39 modules. | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-09-23 | BUG-069 | Correction | `frontend/js/auth.js`, `frontend/locales/{fr,en}.json`, `tests/frontend/mfa-settings.test.mjs`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md` | **BUG-069** : login 2FA bloqué sans erreur — après user+pwd corrects, `showMfaChallenge` cherchait `.login-box` (inexistant dans `index.html`, marquage réel `#login-screen > .login-card`) et faisait un `return` silencieux : page de login figée, aucune erreur. Correctif : montage dans `.login-card` (repli `#login-screen`) + erreur visible `mfa.challenge_unavailable` (FR/EN) si le point de montage manque. **Reproduit au navigateur** (Playwright, instance Docker `obsigate-test`, compte jetable avec TOTP) : avant → challenge jamais affiché ; après → challenge affiché, code erroné → erreur, code valide (verify 200) → app. Tests : `mfa-settings.test.mjs` 11/11 (+2 ancrage DOM), unit 10/10, validate-imports 39 modules. | 🟢 corrigé (en attente vérif utilisateur) | --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d571cf1..f1fa0db 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.16.1 | **Dernière mise à jour :** 2026-09-22 +> **Version :** 2.16.2 | **Dernière mise à jour :** 2026-09-22 > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > vers les fonctionnalités livrées. > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** diff --git a/frontend/js/auth.js b/frontend/js/auth.js index 0c63109..cd1c208 100644 --- a/frontend/js/auth.js +++ b/frontend/js/auth.js @@ -552,8 +552,22 @@ function _startWebauthnLogin(mfaSection, username, rememberMe) { function showMfaChallenge(username, rememberMe, loginBtn, loginErrorEl, mfaMethod) { - const loginBox = document.querySelector(".login-box"); - if (!loginBox) return; + // BUG-069: the challenge used to mount into `.login-box`, which does not + // exist in index.html (the login markup is `#login-screen > .login-card > + // #login-form`) — querySelector returned null and the function silently + // returned, leaving the user stuck on the login page with no error after + // entering correct credentials. Mount into the real card, and never fail + // silently: surface the problem in the login error box instead. + const loginBox = document.querySelector(".login-card") + || document.getElementById("login-screen"); + if (!loginBox) { + const fallback = loginErrorEl || document.getElementById("login-error"); + if (fallback) { + fallback.textContent = t("mfa.challenge_unavailable"); + fallback.classList.remove("hidden"); + } + return; + } // Hide the normal login form const loginForm = document.getElementById("login-form"); diff --git a/frontend/locales/en.json b/frontend/locales/en.json index 8326918..abcf9a2 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -1899,6 +1899,7 @@ "mfa.password_change_btn": "Change password", "mfa.password_mismatch": "The two passwords do not match.", "mfa.password_changed": "Password updated.", + "mfa.challenge_unavailable": "Verification screen unavailable — please reload the page.", "bookslm.title": "BooksLM", "bookslm.files_indexed": "{count} files indexed", "bookslm.chars_loaded": "{chars} chars loaded", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 698862d..f2a7b38 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -1899,6 +1899,7 @@ "mfa.password_change_btn": "Changer le mot de passe", "mfa.password_mismatch": "Les deux mots de passe ne correspondent pas.", "mfa.password_changed": "Mot de passe mis à jour.", + "mfa.challenge_unavailable": "Écran de vérification indisponible — veuillez recharger la page.", "bookslm.title": "BooksLM", "bookslm.files_indexed": "{count} fichiers indexés", "bookslm.chars_loaded": "{chars} caractères chargés", diff --git a/package.json b/package.json index 1cfeacd..98c1d2c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.16.1", + "version": "2.16.2", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "main": "patch.js", "directories": { diff --git a/tests/frontend/mfa-settings.test.mjs b/tests/frontend/mfa-settings.test.mjs index 1618427..9b534da 100644 --- a/tests/frontend/mfa-settings.test.mjs +++ b/tests/frontend/mfa-settings.test.mjs @@ -31,6 +31,7 @@ const ROOT = path.join(__dirname, "..", ".."); const auth = readFileSync(path.join(ROOT, "frontend", "js", "auth.js"), "utf8"); const css = readFileSync(path.join(ROOT, "frontend", "style.css"), "utf8"); const router = readFileSync(path.join(ROOT, "backend", "auth", "router.py"), "utf8"); +const indexHtml = readFileSync(path.join(ROOT, "frontend", "index.html"), "utf8"); const fr = JSON.parse(readFileSync(path.join(ROOT, "frontend", "locales", "fr.json"), "utf8")); const en = JSON.parse(readFileSync(path.join(ROOT, "frontend", "locales", "en.json"), "utf8")); @@ -96,12 +97,38 @@ test("i18n — password + QR strings exist in FR and EN", () => { "mfa.password_change_btn", "mfa.password_mismatch", "mfa.password_changed", + "mfa.challenge_unavailable", ]) { assert.ok(fr[key], `fr.json missing ${key}`); assert.ok(en[key], `en.json missing ${key}`); } }); +// ── BUG-069: the MFA challenge must mount into a real DOM node ────────────── +test("auth.js — challenge mounts into .login-card (exists in index.html)", () => { + assert.doesNotMatch( + auth, + /querySelector\("\.login-box"\)/, + "showMfaChallenge queried .login-box, which never existed in index.html → silent return, login stuck with no error", + ); + assert.match( + auth, + /querySelector\("\.login-card"\)/, + "the challenge must mount into the real login container", + ); + assert.ok( + indexHtml.includes('class="login-card"'), + "index.html must contain the .login-card mount point", + ); +}); + +test("auth.js — showMfaChallenge never fails silently", () => { + const fn = auth.match(/function showMfaChallenge\(username, rememberMe, loginBtn, loginErrorEl, mfaMethod\) \{([\s\S]*?)\n \/\/ WebAuthn second factor/); + assert.ok(fn, "showMfaChallenge helper not found"); + assert.match(fn[1], /challenge_unavailable/, "a missing mount point must surface an error, not silently return"); + assert.doesNotMatch(fn[1], /if \(!loginBox\) return;/, "bare silent return is forbidden in the challenge flow"); +}); + if (process.exitCode) { console.error("\nMFA settings tests FAILED"); } else {