pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
3.1 KiB
92 lines
3.1 KiB
from typing import Any, Awaitable, Callable
|
|
|
|
from starlette.requests import Request
|
|
from starlette.responses import JSONResponse
|
|
from starlette.staticfiles import StaticFiles as StaticFiles # noqa
|
|
from starlette.types import Receive, Scope, Send
|
|
|
|
|
|
class AuthStaticFiles(StaticFiles):
|
|
"""
|
|
A static files handler that requires authentication before serving files.
|
|
|
|
This solves the problem where `app.mount("/static", StaticFiles(...))` serves
|
|
files without any authentication, making it impossible to protect private files.
|
|
|
|
`AuthStaticFiles` accepts an `auth` callable that receives a `Request` and
|
|
should either return successfully (authenticated) or raise an `HTTPException`
|
|
(not authenticated).
|
|
|
|
## Usage
|
|
|
|
```python
|
|
from fastapi import FastAPI, HTTPException, Request
|
|
from fastapi.staticfiles import AuthStaticFiles
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
async def verify_token(request: Request) -> None:
|
|
token = request.headers.get("Authorization")
|
|
if token != "Bearer mysecrettoken":
|
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
|
|
|
|
|
app.mount(
|
|
"/private",
|
|
AuthStaticFiles(directory="private_files", auth=verify_token),
|
|
name="private",
|
|
)
|
|
```
|
|
|
|
## Parameters
|
|
|
|
* `auth`: An async callable that takes a `Request` object and performs
|
|
authentication. It should raise an `HTTPException` if authentication
|
|
fails, or return `None` if authentication succeeds.
|
|
* `directory`: The directory to serve files from.
|
|
* `packages`: A list of Python packages to serve files from.
|
|
* `html`: If `True`, serves `index.html` files for directories.
|
|
* `check_dir`: If `True`, checks that the directory exists on startup.
|
|
* `follow_symlink`: If `True`, follows symbolic links.
|
|
|
|
Ref: https://github.com/fastapi/fastapi/issues/858
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
directory: str | None = None,
|
|
packages: list[str | tuple[str, str]] | None = None,
|
|
html: bool = False,
|
|
check_dir: bool = True,
|
|
follow_symlink: bool = False,
|
|
auth: Callable[[Request], Awaitable[Any]],
|
|
) -> None:
|
|
super().__init__(
|
|
directory=directory,
|
|
packages=packages,
|
|
html=html,
|
|
check_dir=check_dir,
|
|
follow_symlink=follow_symlink,
|
|
)
|
|
self.auth = auth
|
|
|
|
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
if scope["type"] == "http":
|
|
request = Request(scope, receive)
|
|
try:
|
|
await self.auth(request)
|
|
except Exception as exc:
|
|
from fastapi.exceptions import HTTPException
|
|
|
|
if isinstance(exc, HTTPException):
|
|
response = JSONResponse(
|
|
{"detail": exc.detail},
|
|
status_code=exc.status_code,
|
|
headers=getattr(exc, "headers", None),
|
|
)
|
|
await response(scope, receive, send)
|
|
return
|
|
raise
|
|
await super().__call__(scope, receive, send)
|
|
|