Phase 4: Authentication System (T039-T048)
Implemented complete JWT-based authentication system with RBAC: **Tests (TDD Approach):** - Created contract tests for /api/v1/auth/login endpoint - Created contract tests for /api/v1/auth/logout endpoint - Created unit tests for AuthService (login, logout, validate_token, password hashing) - Created pytest configuration and fixtures (test DB, test users, tokens) **Schemas:** - LoginRequest: username/password validation - TokenResponse: access_token, refresh_token, user info - LogoutResponse: logout confirmation - RefreshTokenRequest: token refresh payload - UserInfo: user data (excludes password_hash) **Services:** - AuthService: login(), logout(), validate_token(), hash_password(), verify_password() - Integrated bcrypt password hashing - JWT token generation (access + refresh tokens) - Token blacklisting in Redis - Audit logging for all auth operations **Middleware:** - Authentication middleware with JWT validation - Role-based access control (RBAC) helpers - require_role() dependency factory - Convenience dependencies: require_viewer(), require_operator(), require_administrator() - Client IP and User-Agent extraction **Router:** - POST /api/v1/auth/login - Authenticate and get tokens - POST /api/v1/auth/logout - Blacklist token - POST /api/v1/auth/refresh - Refresh access token - GET /api/v1/auth/me - Get current user info **Integration:** - Registered auth router in main.py - Updated startup event to initialize Redis and SDK Bridge clients - Updated shutdown event to cleanup connections properly - Fixed error translation utilities - Added asyncpg dependency for PostgreSQL async driver 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -82,9 +82,25 @@ async def startup_event():
|
||||
version=settings.API_VERSION,
|
||||
environment=settings.ENVIRONMENT)
|
||||
|
||||
# TODO: Initialize database connection pool
|
||||
# TODO: Initialize Redis connection
|
||||
# TODO: Initialize gRPC SDK Bridge client
|
||||
# Initialize Redis connection
|
||||
try:
|
||||
from clients.redis_client import redis_client
|
||||
await redis_client.connect()
|
||||
logger.info("redis_connected", host=settings.REDIS_HOST, port=settings.REDIS_PORT)
|
||||
except Exception as e:
|
||||
logger.error("redis_connection_failed", error=str(e))
|
||||
# Non-fatal: API can run without Redis (no caching/token blacklist)
|
||||
|
||||
# Initialize gRPC SDK Bridge client
|
||||
try:
|
||||
from clients.sdk_bridge_client import sdk_bridge_client
|
||||
await sdk_bridge_client.connect()
|
||||
logger.info("sdk_bridge_connected", url=settings.sdk_bridge_url)
|
||||
except Exception as e:
|
||||
logger.error("sdk_bridge_connection_failed", error=str(e))
|
||||
# Non-fatal: API can run without SDK Bridge (for testing)
|
||||
|
||||
# Database connection pool is initialized lazily via AsyncSessionLocal
|
||||
|
||||
logger.info("startup_complete")
|
||||
|
||||
@@ -94,9 +110,31 @@ async def shutdown_event():
|
||||
"""Cleanup on shutdown"""
|
||||
logger.info("shutdown")
|
||||
|
||||
# TODO: Close database connections
|
||||
# TODO: Close Redis connections
|
||||
# TODO: Close gRPC connections
|
||||
# Close Redis connections
|
||||
try:
|
||||
from clients.redis_client import redis_client
|
||||
await redis_client.disconnect()
|
||||
logger.info("redis_disconnected")
|
||||
except Exception as e:
|
||||
logger.error("redis_disconnect_failed", error=str(e))
|
||||
|
||||
# Close gRPC SDK Bridge connections
|
||||
try:
|
||||
from clients.sdk_bridge_client import sdk_bridge_client
|
||||
await sdk_bridge_client.disconnect()
|
||||
logger.info("sdk_bridge_disconnected")
|
||||
except Exception as e:
|
||||
logger.error("sdk_bridge_disconnect_failed", error=str(e))
|
||||
|
||||
# Close database connections
|
||||
try:
|
||||
from models import engine
|
||||
await engine.dispose()
|
||||
logger.info("database_disconnected")
|
||||
except Exception as e:
|
||||
logger.error("database_disconnect_failed", error=str(e))
|
||||
|
||||
logger.info("shutdown_complete")
|
||||
|
||||
# Health check endpoint
|
||||
@app.get("/health", tags=["system"])
|
||||
@@ -119,12 +157,15 @@ async def root():
|
||||
"health": "/health"
|
||||
}
|
||||
|
||||
# Register routers (TODO: will add as we implement phases)
|
||||
# from routers import auth, cameras, monitors, crossswitch
|
||||
# app.include_router(auth.router, prefix="/api/v1/auth", tags=["authentication"])
|
||||
# app.include_router(cameras.router, prefix="/api/v1/cameras", tags=["cameras"])
|
||||
# app.include_router(monitors.router, prefix="/api/v1/monitors", tags=["monitors"])
|
||||
# app.include_router(crossswitch.router, prefix="/api/v1", tags=["crossswitch"])
|
||||
# Register routers
|
||||
from routers import auth
|
||||
app.include_router(auth.router)
|
||||
|
||||
# TODO: Add remaining routers as phases complete
|
||||
# from routers import cameras, monitors, crossswitch
|
||||
# app.include_router(cameras.router)
|
||||
# app.include_router(monitors.router)
|
||||
# app.include_router(crossswitch.router)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
Reference in New Issue
Block a user