From 2f2cd151fbf74d76ef9b0f04ec37362573df6342 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Fri, 3 Jul 2026 09:41:57 +0200 Subject: [PATCH] feat(admin): add is_admin() access guard Implement access control function for admin panel. Returns True only if ADMIN_EMAIL env var is set, user is authenticated, and email matches case-insensitively. Co-Authored-By: Claude Sonnet 5 --- src/admin/guard.py | 12 +++++++++++ tests/admin/test_guard.py | 42 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/admin/guard.py create mode 100644 tests/admin/test_guard.py diff --git a/src/admin/guard.py b/src/admin/guard.py new file mode 100644 index 0000000..8d1057e --- /dev/null +++ b/src/admin/guard.py @@ -0,0 +1,12 @@ +import os + +from flask_login import current_user + + +def is_admin() -> bool: + admin_email = os.getenv("ADMIN_EMAIL") + return bool( + admin_email + and current_user.is_authenticated + and current_user.email.lower() == admin_email.lower() + ) diff --git a/tests/admin/test_guard.py b/tests/admin/test_guard.py new file mode 100644 index 0000000..2fdcaa3 --- /dev/null +++ b/tests/admin/test_guard.py @@ -0,0 +1,42 @@ +from unittest.mock import patch + +from src.admin import guard + + +def _fake_user(email: str, authenticated: bool = True): + user = type("U", (), {})() + user.is_authenticated = authenticated + user.email = email + return user + + +def test_is_admin_true_for_matching_email(monkeypatch): + monkeypatch.setenv("ADMIN_EMAIL", "admin@ex.fr") + with patch("src.admin.guard.current_user", _fake_user("admin@ex.fr")): + assert guard.is_admin() is True + + +def test_is_admin_true_case_insensitive(monkeypatch): + monkeypatch.setenv("ADMIN_EMAIL", "Admin@Ex.fr") + with patch("src.admin.guard.current_user", _fake_user("admin@ex.fr")): + assert guard.is_admin() is True + + +def test_is_admin_false_for_different_email(monkeypatch): + monkeypatch.setenv("ADMIN_EMAIL", "admin@ex.fr") + with patch("src.admin.guard.current_user", _fake_user("someone@ex.fr")): + assert guard.is_admin() is False + + +def test_is_admin_false_for_anonymous(monkeypatch): + monkeypatch.setenv("ADMIN_EMAIL", "admin@ex.fr") + with patch( + "src.admin.guard.current_user", _fake_user("admin@ex.fr", authenticated=False) + ): + assert guard.is_admin() is False + + +def test_is_admin_false_when_admin_email_unset(monkeypatch): + monkeypatch.delenv("ADMIN_EMAIL", raising=False) + with patch("src.admin.guard.current_user", _fake_user("admin@ex.fr")): + assert guard.is_admin() is False