fauth 0.2.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fauth-0.2.2 → fauth-0.3.0}/.pre-commit-config.yaml +2 -2
- {fauth-0.2.2 → fauth-0.3.0}/CHANGELOG.md +79 -0
- {fauth-0.2.2 → fauth-0.3.0}/PKG-INFO +1 -1
- {fauth-0.2.2 → fauth-0.3.0}/fauth/__init__.py +2 -0
- fauth-0.3.0/fauth/middleware/__init__.py +3 -0
- fauth-0.3.0/fauth/middleware/auth.py +88 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/providers/provider.py +9 -1
- {fauth-0.2.2 → fauth-0.3.0}/pyproject.toml +1 -1
- fauth-0.3.0/tests/middleware/conftest.py +157 -0
- fauth-0.3.0/tests/middleware/test_auth_middleware.py +105 -0
- fauth-0.3.0/tests/testing/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/uv.lock +55 -55
- {fauth-0.2.2 → fauth-0.3.0}/.github/actions/setup-python-env/action.yml +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/.github/dependabot.yml +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/.github/workflows/cicd.yml +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/.github/workflows/pre-commit-autoupdate.yml +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/.gitignore +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/LICENSE +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/README.md +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/api/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/api/router.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/core/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/core/config.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/core/exceptions.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/core/schemas.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/crypto/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/crypto/jwt.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/crypto/password.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/providers/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/providers/protocols.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/testing/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/testing/config.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/testing/fakes.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/testing/provider.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/transports/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/transports/base.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/transports/bearer.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/utils/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/fauth/utils/logging.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/pytest.ini +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/api/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/api/conftest.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/api/test_openapi.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/api/test_router.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/conftest.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/core/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/core/conftest.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/core/test_config.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/core/test_exceptions.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/crypto/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/crypto/conftest.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/crypto/test_jwt.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/crypto/test_password.py +0 -0
- {fauth-0.2.2/tests/providers → fauth-0.3.0/tests/middleware}/__init__.py +0 -0
- {fauth-0.2.2/tests/testing → fauth-0.3.0/tests/providers}/__init__.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/providers/conftest.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/providers/test_provider.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/testing/test_testing.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/utils/conftest.py +0 -0
- {fauth-0.2.2 → fauth-0.3.0}/tests/utils/test_logging.py +0 -0
|
@@ -17,7 +17,7 @@ repos:
|
|
|
17
17
|
- id: gitleaks
|
|
18
18
|
|
|
19
19
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
20
|
-
rev: 'v0.15.
|
|
20
|
+
rev: 'v0.15.10'
|
|
21
21
|
hooks:
|
|
22
22
|
- id: ruff-check
|
|
23
23
|
name: ruff
|
|
@@ -44,7 +44,7 @@ repos:
|
|
|
44
44
|
- fastapi==0.135.1
|
|
45
45
|
|
|
46
46
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
47
|
-
rev: v1.20.
|
|
47
|
+
rev: v1.20.1
|
|
48
48
|
hooks:
|
|
49
49
|
- id: mypy
|
|
50
50
|
additional_dependencies:
|
|
@@ -1,6 +1,85 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.3.0 (2026-04-15)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- Correct typo in pylint disable directive within auth middleware
|
|
9
|
+
([`bc06572`](https://github.com/justmatias/fauth/commit/bc0657237d634e604eaa6be046fbc68fa86a8396))
|
|
10
|
+
|
|
11
|
+
### Chores
|
|
12
|
+
|
|
13
|
+
- Disable pylint complexity checks for auth middleware dispatch method
|
|
14
|
+
([`7c335aa`](https://github.com/justmatias/fauth/commit/7c335aa3165bd5de2234084b6df6a9361f815922))
|
|
15
|
+
|
|
16
|
+
- **config**: Update pre-commit hooks
|
|
17
|
+
([`34ca640`](https://github.com/justmatias/fauth/commit/34ca64099763b3d5faca1c1a2b56673584c4c321))
|
|
18
|
+
|
|
19
|
+
- **config**: Update pre-commit hooks
|
|
20
|
+
([`e6225f9`](https://github.com/justmatias/fauth/commit/e6225f946ef5a3f89c6a15cd490656bdb57f3425))
|
|
21
|
+
|
|
22
|
+
- **deps**: Bump cryptography from 46.0.6 to 46.0.7
|
|
23
|
+
([`c57f70f`](https://github.com/justmatias/fauth/commit/c57f70f21998cb49b83673cc7c47e8c3826dc65c))
|
|
24
|
+
|
|
25
|
+
Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.6 to 46.0.7. -
|
|
26
|
+
[Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) -
|
|
27
|
+
[Commits](https://github.com/pyca/cryptography/compare/46.0.6...46.0.7)
|
|
28
|
+
|
|
29
|
+
--- updated-dependencies: - dependency-name: cryptography dependency-version: 46.0.7
|
|
30
|
+
|
|
31
|
+
dependency-type: indirect ...
|
|
32
|
+
|
|
33
|
+
Signed-off-by: dependabot[bot] <support@github.com>
|
|
34
|
+
|
|
35
|
+
- **deps**: Bump pygments from 2.19.2 to 2.20.0
|
|
36
|
+
([`c022333`](https://github.com/justmatias/fauth/commit/c022333b5f61003c07242810126fec443bd93a91))
|
|
37
|
+
|
|
38
|
+
Bumps [pygments](https://github.com/pygments/pygments) from 2.19.2 to 2.20.0. - [Release
|
|
39
|
+
notes](https://github.com/pygments/pygments/releases) -
|
|
40
|
+
[Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) -
|
|
41
|
+
[Commits](https://github.com/pygments/pygments/compare/2.19.2...2.20.0)
|
|
42
|
+
|
|
43
|
+
--- updated-dependencies: - dependency-name: pygments dependency-version: 2.20.0
|
|
44
|
+
|
|
45
|
+
dependency-type: indirect ...
|
|
46
|
+
|
|
47
|
+
Signed-off-by: dependabot[bot] <support@github.com>
|
|
48
|
+
|
|
49
|
+
- **deps**: Bump pyjwt from 2.11.0 to 2.12.0
|
|
50
|
+
([`0c443a9`](https://github.com/justmatias/fauth/commit/0c443a92b921cea23f1ac71ba837fc335fed183f))
|
|
51
|
+
|
|
52
|
+
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.11.0 to 2.12.0. - [Release
|
|
53
|
+
notes](https://github.com/jpadilla/pyjwt/releases) -
|
|
54
|
+
[Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst) -
|
|
55
|
+
[Commits](https://github.com/jpadilla/pyjwt/compare/2.11.0...2.12.0)
|
|
56
|
+
|
|
57
|
+
--- updated-dependencies: - dependency-name: pyjwt dependency-version: 2.12.0
|
|
58
|
+
|
|
59
|
+
dependency-type: indirect ...
|
|
60
|
+
|
|
61
|
+
Signed-off-by: dependabot[bot] <support@github.com>
|
|
62
|
+
|
|
63
|
+
- **deps-dev**: Bump pytest from 9.0.2 to 9.0.3
|
|
64
|
+
([`f1372ad`](https://github.com/justmatias/fauth/commit/f1372addbf826fe305623d713cb492ee51e6ced8))
|
|
65
|
+
|
|
66
|
+
Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to 9.0.3. - [Release
|
|
67
|
+
notes](https://github.com/pytest-dev/pytest/releases) -
|
|
68
|
+
[Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) -
|
|
69
|
+
[Commits](https://github.com/pytest-dev/pytest/compare/9.0.2...9.0.3)
|
|
70
|
+
|
|
71
|
+
--- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3
|
|
72
|
+
|
|
73
|
+
dependency-type: direct:development ...
|
|
74
|
+
|
|
75
|
+
Signed-off-by: dependabot[bot] <support@github.com>
|
|
76
|
+
|
|
77
|
+
### Features
|
|
78
|
+
|
|
79
|
+
- Implement AuthMiddleware for JWT-based request authentication and user loading
|
|
80
|
+
([`0527038`](https://github.com/justmatias/fauth/commit/0527038d3f5e897fc77258820a26c062d3fc075e))
|
|
81
|
+
|
|
82
|
+
|
|
4
83
|
## v0.2.2 (2026-04-04)
|
|
5
84
|
|
|
6
85
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fauth
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Ergonomic, lightweight JWT authentication for FastAPI. Secure your routes instantly with plug-and-play dependency injection
|
|
5
5
|
Project-URL: Homepage, https://github.com/justmatias/fauth
|
|
6
6
|
Project-URL: Repository, https://github.com/justmatias/fauth
|
|
@@ -18,6 +18,7 @@ from .crypto import (
|
|
|
18
18
|
hash_password,
|
|
19
19
|
verify_password,
|
|
20
20
|
)
|
|
21
|
+
from .middleware import AuthMiddleware
|
|
21
22
|
from .providers import AuthProvider, UserLoader
|
|
22
23
|
from .transports import BearerTransport, Transport
|
|
23
24
|
|
|
@@ -25,6 +26,7 @@ __version__ = version("fauth")
|
|
|
25
26
|
|
|
26
27
|
__all__ = [
|
|
27
28
|
"AuthConfig",
|
|
29
|
+
"AuthMiddleware",
|
|
28
30
|
"AuthProvider",
|
|
29
31
|
"BearerTransport",
|
|
30
32
|
"FAuthError",
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from collections.abc import Awaitable, Callable
|
|
2
|
+
|
|
3
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
4
|
+
from starlette.requests import Request
|
|
5
|
+
from starlette.responses import JSONResponse, Response
|
|
6
|
+
from starlette.types import ASGIApp
|
|
7
|
+
|
|
8
|
+
from fauth.core import AuthConfig, InvalidTokenError, TokenExpiredError, TokenPayload
|
|
9
|
+
from fauth.crypto import decode_token
|
|
10
|
+
from fauth.providers.protocols import UserLoader
|
|
11
|
+
from fauth.transports import BearerTransport, Transport
|
|
12
|
+
from fauth.utils import logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AuthMiddleware(BaseHTTPMiddleware):
|
|
16
|
+
"""
|
|
17
|
+
ASGI middleware that extracts and validates the Bearer token from the
|
|
18
|
+
Authorization header, then stores the loaded user in ``request.state.user``
|
|
19
|
+
and the decoded payload in ``request.state.token_payload``.
|
|
20
|
+
|
|
21
|
+
When ``auto_error=True`` (default), any missing, expired, or invalid token
|
|
22
|
+
produces a 401 JSON response before the route handler is reached. Set
|
|
23
|
+
``auto_error=False`` to let unauthenticated requests pass through so that
|
|
24
|
+
route-level dependencies can decide how to handle them.
|
|
25
|
+
|
|
26
|
+
Use ``exclude_paths`` to bypass middleware enforcement for specific paths
|
|
27
|
+
such as ``/login`` or ``/health``.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
|
31
|
+
self,
|
|
32
|
+
app: ASGIApp,
|
|
33
|
+
config: AuthConfig,
|
|
34
|
+
user_loader: UserLoader,
|
|
35
|
+
transport: Transport | None = None,
|
|
36
|
+
token_payload_schema: type[TokenPayload] = TokenPayload,
|
|
37
|
+
auto_error: bool = True,
|
|
38
|
+
exclude_paths: list[str] | None = None,
|
|
39
|
+
):
|
|
40
|
+
super().__init__(app)
|
|
41
|
+
self.config = config
|
|
42
|
+
self.user_loader = user_loader
|
|
43
|
+
self.transport = transport or BearerTransport()
|
|
44
|
+
self.token_payload_schema = token_payload_schema
|
|
45
|
+
self.auto_error = auto_error
|
|
46
|
+
self.exclude_paths = set(exclude_paths or [])
|
|
47
|
+
|
|
48
|
+
async def dispatch( # pylint: disable=too-complex, too-many-return-statements
|
|
49
|
+
self, request: Request, call_next: Callable[[Request], Awaitable[Response]]
|
|
50
|
+
) -> Response:
|
|
51
|
+
if request.url.path in self.exclude_paths:
|
|
52
|
+
return await call_next(request)
|
|
53
|
+
|
|
54
|
+
token = await self.transport(request)
|
|
55
|
+
request.state.token = token
|
|
56
|
+
|
|
57
|
+
if not token:
|
|
58
|
+
if self.auto_error:
|
|
59
|
+
await logger.error("Authentication failed", reason="Missing token")
|
|
60
|
+
return JSONResponse({"detail": "Not authenticated"}, status_code=401)
|
|
61
|
+
return await call_next(request)
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
payload = decode_token(token, self.config, self.token_payload_schema)
|
|
65
|
+
except TokenExpiredError:
|
|
66
|
+
if self.auto_error:
|
|
67
|
+
await logger.warning("Authentication failed", reason="Token expired")
|
|
68
|
+
return JSONResponse({"detail": "Token expired"}, status_code=401)
|
|
69
|
+
return await call_next(request)
|
|
70
|
+
except InvalidTokenError:
|
|
71
|
+
if self.auto_error:
|
|
72
|
+
await logger.warning("Authentication failed", reason="Invalid token")
|
|
73
|
+
return JSONResponse({"detail": "Invalid token"}, status_code=401)
|
|
74
|
+
return await call_next(request)
|
|
75
|
+
|
|
76
|
+
user = await self.user_loader(payload)
|
|
77
|
+
if not user:
|
|
78
|
+
if self.auto_error:
|
|
79
|
+
await logger.warning(
|
|
80
|
+
"Authentication failed", reason="User not found", sub=payload.sub
|
|
81
|
+
)
|
|
82
|
+
return JSONResponse({"detail": "User does not exist"}, status_code=401)
|
|
83
|
+
return await call_next(request)
|
|
84
|
+
|
|
85
|
+
request.state.user = user
|
|
86
|
+
request.state.token_payload = payload
|
|
87
|
+
await logger.debug("User authenticated via middleware", sub=payload.sub)
|
|
88
|
+
return await call_next(request)
|
|
@@ -51,7 +51,15 @@ class AuthProvider(Generic[T]):
|
|
|
51
51
|
self.transport = transport
|
|
52
52
|
|
|
53
53
|
async def require_user(self, request: Request) -> T:
|
|
54
|
-
"""Utility to get the currently authenticated user.
|
|
54
|
+
"""Utility to get the currently authenticated user.
|
|
55
|
+
|
|
56
|
+
If AuthMiddleware is registered, reads directly from request.state (fast path).
|
|
57
|
+
Otherwise extracts and validates the Bearer token from the Authorization header.
|
|
58
|
+
"""
|
|
59
|
+
user: T | None = getattr(request.state, "user", None)
|
|
60
|
+
if user:
|
|
61
|
+
return user
|
|
62
|
+
|
|
55
63
|
token = await self.transport(request)
|
|
56
64
|
if not token:
|
|
57
65
|
await logger.error("Authentication failed", reason="Missing token")
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# pylint: disable=duplicate-code
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any
|
|
4
|
+
from uuid import UUID, uuid4
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from fastapi import Depends, FastAPI, Request
|
|
8
|
+
from fastapi.testclient import TestClient
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from fauth.core import AuthConfig
|
|
12
|
+
from fauth.crypto import create_access_token, hash_password
|
|
13
|
+
from fauth.middleware import AuthMiddleware
|
|
14
|
+
from fauth.providers import AuthProvider
|
|
15
|
+
from fauth.testing import FakeUserLoader
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DummyUser(BaseModel):
|
|
19
|
+
id_: UUID = Field(default_factory=uuid4)
|
|
20
|
+
hashed_password: str = Field(default=hash_password("secret_password"))
|
|
21
|
+
is_active: bool = Field(default=True)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def user() -> DummyUser:
|
|
26
|
+
return DummyUser()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture
|
|
30
|
+
def user_loader() -> FakeUserLoader[DummyUser]:
|
|
31
|
+
return FakeUserLoader()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def provider(
|
|
36
|
+
auth_config: AuthConfig,
|
|
37
|
+
user_loader: FakeUserLoader[DummyUser],
|
|
38
|
+
) -> AuthProvider[DummyUser]:
|
|
39
|
+
return AuthProvider(config=auth_config, user_loader=user_loader)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def app(
|
|
44
|
+
auth_config: AuthConfig,
|
|
45
|
+
user_loader: FakeUserLoader[DummyUser],
|
|
46
|
+
) -> FastAPI:
|
|
47
|
+
_app = FastAPI()
|
|
48
|
+
_app.add_middleware(
|
|
49
|
+
AuthMiddleware,
|
|
50
|
+
config=auth_config,
|
|
51
|
+
user_loader=user_loader,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@_app.get("/protected")
|
|
55
|
+
async def protected(request: Request) -> dict[str, Any]:
|
|
56
|
+
return {"id": str(request.state.user.id_)}
|
|
57
|
+
|
|
58
|
+
@_app.get("/public")
|
|
59
|
+
async def public() -> dict[str, Any]:
|
|
60
|
+
return {"msg": "ok"}
|
|
61
|
+
|
|
62
|
+
return _app
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.fixture
|
|
66
|
+
def app_auto_error_off(
|
|
67
|
+
auth_config: AuthConfig,
|
|
68
|
+
user_loader: FakeUserLoader[DummyUser],
|
|
69
|
+
) -> FastAPI:
|
|
70
|
+
_app = FastAPI()
|
|
71
|
+
_app.add_middleware(
|
|
72
|
+
AuthMiddleware,
|
|
73
|
+
config=auth_config,
|
|
74
|
+
user_loader=user_loader,
|
|
75
|
+
auto_error=False,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@_app.get("/maybe-protected")
|
|
79
|
+
async def maybe_protected(request: Request) -> dict[str, Any]:
|
|
80
|
+
user = getattr(request.state, "user", None)
|
|
81
|
+
token = getattr(request.state, "token", None)
|
|
82
|
+
return {"authenticated": user is not None, "has_token": token is not None}
|
|
83
|
+
|
|
84
|
+
return _app
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture
|
|
88
|
+
def app_with_excludes(
|
|
89
|
+
auth_config: AuthConfig,
|
|
90
|
+
user_loader: FakeUserLoader[DummyUser],
|
|
91
|
+
) -> FastAPI:
|
|
92
|
+
_app = FastAPI()
|
|
93
|
+
_app.add_middleware(
|
|
94
|
+
AuthMiddleware,
|
|
95
|
+
config=auth_config,
|
|
96
|
+
user_loader=user_loader,
|
|
97
|
+
exclude_paths=["/health"],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@_app.get("/health")
|
|
101
|
+
async def health() -> dict[str, Any]:
|
|
102
|
+
return {"status": "ok"}
|
|
103
|
+
|
|
104
|
+
@_app.get("/protected")
|
|
105
|
+
async def protected(request: Request) -> dict[str, Any]:
|
|
106
|
+
return {"id": str(request.state.user.id_)}
|
|
107
|
+
|
|
108
|
+
return _app
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.fixture
|
|
112
|
+
def app_with_require_user(
|
|
113
|
+
auth_config: AuthConfig,
|
|
114
|
+
user_loader: FakeUserLoader[DummyUser],
|
|
115
|
+
provider: AuthProvider[DummyUser],
|
|
116
|
+
) -> FastAPI:
|
|
117
|
+
_app = FastAPI()
|
|
118
|
+
_app.add_middleware(
|
|
119
|
+
AuthMiddleware,
|
|
120
|
+
config=auth_config,
|
|
121
|
+
user_loader=user_loader,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@_app.get("/me")
|
|
125
|
+
async def me(
|
|
126
|
+
current_user: DummyUser = Depends(provider.require_user),
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
return {"id": str(current_user.id_)}
|
|
129
|
+
|
|
130
|
+
return _app
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@pytest.fixture
|
|
134
|
+
def client(app: FastAPI) -> TestClient:
|
|
135
|
+
return TestClient(app)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@pytest.fixture
|
|
139
|
+
def user_token(auth_config: AuthConfig, user: DummyUser) -> str:
|
|
140
|
+
return create_access_token(sub=str(user.id_), config=auth_config)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@pytest.fixture
|
|
144
|
+
def expired_token(auth_config: AuthConfig) -> str:
|
|
145
|
+
expired_config = AuthConfig(
|
|
146
|
+
secret_key=auth_config.secret_key,
|
|
147
|
+
algorithm=auth_config.algorithm,
|
|
148
|
+
access_token_expire_minutes=0,
|
|
149
|
+
)
|
|
150
|
+
token = create_access_token(sub="any-user", config=expired_config)
|
|
151
|
+
time.sleep(1)
|
|
152
|
+
return token
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.fixture
|
|
156
|
+
def _populate_user(user_loader: FakeUserLoader[DummyUser], user: DummyUser) -> None:
|
|
157
|
+
user_loader.add_user(str(user.id_), user)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from fastapi import FastAPI
|
|
3
|
+
from fastapi.testclient import TestClient
|
|
4
|
+
|
|
5
|
+
from .conftest import DummyUser
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_missing_token_returns_401(client: TestClient) -> None:
|
|
9
|
+
response = client.get("/protected")
|
|
10
|
+
assert response.status_code == 401
|
|
11
|
+
assert response.json()["detail"] == "Not authenticated"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_malformed_token_returns_401(client: TestClient) -> None:
|
|
15
|
+
response = client.get(
|
|
16
|
+
"/protected", headers={"Authorization": "Bearer not-a-valid-jwt"}
|
|
17
|
+
)
|
|
18
|
+
assert response.status_code == 401
|
|
19
|
+
assert response.json()["detail"] == "Invalid token"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_expired_token_returns_401(client: TestClient, expired_token: str) -> None:
|
|
23
|
+
response = client.get(
|
|
24
|
+
"/protected", headers={"Authorization": f"Bearer {expired_token}"}
|
|
25
|
+
)
|
|
26
|
+
assert response.status_code == 401
|
|
27
|
+
assert response.json()["detail"] == "Token expired"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.usefixtures("_populate_user")
|
|
31
|
+
def test_valid_token_populates_state(
|
|
32
|
+
client: TestClient, user: DummyUser, user_token: str
|
|
33
|
+
) -> None:
|
|
34
|
+
response = client.get(
|
|
35
|
+
"/protected", headers={"Authorization": f"Bearer {user_token}"}
|
|
36
|
+
)
|
|
37
|
+
assert response.status_code == 200
|
|
38
|
+
assert response.json()["id"] == str(user.id_)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_unknown_user_returns_401(client: TestClient, user_token: str) -> None:
|
|
42
|
+
# user_loader is empty — token is valid but user does not exist
|
|
43
|
+
response = client.get(
|
|
44
|
+
"/protected", headers={"Authorization": f"Bearer {user_token}"}
|
|
45
|
+
)
|
|
46
|
+
assert response.status_code == 401
|
|
47
|
+
assert response.json()["detail"] == "User does not exist"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_auto_error_off_passes_unauthenticated_request(
|
|
51
|
+
app_auto_error_off: FastAPI,
|
|
52
|
+
) -> None:
|
|
53
|
+
client = TestClient(app_auto_error_off)
|
|
54
|
+
response = client.get("/maybe-protected")
|
|
55
|
+
assert response.status_code == 200
|
|
56
|
+
assert response.json()["authenticated"] is False
|
|
57
|
+
assert response.json()["has_token"] is False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.usefixtures("_populate_user")
|
|
61
|
+
def test_auto_error_off_populates_state_when_token_valid(
|
|
62
|
+
app_auto_error_off: FastAPI, user_token: str
|
|
63
|
+
) -> None:
|
|
64
|
+
client = TestClient(app_auto_error_off)
|
|
65
|
+
response = client.get(
|
|
66
|
+
"/maybe-protected", headers={"Authorization": f"Bearer {user_token}"}
|
|
67
|
+
)
|
|
68
|
+
assert response.status_code == 200
|
|
69
|
+
assert response.json()["authenticated"] is True
|
|
70
|
+
assert response.json()["has_token"] is True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_raw_token_stored_on_state_even_when_decode_fails(
|
|
74
|
+
app_auto_error_off: FastAPI,
|
|
75
|
+
) -> None:
|
|
76
|
+
client = TestClient(app_auto_error_off)
|
|
77
|
+
response = client.get(
|
|
78
|
+
"/maybe-protected", headers={"Authorization": "Bearer not-a-valid-jwt"}
|
|
79
|
+
)
|
|
80
|
+
assert response.status_code == 200
|
|
81
|
+
assert response.json()["authenticated"] is False
|
|
82
|
+
assert response.json()["has_token"] is True
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_excluded_path_bypasses_enforcement(app_with_excludes: FastAPI) -> None:
|
|
86
|
+
client = TestClient(app_with_excludes)
|
|
87
|
+
response = client.get("/health")
|
|
88
|
+
assert response.status_code == 200
|
|
89
|
+
assert response.json()["status"] == "ok"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_non_excluded_path_still_enforced(app_with_excludes: FastAPI) -> None:
|
|
93
|
+
client = TestClient(app_with_excludes)
|
|
94
|
+
response = client.get("/protected")
|
|
95
|
+
assert response.status_code == 401
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.usefixtures("_populate_user")
|
|
99
|
+
def test_require_user_reads_from_state_when_middleware_set(
|
|
100
|
+
app_with_require_user: FastAPI, user: DummyUser, user_token: str
|
|
101
|
+
) -> None:
|
|
102
|
+
client = TestClient(app_with_require_user)
|
|
103
|
+
response = client.get("/me", headers={"Authorization": f"Bearer {user_token}"})
|
|
104
|
+
assert response.status_code == 200
|
|
105
|
+
assert response.json()["id"] == str(user.id_)
|
|
File without changes
|
|
@@ -250,55 +250,55 @@ wheels = [
|
|
|
250
250
|
|
|
251
251
|
[[package]]
|
|
252
252
|
name = "cryptography"
|
|
253
|
-
version = "46.0.
|
|
253
|
+
version = "46.0.7"
|
|
254
254
|
source = { registry = "https://pypi.org/simple" }
|
|
255
255
|
dependencies = [
|
|
256
256
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
|
257
257
|
]
|
|
258
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
259
|
-
wheels = [
|
|
260
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
261
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
262
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
263
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
264
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
265
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
266
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
267
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
268
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
269
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
270
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
271
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
272
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
273
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
274
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
275
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
276
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
277
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
278
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
279
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
280
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
281
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
282
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
283
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
284
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
285
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
286
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
287
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
288
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
289
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
290
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
291
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
292
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
293
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
294
|
-
{ url = "https://files.pythonhosted.org/packages/01/
|
|
295
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
296
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
297
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
298
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
299
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
300
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
301
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
258
|
+
sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
|
|
259
|
+
wheels = [
|
|
260
|
+
{ url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
|
|
261
|
+
{ url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
|
|
262
|
+
{ url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
|
|
263
|
+
{ url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
|
|
264
|
+
{ url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
|
|
265
|
+
{ url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
|
|
266
|
+
{ url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
|
|
267
|
+
{ url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
|
|
268
|
+
{ url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
|
|
269
|
+
{ url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
|
|
270
|
+
{ url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
|
|
271
|
+
{ url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
|
|
272
|
+
{ url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
|
|
273
|
+
{ url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
|
|
274
|
+
{ url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" },
|
|
275
|
+
{ url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" },
|
|
276
|
+
{ url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" },
|
|
277
|
+
{ url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" },
|
|
278
|
+
{ url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" },
|
|
279
|
+
{ url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" },
|
|
280
|
+
{ url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" },
|
|
281
|
+
{ url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" },
|
|
282
|
+
{ url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" },
|
|
283
|
+
{ url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" },
|
|
284
|
+
{ url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" },
|
|
285
|
+
{ url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" },
|
|
286
|
+
{ url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" },
|
|
287
|
+
{ url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" },
|
|
288
|
+
{ url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
|
|
289
|
+
{ url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
|
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
|
|
291
|
+
{ url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
|
|
292
|
+
{ url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
|
|
293
|
+
{ url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
|
|
294
|
+
{ url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
|
|
295
|
+
{ url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
|
|
296
|
+
{ url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
|
|
297
|
+
{ url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
|
|
298
|
+
{ url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
|
|
299
|
+
{ url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
|
|
300
|
+
{ url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
|
|
301
|
+
{ url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
|
|
302
302
|
]
|
|
303
303
|
|
|
304
304
|
[[package]]
|
|
@@ -340,7 +340,7 @@ wheels = [
|
|
|
340
340
|
|
|
341
341
|
[[package]]
|
|
342
342
|
name = "fauth"
|
|
343
|
-
version = "0.2.
|
|
343
|
+
version = "0.2.2"
|
|
344
344
|
source = { editable = "." }
|
|
345
345
|
dependencies = [
|
|
346
346
|
{ name = "fastapi" },
|
|
@@ -670,20 +670,20 @@ wheels = [
|
|
|
670
670
|
|
|
671
671
|
[[package]]
|
|
672
672
|
name = "pygments"
|
|
673
|
-
version = "2.
|
|
673
|
+
version = "2.20.0"
|
|
674
674
|
source = { registry = "https://pypi.org/simple" }
|
|
675
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
675
|
+
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
|
|
676
676
|
wheels = [
|
|
677
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
677
|
+
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
|
678
678
|
]
|
|
679
679
|
|
|
680
680
|
[[package]]
|
|
681
681
|
name = "pyjwt"
|
|
682
|
-
version = "2.
|
|
682
|
+
version = "2.12.0"
|
|
683
683
|
source = { registry = "https://pypi.org/simple" }
|
|
684
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
684
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a8/10/e8192be5f38f3e8e7e046716de4cae33d56fd5ae08927a823bb916be36c1/pyjwt-2.12.0.tar.gz", hash = "sha256:2f62390b667cd8257de560b850bb5a883102a388829274147f1d724453f8fb02", size = 102511, upload-time = "2026-03-12T17:15:30.831Z" }
|
|
685
685
|
wheels = [
|
|
686
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
686
|
+
{ url = "https://files.pythonhosted.org/packages/15/70/70f895f404d363d291dcf62c12c85fdd47619ad9674ac0f53364d035925a/pyjwt-2.12.0-py3-none-any.whl", hash = "sha256:9bb459d1bdd0387967d287f5656bf7ec2b9a26645d1961628cda1764e087fd6e", size = 29700, upload-time = "2026-03-12T17:15:29.257Z" },
|
|
687
687
|
]
|
|
688
688
|
|
|
689
689
|
[package.optional-dependencies]
|
|
@@ -693,7 +693,7 @@ crypto = [
|
|
|
693
693
|
|
|
694
694
|
[[package]]
|
|
695
695
|
name = "pytest"
|
|
696
|
-
version = "9.0.
|
|
696
|
+
version = "9.0.3"
|
|
697
697
|
source = { registry = "https://pypi.org/simple" }
|
|
698
698
|
dependencies = [
|
|
699
699
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
@@ -702,9 +702,9 @@ dependencies = [
|
|
|
702
702
|
{ name = "pluggy" },
|
|
703
703
|
{ name = "pygments" },
|
|
704
704
|
]
|
|
705
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
705
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
|
|
706
706
|
wheels = [
|
|
707
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
707
|
+
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
|
|
708
708
|
]
|
|
709
709
|
|
|
710
710
|
[[package]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|