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.
Files changed (31) hide show
  1. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/PKG-INFO +6 -6
  2. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/README.md +3 -3
  3. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/pyproject.toml +4 -4
  4. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/pyproject.toml.orig +4 -6
  5. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/__init__.py +1 -1
  6. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/_http.py +21 -11
  7. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/abc.py +233 -91
  8. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/introspection.py +18 -64
  9. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/jwt.py +95 -34
  10. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/client.py +77 -22
  11. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/id_token.py +5 -2
  12. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/utils.py +7 -3
  13. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/basic.py +2 -0
  14. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/bearer.py +22 -7
  15. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/cookie.py +28 -24
  16. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/dpop.py +19 -11
  17. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/header.py +2 -0
  18. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/multi.py +142 -18
  19. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/signature.py +30 -14
  20. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/surface.py +101 -54
  21. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/totp.py +11 -9
  22. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/utils.py +187 -5
  23. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/LICENSE +0 -0
  24. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/_imports.py +0 -0
  25. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/amr.py +0 -0
  26. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/exceptions.py +0 -0
  27. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/oauth/__init__.py +0 -0
  28. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/py.typed +0 -0
  29. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/__init__.py +0 -0
  30. {fastapi_multiauth-1.0.0rc2 → fastapi_multiauth-1.2.0}/src/fastapi_multiauth/sources/oauth2.py +0 -0
  31. {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.0rc2
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 :: 4 - Beta
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 `csrf=True` to refuse cross-origin requests.
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 `csrf=True` to refuse cross-origin requests.
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.0rc2"
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 :: 4 - Beta",
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.0rc2"
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 :: 4 - Beta",
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"]
@@ -43,7 +43,7 @@ from .utils import (
43
43
  verify_token_hash,
44
44
  )
45
45
 
46
- __version__ = "1.0.0rc2"
46
+ __version__ = "1.2.0"
47
47
 
48
48
  logging.getLogger(__name__).addHandler(logging.NullHandler())
49
49
 
@@ -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 parsed.scheme == "http" and parsed.hostname in _LOOPBACK_HOSTS:
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
- return resp.json()
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 json.JSONDecodeError as exc:
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,