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:
18
backend/app/schemas/__init__.py
Normal file
18
backend/app/schemas/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from app.schemas.auth import LoginRequest, LoginResponse, TokenPayload
|
||||
from app.schemas.user import UserCreate, UserUpdate, UserResponse, UserInDB
|
||||
from app.schemas.department import DepartmentCreate, DepartmentUpdate, DepartmentResponse
|
||||
from app.schemas.role import RoleResponse
|
||||
|
||||
__all__ = [
|
||||
"LoginRequest",
|
||||
"LoginResponse",
|
||||
"TokenPayload",
|
||||
"UserCreate",
|
||||
"UserUpdate",
|
||||
"UserResponse",
|
||||
"UserInDB",
|
||||
"DepartmentCreate",
|
||||
"DepartmentUpdate",
|
||||
"DepartmentResponse",
|
||||
"RoleResponse",
|
||||
]
|
||||
36
backend/app/schemas/auth.py
Normal file
36
backend/app/schemas/auth.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: str
|
||||
password: str
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
user: "UserInfo"
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
name: str
|
||||
role: Optional[str] = None
|
||||
department_id: Optional[str] = None
|
||||
is_system_admin: bool = False
|
||||
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
sub: str
|
||||
email: str
|
||||
role: Optional[str] = None
|
||||
department_id: Optional[str] = None
|
||||
is_system_admin: bool = False
|
||||
exp: int
|
||||
iat: int
|
||||
|
||||
|
||||
# Update forward reference
|
||||
LoginResponse.model_rebuild()
|
||||
25
backend/app/schemas/department.py
Normal file
25
backend/app/schemas/department.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DepartmentBase(BaseModel):
|
||||
name: str
|
||||
parent_id: Optional[str] = None
|
||||
|
||||
|
||||
class DepartmentCreate(DepartmentBase):
|
||||
pass
|
||||
|
||||
|
||||
class DepartmentUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
parent_id: Optional[str] = None
|
||||
|
||||
|
||||
class DepartmentResponse(DepartmentBase):
|
||||
id: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
14
backend/app/schemas/role.py
Normal file
14
backend/app/schemas/role.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class RoleResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
permissions: Dict[str, Any]
|
||||
is_system_role: bool
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
41
backend/app/schemas/user.py
Normal file
41
backend/app/schemas/user.py
Normal file
@@ -0,0 +1,41 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
name: str
|
||||
department_id: Optional[str] = None
|
||||
role_id: Optional[str] = None
|
||||
skills: Optional[List[str]] = None
|
||||
capacity: Optional[Decimal] = Decimal("40.00")
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
pass
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
department_id: Optional[str] = None
|
||||
role_id: Optional[str] = None
|
||||
skills: Optional[List[str]] = None
|
||||
capacity: Optional[Decimal] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
|
||||
class UserResponse(UserBase):
|
||||
id: str
|
||||
is_active: bool
|
||||
is_system_admin: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class UserInDB(UserResponse):
|
||||
pass
|
||||
Reference in New Issue
Block a user