usso 0.27.0__tar.gz → 0.27.2__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 (32) hide show
  1. {usso-0.27.0/src/usso.egg-info → usso-0.27.2}/PKG-INFO +1 -1
  2. {usso-0.27.0 → usso-0.27.2}/pyproject.toml +1 -1
  3. usso-0.27.2/src/usso/client/__init__.py +4 -0
  4. {usso-0.27.0/src/usso → usso-0.27.2/src/usso/client}/async_api.py +32 -52
  5. {usso-0.27.0 → usso-0.27.2}/src/usso/core.py +10 -36
  6. usso-0.27.2/src/usso/schemas.py +39 -0
  7. usso-0.27.2/src/usso/session/__init__.py +4 -0
  8. usso-0.27.2/src/usso/session/async_session.py +65 -0
  9. usso-0.27.2/src/usso/session/session.py +116 -0
  10. {usso-0.27.0 → usso-0.27.2/src/usso.egg-info}/PKG-INFO +1 -1
  11. {usso-0.27.0 → usso-0.27.2}/src/usso.egg-info/SOURCES.txt +7 -5
  12. usso-0.27.0/src/usso/async_session.py +0 -124
  13. usso-0.27.0/src/usso/httpx_session.py +0 -87
  14. usso-0.27.0/src/usso/session.py +0 -78
  15. {usso-0.27.0 → usso-0.27.2}/LICENSE.txt +0 -0
  16. {usso-0.27.0 → usso-0.27.2}/README.md +0 -0
  17. {usso-0.27.0 → usso-0.27.2}/setup.cfg +0 -0
  18. {usso-0.27.0 → usso-0.27.2}/src/usso/__init__.py +0 -0
  19. {usso-0.27.0 → usso-0.27.2}/src/usso/b64tools.py +0 -0
  20. {usso-0.27.0/src/usso → usso-0.27.2/src/usso/client}/api.py +0 -0
  21. {usso-0.27.0 → usso-0.27.2}/src/usso/django/__init__.py +0 -0
  22. {usso-0.27.0 → usso-0.27.2}/src/usso/django/middleware.py +0 -0
  23. {usso-0.27.0 → usso-0.27.2}/src/usso/exceptions.py +0 -0
  24. {usso-0.27.0 → usso-0.27.2}/src/usso/fastapi/__init__.py +0 -0
  25. {usso-0.27.0 → usso-0.27.2}/src/usso/fastapi/integration.py +0 -0
  26. {usso-0.27.0 → usso-0.27.2}/src/usso.egg-info/dependency_links.txt +0 -0
  27. {usso-0.27.0 → usso-0.27.2}/src/usso.egg-info/entry_points.txt +0 -0
  28. {usso-0.27.0 → usso-0.27.2}/src/usso.egg-info/requires.txt +0 -0
  29. {usso-0.27.0 → usso-0.27.2}/src/usso.egg-info/top_level.txt +0 -0
  30. {usso-0.27.0 → usso-0.27.2}/tests/test_api.py +0 -0
  31. {usso-0.27.0 → usso-0.27.2}/tests/test_core.py +0 -0
  32. {usso-0.27.0 → usso-0.27.2}/tests/test_simple.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: usso
3
- Version: 0.27.0
3
+ Version: 0.27.2
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>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "usso"
7
- version = "0.27.0"
7
+ version = "0.27.2"
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"
@@ -0,0 +1,4 @@
1
+ from .api import UssoAPI
2
+ from .async_api import AsyncUssoAPI
3
+
4
+ __all__ = ["UssoAPI", "AsyncUssoAPI"]
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
 
3
- import aiohttp
3
+ import httpx
4
4
  from singleton import Singleton
5
5
 
6
6
  from usso.core import UserData, Usso
@@ -28,18 +28,16 @@ class AsyncUssoAPI(metaclass=Singleton):
28
28
  return
29
29
 
30
30
  url = f"{self.url}/auth/refresh"
31
+ headers = {
32
+ "Authorization": f"Bearer {self.refresh_token}",
33
+ "Content-Type": "application/json",
34
+ }
31
35
 
32
- if self.refresh_token:
33
- headers = {
34
- "Authorization": f"Bearer {self.refresh_token}",
35
- "content-type": "application/json",
36
- }
37
-
38
- async with aiohttp.ClientSession() as session:
39
- async with session.post(url, headers=headers) as resp:
40
- if kwargs.get("raise_exception", True):
41
- resp.raise_for_status()
42
- self.access_token = (await resp.json()).get("access_token")
36
+ async with httpx.AsyncClient() as client:
37
+ resp = await client.post(url, headers=headers)
38
+ if kwargs.get("raise_exception", True):
39
+ resp.raise_for_status()
40
+ self.access_token = resp.json().get("access_token")
43
41
 
44
42
  def _access_valid(self) -> bool:
45
43
  if not self.access_token:
@@ -48,9 +46,7 @@ class AsyncUssoAPI(metaclass=Singleton):
48
46
  user_data = Usso(
49
47
  jwks_url=f"{self.url}/website/jwks.json?"
50
48
  ).user_data_from_token(self.access_token)
51
- if user_data:
52
- return True
53
- return False
49
+ return bool(user_data)
54
50
 
55
51
  async def _request(
56
52
  self,
@@ -60,7 +56,7 @@ class AsyncUssoAPI(metaclass=Singleton):
60
56
  **kwargs,
61
57
  ) -> dict:
62
58
  url = f"{self.url}/{endpoint}"
63
- headers = {"content-type": "application/json"}
59
+ headers = {"Content-Type": "application/json"}
64
60
  if self.api_key:
65
61
  headers["x-api-key"] = self.api_key
66
62
  elif self.refresh_token:
@@ -68,54 +64,41 @@ class AsyncUssoAPI(metaclass=Singleton):
68
64
  await self._refresh()
69
65
  headers["Authorization"] = f"Bearer {self.access_token}"
70
66
 
71
- async with aiohttp.ClientSession() as session:
72
- async with session.request(
73
- method,
74
- url,
75
- headers=headers,
76
- json=data,
77
- ) as resp:
78
- try:
79
- resp_json = await resp.json()
80
- resp.raise_for_status()
81
- except aiohttp.ClientError as e:
82
- logging.error(f"Error: {e}")
83
- logging.error(f"Response: {resp_json}")
84
- raise e
85
- except Exception as e:
86
- logging.error(f"Error: {e}")
87
- logging.error(f"Response: {resp.text}")
88
- raise e
89
- return await resp.json()
67
+ async with httpx.AsyncClient() as client:
68
+ try:
69
+ resp = await client.request(
70
+ method,
71
+ url,
72
+ headers=headers,
73
+ json=data,
74
+ )
75
+ resp.raise_for_status()
76
+ return resp.json()
77
+ except httpx.HTTPStatusError as e:
78
+ logging.error(f"HTTP error: {e.response.status_code} {e.response.text}")
79
+ raise e
80
+ except Exception as e:
81
+ logging.error(f"Unexpected error: {e}")
82
+ raise e
90
83
 
91
84
  async def get_users(self, **kwargs) -> list[UserData]:
92
85
  users_dict = await self._request(endpoint="website/users", **kwargs)
93
-
94
86
  return [UserData(user_id=user.get("uid"), **user) for user in users_dict]
95
87
 
96
88
  async def get_user(self, user_id: str, **kwargs) -> UserData:
97
- user_dict = await self._request(
98
- endpoint=f"website/users/{user_id}",
99
- **kwargs,
100
- )
89
+ user_dict = await self._request(endpoint=f"website/users/{user_id}", **kwargs)
101
90
  return UserData(user_id=user_dict.get("uid"), **user_dict)
102
91
 
103
92
  async def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
104
93
  user_dict = await self._request(
105
- endpoint="website/users/credentials",
106
- data=credentials,
107
- **kwargs,
94
+ endpoint="website/users/credentials", data=credentials, **kwargs
108
95
  )
109
96
  return UserData(user_id=user_dict.get("uid"), **user_dict)
110
97
 
111
98
  async def create_user(self, user_data: dict, **kwargs) -> UserData:
112
99
  user_dict = await self._request(
113
- method="post",
114
- endpoint="website/users",
115
- data=user_data,
116
- **kwargs,
100
+ method="post", endpoint="website/users", data=user_data, **kwargs
117
101
  )
118
-
119
102
  return UserData(user_id=user_dict.get("uid"), **user_dict)
120
103
 
121
104
  async def create_user_credentials(
@@ -139,10 +122,7 @@ class AsyncUssoAPI(metaclass=Singleton):
139
122
  if credentials:
140
123
  user_data["authenticators"] = [credentials]
141
124
  user_dict = await self._request(
142
- method="post",
143
- endpoint="website/users",
144
- data=credentials,
145
- **kwargs,
125
+ method="post", endpoint="website/users", data=credentials, **kwargs
146
126
  )
147
127
  return UserData(user_id=user_dict.get("uid"), **user_dict)
148
128
 
@@ -1,7 +1,7 @@
1
1
  import json
2
2
  import logging
3
3
  import os
4
- import uuid
4
+ from datetime import datetime, timedelta
5
5
  from urllib.parse import urlparse
6
6
 
7
7
  import cachetools.func
@@ -10,46 +10,12 @@ import requests
10
10
  from cachetools import TTLCache, cached
11
11
  from pydantic import BaseModel, model_validator
12
12
 
13
- from . import b64tools
14
13
  from .exceptions import USSOException
14
+ from .schemas import UserData
15
15
 
16
16
  logger = logging.getLogger("usso")
17
17
 
18
18
 
19
- class UserData(BaseModel):
20
- user_id: str
21
- workspace_id: str | None = None
22
- workspace_ids: list[str] = []
23
- token_type: str = "access"
24
-
25
- email: str | None = None
26
- phone: str | None = None
27
- username: str | None = None
28
-
29
- authentication_method: str | None = None
30
- is_active: bool = False
31
-
32
- jti: str | None = None
33
- data: dict | None = None
34
-
35
- token: str | None = None
36
-
37
- @property
38
- def uid(self) -> uuid.UUID:
39
- user_id = self.user_id
40
-
41
- if user_id.startswith("u_"):
42
- user_id = user_id[2:]
43
- if 22 <= len(user_id) <= 24:
44
- user_id = b64tools.b64_decode_uuid(user_id)
45
-
46
- return uuid.UUID(user_id)
47
-
48
- @property
49
- def b64id(self) -> uuid.UUID:
50
- return b64tools.b64_encode_uuid_strip(self.uid)
51
-
52
-
53
19
  def get_authorization_scheme_param(
54
20
  authorization_header_value: str | None,
55
21
  ) -> tuple[str, str]:
@@ -92,6 +58,14 @@ def decode_token(key, token: str, algorithms=["RS256"], **kwargs) -> dict:
92
58
  logger.error(e)
93
59
 
94
60
 
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
+
95
69
  @cached(TTLCache(maxsize=128, ttl=10 * 60))
96
70
  def get_jwk_keys(jwk_url: str) -> jwt.PyJWKClient:
97
71
  return jwt.PyJWKClient(jwk_url, headers={"User-Agent": "usso-python"})
@@ -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)
@@ -0,0 +1,4 @@
1
+ from .async_session import AsyncUssoSession
2
+ from .session import UssoSession
3
+
4
+ __all__ = ["UssoSession", "AsyncUssoSession"]
@@ -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,116 @@
1
+ from urllib.parse import urlparse
2
+
3
+ import requests
4
+
5
+ from ..core import is_expired
6
+
7
+
8
+ class BaseUssoSession:
9
+ def __init__(
10
+ self,
11
+ usso_base_url: str | None = None,
12
+ api_key: str | None = None,
13
+ usso_refresh_url: str | None = None,
14
+ refresh_token: str | None = None,
15
+ usso_api_key: str | None = None,
16
+ user_id: str | None = None,
17
+ ):
18
+ assert (
19
+ usso_base_url or usso_refresh_url
20
+ ), "usso_base_url or usso_refresh_url is required"
21
+ assert (
22
+ refresh_token or api_key or usso_api_key
23
+ ), "refresh_token or api_key or usso_api_key is required"
24
+
25
+ if not usso_base_url:
26
+ url_parts = urlparse(usso_refresh_url)
27
+ usso_base_url = f"{url_parts.scheme}://{url_parts.netloc}"
28
+ if usso_base_url.endswith("/"):
29
+ usso_base_url = usso_base_url[:-1]
30
+
31
+ self.usso_refresh_url = usso_refresh_url or f"{usso_base_url}/auth/refresh"
32
+ self._refresh_token = refresh_token
33
+ self.session = requests.Session()
34
+ self.access_token = None
35
+ self.api_key = api_key
36
+ self.usso_api_key = usso_api_key
37
+ self.user_id = user_id
38
+ self.headers = {}
39
+ if api_key:
40
+ self.headers = {"x-api-key": api_key}
41
+ self.session.headers.update(self.headers)
42
+
43
+ @property
44
+ def refresh_token(self):
45
+ if self._refresh_token and is_expired(self._refresh_token):
46
+ self._refresh_token = None
47
+
48
+ return self._refresh_token
49
+
50
+ def request(self, method: str, url: str, **kwargs):
51
+ return self._request(method, url, **kwargs)
52
+
53
+ def get(self, url: str, **kwargs):
54
+ return self._request("GET", url, **kwargs)
55
+
56
+ def post(self, url: str, **kwargs):
57
+ return self._request("POST", url, **kwargs)
58
+
59
+ def put(self, url: str, **kwargs):
60
+ return self._request("PUT", url, **kwargs)
61
+
62
+ def patch(self, url: str, **kwargs):
63
+ return self._request("PATCH", url, **kwargs)
64
+
65
+ def delete(self, url: str, **kwargs):
66
+ return self._request("DELETE", url, **kwargs)
67
+
68
+ def head(self, url: str, **kwargs):
69
+ return self._request("HEAD", url, **kwargs)
70
+
71
+ def options(self, url: str, **kwargs):
72
+ return self._request("OPTIONS", url, **kwargs)
73
+
74
+
75
+ class UssoSession(BaseUssoSession):
76
+ def _refresh_api(self):
77
+ params = {"user_id": self.user_id} if self.user_id else {}
78
+ response = requests.get(
79
+ f"{self.usso_refresh_url}/api",
80
+ headers={"x-api-key": self.usso_api_key},
81
+ params=params,
82
+ )
83
+ response.raise_for_status()
84
+ data: dict = response.json()
85
+ self._refresh_token = data.get("token", {}).get("refresh_token")
86
+
87
+ def _refresh(self):
88
+ assert (
89
+ self.refresh_token or self.usso_api_key
90
+ ), "refresh_token or usso_api_key is required"
91
+
92
+ if self.usso_api_key and not self.refresh_token:
93
+ self._refresh_api()
94
+
95
+ response = requests.post(
96
+ self.usso_refresh_url, json={"refresh_token": f"{self.refresh_token}"}
97
+ )
98
+ response.raise_for_status()
99
+ self.access_token = response.json().get("access_token")
100
+ self.session.headers.update({"Authorization": f"Bearer {self.access_token}"})
101
+ return response.json()
102
+
103
+ def get_session(self):
104
+ if self.api_key:
105
+ return self.session
106
+
107
+ if not self.access_token or is_expired(self.access_token):
108
+ self._refresh()
109
+ return self.session
110
+
111
+ def _request(self, method: str, url: str, **kwargs):
112
+ session = self.get_session()
113
+ return session.request(method, url, **kwargs)
114
+
115
+ def close(self):
116
+ self.session.close()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: usso
3
- Version: 0.27.0
3
+ Version: 0.27.2
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>
@@ -2,24 +2,26 @@ LICENSE.txt
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/usso/__init__.py
5
- src/usso/api.py
6
- src/usso/async_api.py
7
- src/usso/async_session.py
8
5
  src/usso/b64tools.py
9
6
  src/usso/core.py
10
7
  src/usso/exceptions.py
11
- src/usso/httpx_session.py
12
- src/usso/session.py
8
+ src/usso/schemas.py
13
9
  src/usso.egg-info/PKG-INFO
14
10
  src/usso.egg-info/SOURCES.txt
15
11
  src/usso.egg-info/dependency_links.txt
16
12
  src/usso.egg-info/entry_points.txt
17
13
  src/usso.egg-info/requires.txt
18
14
  src/usso.egg-info/top_level.txt
15
+ src/usso/client/__init__.py
16
+ src/usso/client/api.py
17
+ src/usso/client/async_api.py
19
18
  src/usso/django/__init__.py
20
19
  src/usso/django/middleware.py
21
20
  src/usso/fastapi/__init__.py
22
21
  src/usso/fastapi/integration.py
22
+ src/usso/session/__init__.py
23
+ src/usso/session/async_session.py
24
+ src/usso/session/session.py
23
25
  tests/test_api.py
24
26
  tests/test_core.py
25
27
  tests/test_simple.py
@@ -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
@@ -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)
@@ -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
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