Portal sign-in with the Ecare office login

Ecare staff sign in with their office username and password, checked by
the Ecare sign-in service from the backend (AUTH_TOKEN_API_URL); a good
Ecare login still needs a Portal User. Service down = "try again
shortly", never a wrong password. /api/auth/login, me, logout; sign-in
tries limited per address. Users page: Ecare office login or Portal
password per user. Context.md: 27/10 runs, the login retry bug.
71 test files, 1239 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-27 17:55:50 +05:30
1 parent 83a0cf65ae
commit 64efb68ea1
10 files changed
+556 -48

No files matched your search

+118 -4
View File
@@ -234,17 +234,18 @@ def an_admin_adds_a_user_who_then_must_change():
c, _ = _sign_in(app, "asha")
r = c.post("/admin/users", headers=ORIGIN, data=_form(
app, username="Ravi", display_name="Ravi K", role="Viewer",
password="temp-password-1"))
password="temp-password-1", signin="portal"))
assert r.status_code == 200 and "ravi added" in r.text, r.text[:300]
r = c.post("/admin/users", headers=ORIGIN, data=_form(
app, username="kim", display_name="", role="Admin",
password="temp-password-1"))
assert r.status_code == 400 and "An Admin can add or change Viewer only" in r.text and "kim" not in store.users
password="temp-password-1", signin="portal"))
assert r.status_code == 400 and "kim" not in store.users
assert "An Admin can add or change Viewer only" in r.text
assert store.users["ravi"]["must_change"] is True
assert ("ravi", "user_created", "asha") in store.events
r = c.post("/admin/users", headers=ORIGIN, data=_form(
app, username="x", display_name="", role="Viewer",
password="temp-password-1"))
password="temp-password-1", signin="portal"))
assert r.status_code == 400 and "Username" in r.text
@@ -402,6 +403,119 @@ def the_container_keeps_india_time():
assert "tzdata" in line, line
# ── Ecare office sign-in (2026-10-27) ────────────────────────────────────────
def _ecare_portal(verdict="ok"):
"""(app, store, ecare): sam (Super Admin, Portal password), ravi
(Viewer, Ecare login); the Ecare service a fake giving `verdict`."""
from test_portal_users import FakeEcare
store, ecare = FakeUserStore(), FakeEcare(verdict)
acc = portal_users.Accounts(store)
acc.create_user("sam", "Sam", "Super Admin", PW)
acc.create_user("ravi", "Ravi", "Viewer", None, by="sam", ecare=True)
app = web_app.create_app(FakeRepo(), runner=FakeRunner(),
users_store=store, ecare=ecare,
logs_dir=tempfile.mkdtemp())
return app, store, ecare
def _api_login(app, client, username, password):
return client.post("/api/auth/login", json={
"username": username, "password": password},
headers={**ORIGIN, "X-Run-Token": app.state.run_token})
@case
def the_sign_in_page_answers_each_ecare_outcome():
for verdict, name, code, words in (
("bad", "ravi", 401, "Wrong username or password"),
("down", "ravi", 503, "try again shortly"),
("ok", "stranger", 403, "no access to this Portal")):
app, _, _ = _ecare_portal(verdict)
_, r = _sign_in(app, name, "office-pass")
assert r.status_code == code and words in r.text, (verdict, code,
r.status_code)
app, store, ecare = _ecare_portal("ok")
c, r = _sign_in(app, "ravi", "office-pass", next_url="/days")
assert r.status_code == 303 and r.headers["location"] == "/days"
assert "office-pass" not in str(store.events)
assert c.get("/days").status_code == 200
@case
def the_auth_api_signs_in_says_who_and_signs_out():
app, _, _ = _ecare_portal("ok")
c = TestClient(app, base_url=BASE)
assert c.get("/api/auth/me").status_code == 401
r = _api_login(app, c, " ravi ", "office-pass")
assert r.status_code == 200, r.text
assert r.json() == {"username": "ravi", "display_name": "Ravi",
"role": "Viewer", "must_change": False}, r.json()
assert "httponly" in r.headers["set-cookie"].lower()
assert "token" not in r.text.lower(), "the Ecare token stays out"
assert c.get("/api/auth/me").json()["role"] == "Viewer"
r = c.post("/api/auth/logout",
headers={**ORIGIN, "X-Run-Token": app.state.run_token})
assert r.status_code == 200
assert c.get("/api/auth/me").status_code == 401
@case
def the_auth_api_gives_400_401_403_503_and_needs_the_token():
for verdict, name, password, code in (("ok", " ", "x", 400),
("ok", "ravi", " ", 400),
("bad", "ravi", "nope", 401),
("ok", "stranger", "pw", 403),
("down", "ravi", "pw", 503)):
app, _, _ = _ecare_portal(verdict)
r = _api_login(app, TestClient(app, base_url=BASE), name, password)
assert r.status_code == code, (verdict, name, r.status_code)
app, _, ecare = _ecare_portal("ok")
r = TestClient(app, base_url=BASE).post(
"/api/auth/login", json={"username": "ravi", "password": "pw"},
headers=ORIGIN)
assert r.status_code == 403 and ecare.calls == [], "no X-Run-Token"
@case
def sign_in_attempts_from_one_address_are_limited():
app, _, _ = _ecare_portal("bad")
c = TestClient(app, base_url=BASE)
for i in range(web_app.SIGN_IN_LIMIT):
assert _api_login(app, c, f"user{i}", "nope").status_code == 401, i
assert _api_login(app, c, "ravi", "nope").status_code == 429
_, r = _sign_in(app, "ravi", "nope")
assert r.status_code == 429, "the form shares the limit"
@case
def the_users_page_adds_an_ecare_user_without_a_password():
app, store, _ = _ecare_portal("ok")
c, _ = _sign_in(app, "sam")
page = c.get("/admin/users").text
assert 'name="signin"' in page and "Ecare office login" in page
assert 'action="/admin/users/ravi/reset"' not in page, \
"an Ecare user's password is not reset here"
r = c.post("/admin/users", headers=ORIGIN, data=_form(
app, username="mani", display_name="Mani", role="Admin",
signin="ecare", password=""))
assert r.status_code == 200 and "mani added" in r.text, r.text[:300]
assert portal_users.is_ecare(store.users["mani"])
assert store.users["mani"]["must_change"] is False
r = c.post("/admin/users", headers=ORIGIN, data=_form(
app, username="client1", display_name="Client", role="Viewer",
signin="portal", password="short"))
assert r.status_code == 400, "a Portal password still has the rules"
@case
def an_ecare_user_is_told_to_change_the_password_in_ecare():
app, _, _ = _ecare_portal("ok")
c, _ = _sign_in(app, "ravi", "office-pass")
page = c.get("/account/password").text
assert "Ecare office password" in page and 'name="current"' not in page
@case
def no_signed_in_page_carries_an_inline_style():
app, _, _ = _portal()