usso 0.26.1__py3-none-any.whl → 0.27.1__py3-none-any.whl

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.
File without changes
usso/core.py CHANGED
@@ -1,53 +1,21 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- import uuid
5
- from functools import lru_cache
4
+ from datetime import datetime, timedelta
5
+ from urllib.parse import urlparse
6
6
 
7
7
  import cachetools.func
8
8
  import jwt
9
+ import requests
10
+ from cachetools import TTLCache, cached
9
11
  from pydantic import BaseModel, model_validator
10
12
 
11
- from . import b64tools
12
13
  from .exceptions import USSOException
14
+ from .schemas import UserData
13
15
 
14
16
  logger = logging.getLogger("usso")
15
17
 
16
18
 
17
- class UserData(BaseModel):
18
- user_id: str
19
- workspace_id: str | None = None
20
- workspace_ids: list[str] = []
21
- token_type: str = "access"
22
-
23
- email: str | None = None
24
- phone: str | None = None
25
- username: str | None = None
26
-
27
- authentication_method: str | None = None
28
- is_active: bool = False
29
-
30
- jti: str | None = None
31
- data: dict | None = None
32
-
33
- token: str | None = None
34
-
35
- @property
36
- def uid(self) -> uuid.UUID:
37
- user_id = self.user_id
38
-
39
- if user_id.startswith("u_"):
40
- user_id = user_id[2:]
41
- if 22 <= len(user_id) <= 24:
42
- user_id = b64tools.b64_decode_uuid(user_id)
43
-
44
- return uuid.UUID(user_id)
45
-
46
- @property
47
- def b64id(self) -> uuid.UUID:
48
- return b64tools.b64_encode_uuid_strip(self.uid)
49
-
50
-
51
19
  def get_authorization_scheme_param(
52
20
  authorization_header_value: str | None,
53
21
  ) -> tuple[str, str]:
@@ -90,7 +58,15 @@ def decode_token(key, token: str, algorithms=["RS256"], **kwargs) -> dict:
90
58
  logger.error(e)
91
59
 
92
60
 
93
- @lru_cache
61
+ def is_expired(token: str, **kwargs) -> bool:
62
+ now = datetime.now()
63
+ decoded_token: dict = jwt.decode(token, options={"verify_signature": False})
64
+ exp = decoded_token.get("exp", (now + timedelta(days=1)).timestamp())
65
+ exp = datetime.fromtimestamp(exp)
66
+ return exp >= now
67
+
68
+
69
+ @cached(TTLCache(maxsize=128, ttl=10 * 60))
94
70
  def get_jwk_keys(jwk_url: str) -> jwt.PyJWKClient:
95
71
  return jwt.PyJWKClient(jwk_url, headers={"User-Agent": "usso-python"})
96
72
 
@@ -115,6 +91,15 @@ def decode_token_jwk(jwk_url: str, token: str, **kwargs) -> UserData | None:
115
91
  logger.error(e)
116
92
 
117
93
 
94
+ @cached(TTLCache(maxsize=128, ttl=10 * 60))
95
+ def get_api_key_data(jwk_url: str, api_key: str):
96
+ parsed = urlparse(jwk_url)
97
+ url = f"{parsed.scheme}://{parsed.netloc}/api_key/verify"
98
+ response = requests.post(url, json={"api_key": api_key})
99
+ response.raise_for_status()
100
+ return UserData(**response.json())
101
+
102
+
118
103
  class JWTConfig(BaseModel):
119
104
  jwk_url: str | None = None
120
105
  secret: str | None = None
@@ -147,7 +132,9 @@ class Usso:
147
132
  def __init__(
148
133
  self,
149
134
  *,
150
- jwt_config: str | dict | JWTConfig | list[str] | list[dict] | list[JWTConfig] | None = None,
135
+ jwt_config: (
136
+ str | dict | JWTConfig | list[str] | list[dict] | list[JWTConfig] | None
137
+ ) = None,
151
138
  jwk_url: str | None = None,
152
139
  secret: str | None = None,
153
140
  ):
@@ -160,7 +147,7 @@ class Usso:
160
147
  if jwk_url:
161
148
  self.jwt_configs = [JWTConfig(jwk_url=jwk_url)]
162
149
  return
163
-
150
+
164
151
  if not secret:
165
152
  secret = os.getenv("USSO_SECRET")
166
153
  if secret:
@@ -216,3 +203,31 @@ class Usso:
216
203
  status_code=401,
217
204
  error="unauthorized",
218
205
  )
206
+
207
+ def user_data_api_key(self, api_key: str, **kwargs) -> UserData | None:
208
+ """get user data from auth server by api_key."""
209
+ for jwk_config in self.jwt_configs:
210
+ try:
211
+ user_data = jwk_config.decode(api_key)
212
+ if user_data.token_type.lower() != kwargs.get("token_type", "access"):
213
+ raise USSOException(
214
+ status_code=401,
215
+ error="invalid_token_type",
216
+ message="Token type must be 'access'",
217
+ )
218
+
219
+ return user_data
220
+
221
+ except USSOException as e:
222
+ exp = e
223
+
224
+ if kwargs.get("raise_exception", True):
225
+ if exp:
226
+ raise exp
227
+ raise USSOException(
228
+ status_code=401,
229
+ error="unauthorized",
230
+ )
231
+
232
+ def user_data_from_api_key(self, api_key: str):
233
+ return get_api_key_data(self.jwt_configs[0].jwk_url, api_key)
usso/django/middleware.py CHANGED
@@ -6,7 +6,6 @@ from django.db.utils import IntegrityError
6
6
  from django.http import JsonResponse
7
7
  from django.http.request import HttpRequest
8
8
  from django.utils.deprecation import MiddlewareMixin
9
-
10
9
  from usso import UserData, Usso, USSOException
11
10
 
12
11
  logger = logging.getLogger("usso")
@@ -1,8 +1,8 @@
1
1
  import logging
2
2
 
3
- from fastapi import Request, WebSocket
4
3
  from starlette.status import HTTP_401_UNAUTHORIZED
5
4
 
5
+ from fastapi import Request, WebSocket
6
6
  from usso.exceptions import USSOException
7
7
 
8
8
  from ..core import UserData, Usso, get_authorization_scheme_param
@@ -27,16 +27,26 @@ def get_request_token(request: Request | WebSocket) -> UserData | None:
27
27
  return token
28
28
 
29
29
 
30
- def jwt_access_security_None(request: Request, jwt_config = None) -> UserData | None:
30
+ def jwt_access_security_None(request: Request, jwt_config=None) -> UserData | None:
31
31
  """Return the user associated with a token value."""
32
+ api_key = request.headers.get("x-api-key")
33
+ if api_key:
34
+ return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
35
+
32
36
  token = get_request_token(request)
33
37
  if not token:
34
38
  return None
35
- return Usso(jwt_config=jwt_config).user_data_from_token(token, raise_exception=False)
39
+ return Usso(jwt_config=jwt_config).user_data_from_token(
40
+ token, raise_exception=False
41
+ )
36
42
 
37
43
 
38
44
  def jwt_access_security(request: Request, jwt_config=None) -> UserData | None:
39
45
  """Return the user associated with a token value."""
46
+ api_key = request.headers.get("x-api-key")
47
+ if api_key:
48
+ return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
49
+
40
50
  token = get_request_token(request)
41
51
  if not token:
42
52
  raise USSOException(
@@ -50,6 +60,10 @@ def jwt_access_security(request: Request, jwt_config=None) -> UserData | None:
50
60
 
51
61
  def jwt_access_security_ws(websocket: WebSocket, jwt_config=None) -> UserData | None:
52
62
  """Return the user associated with a token value."""
63
+ api_key = websocket.headers.get("x-api-key")
64
+ if api_key:
65
+ return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
66
+
53
67
  token = get_request_token(websocket)
54
68
  if not token:
55
69
  raise USSOException(
usso/schemas.py ADDED
@@ -0,0 +1,39 @@
1
+ import uuid
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from . import b64tools
6
+
7
+
8
+ class UserData(BaseModel):
9
+ user_id: str
10
+ workspace_id: str | None = None
11
+ workspace_ids: list[str] = []
12
+ token_type: str = "access"
13
+
14
+ email: str | None = None
15
+ phone: str | None = None
16
+ username: str | None = None
17
+
18
+ authentication_method: str | None = None
19
+ is_active: bool = False
20
+
21
+ jti: str | None = None
22
+ data: dict | None = None
23
+
24
+ token: str | None = None
25
+
26
+ @property
27
+ def uid(self) -> uuid.UUID:
28
+ user_id = self.user_id
29
+
30
+ if user_id.startswith("u_"):
31
+ user_id = user_id[2:]
32
+ if 22 <= len(user_id) <= 24:
33
+ user_id = b64tools.b64_decode_uuid(user_id)
34
+
35
+ return uuid.UUID(user_id)
36
+
37
+ @property
38
+ def b64id(self) -> uuid.UUID:
39
+ return b64tools.b64_encode_uuid_strip(self.uid)
File without changes
@@ -0,0 +1,65 @@
1
+ import httpx
2
+
3
+ from ..core import is_expired
4
+ from .session import BaseUssoSession
5
+
6
+
7
+ class AsyncUssoSession(httpx.AsyncClient, BaseUssoSession):
8
+ def __init__(
9
+ self,
10
+ usso_base_url: str | None = None,
11
+ api_key: str | None = None,
12
+ usso_refresh_url: str | None = None,
13
+ refresh_token: str | None = None,
14
+ usso_api_key: str | None = None,
15
+ user_id: str | None = None,
16
+ ):
17
+ BaseUssoSession.__init__(
18
+ self,
19
+ usso_base_url,
20
+ api_key,
21
+ usso_refresh_url,
22
+ refresh_token,
23
+ usso_api_key,
24
+ user_id,
25
+ )
26
+ httpx.AsyncClient.__init__(self, headers=self.headers)
27
+
28
+ async def _refresh_api(self):
29
+ params = {"user_id": self.user_id} if self.user_id else {}
30
+ response = await self.get(
31
+ f"{self.usso_refresh_url}/api",
32
+ headers={"x-api-key": self.usso_api_key},
33
+ params=params,
34
+ )
35
+ response.raise_for_status()
36
+ data: dict = response.json()
37
+ self._refresh_token = data.get("token", {}).get("refresh_token")
38
+
39
+ async def _refresh(self):
40
+ assert (
41
+ self.refresh_token or self.usso_api_key
42
+ ), "refresh_token or usso_api_key is required"
43
+
44
+ if self.usso_api_key and not self.refresh_token:
45
+ await self._refresh_api()
46
+
47
+ response = await self.post(
48
+ self.usso_refresh_url, json={"refresh_token": f"{self.refresh_token}"}
49
+ )
50
+ response.raise_for_status()
51
+ self.access_token = response.json().get("access_token")
52
+ self.headers.update({"Authorization": f"Bearer {self.access_token}"})
53
+ return response.json()
54
+
55
+ async def get_session(self):
56
+ if self.api_key:
57
+ return self
58
+
59
+ if not self.access_token or is_expired(self.access_token):
60
+ await self._refresh()
61
+ return self
62
+
63
+ async def _request(self, method: str, url: str, **kwargs):
64
+ session = await self.get_session()
65
+ return await session.request(method, url, **kwargs)
@@ -0,0 +1,118 @@
1
+ from urllib.parse import urlparse
2
+
3
+ import requests
4
+ from singleton import Singleton
5
+
6
+ from ..core import is_expired
7
+
8
+
9
+ class BaseUssoSession(metaclass=Singleton):
10
+
11
+ def __init__(
12
+ self,
13
+ usso_base_url: str | None = None,
14
+ api_key: str | None = None,
15
+ usso_refresh_url: str | None = None,
16
+ refresh_token: str | None = None,
17
+ usso_api_key: str | None = None,
18
+ user_id: str | None = None,
19
+ ):
20
+ assert (
21
+ usso_base_url or usso_refresh_url
22
+ ), "usso_base_url or usso_refresh_url is required"
23
+ assert (
24
+ refresh_token or api_key or usso_api_key
25
+ ), "refresh_token or api_key or usso_api_key is required"
26
+
27
+ if not usso_base_url:
28
+ url_parts = urlparse(usso_refresh_url)
29
+ usso_base_url = f"{url_parts.scheme}://{url_parts.netloc}"
30
+ if usso_base_url.endswith("/"):
31
+ usso_base_url = usso_base_url[:-1]
32
+
33
+ self.usso_refresh_url = usso_refresh_url or f"{usso_base_url}/auth/refresh"
34
+ self._refresh_token = refresh_token
35
+ self.session = requests.Session()
36
+ self.access_token = None
37
+ self.api_key = api_key
38
+ self.usso_api_key = usso_api_key
39
+ self.user_id = user_id
40
+ self.headers = {}
41
+ if api_key:
42
+ self.headers = {"x-api-key": api_key}
43
+ self.session.headers.update(self.headers)
44
+
45
+ @property
46
+ def refresh_token(self):
47
+ if self._refresh_token and is_expired(self._refresh_token):
48
+ self._refresh_token = None
49
+
50
+ return self._refresh_token
51
+
52
+ def request(self, method: str, url: str, **kwargs):
53
+ return self._request(method, url, **kwargs)
54
+
55
+ def get(self, url: str, **kwargs):
56
+ return self._request("GET", url, **kwargs)
57
+
58
+ def post(self, url: str, **kwargs):
59
+ return self._request("POST", url, **kwargs)
60
+
61
+ def put(self, url: str, **kwargs):
62
+ return self._request("PUT", url, **kwargs)
63
+
64
+ def patch(self, url: str, **kwargs):
65
+ return self._request("PATCH", url, **kwargs)
66
+
67
+ def delete(self, url: str, **kwargs):
68
+ return self._request("DELETE", url, **kwargs)
69
+
70
+ def head(self, url: str, **kwargs):
71
+ return self._request("HEAD", url, **kwargs)
72
+
73
+ def options(self, url: str, **kwargs):
74
+ return self._request("OPTIONS", url, **kwargs)
75
+
76
+
77
+ class UssoSession(BaseUssoSession):
78
+ def _refresh_api(self):
79
+ params = {"user_id": self.user_id} if self.user_id else {}
80
+ response = requests.get(
81
+ f"{self.usso_refresh_url}/api",
82
+ headers={"x-api-key": self.usso_api_key},
83
+ params=params,
84
+ )
85
+ response.raise_for_status()
86
+ data: dict = response.json()
87
+ self._refresh_token = data.get("token", {}).get("refresh_token")
88
+
89
+ def _refresh(self):
90
+ assert (
91
+ self.refresh_token or self.usso_api_key
92
+ ), "refresh_token or usso_api_key is required"
93
+
94
+ if self.usso_api_key and not self.refresh_token:
95
+ self._refresh_api()
96
+
97
+ response = requests.post(
98
+ self.usso_refresh_url, json={"refresh_token": f"{self.refresh_token}"}
99
+ )
100
+ response.raise_for_status()
101
+ self.access_token = response.json().get("access_token")
102
+ self.session.headers.update({"Authorization": f"Bearer {self.access_token}"})
103
+ return response.json()
104
+
105
+ def get_session(self):
106
+ if self.api_key:
107
+ return self.session
108
+
109
+ if not self.access_token or is_expired(self.access_token):
110
+ self._refresh()
111
+ return self.session
112
+
113
+ def _request(self, method: str, url: str, **kwargs):
114
+ session = self.get_session()
115
+ return session.request(method, url, **kwargs)
116
+
117
+ def close(self):
118
+ self.session.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: usso
3
- Version: 0.26.1
3
+ Version: 0.27.1
4
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
5
  Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
6
  Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
@@ -42,28 +42,28 @@ Classifier: Programming Language :: Python :: 3 :: Only
42
42
  Requires-Python: >=3.9
43
43
  Description-Content-Type: text/markdown
44
44
  License-File: LICENSE.txt
45
- Requires-Dist: pydantic >=2
46
- Requires-Dist: requests >=2.26.0
45
+ Requires-Dist: pydantic>=2
46
+ Requires-Dist: requests>=2.26.0
47
47
  Requires-Dist: pyjwt[crypto]
48
48
  Requires-Dist: cachetools
49
- Provides-Extra: all
50
- Requires-Dist: fastapi ; extra == 'all'
51
- Requires-Dist: uvicorn ; extra == 'all'
52
- Requires-Dist: django ; extra == 'all'
53
- Requires-Dist: httpx ; extra == 'all'
54
- Requires-Dist: dev ; extra == 'all'
55
- Requires-Dist: test ; extra == 'all'
56
- Provides-Extra: dev
57
- Requires-Dist: check-manifest ; extra == 'dev'
58
- Provides-Extra: django
59
- Requires-Dist: Django >=3.2 ; extra == 'django'
60
49
  Provides-Extra: fastapi
61
- Requires-Dist: fastapi >=0.65.0 ; extra == 'fastapi'
62
- Requires-Dist: uvicorn[standard] >=0.13.0 ; extra == 'fastapi'
50
+ Requires-Dist: fastapi>=0.65.0; extra == "fastapi"
51
+ Requires-Dist: uvicorn[standard]>=0.13.0; extra == "fastapi"
52
+ Provides-Extra: django
53
+ Requires-Dist: Django>=3.2; extra == "django"
63
54
  Provides-Extra: httpx
64
- Requires-Dist: httpx ; extra == 'httpx'
55
+ Requires-Dist: httpx; extra == "httpx"
56
+ Provides-Extra: dev
57
+ Requires-Dist: check-manifest; extra == "dev"
65
58
  Provides-Extra: test
66
- Requires-Dist: coverage ; extra == 'test'
59
+ Requires-Dist: coverage; extra == "test"
60
+ Provides-Extra: all
61
+ Requires-Dist: fastapi; extra == "all"
62
+ Requires-Dist: uvicorn; extra == "all"
63
+ Requires-Dist: django; extra == "all"
64
+ Requires-Dist: httpx; extra == "all"
65
+ Requires-Dist: dev; extra == "all"
66
+ Requires-Dist: test; extra == "all"
67
67
 
68
68
  # USSO-Client
69
69
 
@@ -0,0 +1,21 @@
1
+ usso/__init__.py,sha256=NnOS_S1a-JKTOlGe1nw-kCL3m0y82mA2mDraus7BQ2o,120
2
+ usso/b64tools.py,sha256=HGQ0E59vzjrQo2-4jrcY03ebtTaYwTtCZ7KgJaEmxO0,610
3
+ usso/core.py,sha256=KI_61zIxZNDB1QeZsYN7wgKYy54Huhdm2T2WDFq69Sw,8043
4
+ usso/exceptions.py,sha256=hawOAuVbvQtjgRfwp1KFZ4SmV7fh720y5Gom9JVA8W8,504
5
+ usso/schemas.py,sha256=nYFqBMtnGJw13cSKSIMIZdxKVz3AIbnETDuiENHdl5g,850
6
+ usso/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ usso/client/api.py,sha256=xlDq2nZNpq3mhAvqIbGEfANHNjJpPquSeULBfS7iMJw,5094
8
+ usso/client/async_api.py,sha256=rb-Xh5oudmZrPYM_iH_B75b5Z0Fvi1V1uurdcKE51w0,5551
9
+ usso/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ usso/django/middleware.py,sha256=CJ_poyF9dUTAm-z34s_2DGts-g30fX2PJgzouxVeQY0,3244
11
+ usso/fastapi/__init__.py,sha256=0EcdOzb4f3yu9nILIdGWnlyUz-0VaVX2az1e3f2BusI,201
12
+ usso/fastapi/integration.py,sha256=HCLveG2s6pFsuu2zU0JbgerJ4BgaF712ElmEUPHhwyk,2321
13
+ usso/session/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ usso/session/async_session.py,sha256=SSfB1tDcq5p0eXd2awfTsVYQWm5IRL-1GeMo1zsVFks,2081
15
+ usso/session/session.py,sha256=VRxt-HXXqXrieDt1oA3vBYMjj30PFGAFVwIPtu86JYI,3792
16
+ usso-0.27.1.dist-info/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
17
+ usso-0.27.1.dist-info/METADATA,sha256=hQkZJFA_Z36wxxi9FXBe220pEH81RiO00EKNtOsQiGc,4489
18
+ usso-0.27.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
19
+ usso-0.27.1.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
20
+ usso-0.27.1.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
21
+ usso-0.27.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.6.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
usso/async_session.py DELETED
@@ -1,124 +0,0 @@
1
- from contextlib import asynccontextmanager
2
- from datetime import datetime, timedelta
3
-
4
- import aiohttp
5
- import jwt
6
-
7
-
8
- class AsyncUssoSession:
9
- def __init__(
10
- self,
11
- sso_refresh_url: str,
12
- refresh_token: str | None = None,
13
- api_key: str | None = None,
14
- user_id: str | None = None,
15
- ):
16
- self.sso_refresh_url = sso_refresh_url
17
- self._refresh_token = refresh_token
18
- self.access_token = None
19
- self.session = None # This will hold the aiohttp session
20
- self.api_key = api_key
21
- self.user_id = user_id
22
-
23
- @property
24
- def refresh_token(self):
25
- if self._refresh_token:
26
- decoded_token = jwt.decode(
27
- self._refresh_token, options={"verify_signature": False}
28
- )
29
- exp = decoded_token.get(
30
- "exp", (datetime.now() + timedelta(days=1)).timestamp()
31
- )
32
- exp = datetime.fromtimestamp(exp)
33
- if exp < datetime.now():
34
- self._refresh_token = None
35
-
36
- return self._refresh_token
37
-
38
- async def _refresh_api(self):
39
- params = {"user_id": self.user_id} if self.user_id else {}
40
- async with aiohttp.ClientSession() as session:
41
- async with session.get(
42
- f"{self.sso_refresh_url}/api",
43
- headers={"x-api-key": self.api_key},
44
- params=params,
45
- ) as response:
46
- response.raise_for_status()
47
- data: dict = await response.json()
48
- self._refresh_token = data.get("token", {}).get("refresh_token")
49
-
50
- async def _refresh(self):
51
- if not self.refresh_token and not self.api_key:
52
- raise ValueError("Refresh token not provided or invalid.")
53
-
54
- if self.api_key and not self.refresh_token:
55
- await self._refresh_api()
56
-
57
- async with aiohttp.ClientSession() as session:
58
- async with session.post(
59
- self.sso_refresh_url,
60
- json={"refresh_token": self.refresh_token},
61
- ) as response:
62
- response.raise_for_status()
63
- return await response.json()
64
-
65
- async def _ensure_valid_token(self):
66
- if self.access_token:
67
- decoded_token = jwt.decode(
68
- self.access_token, options={"verify_signature": False}
69
- )
70
- exp = decoded_token.get("exp")
71
-
72
- if exp and datetime.fromtimestamp(exp) < datetime.now():
73
- self.access_token = None # Token expired, need a new one
74
-
75
- if not self.access_token:
76
- # Get a new token if none exists or it has expired
77
- token_data = await self._refresh()
78
- self.access_token = token_data.get("access_token")
79
-
80
- # Update headers with the new access token
81
- if self.session:
82
- self.session.headers.update(
83
- {"Authorization": f"Bearer {self.access_token}"}
84
- )
85
-
86
- async def __aenter__(self):
87
- self.session = aiohttp.ClientSession() # Initialize the session
88
- await self._ensure_valid_token() # Ensure valid token before usage
89
- return self
90
-
91
- async def __aexit__(self, exc_type, exc_value, traceback):
92
- if self.session:
93
- await self.session.close() # Close the session properly
94
-
95
- @asynccontextmanager
96
- async def _request(self, method: str, url: str, **kwargs):
97
- await self._ensure_valid_token() # Ensure valid token before any request
98
- async with self.session.request(method, url, **kwargs) as response:
99
- yield response
100
-
101
- def get(self, url: str, **kwargs):
102
- return self._request("GET", url, **kwargs)
103
-
104
- def post(self, url: str, **kwargs):
105
- return self._request("POST", url, **kwargs)
106
-
107
- def put(self, url: str, **kwargs):
108
- return self._request("PUT", url, **kwargs)
109
-
110
- def patch(self, url: str, **kwargs):
111
- return self._request("PATCH", url, **kwargs)
112
-
113
- def delete(self, url: str, **kwargs):
114
- return self._request("DELETE", url, **kwargs)
115
-
116
- def head(self, url: str, **kwargs):
117
- return self._request("HEAD", url, **kwargs)
118
-
119
- def options(self, url: str, **kwargs):
120
- return self._request("OPTIONS", url, **kwargs)
121
-
122
- async def close(self):
123
- await self.session.close()
124
- self.session = None
usso/httpx_session.py DELETED
@@ -1,87 +0,0 @@
1
- from datetime import datetime, timedelta
2
-
3
- import httpx
4
- import jwt
5
-
6
-
7
- class AsyncUssoSession(httpx.AsyncClient):
8
- def __init__(
9
- self,
10
- sso_refresh_url: str,
11
- refresh_token: str | None = None,
12
- api_key: str | None = None,
13
- user_id: str | None = None,
14
- ):
15
- super().__init__()
16
- self.sso_refresh_url = sso_refresh_url
17
- self._refresh_token = refresh_token
18
- self.access_token = None
19
- self.session = None # This will hold the aiohttp session
20
- self.api_key = api_key
21
- self.user_id = user_id
22
-
23
- @property
24
- def refresh_token(self):
25
- if self._refresh_token:
26
- decoded_token = jwt.decode(
27
- self._refresh_token, options={"verify_signature": False}
28
- )
29
- exp = decoded_token.get(
30
- "exp", (datetime.now() + timedelta(days=1)).timestamp()
31
- )
32
- exp = datetime.fromtimestamp(exp)
33
- if exp < datetime.now():
34
- self._refresh_token = None
35
-
36
- return self._refresh_token
37
-
38
- async def _refresh_api(self):
39
- params = {"user_id": self.user_id} if self.user_id else {}
40
- async with httpx.AsyncClient() as session:
41
- response = await session.get(
42
- f"{self.sso_refresh_url}/api",
43
- headers={"x-api-key": self.api_key},
44
- params=params,
45
- )
46
- response.raise_for_status()
47
- data: dict = response.json()
48
- self._refresh_token = data.get("token", {}).get("refresh_token")
49
-
50
- async def _refresh(self):
51
- if not self.refresh_token and not self.api_key:
52
- raise ValueError("Refresh token not provided or invalid.")
53
-
54
- if self.api_key and not self.refresh_token:
55
- await self._refresh_api()
56
-
57
- async with httpx.AsyncClient() as session:
58
- response = await session.post(
59
- self.sso_refresh_url, json={"refresh_token": self.refresh_token}
60
- )
61
- response.raise_for_status()
62
- return response.json()
63
-
64
- async def _ensure_valid_token(self):
65
- if self.access_token:
66
- decoded_token = jwt.decode(
67
- self.access_token, options={"verify_signature": False}
68
- )
69
- exp = decoded_token.get("exp")
70
-
71
- if exp and datetime.fromtimestamp(exp) < datetime.now():
72
- self.access_token = None # Token expired, need a new one
73
-
74
- if not self.access_token:
75
- # Get a new token if none exists or it has expired
76
- token_data = await self._refresh()
77
- self.access_token = token_data.get("access_token")
78
-
79
- async def request(self, method: str, url: str, *args, **kwargs):
80
- await self._ensure_valid_token()
81
-
82
- # Add authorization header to each request
83
- headers = kwargs.pop("headers") or {}
84
- headers["Authorization"] = f"Bearer {self.access_token}"
85
-
86
- # Call the parent's request method
87
- return await super().request(method, url, headers=headers, *args, **kwargs)
usso/session.py DELETED
@@ -1,78 +0,0 @@
1
- from datetime import datetime, timedelta
2
-
3
- import jwt
4
- import requests
5
-
6
-
7
- class UssoSession:
8
-
9
- def __init__(
10
- self,
11
- sso_refresh_url: str,
12
- refresh_token: str | None = None,
13
- api_key: str | None = None,
14
- user_id: str | None = None,
15
- ):
16
- self.sso_refresh_url = sso_refresh_url
17
- self._refresh_token = refresh_token
18
- self.session = requests.Session()
19
- self.access_token = None
20
- self.api_key = api_key
21
- self.user_id = user_id
22
-
23
- @property
24
- def refresh_token(self):
25
- if self._refresh_token:
26
- decoded_token = jwt.decode(
27
- self._refresh_token, options={"verify_signature": False}
28
- )
29
- exp = decoded_token.get(
30
- "exp", (datetime.now() + timedelta(days=1)).timestamp()
31
- )
32
- exp = datetime.fromtimestamp(exp)
33
- if exp < datetime.now():
34
- self._refresh_token = None
35
-
36
- return self._refresh_token
37
-
38
- def _refresh_api(self):
39
- params = {"user_id": self.user_id} if self.user_id else {}
40
- response = requests.get(
41
- f"{self.sso_refresh_url}/api",
42
- headers={"x-api-key": self.api_key},
43
- params=params,
44
- )
45
- response.raise_for_status()
46
- data = response.json()
47
- self._refresh_token = data.get("token", {}).get("refresh_token")
48
-
49
- def _refresh(self):
50
- if not self.refresh_token and not self.api_key:
51
- return
52
-
53
- if self.api_key and not self.refresh_token:
54
- self._refresh_api()
55
-
56
- response = requests.post(
57
- self.sso_refresh_url,
58
- json={"refresh_token": f"{self.refresh_token}"},
59
- )
60
- response.raise_for_status()
61
- self.access_token = response.json().get("access_token")
62
- self.session.headers.update({"Authorization": f"Bearer {self.access_token}"})
63
- return response.json()
64
-
65
- def get_session(self):
66
- if self.access_token:
67
- decoded_token = jwt.decode(
68
- self.access_token, options={"verify_signature": False}
69
- )
70
- exp = datetime.fromtimestamp(decoded_token.get("exp"))
71
- if exp < datetime.now():
72
- self.access_token = None
73
- if not self.access_token:
74
- self.access_token = self._refresh().get("access_token")
75
- self.session.headers.update(
76
- {"Authorization": f"Bearer {self.access_token}"}
77
- )
78
- return self.session
@@ -1,19 +0,0 @@
1
- usso/__init__.py,sha256=NnOS_S1a-JKTOlGe1nw-kCL3m0y82mA2mDraus7BQ2o,120
2
- usso/api.py,sha256=xlDq2nZNpq3mhAvqIbGEfANHNjJpPquSeULBfS7iMJw,5094
3
- usso/async_api.py,sha256=rb-Xh5oudmZrPYM_iH_B75b5Z0Fvi1V1uurdcKE51w0,5551
4
- usso/async_session.py,sha256=nFIrtV3Tp0H-s2ZkMLU9_fVSeVGq1EtY1bGT_XOS5Vw,4336
5
- usso/b64tools.py,sha256=HGQ0E59vzjrQo2-4jrcY03ebtTaYwTtCZ7KgJaEmxO0,610
6
- usso/core.py,sha256=tZzoh_t7HYr-HIual4hN7K1ZVk_nGZdKpaItq5VvkJQ,7087
7
- usso/exceptions.py,sha256=hawOAuVbvQtjgRfwp1KFZ4SmV7fh720y5Gom9JVA8W8,504
8
- usso/httpx_session.py,sha256=jp52thSbve4gpJuVVxnKEgH5o7LbYIZ5owf3Y-7ZDbY,3067
9
- usso/session.py,sha256=E8qx96IWfLWp0CTo1qwb6VUWn0giUnKQIQo-ZRnneEY,2508
10
- usso/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- usso/django/middleware.py,sha256=EEEpHvMQ6QiWw2HY8zQ2Aec0RCATcLWsCKeyiPWJKio,3245
12
- usso/fastapi/__init__.py,sha256=0EcdOzb4f3yu9nILIdGWnlyUz-0VaVX2az1e3f2BusI,201
13
- usso/fastapi/integration.py,sha256=-8MTeqGokvmUO0lxZpEWXdTMYg6n065qtnaJHOwCrzQ,1890
14
- usso-0.26.1.dist-info/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
15
- usso-0.26.1.dist-info/METADATA,sha256=GOWjHISSSaoWSO_Y5iVVrcNovcqxexM7CSpViujFz_0,4506
16
- usso-0.26.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
17
- usso-0.26.1.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
18
- usso-0.26.1.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
19
- usso-0.26.1.dist-info/RECORD,,
File without changes
File without changes