feat: multi-user UI login with admin and monitor roles
Add sac_users table, JWT roles, settings/users API guarded for admin, Users page in UI, and sac_manage_user.py CLI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -18,7 +18,9 @@ from app.auth.jwt_auth import create_access_token
|
||||
from app.database import Base, get_db
|
||||
from app.main import app as fastapi_app
|
||||
import app.models # noqa: F401 — register all tables on Base.metadata
|
||||
from app.models import ApiKey
|
||||
from app.models import ApiKey, User
|
||||
from app.models.user import USER_ROLE_ADMIN, USER_ROLE_MONITOR
|
||||
from app.services.user_auth import hash_password
|
||||
|
||||
TEST_API_KEY = os.environ["SAC_BOOTSTRAP_API_KEY"]
|
||||
|
||||
@@ -57,6 +59,22 @@ def db_session(db_engine):
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
session.add(
|
||||
User(
|
||||
username="test-admin",
|
||||
password_hash=hash_password("test-admin-password"),
|
||||
role=USER_ROLE_ADMIN,
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
session.add(
|
||||
User(
|
||||
username="test-monitor",
|
||||
password_hash=hash_password("test-monitor-password"),
|
||||
role=USER_ROLE_MONITOR,
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
yield session
|
||||
session.close()
|
||||
@@ -65,6 +83,7 @@ def db_session(db_engine):
|
||||
@pytest.fixture
|
||||
def client(db_session, monkeypatch):
|
||||
monkeypatch.setattr("app.main.bootstrap_api_key", lambda: None)
|
||||
monkeypatch.setattr("app.main.bootstrap_users", lambda: None)
|
||||
|
||||
def override_get_db():
|
||||
try:
|
||||
@@ -85,5 +104,11 @@ def auth_headers():
|
||||
|
||||
@pytest.fixture
|
||||
def jwt_headers():
|
||||
token = create_access_token("test-admin")
|
||||
token = create_access_token("test-admin", USER_ROLE_ADMIN)
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def jwt_monitor_headers():
|
||||
token = create_access_token("test-monitor", USER_ROLE_MONITOR)
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Multi-user UI auth and RBAC."""
|
||||
|
||||
from app.models.user import USER_ROLE_MONITOR
|
||||
|
||||
|
||||
def test_login_success(client):
|
||||
response = client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"username": "test-admin", "password": "test-admin-password"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert body["username"] == "test-admin"
|
||||
assert body["role"] == "admin"
|
||||
assert body["access_token"]
|
||||
|
||||
|
||||
def test_login_monitor(client):
|
||||
response = client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"username": "test-monitor", "password": "test-monitor-password"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["role"] == USER_ROLE_MONITOR
|
||||
|
||||
|
||||
def test_login_invalid_password(client):
|
||||
response = client.post(
|
||||
"/api/v1/auth/login",
|
||||
json={"username": "test-admin", "password": "wrong"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_auth_me(client, jwt_headers):
|
||||
response = client.get("/api/v1/auth/me", headers=jwt_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"username": "test-admin", "role": "admin"}
|
||||
|
||||
|
||||
def test_settings_forbidden_for_monitor(client, jwt_monitor_headers):
|
||||
response = client.get("/api/v1/settings/notifications", headers=jwt_monitor_headers)
|
||||
assert response.status_code == 403
|
||||
|
||||
|
||||
def test_events_allowed_for_monitor(client, jwt_monitor_headers):
|
||||
response = client.get("/api/v1/events", headers=jwt_monitor_headers)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_admin_create_monitor_user(client, jwt_headers):
|
||||
response = client.post(
|
||||
"/api/v1/users",
|
||||
headers=jwt_headers,
|
||||
json={"username": "new-monitor", "password": "longpassword1", "role": "monitor"},
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert response.json()["username"] == "new-monitor"
|
||||
assert response.json()["role"] == "monitor"
|
||||
|
||||
|
||||
def test_monitor_cannot_list_users(client, jwt_monitor_headers):
|
||||
response = client.get("/api/v1/users", headers=jwt_monitor_headers)
|
||||
assert response.status_code == 403
|
||||
Reference in New Issue
Block a user