Round 1 on the feature diff found 15; the substantive ones: - The delete control VANISHED on any invite whose address had registered, which is the ordinary sign-up shape (register writes the User and the pending invite together). The guard stays broad — that row is the account's approval gate, and orphaning it strands someone who can then neither sign in nor be approved — but the control is now a disabled button carrying the reason, on both lists, so an admin never sees a row silently missing a button its neighbours have. - Deleting an entry is not always a revoke: is_allowed falls back to the env ALLOWED_EMAILS / ADMIN_EMAILS lists once no row exists, and an ADMIN_EMAILS address is force-promoted on its next Google sign-in. The route now reports still_allowed_by_env and the UI says so instead of claiming access was removed. - "Deny keeps the row so they can't immediately re-ask" was simply false — upsert_pending_invite reopens a denied row. Corrected everywhere it was stated. - The Configuration warning reads me.email_configured, which save() did not invalidate, so it kept warning about fields the admin had just filled in. - The read-only variant was an aria-hidden span explained only by a Tooltip that the hints toggle can switch off; now a disabled button with title + aria-label. - The delete error path threw away the 409's reason (the stale-list case) and left the stale row on screen. - The throttle window was spent even when no notice was written, silencing the next hour on the path whose job is to stop silent drops. - email_enabled() opened a second session and decrypted the SMTP password on every /api/me poll; it now takes the caller's session. - welcomeMode.dom.test.ts never rendered the card, so reverting the history wiring to useState left it green. A render test now drives the real links and fails on that revert (verified). Plus /api/me flag tests, the registration-shaped pending case, the env-allowlist case, and a release note that matches what the code does.
59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
"""/api/me carries whether this instance can actually send email.
|
|
|
|
It drives the operator warnings on the Configuration and Access-requests screens. Admin-only,
|
|
because it is an operator concern and a regular user has nothing to do with it.
|
|
"""
|
|
|
|
from app import email as email_mod
|
|
|
|
_PW = "correct horse battery"
|
|
|
|
|
|
def _signed_in(client, make_user, **kw):
|
|
"""/api/me resolves its user through optional_current_user, which the as_user override does not
|
|
cover — so sign in for real and read the bootstrap payload the SPA actually gets."""
|
|
user = make_user(password=_PW, **kw)
|
|
assert (
|
|
client.post("/auth/password-login", json={"email": user.email, "password": _PW}).status_code
|
|
== 200
|
|
)
|
|
r = client.get("/api/me")
|
|
assert r.status_code == 200 and r.json()["authenticated"] is True
|
|
return r.json()
|
|
|
|
|
|
def test_an_admin_is_told_when_email_is_not_configured(client, make_user, monkeypatch):
|
|
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: False)
|
|
|
|
me = _signed_in(client, make_user, email="admin@example.com", role="admin")
|
|
|
|
assert me["email_configured"] is False
|
|
|
|
|
|
def test_an_admin_sees_nothing_wrong_when_email_works(client, make_user, monkeypatch):
|
|
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: True)
|
|
|
|
me = _signed_in(client, make_user, email="admin@example.com", role="admin")
|
|
|
|
assert me["email_configured"] is True
|
|
|
|
|
|
def test_a_regular_user_is_never_warned_about_the_mail_setup(client, make_user, monkeypatch):
|
|
# Not their problem, and the flag must not become a way to probe the instance's configuration.
|
|
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: False)
|
|
|
|
me = _signed_in(client, make_user, email="plain@example.com")
|
|
|
|
assert me["email_configured"] is True
|
|
|
|
|
|
def test_the_flag_reuses_the_request_session(client, make_user, monkeypatch):
|
|
# /api/me is polled once a minute per open tab; reading the SMTP settings decrypts a Fernet
|
|
# value, so it must not also check out a second pooled connection each time.
|
|
seen: list[object] = []
|
|
monkeypatch.setattr(email_mod, "email_enabled", lambda db=None: seen.append(db) or True)
|
|
|
|
_signed_in(client, make_user, email="admin@example.com", role="admin")
|
|
|
|
assert seen and seen[0] is not None
|