154 lines
5.9 KiB
Python
154 lines
5.9 KiB
Python
from __future__ import annotations
|
|
from contextlib import asynccontextmanager
|
|
import secrets
|
|
from typing import AsyncGenerator
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
from fastapi import Depends, FastAPI, status
|
|
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
|
|
from fastapi.responses import HTMLResponse
|
|
from fastapi.security import HTTPBasic, HTTPBasicCredentials
|
|
from src.application.commands import PollKycSessionsCommand
|
|
from src.application.domain.enums import LogFormat,LogLevel
|
|
from src.application.domain.exceptions import ApplicationException
|
|
from src.infrastructure.beorg import BeorgService
|
|
from src.infrastructure.config.settings import get_settings
|
|
from src.infrastructure.database.context import async_session_maker
|
|
from src.infrastructure.database.unit_of_work import UnitOfWork
|
|
from src.infrastructure.vault import JwtKeyStore, start_jwt_keys_scheduler
|
|
from src.infrastructure.utils import generate_instance_id
|
|
from src.infrastructure.logger import logger
|
|
from src.infrastructure.config import settings
|
|
from src.presentation.handlers import application_exception_handler, unhandled_exception_handler
|
|
from src.presentation.middleware import TraceIDMiddleware, SecurityHeadersMiddleware
|
|
from src.presentation.routing import kyc_router
|
|
|
|
security = HTTPBasic()
|
|
|
|
|
|
async def verify_credentials(credentials: HTTPBasicCredentials = Depends(security)) -> HTTPBasicCredentials:
|
|
user_ok = secrets.compare_digest(credentials.username, settings.DOCS_USERNAME)
|
|
pass_ok = secrets.compare_digest(credentials.password, settings.DOCS_PASSWORD)
|
|
if not (user_ok and pass_ok):
|
|
raise ApplicationException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
message='Unauthorized',
|
|
headers={'WWW-Authenticate': 'Basic'},
|
|
)
|
|
return credentials
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
|
|
instance_id = generate_instance_id()
|
|
logger.set_format(LogFormat(settings.LOG_FORMAT.lower()))
|
|
logger.set_min_level(LogLevel[settings.LOG_LEVEL.upper()])
|
|
logger.set_instance_id(instance_id)
|
|
logger.info(f'Users service instance started with id {instance_id}')
|
|
|
|
jwt_store = JwtKeyStore(
|
|
vault_addr=settings.VAULT_ADDR,
|
|
vault_role_id=settings.VAULT_ROLE_ID,
|
|
vault_secret_id=settings.VAULT_SECRET_ID,
|
|
vault_namespace=settings.VAULT_NAMESPACE,
|
|
mount_point=settings.VAULT_MOUNT_POINT,
|
|
kid_path=settings.VAULT_JWT_KID_PATH,
|
|
kids_prefix=settings.VAULT_JWT_KIDS_PREFIX,
|
|
)
|
|
|
|
await jwt_store.refresh()
|
|
|
|
jwt_scheduler = start_jwt_keys_scheduler(jwt_store, refresh_seconds=settings.JWT_KEYS_REFRESH_SECONDS)
|
|
kyc_poll_command = PollKycSessionsCommand(
|
|
unit_of_work=UnitOfWork(session_factory=async_session_maker,logger=logger),
|
|
logger=logger,
|
|
beorg_service=BeorgService(
|
|
project_id=settings.BEORG_PROJECT_ID,
|
|
machine_uid=settings.BEORG_MACHINE_UID,
|
|
token=settings.BEORG_TOKEN,
|
|
process_info=settings.BEORG_PROCESS_INFO,
|
|
timeout=settings.BEORG_TIMEOUT,
|
|
),
|
|
batch_size=settings.KYC_POLL_BATCH_SIZE,
|
|
)
|
|
kyc_scheduler = AsyncIOScheduler()
|
|
kyc_scheduler.add_job(
|
|
kyc_poll_command.__call__,
|
|
'interval',
|
|
seconds=settings.KYC_POLL_SECONDS,
|
|
max_instances=1,
|
|
)
|
|
kyc_scheduler.start()
|
|
|
|
app.state.jwt_key_store = jwt_store
|
|
app.state.jwt_keys_scheduler = jwt_scheduler
|
|
app.state.kyc_scheduler = kyc_scheduler
|
|
yield
|
|
app.state.kyc_scheduler.shutdown(wait=False)
|
|
app.state.jwt_keys_scheduler.shutdown(wait=False)
|
|
logger.info(f'Users service instance ended with id {instance_id}')
|
|
|
|
|
|
app: FastAPI = FastAPI(
|
|
redoc_url=None,
|
|
docs_url=None,
|
|
lifespan=lifespan,
|
|
title='Elcsa Users Service'
|
|
)
|
|
|
|
app.add_exception_handler(ApplicationException, application_exception_handler)
|
|
app.add_exception_handler(Exception, unhandled_exception_handler)
|
|
|
|
app.include_router(kyc_router)
|
|
|
|
|
|
app.add_middleware(TraceIDMiddleware, logger=logger)
|
|
app.add_middleware(
|
|
SecurityHeadersMiddleware,
|
|
hsts=True,
|
|
hsts_preload=False,
|
|
frame_options='DENY',
|
|
referrer_policy='strict-origin-when-cross-origin',
|
|
content_security_policy=(
|
|
"default-src 'self'; "
|
|
"script-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; "
|
|
"style-src 'self' 'unsafe-inline' https://unpkg.com https://cdn.jsdelivr.net; "
|
|
"img-src 'self' data: https:; "
|
|
"font-src 'self' https://unpkg.com https://cdn.jsdelivr.net data:; "
|
|
"connect-src 'self'; "
|
|
"frame-ancestors 'none'; "
|
|
"base-uri 'self'; "
|
|
"object-src 'none'"
|
|
),
|
|
)
|
|
|
|
|
|
@app.get('/docs', include_in_schema=False)
|
|
async def custom_swagger_ui_html(_credentials: HTTPBasicCredentials = Depends(verify_credentials)) -> HTMLResponse:
|
|
'''Custom Swagger documentation, optionally protected with basic authentication.'''
|
|
return get_swagger_ui_html(
|
|
openapi_url=getattr(app, 'openapi_url', '/openapi.json'),
|
|
title=getattr(app, 'title', 'FastAPI') + ' - Swagger UI',
|
|
oauth2_redirect_url=getattr(app, 'swagger_ui_oauth2_redirect_url', None),
|
|
swagger_js_url='https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js',
|
|
swagger_css_url='https://unpkg.com/swagger-ui-dist@5/swagger-ui.css',
|
|
)
|
|
|
|
|
|
@app.get('/redoc', include_in_schema=False)
|
|
async def custom_redoc_html(_credentials: HTTPBasicCredentials = Depends(verify_credentials)) -> HTMLResponse:
|
|
'''Custom ReDoc documentation, optionally protected with basic authentication.'''
|
|
return get_redoc_html(
|
|
openapi_url=getattr(app, 'openapi_url', '/openapi.json'),
|
|
title=getattr(app, 'title', 'FastAPI') + ' - ReDoc',
|
|
redoc_js_url='https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js',
|
|
)
|
|
|
|
|
|
@app.post('/ping')
|
|
async def ping() -> dict[str, str]:
|
|
return {
|
|
'message': 'pong',
|
|
'status': 'ok',
|
|
}
|