Owner accounts: auth, password reset, CRM dashboard visibility (#19)

Flask-session login scoped to one client_id (never a request param),
self-service + operator-triggered password reset via single-use tokens,
and an Owner accounts tab on the CRM dashboard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-03 17:00:37 +02:00
parent 644c99ee30
commit 59c35ce39f
14 changed files with 635 additions and 0 deletions
+27
View File
@@ -234,3 +234,30 @@ def test_consume_expired_password_reset_token_fails():
def test_consume_unknown_token_fails():
assert bdb.consume_password_reset_token("not-a-real-token", "new-password") is None
# ---- login lookup / operator listing (#19) ----
def test_find_user_by_email_is_not_tenant_scoped():
"""Login happens before client_id is known -- find_user_by_email looks up
by the globally-unique email alone, unlike get_user_by_email."""
u = bdb.create_user(CLIENT_A, "owner@example.com", "pw12345")
found = bdb.find_user_by_email("owner@example.com")
assert found["user_id"] == u["user_id"]
assert bdb.find_user_by_email("nobody@example.com") is None
def test_get_user_by_id():
u = bdb.create_user(CLIENT_A, "owner@example.com", "pw12345")
assert bdb.get_user(u["user_id"])["email"] == "owner@example.com"
assert bdb.get_user("U-does-not-exist") is None
def test_list_users_scoped_and_unscoped():
a = bdb.create_user(CLIENT_A, "a@example.com", "pw12345")
bdb.create_user(CLIENT_B, "b@example.com", "pw12345")
scoped = bdb.list_users(CLIENT_A)
assert [r["user_id"] for r in scoped] == [a["user_id"]]
everyone = bdb.list_users()
assert {r["client_id"] for r in everyone} == {CLIENT_A, CLIENT_B}
assert "password_hash" not in everyone[0]