feat: implement user authentication module
- Backend (FastAPI): - External API authentication (pj-auth-api.vercel.app) - JWT token validation with Redis session storage - RBAC with department isolation - User, Role, Department models with pjctrl_ prefix - Alembic migrations with project-specific version table - Complete test coverage (13 tests) - Frontend (React + Vite): - AuthContext for state management - Login page with error handling - Protected route component - Dashboard with user info display - OpenSpec: - 7 capability specs defined - add-user-auth change archived 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
84
backend/tests/test_auth.py
Normal file
84
backend/tests/test_auth.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import pytest
|
||||
from app.core.security import create_access_token, decode_access_token, create_token_payload
|
||||
|
||||
|
||||
class TestJWT:
|
||||
"""Test JWT token creation and validation."""
|
||||
|
||||
def test_create_access_token(self):
|
||||
"""Test creating an access token."""
|
||||
data = {"sub": "user123", "email": "test@example.com"}
|
||||
token = create_access_token(data)
|
||||
|
||||
assert token is not None
|
||||
assert isinstance(token, str)
|
||||
|
||||
def test_decode_valid_token(self):
|
||||
"""Test decoding a valid token."""
|
||||
data = create_token_payload(
|
||||
user_id="user123",
|
||||
email="test@example.com",
|
||||
role="engineer",
|
||||
department_id="dept123",
|
||||
is_system_admin=False,
|
||||
)
|
||||
token = create_access_token(data)
|
||||
payload = decode_access_token(token)
|
||||
|
||||
assert payload is not None
|
||||
assert payload["sub"] == "user123"
|
||||
assert payload["email"] == "test@example.com"
|
||||
assert payload["role"] == "engineer"
|
||||
assert payload["is_system_admin"] is False
|
||||
|
||||
def test_decode_invalid_token(self):
|
||||
"""Test decoding an invalid token."""
|
||||
payload = decode_access_token("invalid.token.here")
|
||||
assert payload is None
|
||||
|
||||
def test_token_payload_structure(self):
|
||||
"""Test token payload has correct structure."""
|
||||
payload = create_token_payload(
|
||||
user_id="user123",
|
||||
email="test@example.com",
|
||||
role="engineer",
|
||||
department_id="dept123",
|
||||
is_system_admin=False,
|
||||
)
|
||||
|
||||
assert "sub" in payload
|
||||
assert "email" in payload
|
||||
assert "role" in payload
|
||||
assert "department_id" in payload
|
||||
assert "is_system_admin" in payload
|
||||
|
||||
|
||||
class TestAuthEndpoints:
|
||||
"""Test authentication API endpoints."""
|
||||
|
||||
def test_get_me_without_auth(self, client):
|
||||
"""Test accessing /me without authentication."""
|
||||
response = client.get("/api/auth/me")
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_me_with_auth(self, client, admin_token):
|
||||
"""Test accessing /me with valid authentication."""
|
||||
response = client.get(
|
||||
"/api/auth/me",
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["email"] == "ymirliu@panjit.com.tw"
|
||||
assert data["is_system_admin"] is True
|
||||
|
||||
def test_logout(self, client, admin_token, mock_redis):
|
||||
"""Test logout endpoint."""
|
||||
response = client.post(
|
||||
"/api/auth/logout",
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Verify session is removed
|
||||
assert mock_redis.get("session:00000000-0000-0000-0000-000000000001") is None
|
||||
Reference in New Issue
Block a user