Spaces:
Runtime error
Runtime error
Download backend/tests/test_auth.py from firebird-technologies/NoteStack-Test: direct link, hf CLI and curl.
- Browser
- Download file 9.73 kB
-
https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/tests/test_auth.py
- Command line
-
hf download hf://spaces/firebird-technologies/NoteStack-Test/backend/tests/test_auth.py
-
curl -L -o test_auth.py https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/tests/test_auth.py
9.73 kB
| from app.services.email import ConsoleEmailProvider | |
| from tests.conftest import last_code | |
| def _signup(client, email="writer@example.com", password="stardust-42"): | |
| r = client.post("/api/auth/email/register/start", json={"email": email, "password": password, "name": "Ada L"}) | |
| assert r.status_code == 200, r.text | |
| r = client.post("/api/auth/email/register/verify", json={"email": email, "code": last_code()}) | |
| assert r.status_code == 200, r.text | |
| return r.json() | |
| def test_signup_login_me_logout(client): | |
| data = _signup(client) | |
| assert data["created"] is True | |
| assert any(e.subject == "Welcome to Notestack" for e in ConsoleEmailProvider.sent) | |
| headers = {"Authorization": f"Bearer {data['access_token']}"} | |
| assert client.get("/api/auth/me", headers=headers).json()["email"] == "writer@example.com" | |
| r = client.post("/api/auth/email/login", json={"email": "WRITER@example.com", "password": "stardust-42"}) | |
| assert r.status_code == 200 | |
| assert client.post("/api/auth/logout", headers=headers).status_code == 200 | |
| assert client.get("/api/auth/me", headers=headers).status_code == 401 # token_version bumped | |
| def test_wrong_code_and_attempt_limit(client): | |
| client.post("/api/auth/email/register/start", json={"email": "a@b.co", "password": "longenough"}) | |
| for _ in range(5): | |
| r = client.post("/api/auth/email/register/verify", json={"email": "a@b.co", "code": "000000"}) | |
| if last_code() == "000000": | |
| return # astronomically unlikely collision | |
| assert r.status_code == 400 | |
| r = client.post("/api/auth/email/register/verify", json={"email": "a@b.co", "code": last_code()}) | |
| assert r.json()["detail"]["code"] == "too_many_attempts" | |
| def test_wrong_code_counts_down_and_lockout_allows_resend(client): | |
| client.post("/api/auth/email/register/start", json={"email": "e@f.co", "password": "longenough"}) | |
| if last_code() == "000000": | |
| return | |
| r = client.post("/api/auth/email/register/verify", json={"email": "e@f.co", "code": "000000"}) | |
| assert r.json()["detail"]["message"].endswith("4 tries left.") | |
| for _ in range(4): | |
| r = client.post("/api/auth/email/register/verify", json={"email": "e@f.co", "code": "000000"}) | |
| assert r.json()["detail"]["code"] == "too_many_attempts" | |
| # A dead code does not hold up a new one, and the pending sign up survives the lockout. | |
| assert client.post("/api/auth/email/register/resend", json={"email": "e@f.co"}).status_code == 200 | |
| r = client.post("/api/auth/email/register/verify", json={"email": "e@f.co", "code": last_code()}) | |
| assert r.status_code == 200 | |
| def test_email_send_failure_is_reported(client, monkeypatch): | |
| from app.services.email import email_service | |
| monkeypatch.setattr(email_service, "send_verification_code", lambda to, code: False) | |
| r = client.post("/api/auth/email/register/start", json={"email": "f@g.co", "password": "longenough"}) | |
| assert r.status_code == 502 | |
| assert r.json()["detail"]["code"] == "email_failed" | |
| def test_forgot_password_unknown_and_google(client, db_session): | |
| from app.services.auth_identity import resolve_or_create_google_user | |
| r = client.post("/api/auth/password/forgot/start", json={"email": "nobody@example.com"}) | |
| assert r.status_code == 404 | |
| resolve_or_create_google_user(db_session, email="g2@example.com", google_id="456", name="G", avatar_url=None) | |
| db_session.commit() | |
| r = client.post("/api/auth/password/forgot/start", json={"email": "g2@example.com"}) | |
| assert r.status_code == 409 | |
| assert r.json()["detail"]["code"] == "wrong_provider" | |
| def test_resend_cooldown(client): | |
| client.post("/api/auth/email/register/start", json={"email": "c@d.co", "password": "longenough"}) | |
| r = client.post("/api/auth/email/register/resend", json={"email": "c@d.co"}) | |
| assert r.status_code == 429 | |
| def test_bad_password_and_weak_password(client): | |
| _signup(client) | |
| r = client.post("/api/auth/email/login", json={"email": "writer@example.com", "password": "nope-nope"}) | |
| assert r.status_code == 401 | |
| r = client.post("/api/auth/email/register/start", json={"email": "x@y.co", "password": "short"}) | |
| assert r.status_code == 400 | |
| def test_password_reset_revokes_old_tokens(client): | |
| data = _signup(client) | |
| old = {"Authorization": f"Bearer {data['access_token']}"} | |
| client.post("/api/auth/password/forgot/start", json={"email": "writer@example.com"}) | |
| code = last_code() | |
| r = client.post("/api/auth/password/forgot/check", json={"email": "writer@example.com", "code": code}) | |
| assert r.status_code == 200 | |
| r = client.post("/api/auth/password/forgot/complete", | |
| json={"email": "writer@example.com", "code": code, "password": "new-orbit-99"}) | |
| assert r.status_code == 200 | |
| assert client.get("/api/auth/me", headers=old).status_code == 401 | |
| def test_google_user_cannot_use_password(client, db_session): | |
| from app.services.auth_identity import resolve_or_create_google_user | |
| resolve_or_create_google_user(db_session, email="g@example.com", google_id="123", name="G", avatar_url=None) | |
| db_session.commit() | |
| r = client.post("/api/auth/email/register/start", json={"email": "g@example.com", "password": "longenough"}) | |
| assert r.status_code == 409 | |
| assert r.json()["detail"]["code"] == "wrong_provider" | |
| def test_delete_account(client): | |
| data = _signup(client) | |
| headers = {"Authorization": f"Bearer {data['access_token']}"} | |
| assert client.post("/api/auth/delete-account", headers=headers).status_code == 200 | |
| r = client.post("/api/auth/email/login", json={"email": "writer@example.com", "password": "stardust-42"}) | |
| assert r.status_code == 403 | |
| def _google_redirect(client, monkeypatch, claims: dict, next_path="/app/notebooks"): | |
| from urllib.parse import parse_qs, urlparse | |
| from app.config import settings | |
| from app.routers import auth as auth_router | |
| monkeypatch.setattr(settings, "google_client_id", "cid") | |
| monkeypatch.setattr(settings, "google_client_secret", "secret") | |
| assert client.get("/api/auth/providers").json() == {"google": "redirect"} | |
| r = client.get("/api/auth/google/start", params={"next": next_path}, follow_redirects=False) | |
| assert r.status_code == 302 | |
| q = parse_qs(urlparse(r.headers["location"]).query) | |
| assert q["redirect_uri"] == ["http://localhost:8000/api/auth/google/callback"] | |
| monkeypatch.setattr(auth_router, "exchange_google_code", lambda code: {"nonce": q["nonce"][0], **claims}) | |
| r = client.get("/api/auth/google/callback", params={"code": "abc", "state": q["state"][0]}, | |
| follow_redirects=False) | |
| assert r.status_code == 302 | |
| location = urlparse(r.headers["location"]) | |
| return location, parse_qs(location.fragment) | |
| def test_google_redirect_flow(client, monkeypatch): | |
| claims = {"email": "Star@Example.com", "email_verified": True, "sub": "g-1", "name": "Star"} | |
| location, frag = _google_redirect(client, monkeypatch, claims) | |
| assert location.path == "/auth/callback" | |
| assert frag["next"] == ["/app/notebooks"] | |
| r = client.post("/api/auth/ticket", json={"ticket": frag["ticket"][0]}) | |
| assert r.status_code == 200 | |
| data = r.json() | |
| assert data["created"] is True and data["user"]["email"] == "star@example.com" | |
| me = client.get("/api/auth/me", headers={"Authorization": f"Bearer {data['access_token']}"}) | |
| assert me.json()["auth_provider"] == "google" | |
| # A ticket is not a session token. | |
| assert client.get("/api/auth/me", headers={"Authorization": f"Bearer {frag['ticket'][0]}"}).status_code == 401 | |
| def test_google_redirect_rejects_open_redirect_and_email_accounts(client, monkeypatch): | |
| _signup(client, email="taken@example.com") | |
| claims = {"email": "taken@example.com", "email_verified": True, "sub": "g-2"} | |
| location, frag = _google_redirect(client, monkeypatch, claims, next_path="//evil.example") | |
| assert location.path == "/auth" | |
| assert "password" in frag["error"][0] | |
| def test_google_callback_requires_state_cookie(client, monkeypatch): | |
| from datetime import timedelta | |
| from app.auth import encode_signed | |
| state = encode_signed({"typ": "oauth_state", "nonce": "n", "next": "/app"}, timedelta(minutes=5)) | |
| r = client.get("/api/auth/google/callback", params={"code": "abc", "state": state}, follow_redirects=False) | |
| assert r.headers["location"].startswith("http://localhost:5173/auth#error=") | |
| def test_safe_next(): | |
| from app.routers.auth import _safe_next | |
| assert _safe_next("/app/notebooks/1?x=1") == "/app/notebooks/1?x=1" | |
| for bad in (None, "", "https://evil.example", "//evil.example", r"/\evil.example"): | |
| assert _safe_next(bad) == "/app" | |
| def test_google_id_token_check_allows_for_a_laptop_clock_that_runs_behind(monkeypatch): | |
| """Seen on a dev machine whose clock was 2 seconds behind Google: google-auth refused the fresh ID token ("Token | |
| used too early") and the writer only saw "Google sign in failed". Both Google flows now allow some drift.""" | |
| from app.config import settings | |
| from app.routers import auth as auth_router | |
| seen = [] | |
| class Reply: | |
| def raise_for_status(self): | |
| pass | |
| def json(self): | |
| return {"id_token": "t"} | |
| def verify(token, request, audience=None, clock_skew_in_seconds=0): | |
| seen.append(clock_skew_in_seconds) | |
| return {"email": "a@b.co"} | |
| monkeypatch.setattr(settings, "google_client_id", "cid") | |
| monkeypatch.setattr(auth_router.httpx, "post", lambda *a, **k: Reply()) | |
| monkeypatch.setattr(auth_router.google_id_token, "verify_oauth2_token", verify) | |
| auth_router.exchange_google_code("code") | |
| assert seen == [auth_router.GOOGLE_CLOCK_SKEW_SECONDS] and seen[0] >= 5 | |