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:
Geutebruck API Developer
2025-12-09 09:04:16 +01:00
parent a4bde18d0f
commit fbebe10711
15 changed files with 1651 additions and 13 deletions

View File

@@ -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