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:
1 parent
83a0cf65ae
commit
64efb68ea1
10 files changed
+556
-48
No files matched your search
+118
-4
@@ -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()
|
||||
|
||||
Reference in new issue
Block a user