Instance locale sans config : mail et lien email optionnels
L'app tourne désormais pour soi sans rien configurer. Sans relais
SMTP, le lien de création de compte s'affiche directement à l'écran,
construit depuis l'URL en cours — aucune config d'URL nécessaire,
même en naviguant par IP sur le LAN (le contrôle anti-open-redirect
du Host ne concerne que le chemin par email). allowed_email_domain
vide accepte tous les emails : l'adresse ne sert que d'identifiant.
Le fichier d'exemple de config était périmé : il datait d'avant les
plages, force_secure_cookies et login_rate_limit_per_min, et
laissait croire le SMTP obligatoire. Il reflète maintenant les clés
réelles, toutes optionnelles et vides par défaut.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@ -597,3 +597,80 @@ def test_rate_limit_can_be_disabled_via_config():
|
||||
cfg["login_rate_limit_per_min"] = main.DEFAULT_LOGIN_MAX_PER_MIN
|
||||
models.save_config(cfg)
|
||||
main._LOGIN_ATTEMPTS.clear()
|
||||
|
||||
|
||||
# ── Config mail optionnelle : usage local sans relais SMTP ────────────────────
|
||||
|
||||
def test_sans_smtp_le_lien_de_compte_s_affiche_a_l_ecran(monkeypatch):
|
||||
"""Sans smtp.host, la création de compte doit fonctionner sans email : le
|
||||
lien de définition de mot de passe s'affiche directement dans la page
|
||||
(construit depuis l'URL en cours, pas de vecteur de phishing email)."""
|
||||
cfg = models.load_config()
|
||||
ancien_smtp = cfg.get("smtp")
|
||||
ancien_domaine = cfg.get("allowed_email_domain")
|
||||
cfg["smtp"] = {}
|
||||
cfg["allowed_email_domain"] = ""
|
||||
models.save_config(cfg)
|
||||
try:
|
||||
r = client.post("/login", data={"email": "solo.perso@nimporte.fr"},
|
||||
headers={"Host": "localhost:8000"}, follow_redirects=False)
|
||||
assert r.status_code == 200
|
||||
assert "/set-password/" in r.text # le lien est affiché
|
||||
assert "SMTP" in r.text # avec l'explication
|
||||
# Le lien affiché fonctionne : définir le mot de passe puis se connecter
|
||||
import re as _re
|
||||
token = _re.search(r"/set-password/([A-Za-z0-9_\-]+)", r.text).group(1)
|
||||
r = client.post(f"/set-password/{token}",
|
||||
data={"password": "s" * 12, "password2": "s" * 12},
|
||||
follow_redirects=False)
|
||||
assert r.status_code == 302
|
||||
r = client.post("/login/password",
|
||||
data={"email": "solo.perso@nimporte.fr", "password": "s" * 12},
|
||||
follow_redirects=False)
|
||||
assert r.status_code == 302 # connexion réussie
|
||||
finally:
|
||||
cfg["smtp"] = ancien_smtp
|
||||
cfg["allowed_email_domain"] = ancien_domaine or "x.fr"
|
||||
models.save_config(cfg)
|
||||
|
||||
|
||||
def test_domaine_vide_accepte_tous_les_domaines(monkeypatch):
|
||||
"""allowed_email_domain vide (défaut, instance locale) : tout domaine accepté
|
||||
à la connexion par mot de passe. Renseigné, il reste strictement appliqué."""
|
||||
cfg = models.load_config()
|
||||
ancien = cfg.get("allowed_email_domain")
|
||||
cfg["allowed_email_domain"] = ""
|
||||
models.save_config(cfg)
|
||||
try:
|
||||
_seed_user("libre.nimportequi")
|
||||
r = client.post("/login/password",
|
||||
data={"email": "libre.nimportequi@ailleurs.org", "password": "x" * 12},
|
||||
follow_redirects=False)
|
||||
assert r.status_code == 302 # accepté malgré @ailleurs.org
|
||||
# Un email sans domaine valide reste refusé
|
||||
r = client.post("/login", data={"email": "pas-un-email"}, follow_redirects=False)
|
||||
assert "invalide" in r.text.lower()
|
||||
finally:
|
||||
cfg["allowed_email_domain"] = ancien or "x.fr"
|
||||
models.save_config(cfg)
|
||||
|
||||
def test_sans_smtp_le_lien_utilise_l_url_courante_sans_base_url():
|
||||
"""Sans SMTP, aucune config d'URL n'est nécessaire : le lien affiché est
|
||||
construit depuis l'URL en cours (ex. http://192.168.1.10:8000 sur le LAN),
|
||||
même avec base_url vide et allowed_email_domain vide. Le contrôle Host
|
||||
strict (_safe_base_url) ne concerne que le chemin par email."""
|
||||
cfg = models.load_config()
|
||||
ancien_smtp = cfg.get("smtp")
|
||||
ancien_domaine = cfg.get("allowed_email_domain")
|
||||
ancienne_base = cfg.get("base_url")
|
||||
cfg.update({"smtp": {}, "allowed_email_domain": "", "base_url": ""})
|
||||
models.save_config(cfg)
|
||||
try:
|
||||
r = client.post("/login", data={"email": "lan.user@chez.moi"},
|
||||
headers={"Host": "192.168.1.10:8000"}, follow_redirects=False)
|
||||
assert r.status_code == 200
|
||||
assert "http://192.168.1.10:8000/set-password/" in r.text
|
||||
finally:
|
||||
cfg.update({"smtp": ancien_smtp, "allowed_email_domain": ancien_domaine or "x.fr",
|
||||
"base_url": ancienne_base or ""})
|
||||
models.save_config(cfg)
|
||||
|
||||
Reference in New Issue
Block a user