fastapi-multiauth 1.0.0rc2__tar.gz → 1.2.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.
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/PKG-INFO +6 -6
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/README.md +3 -3
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/pyproject.toml +4 -4
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/pyproject.toml.orig +4 -6
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/__init__.py +1 -1
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/_http.py +21 -11
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/abc.py +233 -91
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/introspection.py +18 -64
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/jwt.py +95 -34
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/client.py +77 -22
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/id_token.py +5 -2
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/utils.py +7 -3
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/basic.py +2 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/bearer.py +22 -7
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/cookie.py +28 -24
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/dpop.py +19 -11
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/header.py +2 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/multi.py +142 -18
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/signature.py +30 -14
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/surface.py +101 -54
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/totp.py +11 -9
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/utils.py +187 -5
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/LICENSE +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/_imports.py +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/amr.py +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/exceptions.py +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/__init__.py +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/py.typed +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/__init__.py +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/oauth2.py +0 -0
- {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/query.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-multiauth
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes, correct OpenAPI by construction
|
|
5
5
|
Keywords: fastapi,authentication,security,oauth,api-key,bearer,cookie
|
|
6
6
|
Author: d3vyce
|
|
7
7
|
Author-email: d3vyce <contact@d3vyce.fr>
|
|
8
8
|
License-Expression: MIT
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
11
|
Classifier: Framework :: AsyncIO
|
|
12
12
|
Classifier: Framework :: FastAPI
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -18,13 +18,13 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.15
|
|
21
|
+
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
21
22
|
Classifier: Topic :: Internet :: WWW/HTTP :: Session
|
|
22
23
|
Classifier: Topic :: Security
|
|
23
24
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
25
|
Classifier: Topic :: Software Development :: Libraries
|
|
25
26
|
Classifier: Typing :: Typed
|
|
26
27
|
Requires-Dist: anyio>=3.7
|
|
27
|
-
Requires-Dist: async-lru>=2.0
|
|
28
28
|
Requires-Dist: fastapi>=0.123.0,!=0.137.0,!=0.137.1
|
|
29
29
|
Requires-Dist: httpx>=0.25.0
|
|
30
30
|
Requires-Dist: itsdangerous>=2.1
|
|
@@ -123,13 +123,13 @@ Set `SECRET_KEY` to at least 32 random bytes, for example the output of `python
|
|
|
123
123
|
|
|
124
124
|
- **Every standard scheme**: bearer tokens with Stripe-style prefixes, signed cookie sessions, API keys, HTTP Basic, OAuth 2.0 and OpenID Connect.
|
|
125
125
|
- **`MultiAuth`**: several schemes on one route, all documented in OpenAPI, plus `optional()` for routes that also serve anonymous callers.
|
|
126
|
-
- **Stateless sessions**: HMAC-signed cookies with expiry, key rotation, opt-in revocable session ids, and
|
|
127
|
-
- **Tokens done right**: secure generation, hash-only storage, checksums, `JWTValidator` for provider JWTs (JWKS, rotation, `aud`/`iss`), `IntrospectionValidator` for opaque ones, and `HTTPDPoPAuth` for tokens bound to the client's key.
|
|
126
|
+
- **Stateless sessions**: HMAC-signed cookies with expiry, key rotation, opt-in revocable session ids, and cross-origin requests refused by default.
|
|
127
|
+
- **Tokens done right**: secure generation, hash-only storage, checksums, `JWTValidator` for provider JWTs (JWKS, rotation, `aud`/`iss`/`typ`), `IntrospectionValidator` for opaque ones, and `HTTPDPoPAuth` for tokens bound to the client's key.
|
|
128
128
|
- **Signed webhooks**: `HMACSignatureAuth` checks the sender's signature over the raw body, with timestamp tolerance and secret rotation.
|
|
129
129
|
- **Scopes and step-up**: fail-closed scope checks, and `fresh()` / `stepped_up()` with RFC 9470 challenges for sensitive routes.
|
|
130
130
|
- **Second factor**: TOTP helpers that refuse a replayed code, and the login level signed into the session cookie.
|
|
131
131
|
- **OAuth login helpers**: discovery, CSRF `state`, PKCE, code exchange and ID token validation.
|
|
132
|
-
- **Observable**: reason-coded security logs and `auth_surface(app)` to audit what guards every route.
|
|
132
|
+
- **Observable**: reason-coded security logs and `auth_surface(app)` to audit what guards every route, `dependency_overrides` included.
|
|
133
133
|
|
|
134
134
|
See the [comparison](https://fastapi-multiauth.d3vyce.fr/guide/comparison/) for how it sits next to `fastapi.security`, fastapi-users, AuthX and Authlib, and the [security model](https://fastapi-multiauth.d3vyce.fr/security-model/) for what it guarantees.
|
|
135
135
|
|
|
@@ -82,13 +82,13 @@ Set `SECRET_KEY` to at least 32 random bytes, for example the output of `python
|
|
|
82
82
|
|
|
83
83
|
- **Every standard scheme**: bearer tokens with Stripe-style prefixes, signed cookie sessions, API keys, HTTP Basic, OAuth 2.0 and OpenID Connect.
|
|
84
84
|
- **`MultiAuth`**: several schemes on one route, all documented in OpenAPI, plus `optional()` for routes that also serve anonymous callers.
|
|
85
|
-
- **Stateless sessions**: HMAC-signed cookies with expiry, key rotation, opt-in revocable session ids, and
|
|
86
|
-
- **Tokens done right**: secure generation, hash-only storage, checksums, `JWTValidator` for provider JWTs (JWKS, rotation, `aud`/`iss`), `IntrospectionValidator` for opaque ones, and `HTTPDPoPAuth` for tokens bound to the client's key.
|
|
85
|
+
- **Stateless sessions**: HMAC-signed cookies with expiry, key rotation, opt-in revocable session ids, and cross-origin requests refused by default.
|
|
86
|
+
- **Tokens done right**: secure generation, hash-only storage, checksums, `JWTValidator` for provider JWTs (JWKS, rotation, `aud`/`iss`/`typ`), `IntrospectionValidator` for opaque ones, and `HTTPDPoPAuth` for tokens bound to the client's key.
|
|
87
87
|
- **Signed webhooks**: `HMACSignatureAuth` checks the sender's signature over the raw body, with timestamp tolerance and secret rotation.
|
|
88
88
|
- **Scopes and step-up**: fail-closed scope checks, and `fresh()` / `stepped_up()` with RFC 9470 challenges for sensitive routes.
|
|
89
89
|
- **Second factor**: TOTP helpers that refuse a replayed code, and the login level signed into the session cookie.
|
|
90
90
|
- **OAuth login helpers**: discovery, CSRF `state`, PKCE, code exchange and ID token validation.
|
|
91
|
-
- **Observable**: reason-coded security logs and `auth_surface(app)` to audit what guards every route.
|
|
91
|
+
- **Observable**: reason-coded security logs and `auth_surface(app)` to audit what guards every route, `dependency_overrides` included.
|
|
92
92
|
|
|
93
93
|
See the [comparison](https://fastapi-multiauth.d3vyce.fr/guide/comparison/) for how it sits next to `fastapi.security`, fastapi-users, AuthX and Authlib, and the [security model](https://fastapi-multiauth.d3vyce.fr/security-model/) for what it guarantees.
|
|
94
94
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-multiauth"
|
|
3
|
-
version = "1.0
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes, correct OpenAPI by construction"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -16,7 +16,7 @@ keywords = [
|
|
|
16
16
|
"cookie",
|
|
17
17
|
]
|
|
18
18
|
classifiers = [
|
|
19
|
-
"Development Status ::
|
|
19
|
+
"Development Status :: 5 - Production/Stable",
|
|
20
20
|
"Framework :: AsyncIO",
|
|
21
21
|
"Framework :: FastAPI",
|
|
22
22
|
"Intended Audience :: Developers",
|
|
@@ -27,6 +27,7 @@ classifiers = [
|
|
|
27
27
|
"Programming Language :: Python :: 3.13",
|
|
28
28
|
"Programming Language :: Python :: 3.14",
|
|
29
29
|
"Programming Language :: Python :: 3.15",
|
|
30
|
+
"Programming Language :: Python :: Free Threading :: 3 - Stable",
|
|
30
31
|
"Topic :: Internet :: WWW/HTTP :: Session",
|
|
31
32
|
"Topic :: Security",
|
|
32
33
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -35,7 +36,6 @@ classifiers = [
|
|
|
35
36
|
]
|
|
36
37
|
dependencies = [
|
|
37
38
|
"anyio>=3.7",
|
|
38
|
-
"async-lru>=2.0",
|
|
39
39
|
"fastapi>=0.123.0,!=0.137.0,!=0.137.1",
|
|
40
40
|
"httpx>=0.25.0",
|
|
41
41
|
"itsdangerous>=2.1",
|
|
@@ -66,6 +66,7 @@ dev = [
|
|
|
66
66
|
]
|
|
67
67
|
tests = [
|
|
68
68
|
"coverage>=7.0.0",
|
|
69
|
+
"httpx2>=2.16",
|
|
69
70
|
"pyotp>=2.10.0",
|
|
70
71
|
"pytest-anyio>=0.0.0",
|
|
71
72
|
"pytest-codspeed>=5.0",
|
|
@@ -120,7 +121,6 @@ convention = "google"
|
|
|
120
121
|
testpaths = ["tests"]
|
|
121
122
|
pythonpath = ["."]
|
|
122
123
|
anyio_mode = "auto"
|
|
123
|
-
filterwarnings = ["ignore::DeprecationWarning"]
|
|
124
124
|
|
|
125
125
|
[tool.coverage.run]
|
|
126
126
|
source = ["src/fastapi_multiauth"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fastapi-multiauth"
|
|
3
|
-
version = "1.0
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "Composable authentication sources for FastAPI: bring your own validator, combine multiple schemes, correct OpenAPI by construction"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -11,7 +11,7 @@ authors = [
|
|
|
11
11
|
]
|
|
12
12
|
keywords = ["fastapi", "authentication", "security", "oauth", "api-key", "bearer", "cookie"]
|
|
13
13
|
classifiers = [
|
|
14
|
-
"Development Status ::
|
|
14
|
+
"Development Status :: 5 - Production/Stable",
|
|
15
15
|
"Framework :: AsyncIO",
|
|
16
16
|
"Framework :: FastAPI",
|
|
17
17
|
"Intended Audience :: Developers",
|
|
@@ -22,6 +22,7 @@ classifiers = [
|
|
|
22
22
|
"Programming Language :: Python :: 3.13",
|
|
23
23
|
"Programming Language :: Python :: 3.14",
|
|
24
24
|
"Programming Language :: Python :: 3.15",
|
|
25
|
+
"Programming Language :: Python :: Free Threading :: 3 - Stable",
|
|
25
26
|
"Topic :: Internet :: WWW/HTTP :: Session",
|
|
26
27
|
"Topic :: Security",
|
|
27
28
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -30,7 +31,6 @@ classifiers = [
|
|
|
30
31
|
]
|
|
31
32
|
dependencies = [
|
|
32
33
|
"anyio>=3.7",
|
|
33
|
-
"async-lru>=2.0",
|
|
34
34
|
"fastapi>=0.123.0,!=0.137.0,!=0.137.1",
|
|
35
35
|
"httpx>=0.25.0",
|
|
36
36
|
"itsdangerous>=2.1",
|
|
@@ -61,6 +61,7 @@ dev = [
|
|
|
61
61
|
]
|
|
62
62
|
tests = [
|
|
63
63
|
"coverage>=7.0.0",
|
|
64
|
+
"httpx2>=2.16",
|
|
64
65
|
"pyotp>=2.10.0",
|
|
65
66
|
"pytest-anyio>=0.0.0",
|
|
66
67
|
"pytest-codspeed>=5.0",
|
|
@@ -97,9 +98,6 @@ convention = "google"
|
|
|
97
98
|
testpaths = ["tests"]
|
|
98
99
|
pythonpath = ["."]
|
|
99
100
|
anyio_mode = "auto"
|
|
100
|
-
filterwarnings = [
|
|
101
|
-
"ignore::DeprecationWarning",
|
|
102
|
-
]
|
|
103
101
|
|
|
104
102
|
[tool.coverage.run]
|
|
105
103
|
source = ["src/fastapi_multiauth"]
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Shared HTTP transport hygiene for the OAuth and JWT modules."""
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
-
import json
|
|
5
4
|
from typing import Any
|
|
6
5
|
from urllib.parse import urlsplit
|
|
7
6
|
|
|
@@ -13,14 +12,22 @@ DEFAULT_TIMEOUT = 10.0
|
|
|
13
12
|
"""Default timeout (seconds) applied to every HTTP call made by this library."""
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
def _require_https(url: str, description: str) -> str:
|
|
17
|
-
"""Reject a URL that is not a string, or would send credentials over HTTP.
|
|
15
|
+
def _require_https(url: str, description: str, *, allow_loopback: bool = True) -> str:
|
|
16
|
+
"""Reject a URL that is not a string, or would send credentials over HTTP.
|
|
17
|
+
|
|
18
|
+
Plain HTTP passes for a loopback host, for local development, unless
|
|
19
|
+
`allow_loopback` is off.
|
|
20
|
+
"""
|
|
18
21
|
if not isinstance(url, str):
|
|
19
22
|
raise ValueError(f"{description} must be a string URL (got {url!r})") # noqa: TRY004
|
|
20
23
|
parsed = urlsplit(url)
|
|
21
|
-
if parsed.scheme == "https":
|
|
24
|
+
if parsed.scheme == "https" and parsed.hostname:
|
|
22
25
|
return url
|
|
23
|
-
if
|
|
26
|
+
if (
|
|
27
|
+
allow_loopback
|
|
28
|
+
and parsed.scheme == "http"
|
|
29
|
+
and parsed.hostname in _LOOPBACK_HOSTS
|
|
30
|
+
):
|
|
24
31
|
return url
|
|
25
32
|
raise ValueError(f"{description} must use https:// (got {url!r})")
|
|
26
33
|
|
|
@@ -35,8 +42,8 @@ async def _request_json(
|
|
|
35
42
|
headers: dict[str, str] | None = None,
|
|
36
43
|
data: dict[str, str] | None = None,
|
|
37
44
|
client: httpx.AsyncClient | None = None,
|
|
38
|
-
) -> Any:
|
|
39
|
-
"""Send a request and decode the JSON body, mapping failures to `error_cls`."""
|
|
45
|
+
) -> dict[str, Any]:
|
|
46
|
+
"""Send a request and decode the JSON object body, mapping failures to `error_cls`."""
|
|
40
47
|
async with (
|
|
41
48
|
contextlib.nullcontext(client)
|
|
42
49
|
if client is not None
|
|
@@ -45,11 +52,14 @@ async def _request_json(
|
|
|
45
52
|
try:
|
|
46
53
|
resp = await http.request(method, url, headers=headers, data=data)
|
|
47
54
|
resp.raise_for_status()
|
|
48
|
-
|
|
55
|
+
body = resp.json()
|
|
49
56
|
except httpx.HTTPError as exc:
|
|
50
57
|
raise error_cls(f"failed to fetch {description}: {exc}") from exc
|
|
51
|
-
except
|
|
58
|
+
except (ValueError, RecursionError) as exc:
|
|
52
59
|
raise error_cls(f"{description} is not valid JSON") from exc
|
|
60
|
+
if not isinstance(body, dict):
|
|
61
|
+
raise error_cls(f"{description} is not a JSON object")
|
|
62
|
+
return body
|
|
53
63
|
|
|
54
64
|
|
|
55
65
|
async def _get_json(
|
|
@@ -60,8 +70,8 @@ async def _get_json(
|
|
|
60
70
|
description: str,
|
|
61
71
|
headers: dict[str, str] | None = None,
|
|
62
72
|
client: httpx.AsyncClient | None = None,
|
|
63
|
-
) -> Any:
|
|
64
|
-
"""GET `url` and decode the JSON body, mapping failures to `error_cls`."""
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
"""GET `url` and decode the JSON object body, mapping failures to `error_cls`."""
|
|
65
75
|
return await _request_json(
|
|
66
76
|
"GET",
|
|
67
77
|
url,
|