From 076b5a4fa25708337e0d29e9df97ccc9fedba4b5 Mon Sep 17 00:00:00 2001 From: Jacquin Antoine Date: Mon, 20 Jul 2026 00:01:12 +0200 Subject: [PATCH] =?UTF-8?q?Ajoute=20le=20support=20d'un=20jeton=20d'acc?= =?UTF-8?q?=C3=A8s=20pour=20les=20serveurs=20ntfy=20prot=C3=A9g=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Certains serveurs ntfy auto-hébergés (ex: ntfy.arkel.fr) renvoient 403 Forbidden sans authentification. Un nouveau champ "Jeton d'accès" (optionnel, type password) sur /settings permet de renseigner un token qui sera envoyé comme "Authorization: Bearer ..." à chaque publication, pour les rappels réels comme pour le bouton de test. La page /aide documente le cas 403 et la marche à suivre. 💘 Generated with Crush Assisted-by: Crush:glm-5.2 --- app/main.py | 9 +++-- app/models.py | 6 +++- app/notifications.py | 12 ++++--- app/templates/aide.html | 7 ++++ app/templates/settings.html | 10 ++++++ app/tests/test_logs.py | 65 +++++++++++++++++++++++++++++++++++++ 6 files changed, 101 insertions(+), 8 deletions(-) diff --git a/app/main.py b/app/main.py index ff5eb72..8bc386d 100644 --- a/app/main.py +++ b/app/main.py @@ -563,6 +563,7 @@ def settings_page(request: Request): "current_user": user_id, "ntfy_topic": notif.get("ntfy_topic") or "", "ntfy_server": notif.get("ntfy_server") or "https://ntfy.sh", + "ntfy_token": notif.get("ntfy_token") or "", "rappel_debut_h": notif.get("rappel_debut_h", 7), "rappel_fin_h": notif.get("rappel_fin_h", 20), "arrivee_url": f"{base_url}/presence/{notif['token']}/arrivee", @@ -578,6 +579,7 @@ def settings_page(request: Request): def settings_save( request: Request, ntfy_topic: str = Form(""), ntfy_server: str = Form(""), rappel_debut_h: int = Form(7), rappel_fin_h: int = Form(20), + ntfy_token: str = Form(""), ): user_id = _get_user(request) if not user_id: @@ -587,10 +589,11 @@ def settings_save( rappel_fin_h = max(rappel_debut_h + 1, min(24, rappel_fin_h)) logger.info( - "réglages notif mis à jour: user_id=%r ntfy_topic=%r ntfy_server=%r plage=%sh-%sh", + "réglages notif mis à jour: user_id=%r ntfy_topic=%r ntfy_server=%r plage=%sh-%sh token=%s", user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h, + "(défini)" if ntfy_token.strip() else "(vide)", ) - save_notif_config(user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h) + save_notif_config(user_id, ntfy_topic, ntfy_server, rappel_debut_h, rappel_fin_h, ntfy_token) return RedirectResponse("/settings?saved=1", status_code=303) @@ -616,7 +619,7 @@ def settings_test_ntfy(request: Request): ok = send_ntfy( server, topic, "Ceci est une notification de test — ta configuration ntfy fonctionne.", - "Test pointeuse", user_id=user_id, + "Test pointeuse", user_id=user_id, token=notif.get("ntfy_token", ""), ) cls = "settings-saved" if ok else "settings-error" msg = ("Notification envoyée. Vérifie ton téléphone, elle doit arriver en quelques secondes." diff --git a/app/models.py b/app/models.py index 3e024db..a6d6012 100644 --- a/app/models.py +++ b/app/models.py @@ -156,11 +156,12 @@ DEFAULT_RAPPEL_FIN_H = 20 def load_notif_config(user_id: str) -> dict: - """Return {"ntfy_topic", "ntfy_server", "rappel_debut_h", "rappel_fin_h", "token"}.""" + """Return {ntfy_topic, ntfy_server, ntfy_token, rappel_debut_h, rappel_fin_h, token}.""" f = _notif_file(user_id) if f.exists(): cfg = json.loads(f.read_text()) cfg.setdefault("ntfy_server", DEFAULT_NTFY_SERVER) + cfg.setdefault("ntfy_token", "") cfg.setdefault("rappel_debut_h", DEFAULT_RAPPEL_DEBUT_H) cfg.setdefault("rappel_fin_h", DEFAULT_RAPPEL_FIN_H) if cfg.get("token"): @@ -168,6 +169,7 @@ def load_notif_config(user_id: str) -> dict: else: cfg = { "ntfy_topic": None, "ntfy_server": DEFAULT_NTFY_SERVER, + "ntfy_token": "", "rappel_debut_h": DEFAULT_RAPPEL_DEBUT_H, "rappel_fin_h": DEFAULT_RAPPEL_FIN_H, } cfg["token"] = secrets.token_hex(16) @@ -179,10 +181,12 @@ def load_notif_config(user_id: str) -> dict: def save_notif_config( user_id: str, ntfy_topic: str, ntfy_server: str = "", rappel_debut_h: int = DEFAULT_RAPPEL_DEBUT_H, rappel_fin_h: int = DEFAULT_RAPPEL_FIN_H, + ntfy_token: str = "", ): cfg = load_notif_config(user_id) cfg["ntfy_topic"] = ntfy_topic.strip() or None cfg["ntfy_server"] = ntfy_server.strip().rstrip("/") or DEFAULT_NTFY_SERVER + cfg["ntfy_token"] = ntfy_token.strip() cfg["rappel_debut_h"] = rappel_debut_h cfg["rappel_fin_h"] = rappel_fin_h _ensure_user_dirs(user_id) diff --git a/app/notifications.py b/app/notifications.py index 4957081..7e1d0ad 100644 --- a/app/notifications.py +++ b/app/notifications.py @@ -11,11 +11,14 @@ logger = logging.getLogger("pointeuse.notifications") CHECK_INTERVAL_SECONDS = 300 -def send_ntfy(server: str, topic: str, message: str, title: str, user_id: str | None = None) -> bool: +def send_ntfy(server: str, topic: str, message: str, title: str, user_id: str | None = None, token: str = "") -> bool: + headers = {"Title": title.encode("utf-8"), "Priority": "high"} + if token: + headers["Authorization"] = f"Bearer {token}" req = urllib.request.Request( f"{server}/{topic}", data=message.encode("utf-8"), - headers={"Title": title.encode("utf-8"), "Priority": "high"}, + headers=headers, method="POST", ) try: @@ -43,6 +46,7 @@ async def check_user(user_id: str, now: datetime): notif = load_notif_config(user_id) topic = notif.get("ntfy_topic") server = notif.get("ntfy_server", "https://ntfy.sh") + token = notif.get("ntfy_token", "") debut_h = notif.get("rappel_debut_h", 7) fin_h = notif.get("rappel_fin_h", 20) if not topic: @@ -64,14 +68,14 @@ async def check_user(user_id: str, now: datetime): await asyncio.to_thread( send_ntfy, server, topic, "Tu es dans les locaux mais pas encore pointé ce matin.", - "⏰ Pense à pointer", user_id, + "⏰ Pense à pointer", user_id, token, ) elif not presence.get("present") and jour.get("matin_entree") and not jour.get("aprem_sortie"): logger.info("rappel 'pense à dépointer' pour %r", user_id) await asyncio.to_thread( send_ntfy, server, topic, "Tu as quitté les locaux sans dépointer la sortie.", - "⏰ Pense à dépointer", user_id, + "⏰ Pense à dépointer", user_id, token, ) diff --git a/app/templates/aide.html b/app/templates/aide.html index cbe55e8..ad5860f 100644 --- a/app/templates/aide.html +++ b/app/templates/aide.html @@ -57,6 +57,13 @@ (Réglages → Applications → ntfy → Batterie → "Aucune restriction"). Sinon Android peut couper les notifications en arrière-plan. +
  • + Serveur protégé par jeton : si ton instance ntfy renvoie 403 Forbidden, + elle exige un jeton d'accès. Crée-le sur le serveur (panneau d'admin ntfy, onglet + "Access tokens"), puis colle-le dans le champ Jeton d'accès sur + /settings. Il est envoyé comme + Authorization: Bearer ... à chaque publication. +
  • Astuce : si tu veux ton propre serveur ntfy (auto-hébergé, pour éviter le topic public), diff --git a/app/templates/settings.html b/app/templates/settings.html index 6dd1e47..6ed47e0 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -84,6 +84,16 @@ placeholder="ex: antoine-pointeuse-x7f2" autocomplete="off" > + +