diff --git a/app/main.py b/app/main.py index 84be412..41f103a 100644 --- a/app/main.py +++ b/app/main.py @@ -849,11 +849,11 @@ def settings_plages_save( return RedirectResponse("/settings?plages_saved=1", status_code=303) -@app.get("/presence/{token}/arrivee") +@app.post("/presence/{token}/arrivee") def presence_arrivee(token: str): user_id = find_user_by_token(token) if not user_id: - logger.warning("GET /presence/.../arrivee: token inconnu") + logger.warning("POST /presence/.../arrivee: token inconnu") return JSONResponse({"error": "invalid token"}, status_code=404) logger.info("présence: arrivée détectée pour %r", user_id) save_presence(user_id, present=True, since=datetime.now().isoformat()) @@ -861,11 +861,11 @@ def presence_arrivee(token: str): return {"ok": True} -@app.get("/presence/{token}/depart") +@app.post("/presence/{token}/depart") def presence_depart(token: str): user_id = find_user_by_token(token) if not user_id: - logger.warning("GET /presence/.../depart: token inconnu") + logger.warning("POST /presence/.../depart: token inconnu") return JSONResponse({"error": "invalid token"}, status_code=404) logger.info("présence: départ détecté pour %r", user_id) save_presence(user_id, present=False, since=datetime.now().isoformat()) diff --git a/app/tests/test_logs.py b/app/tests/test_logs.py index 0b5d64a..337e1c8 100644 --- a/app/tests/test_logs.py +++ b/app/tests/test_logs.py @@ -46,9 +46,9 @@ def test_logs_page_empty_for_new_user(): def test_logs_page_shows_entries_after_presence(): token = models.load_notif_config("alice")["token"] # Simule une arrivée Tasker - client.get(f"/presence/{token}/arrivee") + client.post(f"/presence/{token}/arrivee") # Simule un départ - client.get(f"/presence/{token}/depart") + client.post(f"/presence/{token}/depart") r = client.get("/logs", cookies=COOKIES) assert r.status_code == 200 @@ -267,12 +267,12 @@ def test_regenerate_presence_token_invalidates_old_urls(): assert "Token régénéré" in r.text # L'ancienne URL doit maintenant renvoyer 404 (token inconnu) - r_old = client.get(f"/presence/{old_token}/arrivee") + r_old = client.post(f"/presence/{old_token}/arrivee") assert r_old.status_code == 404 # La nouvelle URL doit fonctionner new_cfg = models.load_notif_config("alice") - r_new = client.get(f"/presence/{new_cfg['token']}/arrivee") + r_new = client.post(f"/presence/{new_cfg['token']}/arrivee") assert r_new.status_code == 200 assert r_new.json() == {"ok": True} diff --git a/app/tests/test_security.py b/app/tests/test_security.py index 73fc356..b856ef9 100644 --- a/app/tests/test_security.py +++ b/app/tests/test_security.py @@ -398,3 +398,26 @@ def test_csrf_accepts_get_regardless_of_origin(): r = client.get("/login", headers={"Origin": "https://evil.attacker"}) # 200 (page login) ou 302 (déjà loggué) mais pas 403 assert r.status_code != 403 + + +# ── Endpoints de présence : POST uniquement (anti CSRF trivial) ────────────── + +def test_presence_endpoints_require_post(): + """Les endpoints /presence/... doivent refuser les GET (CSRF trivial via ).""" + notif = models.load_notif_config("alice") if (models.DATA_DIR / "users" / "alice").exists() else None + # Crée alice si nécessaire et récupère un token valide + _seed_user("alice") + token = models.load_notif_config("alice")["token"] + + # GET doit échouer (405 Method Not Allowed) + r_get_arr = client.get(f"/presence/{token}/arrivee", follow_redirects=False) + r_get_dep = client.get(f"/presence/{token}/depart", follow_redirects=False) + assert r_get_arr.status_code == 405, r_get_arr.status_code + assert r_get_dep.status_code == 405 + + # POST doit fonctionner + r_post_arr = client.post(f"/presence/{token}/arrivee") + r_post_dep = client.post(f"/presence/{token}/depart") + assert r_post_arr.status_code == 200 + assert r_post_dep.status_code == 200 + assert r_post_arr.json() == {"ok": True} diff --git a/docs/notifications.md b/docs/notifications.md index ab085a2..f27a01b 100644 --- a/docs/notifications.md +++ b/docs/notifications.md @@ -32,10 +32,10 @@ contiennent un token secret, ne les partage pas. coupe la géolocalisation quand l'app n'est pas au premier plan). 3. Crée un profil : **Trigger → Location** → renseigne l'adresse/coordonnées du bureau et un rayon (~100–150 m) → événement **Enter**. - Tâche associée : **Net → HTTP Request**, méthode `GET`, URL = ton lien + Tâche associée : **Net → HTTP Request**, méthode `POST`, URL = ton lien **Arrivée** (copié depuis `/settings`). 4. Crée un second profil symétrique : même géofence, événement **Exit**, - tâche HTTP GET vers ton lien **Départ**. + tâche HTTP POST vers ton lien **Départ**. ### 3. Vérifier @@ -86,10 +86,10 @@ current_ssid=$(nmcli -t -f active,ssid dev wifi | grep '^yes' | cut -d: -f2) case "$2" in up) - [ "$current_ssid" = "$BUREAU_SSID" ] && curl -fsS "$URL_ARRIVEE" + [ "$current_ssid" = "$BUREAU_SSID" ] && curl -fsS -X POST "$URL_ARRIVEE" ;; down) - [ "$current_ssid" != "$BUREAU_SSID" ] && curl -fsS "$URL_DEPART" + [ "$current_ssid" != "$BUREAU_SSID" ] && curl -fsS -X POST "$URL_DEPART" ;; esac ```