Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected Redirection of /docs Endpoint with Custom APP_MODULE Setup #254

Open
jda5 opened this issue Nov 15, 2023 · 0 comments
Open

Unexpected Redirection of /docs Endpoint with Custom APP_MODULE Setup #254

jda5 opened this issue Nov 15, 2023 · 0 comments

Comments

@jda5
Copy link

jda5 commented Nov 15, 2023

I am not exactly sure why this is happening, but for some reason the endpoint /docs always redirects to the docs specified in default docker-images/app/main.py file πŸ€·β€β™‚οΈ.

Screenshot 2023-11-15 at 20 47 50

All other endpoints are working fine (serviced by my FastAPI instance) - including /.

My project structure is shown below.

.
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ docker-compose.traefik.yml
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ .env
└── app/
    └── api/
        β”œβ”€β”€ __init__.py
        └── main.py

Here is my main.py file

from api.settings import APP_SECRET_KEY, HASHED_DOCS_PASSWORD

import hashlib
import secrets
from fastapi import FastAPI, Depends, HTTPException, status, APIRouter
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from starlette.middleware.sessions import SessionMiddleware

from api.database import db
from api.dependencies import router as token_router
from api.routes import auth, bot, campaign, client, contact, sequence

# Create an instance of HTTPBasic, which will be used for basic authentication of the docs
security = HTTPBasic()

app = FastAPI(
    docs_url=None,
    redoc_url=None,
    openapi_url=None,
    title="Chase API",
    version="MVP",
)

app.add_middleware(SessionMiddleware, secret_key=str(APP_SECRET_KEY))


def authenticate_docs_access(credentials: HTTPBasicCredentials = Depends(security)) -> bool:
    """
    Authenticates access to the FastAPI documentation.

    Args:
        credentials (HTTPBasicCredentials): The HTTP Basic credentials.

    Returns:
        bool: True if the username is "admin" and the hashed password matches the one saved in the 
              environment file, otherwise an HTTP 401 (unauthorized) response is sent.
    """
    correct_username = secrets.compare_digest(credentials.username, "admin")
    correct_password = secrets.compare_digest(
        hashlib.sha256(credentials.password.encode()
                       ).hexdigest(), str(HASHED_DOCS_PASSWORD)
    )
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password.",
            headers={"WWW-Authenticate": "Basic"},
        )
    return True


@app.get("/docs")
async def get_documentation(auth: bool = Depends(authenticate_docs_access)):
    """
    Provides access to the Swagger-based API documentation UI.

    ### Headers

    - `Authorization`: Basic authentication credentials to verify access to documentation.

    ### Query Parameters

    - `auth` (`bool`): Indicates if the user has authenticated access to the documentation.

    ### Notes

    - Access to the Swagger UI is conditional upon successful basic authentication.
    """
    return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")


@app.get("/openapi.json")
async def openapi(auth: bool = Depends(authenticate_docs_access)):
    """
    Serves the OpenAPI schema for the FastAPI application.

    ### Headers

    - `Authorization`: Basic authentication credentials required for access.

    ### Query Parameters

    - `auth` (`bool`): Determines if the user has permission to view the OpenAPI schema.
    """
    return get_openapi(title=app.title, version=app.version, routes=app.routes)


@app.on_event("startup")
async def startup():
    """
    Event handler that connects to the database on app startup.
    """
    await db.connect()


@app.on_event("shutdown")
async def shutdown():
    """
    Event handler that disconnects from the database on app shutdown.
    """
    await db.disconnect()


# Include the routers for each endpoint
app.include_router(token_router)
app.include_router(auth.router)
app.include_router(bot.router)
app.include_router(campaign.router)
app.include_router(client.router)
app.include_router(contact.router)
app.include_router(sequence.router)

And below is my docker-compose.yml

I have specified a MODULE_NAME and VARIABLE_NAME to reflect my file structure.

version: "3"

services:
  web:
    environment:
      - MODULE_NAME=api.main
      - MAX_WORKERS=2
      - VARIABLE_NAME=app

    build: ./

    restart: always

    labels:
      # === TLS ===

      # Enable Traefik for this specific "backend" service
      - traefik.enable=true
      # Define the port inside of the Docker service to use
      - traefik.http.services.app.loadbalancer.server.port=80
      # Make Traefik use this domain in HTTP
      - traefik.http.routers.app-http.entrypoints=http
      - traefik.http.routers.app-http.rule=Host(`localhost`)
      # Use the traefik-public network (declared below)
      - traefik.docker.network=traefik-public
      # Make Traefik use this domain in HTTPS
      - traefik.http.routers.app-https.entrypoints=https
      - traefik.http.routers.app-https.rule=Host(`localhost`)
      - traefik.http.routers.app-https.tls=true
      # Use the "le" (Let's Encrypt) resolver
      - traefik.http.routers.app-https.tls.certresolver=le
      # https-redirect middleware to redirect HTTP to HTTPS
      - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
      - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
      # Middleware to redirect HTTP to HTTPS
      - traefik.http.routers.app-http.middlewares=https-redirect

    networks:
      # Use the public network created to be shared between Traefik and
      # any other service that needs to be publically available with HTTPS
      - traefik-public

    extra_hosts:
      # Allows a direct connection from inside a docker container to the local
      # machine on Linux based systems.
      - "host.docker.internal:host-gateway"

networks:
  traefik-public:
    external: true

Perhaps even stranger, the docs ARE protected 🀯 as in they use it is protected behind the HTTPBasicCredentials I have specified.

No sure how to resolve this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant