- Replace server-side ConfidentialClientApplication + OAuth callback with MSAL browser popup flow (PKCE, no client_secret required) - Backend: add POST /sso-token endpoint that validates Azure AD ID token via Microsoft JWKS, issues session cookie; remove /login + /callback - Frontend: install @azure/msal-browser + @azure/msal-react, wrap app with MsalProvider, login page uses loginPopup() → sends id_token to backend - Pass NEXT_PUBLIC_AZURE_* env vars through next.config.mjs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
78 lines
2.7 KiB
Python
78 lines
2.7 KiB
Python
"""Tests for auth_service: JWT creation/validation, dev login, user creation."""
|
|
import uuid
|
|
|
|
import pytest
|
|
|
|
from services.auth_service import AuthService
|
|
|
|
|
|
@pytest.fixture
|
|
def auth_service():
|
|
return AuthService()
|
|
|
|
|
|
class TestJWT:
|
|
def test_create_and_validate_token(self, auth_service):
|
|
"""Token round-trip: create → validate → same payload."""
|
|
from models.sql.user import UserModel
|
|
|
|
user = UserModel(
|
|
id=uuid.uuid4(),
|
|
email="jwt@test.com",
|
|
display_name="JWT User",
|
|
role="user",
|
|
is_active=True,
|
|
)
|
|
token = auth_service.create_session_jwt(user)
|
|
payload = auth_service.validate_token(token)
|
|
|
|
assert payload is not None
|
|
assert payload["sub"] == str(user.id)
|
|
assert payload["email"] == "jwt@test.com"
|
|
assert payload["role"] == "user"
|
|
|
|
def test_validate_invalid_token(self, auth_service):
|
|
assert auth_service.validate_token("garbage.token.here") is None
|
|
|
|
def test_validate_tampered_token(self, auth_service):
|
|
from models.sql.user import UserModel
|
|
|
|
user = UserModel(
|
|
id=uuid.uuid4(),
|
|
email="tamper@test.com",
|
|
display_name="Tamper",
|
|
role="user",
|
|
is_active=True,
|
|
)
|
|
token = auth_service.create_session_jwt(user)
|
|
# Tamper with the token
|
|
tampered = token[:-5] + "XXXXX"
|
|
assert auth_service.validate_token(tampered) is None
|
|
|
|
|
|
class TestDevLogin:
|
|
async def test_dev_login_success(self, auth_service, session):
|
|
"""Dev mode: correct password creates user."""
|
|
user = await auth_service.dev_login("dev@test.com", "testpass", session)
|
|
assert user is not None
|
|
assert user.email == "dev@test.com"
|
|
|
|
async def test_dev_login_wrong_password(self, auth_service, session):
|
|
user = await auth_service.dev_login("dev@test.com", "wrongpass", session)
|
|
assert user is None
|
|
|
|
async def test_dev_login_idempotent(self, auth_service, session):
|
|
"""Logging in twice with same email returns same user."""
|
|
user1 = await auth_service.dev_login("dev@test.com", "testpass", session)
|
|
user2 = await auth_service.dev_login("dev@test.com", "testpass", session)
|
|
assert user1.id == user2.id
|
|
|
|
|
|
class TestDevMode:
|
|
def test_is_dev_mode_when_no_azure(self, auth_service):
|
|
"""Without AZURE_AD_TENANT_ID, service is in dev mode."""
|
|
assert auth_service.is_dev_mode is True
|
|
|
|
async def test_validate_azure_token_raises_in_dev_mode(self, auth_service):
|
|
with pytest.raises(ValueError, match="not configured"):
|
|
await auth_service.validate_azure_token("dummy.token.here")
|