82219fe5ad
- AES-GCM encrypt/decrypt module (app/security/aes.py):
- get_aes_key: decode base64 AES master key to 32 bytes
- encrypt_setting: AES-256-GCM with fresh 12-byte nonce per call, returns nonce||ciphertext
- decrypt_setting: decrypts ciphertext, raises AuthenticationError on tamper/wrong key
- Settings endpoint (app/api/settings.py):
- GET /api/v1/settings: returns SettingsView with decrypted values, secrets masked
(woo_app_password never returned, only woo_credentials_configured boolean)
- PUT /api/v1/settings: encrypts all setting values AES-GCM, stores in settings table
- Rejects woo_consumer_key/woo_consumer_secret fields
- Validates woo_create_status (draft|publish)
- App factory (app/main.py): stores settings on app.state for dependency injection
- Unit tests (tests/unit/test_aes.py): 18 tests covering AES-1 (round-trip), AES-2
(missing key refusal), FUND-17 (fresh nonce, tamper rejection), FUND-19
(no plaintext fallback)
Test results: 23/23 passed (18 new AES + 5 existing health tests)
55 lines
1.5 KiB
Python
55 lines
1.5 KiB
Python
"""App factory and lifespan."""
|
|
|
|
import logging
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import FastAPI
|
|
|
|
from app.api import health as health_router
|
|
from app.api import settings as settings_router
|
|
from app.config import Settings
|
|
from app.db.engine import close_db_connection, init_db
|
|
from app.logging_config import configure_logging
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Application lifespan: startup and shutdown hooks.
|
|
|
|
Handles database initialization, logging configuration,
|
|
and cleanup on shutdown.
|
|
"""
|
|
configure_logging()
|
|
logging.info("SlabUploader starting up")
|
|
await init_db()
|
|
yield
|
|
logging.info("SlabUploader shutting down")
|
|
await close_db_connection()
|
|
|
|
|
|
def create_app(app_settings: Settings | None = None) -> FastAPI:
|
|
"""Create a fresh FastAPI app instance.
|
|
|
|
Each call produces an independent app with its own database connection
|
|
and cleared dependency overrides. This is the factory used by tests.
|
|
|
|
Args:
|
|
app_settings: Application settings. Uses env vars if None.
|
|
|
|
Returns:
|
|
Configured FastAPI application.
|
|
"""
|
|
if app_settings is None:
|
|
app_settings = Settings()
|
|
app_settings.validate()
|
|
|
|
app = FastAPI(title="SlabUploader", version="0.1.0", lifespan=lifespan)
|
|
|
|
# Store settings on app state for dependency injection
|
|
app.state.settings = app_settings
|
|
|
|
app.include_router(health_router.router)
|
|
app.include_router(settings_router.router)
|
|
|
|
return app
|