feat: Phase 1 - Complete authentication system with JWT
Backend Implementation (FastAPI + MongoDB): - JWT authentication with access/refresh tokens - User registration and login endpoints - Password hashing with bcrypt (fixed 72-byte limit) - Protected endpoints with JWT middleware - Token refresh mechanism - Role-Based Access Control (RBAC) structure - Pydantic v2 models and async MongoDB with Motor - API endpoints: /api/auth/register, /api/auth/login, /api/auth/me, /api/auth/refresh Frontend Implementation (React + TypeScript + Material-UI): - Login and Register pages with validation - AuthContext for global authentication state - API client with Axios interceptors for token refresh - Protected routes with automatic redirect - User profile display in navigation - Logout functionality Technical Achievements: - Resolved bcrypt 72-byte limit (replaced passlib with native bcrypt) - Fixed Pydantic v2 compatibility (PyObjectId, ConfigDict) - Implemented automatic token refresh on 401 errors - Created comprehensive test suite for all auth endpoints Docker & Kubernetes: - Backend image: yakenator/site11-console-backend:latest - Frontend image: yakenator/site11-console-frontend:latest - Deployed to site11-pipeline namespace - Nginx reverse proxy configuration Documentation: - CONSOLE_ARCHITECTURE.md - Complete system architecture - PHASE1_COMPLETION.md - Detailed completion report - PROGRESS.md - Updated with Phase 1 status All authentication endpoints tested and verified working. 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
89
services/console/backend/app/models/user.py
Normal file
89
services/console/backend/app/models/user.py
Normal file
@ -0,0 +1,89 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Annotated
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator, ConfigDict
|
||||
from pydantic_core import core_schema
|
||||
from bson import ObjectId
|
||||
|
||||
|
||||
class PyObjectId(str):
|
||||
"""Custom ObjectId type for Pydantic v2"""
|
||||
|
||||
@classmethod
|
||||
def __get_pydantic_core_schema__(cls, source_type, handler):
|
||||
return core_schema.union_schema([
|
||||
core_schema.is_instance_schema(ObjectId),
|
||||
core_schema.chain_schema([
|
||||
core_schema.str_schema(),
|
||||
core_schema.no_info_plain_validator_function(cls.validate),
|
||||
])
|
||||
],
|
||||
serialization=core_schema.plain_serializer_function_ser_schema(
|
||||
lambda x: str(x)
|
||||
))
|
||||
|
||||
@classmethod
|
||||
def validate(cls, v):
|
||||
if isinstance(v, ObjectId):
|
||||
return v
|
||||
if isinstance(v, str) and ObjectId.is_valid(v):
|
||||
return ObjectId(v)
|
||||
raise ValueError("Invalid ObjectId")
|
||||
|
||||
|
||||
class UserRole(str):
|
||||
"""User roles"""
|
||||
ADMIN = "admin"
|
||||
EDITOR = "editor"
|
||||
VIEWER = "viewer"
|
||||
|
||||
|
||||
class OAuthProvider(BaseModel):
|
||||
"""OAuth provider information"""
|
||||
provider: str = Field(..., description="OAuth provider name (google, github, azure)")
|
||||
provider_user_id: str = Field(..., description="User ID from the provider")
|
||||
access_token: Optional[str] = Field(None, description="Access token (encrypted)")
|
||||
refresh_token: Optional[str] = Field(None, description="Refresh token (encrypted)")
|
||||
|
||||
|
||||
class UserProfile(BaseModel):
|
||||
"""User profile information"""
|
||||
avatar_url: Optional[str] = None
|
||||
department: Optional[str] = None
|
||||
timezone: str = "Asia/Seoul"
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
"""User model"""
|
||||
id: Optional[PyObjectId] = Field(alias="_id", default=None)
|
||||
email: EmailStr = Field(..., description="User email")
|
||||
username: str = Field(..., min_length=3, max_length=50, description="Username")
|
||||
hashed_password: str = Field(..., description="Hashed password")
|
||||
full_name: Optional[str] = Field(None, description="Full name")
|
||||
role: str = Field(default=UserRole.VIEWER, description="User role")
|
||||
permissions: List[str] = Field(default_factory=list, description="User permissions")
|
||||
oauth_providers: List[OAuthProvider] = Field(default_factory=list)
|
||||
profile: UserProfile = Field(default_factory=UserProfile)
|
||||
status: str = Field(default="active", description="User status")
|
||||
is_active: bool = Field(default=True)
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
last_login_at: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(
|
||||
populate_by_name=True,
|
||||
arbitrary_types_allowed=True,
|
||||
json_encoders={ObjectId: str},
|
||||
json_schema_extra={
|
||||
"example": {
|
||||
"email": "user@example.com",
|
||||
"username": "johndoe",
|
||||
"full_name": "John Doe",
|
||||
"role": "viewer"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class UserInDB(User):
|
||||
"""User model with password hash"""
|
||||
pass
|
||||
Reference in New Issue
Block a user