Add whole-app password protection
This commit is contained in:
+67
-1
@@ -1,16 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient as RawTestClient
|
||||
|
||||
from resume_agent.agent import save_json
|
||||
from resume_agent.models import CareerProfile, TailoringPackage
|
||||
from resume_agent.render import render_ohmycv_resume
|
||||
from resume_agent.webapp import create_app
|
||||
|
||||
TEST_USERNAME = "resume-user"
|
||||
TEST_PASSWORD = "test-password"
|
||||
TEST_AUTHORIZATION = "Basic " + base64.b64encode(
|
||||
f"{TEST_USERNAME}:{TEST_PASSWORD}".encode()
|
||||
).decode()
|
||||
TEST_AUTH_HEADERS = {"Authorization": TEST_AUTHORIZATION}
|
||||
|
||||
|
||||
class TestClient(RawTestClient):
|
||||
def __init__(self, app: Any, **kwargs: Any) -> None:
|
||||
headers = {**TEST_AUTH_HEADERS, **kwargs.pop("headers", {})}
|
||||
super().__init__(app, headers=headers, **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def configure_authentication(monkeypatch: Any) -> None:
|
||||
monkeypatch.setenv("RESUME_AGENT_USERNAME", TEST_USERNAME)
|
||||
monkeypatch.setenv("RESUME_AGENT_PASSWORD", TEST_PASSWORD)
|
||||
|
||||
|
||||
def sample_profile() -> CareerProfile:
|
||||
return CareerProfile.model_validate(
|
||||
@@ -98,6 +119,51 @@ def wait_for_task(client: TestClient, task_id: str) -> dict[str, Any]:
|
||||
raise AssertionError("Background task did not finish in time.")
|
||||
|
||||
|
||||
def test_authentication_protects_ui_api_static_and_editor(tmp_path: Path) -> None:
|
||||
cv_dist = tmp_path / "cv"
|
||||
cv_dist.mkdir()
|
||||
(cv_dist / "index.html").write_text("editor", encoding="utf-8")
|
||||
(cv_dist / "200.html").write_text("editor", encoding="utf-8")
|
||||
app = create_app(
|
||||
profile_path=tmp_path / "profile.json",
|
||||
output_dir=tmp_path / "output",
|
||||
cv_dist=cv_dist,
|
||||
llm_factory=lambda: FakeLLM([]), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
with RawTestClient(app) as client:
|
||||
assert client.get("/healthz").status_code == 200
|
||||
for path in ("/", "/api/status", "/favicon.ico", "/cv/"):
|
||||
response = client.get(path)
|
||||
assert response.status_code == 401
|
||||
assert response.headers["www-authenticate"].startswith("Basic ")
|
||||
wrong = client.get(
|
||||
"/api/status",
|
||||
headers={"Authorization": "Basic " + base64.b64encode(b"wrong:wrong").decode()},
|
||||
)
|
||||
assert wrong.status_code == 401
|
||||
assert client.get("/", headers=TEST_AUTH_HEADERS).status_code == 200
|
||||
assert client.get("/api/status", headers=TEST_AUTH_HEADERS).status_code == 200
|
||||
assert client.get("/favicon.ico", headers=TEST_AUTH_HEADERS).status_code == 200
|
||||
assert client.get("/cv/", headers=TEST_AUTH_HEADERS).status_code == 200
|
||||
|
||||
|
||||
def test_missing_authentication_configuration_fails_closed(
|
||||
tmp_path: Path, monkeypatch: Any
|
||||
) -> None:
|
||||
monkeypatch.delenv("RESUME_AGENT_USERNAME")
|
||||
monkeypatch.delenv("RESUME_AGENT_PASSWORD")
|
||||
app = create_app(
|
||||
profile_path=tmp_path / "profile.json",
|
||||
output_dir=tmp_path / "output",
|
||||
llm_factory=lambda: FakeLLM([]), # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
with RawTestClient(app) as client:
|
||||
assert client.get("/").status_code == 503
|
||||
assert client.get("/healthz").status_code == 503
|
||||
|
||||
|
||||
def test_root_and_status(tmp_path: Path, monkeypatch: Any) -> None:
|
||||
monkeypatch.delenv("RESUME_AGENT_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
|
||||
Reference in New Issue
Block a user