API refactor
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-07 16:25:52 +09:00
parent 76d0d86211
commit 91c7e04474
1171 changed files with 81940 additions and 44117 deletions

View File

@@ -5,11 +5,19 @@ from fastapi.security.base import SecurityBase
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.status import HTTP_403_FORBIDDEN
from typing_extensions import Annotated, Doc # type: ignore [attr-defined]
from typing_extensions import Annotated, Doc
class APIKeyBase(SecurityBase):
pass
@staticmethod
def check_api_key(api_key: Optional[str], auto_error: bool) -> Optional[str]:
if not api_key:
if auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
return None
return api_key
class APIKeyQuery(APIKeyBase):
@@ -76,7 +84,7 @@ class APIKeyQuery(APIKeyBase):
Doc(
"""
By default, if the query parameter is not provided, `APIKeyQuery` will
automatically cancel the request and sebd the client an error.
automatically cancel the request and send the client an error.
If `auto_error` is set to `False`, when the query parameter is not
available, instead of erroring out, the dependency result will be
@@ -92,7 +100,7 @@ class APIKeyQuery(APIKeyBase):
] = True,
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.query}, # type: ignore[arg-type]
**{"in": APIKeyIn.query},
name=name,
description=description,
)
@@ -101,14 +109,7 @@ class APIKeyQuery(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.query_params.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
return self.check_api_key(api_key, self.auto_error)
class APIKeyHeader(APIKeyBase):
@@ -187,7 +188,7 @@ class APIKeyHeader(APIKeyBase):
] = True,
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.header}, # type: ignore[arg-type]
**{"in": APIKeyIn.header},
name=name,
description=description,
)
@@ -196,14 +197,7 @@ class APIKeyHeader(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.headers.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
return self.check_api_key(api_key, self.auto_error)
class APIKeyCookie(APIKeyBase):
@@ -282,7 +276,7 @@ class APIKeyCookie(APIKeyBase):
] = True,
):
self.model: APIKey = APIKey(
**{"in": APIKeyIn.cookie}, # type: ignore[arg-type]
**{"in": APIKeyIn.cookie},
name=name,
description=description,
)
@@ -291,11 +285,4 @@ class APIKeyCookie(APIKeyBase):
async def __call__(self, request: Request) -> Optional[str]:
api_key = request.cookies.get(self.model.name)
if not api_key:
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN, detail="Not authenticated"
)
else:
return None
return api_key
return self.check_api_key(api_key, self.auto_error)

View File

@@ -10,12 +10,12 @@ from fastapi.security.utils import get_authorization_scheme_param
from pydantic import BaseModel
from starlette.requests import Request
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
from typing_extensions import Annotated, Doc # type: ignore [attr-defined]
from typing_extensions import Annotated, Doc
class HTTPBasicCredentials(BaseModel):
"""
The HTTP Basic credendials given as the result of using `HTTPBasic` in a
The HTTP Basic credentials given as the result of using `HTTPBasic` in a
dependency.
Read more about it in the
@@ -277,7 +277,7 @@ class HTTPBearer(HTTPBase):
bool,
Doc(
"""
By default, if the HTTP Bearer token not provided (in an
By default, if the HTTP Bearer token is not provided (in an
`Authorization` header), `HTTPBearer` will automatically cancel the
request and send the client an error.
@@ -380,7 +380,7 @@ class HTTPDigest(HTTPBase):
bool,
Doc(
"""
By default, if the HTTP Digest not provided, `HTTPDigest` will
By default, if the HTTP Digest is not provided, `HTTPDigest` will
automatically cancel the request and send the client an error.
If `auto_error` is set to `False`, when the HTTP Digest is not
@@ -413,8 +413,11 @@ class HTTPDigest(HTTPBase):
else:
return None
if scheme.lower() != "digest":
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,
detail="Invalid authentication credentials",
)
if self.auto_error:
raise HTTPException(
status_code=HTTP_403_FORBIDDEN,
detail="Invalid authentication credentials",
)
else:
return None
return HTTPAuthorizationCredentials(scheme=scheme, credentials=credentials)

View File

@@ -10,7 +10,7 @@ from starlette.requests import Request
from starlette.status import HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN
# TODO: import from typing when deprecating Python 3.9
from typing_extensions import Annotated, Doc # type: ignore [attr-defined]
from typing_extensions import Annotated, Doc
class OAuth2PasswordRequestForm:
@@ -52,9 +52,9 @@ class OAuth2PasswordRequestForm:
```
Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
You could have custom internal logic to separate it by colon caracters (`:`) or
You could have custom internal logic to separate it by colon characters (`:`) or
similar, and get the two parts `items` and `read`. Many applications do that to
group and organize permisions, you could do it as well in your application, just
group and organize permissions, you could do it as well in your application, just
know that that it is application specific, it's not part of the specification.
"""
@@ -63,7 +63,7 @@ class OAuth2PasswordRequestForm:
*,
grant_type: Annotated[
Union[str, None],
Form(pattern="password"),
Form(pattern="^password$"),
Doc(
"""
The OAuth2 spec says it is required and MUST be the fixed string
@@ -85,7 +85,7 @@ class OAuth2PasswordRequestForm:
],
password: Annotated[
str,
Form(),
Form(json_schema_extra={"format": "password"}),
Doc(
"""
`password` string. The OAuth2 spec requires the exact field name
@@ -130,7 +130,7 @@ class OAuth2PasswordRequestForm:
] = None,
client_secret: Annotated[
Union[str, None],
Form(),
Form(json_schema_extra={"format": "password"}),
Doc(
"""
If there's a `client_password` (and a `client_id`), they can be sent
@@ -194,9 +194,9 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
```
Note that for OAuth2 the scope `items:read` is a single scope in an opaque string.
You could have custom internal logic to separate it by colon caracters (`:`) or
You could have custom internal logic to separate it by colon characters (`:`) or
similar, and get the two parts `items` and `read`. Many applications do that to
group and organize permisions, you could do it as well in your application, just
group and organize permissions, you could do it as well in your application, just
know that that it is application specific, it's not part of the specification.
@@ -217,7 +217,7 @@ class OAuth2PasswordRequestFormStrict(OAuth2PasswordRequestForm):
self,
grant_type: Annotated[
str,
Form(pattern="password"),
Form(pattern="^password$"),
Doc(
"""
The OAuth2 spec says it is required and MUST be the fixed string
@@ -353,7 +353,7 @@ class OAuth2(SecurityBase):
bool,
Doc(
"""
By default, if no HTTP Auhtorization header is provided, required for
By default, if no HTTP Authorization header is provided, required for
OAuth2 authentication, it will automatically cancel the request and
send the client an error.
@@ -441,7 +441,7 @@ class OAuth2PasswordBearer(OAuth2):
bool,
Doc(
"""
By default, if no HTTP Auhtorization header is provided, required for
By default, if no HTTP Authorization header is provided, required for
OAuth2 authentication, it will automatically cancel the request and
send the client an error.
@@ -457,11 +457,26 @@ class OAuth2PasswordBearer(OAuth2):
"""
),
] = True,
refreshUrl: Annotated[
Optional[str],
Doc(
"""
The URL to refresh the token and obtain a new one.
"""
),
] = None,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(
password=cast(Any, {"tokenUrl": tokenUrl, "scopes": scopes})
password=cast(
Any,
{
"tokenUrl": tokenUrl,
"refreshUrl": refreshUrl,
"scopes": scopes,
},
)
)
super().__init__(
flows=flows,
@@ -543,7 +558,7 @@ class OAuth2AuthorizationCodeBearer(OAuth2):
bool,
Doc(
"""
By default, if no HTTP Auhtorization header is provided, required for
By default, if no HTTP Authorization header is provided, required for
OAuth2 authentication, it will automatically cancel the request and
send the client an error.

View File

@@ -5,7 +5,7 @@ from fastapi.security.base import SecurityBase
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.status import HTTP_403_FORBIDDEN
from typing_extensions import Annotated, Doc # type: ignore [attr-defined]
from typing_extensions import Annotated, Doc
class OpenIdConnect(SecurityBase):
@@ -49,7 +49,7 @@ class OpenIdConnect(SecurityBase):
bool,
Doc(
"""
By default, if no HTTP Auhtorization header is provided, required for
By default, if no HTTP Authorization header is provided, required for
OpenID Connect authentication, it will automatically cancel the request
and send the client an error.