diff --git a/auth/__init__.py b/auth/__init__.py new file mode 100644 index 0000000..e392a3f --- /dev/null +++ b/auth/__init__.py @@ -0,0 +1,5 @@ +from .static_env import verify_user_pwd + +__all__ = [ + "verify_user_pwd", +] diff --git a/auth/static_env.py b/auth/static_env.py new file mode 100644 index 0000000..b3591d0 --- /dev/null +++ b/auth/static_env.py @@ -0,0 +1,31 @@ +from fastapi import Depends, HTTPException, status +from fastapi.security import APIKeyHeader +from fastapi.security import HTTPBasic, HTTPBasicCredentials +from config import conf +import logging as log + +security = HTTPBasic() +api_key_header = APIKeyHeader(name="X-API-KEY", auto_error=False) + + +# def verify_token(token: str = Depends(api_key_header)): +# if token != conf.zbx.token: +# log.warning("Invalid token") +# raise HTTPException( +# status_code=status.HTTP_401_UNAUTHORIZED, +# detail="Invalid authentication credentials", +# headers={"WWW-Authenticate": "Bearer"}, +# ) + + +def verify_user_pwd(credentials: HTTPBasicCredentials = Depends(security)): + if ( + credentials.username != conf.swagger.login + or credentials.password != conf.swagger.pwd + ): + log.warning("Invalid credentials") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid credentials", + headers={"WWW-Authenticate": "Basic"}, + ) diff --git a/config/.env-template b/config/.env-template new file mode 100644 index 0000000..7949a7c --- /dev/null +++ b/config/.env-template @@ -0,0 +1,9 @@ +OAA_CFG__RUN__HOST=0.0.0.0 +OAA_CFG__RUN__PORT=8000 +OAA_CFG__RUN__RELOAD=True + +OAA_CFG__LOG__LEVEL=30 +OAA_CFG__LOG__LEVEL_TO_FILE=30 + +OAA_CFG__SWAGGER__LOGIN=admin +OAA_CFG__SWAGGER__PWD=P@ssw0rd! diff --git a/config/__init__.py b/config/__init__.py new file mode 100644 index 0000000..1d61cd6 --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,7 @@ +from .config import conf, STATIC_DIR + + +__all__ = [ + "conf", + STATIC_DIR, +] diff --git a/config/config.py b/config/config.py new file mode 100644 index 0000000..0efd19f --- /dev/null +++ b/config/config.py @@ -0,0 +1,86 @@ +from pydantic import BaseModel +from pydantic_settings import ( + BaseSettings, + SettingsConfigDict, +) + +from pathlib import Path + +import logging + + +BASE_DIR = Path(__file__).parent.parent +TEMPLATES_DIR = BASE_DIR / "web" / "templates" +STATIC_DIR = BASE_DIR / "web" / "static" + + +class RunConfig(BaseModel): + host: str = "0.0.0.0" + port: int = 8000 + reload: bool = False + + +class LogConfig(BaseModel): + level: int = 30 + level_to_file: int = 30 + dateformat: str = "%Y-%m-%d %H:%M:%S" + format: str = ( + "[%(asctime)s.%(msecs)03d] %(module)-25s:%(lineno)4d | %(funcName)-20s| %(levelname)-8s | %(message)s" + ) + + +class PrefixConfig(BaseModel): + swagger: str = "/docs" + api_v1: str = "/api/v1" + + +class RedisConfig(BaseModel): + host: str = "localhost" + port: int = 6379 + pwd: str | None = None + + +class SwaggerConfig(BaseModel): + openapi_url: str = "/openapi.json" + title: str = "API" + oauth2_redirect_url: str = "/docs/oauth2-redirect" + swagger_js_url: str = "/static/swagger/swagger-ui-bundle.js" + swagger_css_url: str = "/static/swagger/swagger-ui.css" + swagger_favicon_url: str = "/static/swagger/favicon.png" + login: str + pwd: str + + +class Settings(BaseSettings): + model_config = SettingsConfigDict( + env_file=( + BASE_DIR / "config" / ".env-template", + BASE_DIR / "config" / ".env", + ), + case_sensitive=False, + env_nested_delimiter="__", + env_prefix="OAA_CFG__", + ) + run: RunConfig = RunConfig() + swagger: SwaggerConfig + log: LogConfig = LogConfig() + prefix: PrefixConfig = PrefixConfig() + + +conf = Settings() + + +logging.basicConfig( + level=conf.log.level, + datefmt=conf.log.dateformat, + format=conf.log.format, +) + +file_handler = logging.FileHandler(BASE_DIR / "logfile.log") +file_handler.setLevel(conf.log.level_to_file) +file_handler.setFormatter( + logging.Formatter( + "[%(asctime)s.%(msecs)03d] %(module)s:%(lineno)4d | %(funcName)s| %(levelname)s | %(message)s" + ) +) +logging.getLogger().addHandler(file_handler) diff --git a/logfile.log b/logfile.log new file mode 100644 index 0000000..73025b2 --- /dev/null +++ b/logfile.log @@ -0,0 +1,2 @@ +[2024-10-20 12:43:18,545.545] static_env: 26 | verify_user_pwd| WARNING | Invalid credentials +[2024-10-20 12:43:35,774.774] static_env: 26 | verify_user_pwd| WARNING | Invalid credentials diff --git a/routers/__init__.py b/routers/__init__.py new file mode 100644 index 0000000..d137e4b --- /dev/null +++ b/routers/__init__.py @@ -0,0 +1,12 @@ +from fastapi import APIRouter + +from .swagger import router as swagger_router +from config import conf + + +router = APIRouter() + +router.include_router( + swagger_router, + prefix=conf.prefix.swagger, +) diff --git a/routers/swagger.py b/routers/swagger.py new file mode 100644 index 0000000..924dee3 --- /dev/null +++ b/routers/swagger.py @@ -0,0 +1,29 @@ +from fastapi import APIRouter, Depends + +from fastapi.openapi.docs import get_swagger_ui_html +from fastapi.security import HTTPBasicCredentials + +from config import conf + +from auth import verify_user_pwd + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from fastapi.security import HTTPBasicCredentials + +router = APIRouter() + + +@router.get("", include_in_schema=False) +async def custom_swagger_ui_html( + credentials: HTTPBasicCredentials = Depends(verify_user_pwd), +): + return get_swagger_ui_html( + openapi_url=conf.swagger.openapi_url, + title=conf.swagger.title, + oauth2_redirect_url=conf.swagger.oauth2_redirect_url, + swagger_js_url=conf.swagger.swagger_js_url, + swagger_css_url=conf.swagger.swagger_css_url, + swagger_favicon_url=conf.swagger.swagger_favicon_url, + )