usso 0.27.22__tar.gz → 0.28.1__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 (48) hide show
  1. usso-0.28.1/MANIFEST.in +1 -0
  2. usso-0.28.1/PKG-INFO +172 -0
  3. usso-0.28.1/README.md +124 -0
  4. {usso-0.27.22 → usso-0.28.1}/pyproject.toml +37 -12
  5. usso-0.28.1/pytest.ini +22 -0
  6. usso-0.28.1/src/usso/__init__.py +25 -0
  7. usso-0.28.1/src/usso/auth/__init__.py +9 -0
  8. usso-0.28.1/src/usso/auth/api_key.py +43 -0
  9. usso-0.28.1/src/usso/auth/client.py +87 -0
  10. usso-0.28.1/src/usso/auth/config.py +115 -0
  11. {usso-0.27.22 → usso-0.28.1}/src/usso/exceptions.py +13 -0
  12. usso-0.28.1/src/usso/integrations/django/__init__.py +3 -0
  13. {usso-0.27.22/src/usso → usso-0.28.1/src/usso/integrations}/django/middleware.py +37 -29
  14. usso-0.28.1/src/usso/integrations/fastapi/__init__.py +8 -0
  15. usso-0.28.1/src/usso/integrations/fastapi/dependency.py +80 -0
  16. usso-0.28.1/src/usso/integrations/fastapi/handler.py +16 -0
  17. usso-0.28.1/src/usso/models/user.py +119 -0
  18. {usso-0.27.22 → usso-0.28.1}/src/usso/session/async_session.py +12 -8
  19. usso-0.28.1/src/usso/session/base_session.py +77 -0
  20. usso-0.28.1/src/usso/session/session.py +54 -0
  21. usso-0.28.1/src/usso/utils/method_utils.py +12 -0
  22. usso-0.28.1/src/usso/utils/string_utils.py +7 -0
  23. usso-0.28.1/src/usso.egg-info/PKG-INFO +172 -0
  24. usso-0.28.1/src/usso.egg-info/SOURCES.txt +31 -0
  25. {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/requires.txt +2 -5
  26. usso-0.28.1/tests/test_fastapi.py +102 -0
  27. usso-0.27.22/PKG-INFO +0 -110
  28. usso-0.27.22/README.md +0 -41
  29. usso-0.27.22/src/usso/__init__.py +0 -4
  30. usso-0.27.22/src/usso/b64tools.py +0 -20
  31. usso-0.27.22/src/usso/client/__init__.py +0 -4
  32. usso-0.27.22/src/usso/client/api.py +0 -174
  33. usso-0.27.22/src/usso/client/async_api.py +0 -159
  34. usso-0.27.22/src/usso/core.py +0 -160
  35. usso-0.27.22/src/usso/fastapi/__init__.py +0 -7
  36. usso-0.27.22/src/usso/fastapi/integration.py +0 -88
  37. usso-0.27.22/src/usso/schemas.py +0 -67
  38. usso-0.27.22/src/usso/session/base_session.py +0 -96
  39. usso-0.27.22/src/usso/session/session.py +0 -80
  40. usso-0.27.22/src/usso.egg-info/PKG-INFO +0 -110
  41. usso-0.27.22/src/usso.egg-info/SOURCES.txt +0 -25
  42. {usso-0.27.22 → usso-0.28.1}/LICENSE.txt +0 -0
  43. {usso-0.27.22 → usso-0.28.1}/setup.cfg +0 -0
  44. {usso-0.27.22 → usso-0.28.1}/src/usso/session/__init__.py +0 -0
  45. {usso-0.27.22/src/usso/django → usso-0.28.1/src/usso/utils}/__init__.py +0 -0
  46. {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/dependency_links.txt +0 -0
  47. {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/entry_points.txt +0 -0
  48. {usso-0.27.22 → usso-0.28.1}/src/usso.egg-info/top_level.txt +0 -0
@@ -0,0 +1 @@
1
+ include pytest.ini
usso-0.28.1/PKG-INFO ADDED
@@ -0,0 +1,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: usso
3
+ Version: 0.28.1
4
+ Summary: A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices.
5
+ Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
+ Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/ussoio/usso-python
9
+ Project-URL: Bug Reports, https://github.com/ussoio/usso-python/issues
10
+ Project-URL: Funding, https://github.com/ussoio/usso-python
11
+ Project-URL: Say Thanks!, https://saythanks.io/to/mahdikiani
12
+ Project-URL: Source, https://github.com/ussoio/usso-python
13
+ Keywords: usso,sso,authentication,security,fastapi,django
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Topic :: Software Development :: Build Tools
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE.txt
25
+ Requires-Dist: pydantic>=2
26
+ Requires-Dist: cryptography>=43.0.0
27
+ Requires-Dist: cachetools
28
+ Requires-Dist: singleton_package
29
+ Requires-Dist: json-advanced
30
+ Requires-Dist: httpx
31
+ Requires-Dist: usso-jwt>=0.1.0
32
+ Provides-Extra: fastapi
33
+ Requires-Dist: fastapi>=0.65.0; extra == "fastapi"
34
+ Requires-Dist: uvicorn[standard]>=0.13.0; extra == "fastapi"
35
+ Provides-Extra: django
36
+ Requires-Dist: Django>=3.2; extra == "django"
37
+ Provides-Extra: dev
38
+ Requires-Dist: check-manifest; extra == "dev"
39
+ Provides-Extra: test
40
+ Requires-Dist: coverage; extra == "test"
41
+ Provides-Extra: all
42
+ Requires-Dist: fastapi; extra == "all"
43
+ Requires-Dist: uvicorn; extra == "all"
44
+ Requires-Dist: django; extra == "all"
45
+ Requires-Dist: dev; extra == "all"
46
+ Requires-Dist: test; extra == "all"
47
+ Dynamic: license-file
48
+
49
+ # 🛡️ USSO Python Client SDK
50
+
51
+ The **USSO Python Client SDK** (`usso`) provides a universal, secure JWT authentication layer for Python microservices and web frameworks.
52
+ It’s designed to integrate seamlessly with the [USSO Identity Platform](https://github.com/ussoio/usso) — or any standards-compliant token issuer.
53
+
54
+ ---
55
+
56
+ ## 🔗 Relationship to the USSO Platform
57
+
58
+ This SDK is the official verification client for the **USSO** identity service, which provides multi-tenant authentication, RBAC, token flows, and more.
59
+ You can use the SDK with:
60
+ - Self-hosted USSO via Docker
61
+ - Any identity provider that issues signed JWTs (with proper config)
62
+
63
+ ---
64
+
65
+ ## ✨ Features
66
+
67
+ - ✅ **Token verification** for EdDSA, RS256, HS256, and more
68
+ - ✅ **Claim validation** (`exp`, `nbf`, `aud`, `iss`)
69
+ - ✅ **Remote JWK support** for key rotation
70
+ - ✅ **Typed payload parsing** via `UserData` (Pydantic)
71
+ - ✅ **Token extraction** from:
72
+ - `Authorization` header
73
+ - Cookies
74
+ - Custom headers
75
+ - ✅ **FastAPI integration** with dependency injection
76
+ - ✅ **Django middleware** for request-based user resolution
77
+ - 🧪 90% tested with `pytest` and `tox`
78
+
79
+ ---
80
+
81
+ ## 📦 Installation
82
+
83
+ ```bash
84
+ pip install usso
85
+ ````
86
+
87
+ With framework extras:
88
+
89
+ ```bash
90
+ pip install "usso[fastapi]" # for FastAPI integration
91
+ pip install "usso[django]" # for Django integration
92
+ ```
93
+
94
+ ---
95
+
96
+ ## 🚀 Quick Start (FastAPI)
97
+
98
+ ```python
99
+ from usso.fastapi.integration import get_authenticator
100
+ from usso.schemas import JWTConfig, JWTHeaderConfig, UserData
101
+ from usso.jwt.enums import Algorithm
102
+
103
+ config = JWTConfig(
104
+ key="your-ed25519-public-key",
105
+ issuer="https://sso.example.com",
106
+ audience="api.example.com",
107
+ type=Algorithm.EdDSA,
108
+ header=JWTHeaderConfig(type="Authorization")
109
+ )
110
+
111
+ authenticator = get_authenticator(config)
112
+
113
+ @app.get("/me")
114
+ def get_me(user: UserData = Depends(authenticator)):
115
+ return {"user_id": user.sub, "roles": user.roles}
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 🧱 Project Structure
121
+
122
+ ```
123
+ src/usso/
124
+ ├── fastapi/ # FastAPI adapter
125
+ ├── django/ # Django middleware
126
+ ├── jwt/ # Core JWT logic and algorithms
127
+ ├── session/ # Stateless session support
128
+ ├── models/ # JWTConfig, UserData, etc.
129
+ ├── exceptions/ # Shared exceptions
130
+ ├── authenticator.py # High-level API (token + user resolution)
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 🐳 Integrate with USSO (Docker)
136
+
137
+ Run your own identity provider:
138
+
139
+ ```bash
140
+ docker run -p 8000:8000 ghcr.io/ussoio/usso:latest
141
+ ```
142
+
143
+ Then configure your app to verify tokens issued by this service, using its public JWKS endpoint:
144
+
145
+ ```python
146
+ JWTConfig(
147
+ jwk_url="http://localhost:8000/.well-known/jwks.json",
148
+ ...
149
+ )
150
+ ```
151
+
152
+ ---
153
+
154
+ ## 🧪 Testing
155
+
156
+ ```bash
157
+ pytest
158
+ tox
159
+ ```
160
+
161
+ ---
162
+
163
+ ## 🤝 Contributing
164
+
165
+ We welcome contributions!
166
+
167
+ ---
168
+
169
+ ## 📝 License
170
+
171
+ MIT License © \[mahdikiani]
172
+
usso-0.28.1/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # 🛡️ USSO Python Client SDK
2
+
3
+ The **USSO Python Client SDK** (`usso`) provides a universal, secure JWT authentication layer for Python microservices and web frameworks.
4
+ It’s designed to integrate seamlessly with the [USSO Identity Platform](https://github.com/ussoio/usso) — or any standards-compliant token issuer.
5
+
6
+ ---
7
+
8
+ ## 🔗 Relationship to the USSO Platform
9
+
10
+ This SDK is the official verification client for the **USSO** identity service, which provides multi-tenant authentication, RBAC, token flows, and more.
11
+ You can use the SDK with:
12
+ - Self-hosted USSO via Docker
13
+ - Any identity provider that issues signed JWTs (with proper config)
14
+
15
+ ---
16
+
17
+ ## ✨ Features
18
+
19
+ - ✅ **Token verification** for EdDSA, RS256, HS256, and more
20
+ - ✅ **Claim validation** (`exp`, `nbf`, `aud`, `iss`)
21
+ - ✅ **Remote JWK support** for key rotation
22
+ - ✅ **Typed payload parsing** via `UserData` (Pydantic)
23
+ - ✅ **Token extraction** from:
24
+ - `Authorization` header
25
+ - Cookies
26
+ - Custom headers
27
+ - ✅ **FastAPI integration** with dependency injection
28
+ - ✅ **Django middleware** for request-based user resolution
29
+ - 🧪 90% tested with `pytest` and `tox`
30
+
31
+ ---
32
+
33
+ ## 📦 Installation
34
+
35
+ ```bash
36
+ pip install usso
37
+ ````
38
+
39
+ With framework extras:
40
+
41
+ ```bash
42
+ pip install "usso[fastapi]" # for FastAPI integration
43
+ pip install "usso[django]" # for Django integration
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 🚀 Quick Start (FastAPI)
49
+
50
+ ```python
51
+ from usso.fastapi.integration import get_authenticator
52
+ from usso.schemas import JWTConfig, JWTHeaderConfig, UserData
53
+ from usso.jwt.enums import Algorithm
54
+
55
+ config = JWTConfig(
56
+ key="your-ed25519-public-key",
57
+ issuer="https://sso.example.com",
58
+ audience="api.example.com",
59
+ type=Algorithm.EdDSA,
60
+ header=JWTHeaderConfig(type="Authorization")
61
+ )
62
+
63
+ authenticator = get_authenticator(config)
64
+
65
+ @app.get("/me")
66
+ def get_me(user: UserData = Depends(authenticator)):
67
+ return {"user_id": user.sub, "roles": user.roles}
68
+ ```
69
+
70
+ ---
71
+
72
+ ## 🧱 Project Structure
73
+
74
+ ```
75
+ src/usso/
76
+ ├── fastapi/ # FastAPI adapter
77
+ ├── django/ # Django middleware
78
+ ├── jwt/ # Core JWT logic and algorithms
79
+ ├── session/ # Stateless session support
80
+ ├── models/ # JWTConfig, UserData, etc.
81
+ ├── exceptions/ # Shared exceptions
82
+ ├── authenticator.py # High-level API (token + user resolution)
83
+ ```
84
+
85
+ ---
86
+
87
+ ## 🐳 Integrate with USSO (Docker)
88
+
89
+ Run your own identity provider:
90
+
91
+ ```bash
92
+ docker run -p 8000:8000 ghcr.io/ussoio/usso:latest
93
+ ```
94
+
95
+ Then configure your app to verify tokens issued by this service, using its public JWKS endpoint:
96
+
97
+ ```python
98
+ JWTConfig(
99
+ jwk_url="http://localhost:8000/.well-known/jwks.json",
100
+ ...
101
+ )
102
+ ```
103
+
104
+ ---
105
+
106
+ ## 🧪 Testing
107
+
108
+ ```bash
109
+ pytest
110
+ tox
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🤝 Contributing
116
+
117
+ We welcome contributions!
118
+
119
+ ---
120
+
121
+ ## 📝 License
122
+
123
+ MIT License © \[mahdikiani]
124
+
@@ -4,23 +4,19 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "usso"
7
- version = "0.27.22"
7
+ version = "0.28.1"
8
8
  description = "A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
- license = {file = "LICENSE.txt"}
11
+ license = "MIT"
12
+ license-files = ["LICENSE.txt"]
12
13
  keywords = ["usso", "sso", "authentication", "security", "fastapi", "django"]
13
- authors = [
14
- {name = "Mahdi Kiani", email = "mahdikiany@gmail.com"}
15
- ]
16
- maintainers = [
17
- {name = "Mahdi Kiani", email = "mahdikiany@gmail.com"}
18
- ]
14
+ authors = [{ name = "Mahdi Kiani", email = "mahdikiany@gmail.com" }]
15
+ maintainers = [{ name = "Mahdi Kiani", email = "mahdikiany@gmail.com" }]
19
16
  classifiers = [
20
17
  "Development Status :: 3 - Alpha",
21
18
  "Intended Audience :: Developers",
22
19
  "Topic :: Software Development :: Build Tools",
23
- "License :: OSI Approved :: MIT License",
24
20
  "Programming Language :: Python :: 3",
25
21
  "Programming Language :: Python :: 3.10",
26
22
  "Programming Language :: Python :: 3.11",
@@ -29,13 +25,29 @@ classifiers = [
29
25
  ]
30
26
  dependencies = [
31
27
  "pydantic>=2",
32
- "pyjwt[crypto]",
28
+ "cryptography>=43.0.0",
33
29
  "cachetools",
34
30
  "singleton_package",
35
31
  "json-advanced",
36
32
  "httpx",
33
+ "usso-jwt>=0.1.0",
37
34
  ]
38
- optional-dependencies = {"fastapi"=["fastapi>=0.65.0", "uvicorn[standard]>=0.13.0"], "django"=["Django>=3.2"], "httpx"=["httpx"], "dev"=["check-manifest"], "test" = ["coverage"], "all"=["fastapi", "uvicorn", "django", "httpx", "dev", "test"]}
35
+ optional-dependencies = { "fastapi" = [
36
+ "fastapi>=0.65.0",
37
+ "uvicorn[standard]>=0.13.0",
38
+ ], "django" = [
39
+ "Django>=3.2",
40
+ ], "dev" = [
41
+ "check-manifest",
42
+ ], "test" = [
43
+ "coverage",
44
+ ], "all" = [
45
+ "fastapi",
46
+ "uvicorn",
47
+ "django",
48
+ "dev",
49
+ "test",
50
+ ] }
39
51
 
40
52
  [project.urls]
41
53
  "Homepage" = "https://github.com/ussoio/usso-python"
@@ -48,4 +60,17 @@ optional-dependencies = {"fastapi"=["fastapi>=0.65.0", "uvicorn[standard]>=0.13.
48
60
  usso = "usso:main"
49
61
 
50
62
  [tool.setuptools]
51
- package-data = {"usso" = ["*.dat"]}
63
+ package-data = { "usso_jwt" = ["*.dat"] }
64
+
65
+ [tool.ruff]
66
+ line-length = 79
67
+ target-version = "py313"
68
+ fix = true
69
+ unsafe-fixes = true
70
+ preview = true
71
+
72
+ [tool.ruff.lint]
73
+ select = ["E", "F", "W", "I", "UP", "B"]
74
+
75
+ [tool.ruff.format]
76
+ quote-style = "double"
usso-0.28.1/pytest.ini ADDED
@@ -0,0 +1,22 @@
1
+ [pytest]
2
+ pythonpath = .
3
+ testpaths = tests
4
+
5
+ asyncio_mode = auto
6
+ asyncio_default_fixture_loop_scope = session
7
+
8
+
9
+ addopts =
10
+ --cov=src/usso/integrations/fastapi
11
+ --cov=src/usso/auth
12
+ --cov=src/usso/models
13
+ ; --cov=src/usso/session
14
+ --cov=src/usso/utils
15
+ ; --cov=src/usso
16
+ --cov-report=term-missing
17
+ --cov-report=html
18
+ --cov-fail-under=75
19
+
20
+ filterwarnings =
21
+ ignore:.*pkg_resources.*:DeprecationWarning
22
+
@@ -0,0 +1,25 @@
1
+ """USSO - Universal Single Sign-On Client
2
+
3
+ A plug-and-play client for integrating universal single sign-on (SSO)
4
+ with Python frameworks, enabling secure and seamless authentication
5
+ across microservices.
6
+ """
7
+
8
+ from .auth import APIHeaderConfig, AuthConfig, HeaderConfig, UssoAuth
9
+ from .exceptions import USSOException
10
+ from .models.user import UserData
11
+
12
+ __version__ = "0.28.0"
13
+
14
+ __all__ = [
15
+ # Main client
16
+ "UssoAuth",
17
+ # Configuration
18
+ "AuthConfig",
19
+ "HeaderConfig",
20
+ "APIHeaderConfig",
21
+ # Models
22
+ "UserData",
23
+ # Exceptions
24
+ "USSOException",
25
+ ]
@@ -0,0 +1,9 @@
1
+ """USSO Authentication Module.
2
+
3
+ This module provides the core authentication functionality for USSO.
4
+ """
5
+
6
+ from .client import UssoAuth
7
+ from .config import APIHeaderConfig, AuthConfig, HeaderConfig
8
+
9
+ __all__ = ["UssoAuth", "AuthConfig", "HeaderConfig", "APIHeaderConfig"]
@@ -0,0 +1,43 @@
1
+ import logging
2
+ from urllib.parse import urlparse
3
+
4
+ import cachetools.func
5
+ import httpx
6
+
7
+ from ..exceptions import USSOException
8
+ from ..models.user import UserData
9
+
10
+ logger = logging.getLogger("usso")
11
+
12
+
13
+ def _handle_exception(error_type: str, **kwargs):
14
+ """Handle API key related exceptions."""
15
+ if kwargs.get("raise_exception", True):
16
+ raise USSOException(
17
+ status_code=401, error=error_type, message=kwargs.get("message")
18
+ )
19
+ logger.error(kwargs.get("message") or error_type)
20
+
21
+
22
+ @cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
23
+ def fetch_api_key_data(jwk_url: str, api_key: str):
24
+ """Fetch user data using an API key.
25
+
26
+ Args:
27
+ jwk_url: The JWK URL to use for verification
28
+ api_key: The API key to verify
29
+
30
+ Returns:
31
+ UserData: The user data associated with the API key
32
+
33
+ Raises:
34
+ USSOException: If the API key is invalid or verification fails
35
+ """
36
+ try:
37
+ parsed = urlparse(jwk_url)
38
+ url = f"{parsed.scheme}://{parsed.netloc}/api_key/verify"
39
+ response = httpx.post(url, json={"api_key": api_key})
40
+ response.raise_for_status()
41
+ return UserData(**response.json())
42
+ except Exception as e:
43
+ _handle_exception("error", message=str(e))
@@ -0,0 +1,87 @@
1
+ import logging
2
+
3
+ import usso_jwt.exceptions
4
+ import usso_jwt.schemas
5
+
6
+ from ..exceptions import _handle_exception
7
+ from ..models.user import UserData
8
+ from .api_key import fetch_api_key_data
9
+ from .config import AuthConfig, AvailableJwtConfigs
10
+
11
+ logger = logging.getLogger("usso")
12
+
13
+
14
+ class UssoAuth:
15
+ """Main authentication client for USSO.
16
+
17
+ This client handles token validation, user data retrieval,
18
+ and API key verification.
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ *,
24
+ jwt_config: AvailableJwtConfigs | None = None,
25
+ ):
26
+ """Initialize the USSO authentication client.
27
+
28
+ Args:
29
+ jwt_config: JWT configuration(s) to use for token validation
30
+ """
31
+ if jwt_config is None:
32
+ jwt_config = AuthConfig()
33
+ self.jwt_configs = AuthConfig.validate_jwt_configs(jwt_config)
34
+
35
+ def user_data_from_token(
36
+ self,
37
+ token: str,
38
+ *,
39
+ expected_acr: str | None = "access",
40
+ raise_exception: bool = True,
41
+ **kwargs,
42
+ ) -> UserData | None:
43
+ """Get user data from a JWT token.
44
+
45
+ Args:
46
+ token: The JWT token to validate
47
+ expected_acr: Expected authentication context reference
48
+ raise_exception: Whether to raise exception on error
49
+ **kwargs: Additional arguments to pass to token verification
50
+
51
+ Returns:
52
+ UserData if token is valid, None otherwise
53
+
54
+ Raises:
55
+ USSOException: If token is invalid and raise_exception is True
56
+ """
57
+ exp = None
58
+ for jwk_config in self.jwt_configs:
59
+ try:
60
+ jwt_obj = usso_jwt.schemas.JWT(
61
+ token=token, config=jwk_config, payload_class=UserData
62
+ )
63
+ if jwt_obj.verify(expected_acr=expected_acr, **kwargs):
64
+ return jwt_obj.payload
65
+ except usso_jwt.exceptions.JWTError as e:
66
+ exp = e
67
+
68
+ _handle_exception(
69
+ "Unauthorized",
70
+ message=str(exp) if exp else None,
71
+ raise_exception=raise_exception,
72
+ **kwargs,
73
+ )
74
+
75
+ def user_data_from_api_key(self, api_key: str) -> UserData:
76
+ """Get user data from an API key.
77
+
78
+ Args:
79
+ api_key: The API key to verify
80
+
81
+ Returns:
82
+ UserData: The user data associated with the API key
83
+
84
+ Raises:
85
+ USSOException: If the API key is invalid
86
+ """
87
+ return fetch_api_key_data(self.jwt_configs[0].jwk_url, api_key)
@@ -0,0 +1,115 @@
1
+ import json
2
+ from typing import Any, Literal, Union
3
+
4
+ import usso_jwt.config
5
+ from pydantic import BaseModel, model_validator
6
+
7
+ from ..models.user import UserData
8
+ from ..utils.string_utils import get_authorization_scheme_param
9
+
10
+
11
+ class HeaderConfig(BaseModel):
12
+ type: Literal["Authorization", "Cookie", "CustomHeader"] = "Cookie"
13
+ name: str = "usso_access_token"
14
+
15
+ @model_validator(mode="before")
16
+ def validate_header(cls, data: dict):
17
+ if data.get("type") == "Authorization" and not data.get("name"):
18
+ data["name"] = "Bearer"
19
+ elif data.get("type") == "Cookie":
20
+ data["name"] = data.get("name", "usso_access_token")
21
+ elif data.get("type") == "CustomHeader":
22
+ data["name"] = data.get("name", "x-usso-access-token")
23
+ return data
24
+
25
+ def __hash__(self):
26
+ return hash(self.model_dump_json())
27
+
28
+ def get_key(self, request) -> str | None:
29
+ headers: dict[str, Any] = getattr(request, "headers", {})
30
+ cookies: dict[str, str] = getattr(
31
+ request, "cookies", headers.get("Cookie", {})
32
+ )
33
+ if self.type == "CustomHeader":
34
+ return headers.get(self.name)
35
+ elif self.type == "Cookie":
36
+ return cookies.get(self.name)
37
+ elif self.type == "Authorization":
38
+ authorization = headers.get("Authorization")
39
+ if self.type == "Authorization":
40
+ authorization = headers.get("Authorization")
41
+ scheme, credentials = get_authorization_scheme_param(authorization)
42
+ if scheme.lower() == self.name.lower():
43
+ return credentials
44
+
45
+
46
+ class APIHeaderConfig(HeaderConfig):
47
+ verify_endpoint: str = "https://sso.usso.io/api_key/verify"
48
+
49
+
50
+ class AuthConfig(usso_jwt.config.JWTConfig):
51
+ """Configuration for JWT processing."""
52
+
53
+ api_key_header: APIHeaderConfig | None = APIHeaderConfig(
54
+ type="CustomHeader", name="x-api-key"
55
+ )
56
+ jwt_header: HeaderConfig | None = HeaderConfig()
57
+ static_api_keys: list[str] | None = None
58
+
59
+ def get_api_key(self, request) -> str | None:
60
+ if self.api_key_header:
61
+ return self.api_key_header.get_key(request)
62
+ return None
63
+
64
+ def get_jwt(self, request) -> str | None:
65
+ if self.jwt_header:
66
+ return self.jwt_header.get_key(request)
67
+ return None
68
+
69
+ def verify_token(
70
+ self, token: str, *, raise_exception: bool = True, **kwargs
71
+ ) -> bool:
72
+ from usso_jwt import exceptions as jwt_exceptions
73
+ from usso_jwt import schemas
74
+
75
+ try:
76
+ return schemas.JWT(
77
+ token=token,
78
+ config=self,
79
+ payload_class=UserData,
80
+ ).verify(**kwargs)
81
+ except jwt_exceptions.JWTError as e:
82
+ if raise_exception:
83
+ raise e
84
+ return False
85
+
86
+ @classmethod
87
+ def _parse_config(
88
+ cls, config: Union[str, dict, "AuthConfig"]
89
+ ) -> "AuthConfig":
90
+ """Parse a single JWT configuration."""
91
+ if isinstance(config, str):
92
+ config = json.loads(config)
93
+ if isinstance(config, dict):
94
+ return cls(**config)
95
+ if isinstance(config, cls):
96
+ return config
97
+ raise ValueError("Invalid JWT configuration")
98
+
99
+ @classmethod
100
+ def validate_jwt_configs(
101
+ cls,
102
+ jwt_config: Union[
103
+ str, dict, "AuthConfig", list[str], list[dict], list["AuthConfig"]
104
+ ],
105
+ ) -> list["AuthConfig"]:
106
+ if isinstance(jwt_config, (str, dict, cls)):
107
+ return [cls._parse_config(jwt_config)]
108
+ if isinstance(jwt_config, list):
109
+ return [cls._parse_config(config) for config in jwt_config]
110
+ raise ValueError("Invalid jwt_config format")
111
+
112
+
113
+ AvailableJwtConfigs = (
114
+ str | dict | AuthConfig | list[str] | list[dict] | list[AuthConfig]
115
+ )