usso 0.27.21__py3-none-any.whl → 0.28.0__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.
- usso/__init__.py +23 -2
- usso/auth/__init__.py +9 -0
- usso/auth/api_key.py +43 -0
- usso/auth/client.py +85 -0
- usso/auth/config.py +115 -0
- usso/exceptions.py +13 -0
- usso/integrations/django/__init__.py +3 -0
- usso/{django → integrations/django}/middleware.py +37 -29
- usso/integrations/fastapi/__init__.py +3 -0
- usso/integrations/fastapi/dependency.py +95 -0
- usso/models/user.py +119 -0
- usso/session/async_session.py +16 -10
- usso/session/base_session.py +29 -47
- usso/session/session.py +10 -36
- usso/utils/method_utils.py +12 -0
- usso/utils/string_utils.py +7 -0
- usso-0.28.0.dist-info/METADATA +172 -0
- usso-0.28.0.dist-info/RECORD +24 -0
- {usso-0.27.21.dist-info → usso-0.28.0.dist-info}/WHEEL +1 -1
- usso/b64tools.py +0 -20
- usso/client/__init__.py +0 -4
- usso/client/api.py +0 -174
- usso/client/async_api.py +0 -159
- usso/core.py +0 -160
- usso/fastapi/__init__.py +0 -7
- usso/fastapi/integration.py +0 -88
- usso/schemas.py +0 -67
- usso-0.27.21.dist-info/METADATA +0 -110
- usso-0.27.21.dist-info/RECORD +0 -22
- /usso/{django → utils}/__init__.py +0 -0
- {usso-0.27.21.dist-info → usso-0.28.0.dist-info}/entry_points.txt +0 -0
- {usso-0.27.21.dist-info → usso-0.28.0.dist-info/licenses}/LICENSE.txt +0 -0
- {usso-0.27.21.dist-info → usso-0.28.0.dist-info}/top_level.txt +0 -0
usso/client/api.py
DELETED
@@ -1,174 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
|
3
|
-
import requests
|
4
|
-
from singleton import Singleton
|
5
|
-
|
6
|
-
from usso.core import UserData, Usso
|
7
|
-
|
8
|
-
|
9
|
-
class UssoAPI(metaclass=Singleton):
|
10
|
-
def __init__(
|
11
|
-
self,
|
12
|
-
url: str = "https://api.usso.io",
|
13
|
-
api_key: str = None,
|
14
|
-
refresh_token: str = None,
|
15
|
-
):
|
16
|
-
if url and not url.startswith("http"):
|
17
|
-
url = f"https://{url}"
|
18
|
-
url = url.rstrip("/")
|
19
|
-
self.url = url
|
20
|
-
assert (
|
21
|
-
api_key or refresh_token
|
22
|
-
), "Either api_key or refresh_token must be provided"
|
23
|
-
self.api_key = api_key
|
24
|
-
self.refresh_token = refresh_token
|
25
|
-
self.access_token = None
|
26
|
-
|
27
|
-
def _refresh(self):
|
28
|
-
if not self.refresh_token:
|
29
|
-
return
|
30
|
-
|
31
|
-
url = f"{self.url}/auth/refresh"
|
32
|
-
|
33
|
-
if self.refresh_token:
|
34
|
-
headers = {
|
35
|
-
"Authorization": f"Bearer {self.refresh_token}",
|
36
|
-
"content-type": "application/json",
|
37
|
-
}
|
38
|
-
|
39
|
-
resp = requests.post(url, headers=headers)
|
40
|
-
self.access_token = resp.json().get("access_token")
|
41
|
-
|
42
|
-
def _access_valid(self) -> bool:
|
43
|
-
if not self.access_token:
|
44
|
-
return False
|
45
|
-
|
46
|
-
user_data = Usso(
|
47
|
-
jwks_url=f"{self.url}/website/jwks.json?"
|
48
|
-
).user_data_from_token(self.access_token)
|
49
|
-
if user_data:
|
50
|
-
return True
|
51
|
-
return False
|
52
|
-
|
53
|
-
def _request(
|
54
|
-
self,
|
55
|
-
method="get",
|
56
|
-
endpoint: str = "",
|
57
|
-
data: dict = None,
|
58
|
-
**kwargs,
|
59
|
-
) -> dict:
|
60
|
-
url = f"{self.url}/{endpoint}"
|
61
|
-
headers = {"content-type": "application/json"}
|
62
|
-
if self.api_key:
|
63
|
-
headers["x-api-key"] = self.api_key
|
64
|
-
elif self.refresh_token:
|
65
|
-
if not self.access_token:
|
66
|
-
self._refresh()
|
67
|
-
headers["Authorization"] = f"Bearer {self.access_token}"
|
68
|
-
|
69
|
-
resp = requests.request(
|
70
|
-
method,
|
71
|
-
url,
|
72
|
-
headers=headers,
|
73
|
-
json=data,
|
74
|
-
)
|
75
|
-
if kwargs.get("raise_exception", True):
|
76
|
-
try:
|
77
|
-
resp_json = resp.json()
|
78
|
-
resp.raise_for_status()
|
79
|
-
except requests.HTTPError as e:
|
80
|
-
logging.error(f"Error: {e}")
|
81
|
-
logging.error(f"Response: {resp_json}")
|
82
|
-
raise e
|
83
|
-
except Exception as e:
|
84
|
-
logging.error(f"Error: {e}")
|
85
|
-
logging.error(f"Response: {resp.text}")
|
86
|
-
raise e
|
87
|
-
return resp.json()
|
88
|
-
|
89
|
-
def get_users(self, **kwargs) -> list[UserData]:
|
90
|
-
users_dict = self._request(endpoint="website/users", **kwargs)
|
91
|
-
|
92
|
-
return [UserData(user_id=user.get("uid"), **user) for user in users_dict]
|
93
|
-
|
94
|
-
def get_user(self, user_id: str, **kwargs) -> UserData:
|
95
|
-
user_dict = self._request(
|
96
|
-
endpoint=f"website/users/{user_id}",
|
97
|
-
**kwargs,
|
98
|
-
)
|
99
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
100
|
-
|
101
|
-
def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
|
102
|
-
user_dict = self._request(
|
103
|
-
endpoint="website/users/credentials",
|
104
|
-
data=credentials,
|
105
|
-
**kwargs,
|
106
|
-
)
|
107
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
108
|
-
|
109
|
-
def create_user(self, user_data: dict, **kwargs) -> UserData:
|
110
|
-
user_dict = self._request(
|
111
|
-
method="post",
|
112
|
-
endpoint="website/users",
|
113
|
-
data=user_data,
|
114
|
-
**kwargs,
|
115
|
-
)
|
116
|
-
|
117
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
118
|
-
|
119
|
-
def create_user_credentials(
|
120
|
-
self, user_id: str, credentials: dict, **kwargs
|
121
|
-
) -> UserData:
|
122
|
-
user_dict = self._request(
|
123
|
-
method="post",
|
124
|
-
endpoint=f"website/users/{user_id}/credentials",
|
125
|
-
data=credentials,
|
126
|
-
**kwargs,
|
127
|
-
)
|
128
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
129
|
-
|
130
|
-
def create_user_by_credentials(
|
131
|
-
self,
|
132
|
-
user_data: dict | None = None,
|
133
|
-
credentials: dict | None = None,
|
134
|
-
**kwargs,
|
135
|
-
) -> UserData:
|
136
|
-
user_data = user_data or {}
|
137
|
-
if credentials:
|
138
|
-
user_data["authenticators"] = [credentials]
|
139
|
-
user_dict = self._request(
|
140
|
-
method="post",
|
141
|
-
endpoint="website/users",
|
142
|
-
data=credentials,
|
143
|
-
**kwargs,
|
144
|
-
)
|
145
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
146
|
-
|
147
|
-
def get_user_payload(self, user_id: str, **kwargs) -> dict:
|
148
|
-
return self._request(endpoint=f"website/users/{user_id}/payload", **kwargs)
|
149
|
-
|
150
|
-
def update_user_payload(
|
151
|
-
self,
|
152
|
-
user_id: str,
|
153
|
-
payload: dict,
|
154
|
-
**kwargs,
|
155
|
-
) -> dict:
|
156
|
-
return self._request(
|
157
|
-
method="patch",
|
158
|
-
endpoint=f"website/users/{user_id}/payload",
|
159
|
-
data=payload,
|
160
|
-
**kwargs,
|
161
|
-
)
|
162
|
-
|
163
|
-
def set_user_payload(
|
164
|
-
self,
|
165
|
-
user_id: str,
|
166
|
-
payload: dict,
|
167
|
-
**kwargs,
|
168
|
-
) -> dict:
|
169
|
-
return self._request(
|
170
|
-
method="put",
|
171
|
-
endpoint=f"website/users/{user_id}/payload",
|
172
|
-
data=payload,
|
173
|
-
**kwargs,
|
174
|
-
)
|
usso/client/async_api.py
DELETED
@@ -1,159 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
|
3
|
-
import httpx
|
4
|
-
from singleton import Singleton
|
5
|
-
|
6
|
-
from usso.core import UserData, Usso
|
7
|
-
|
8
|
-
|
9
|
-
class AsyncUssoAPI(metaclass=Singleton):
|
10
|
-
def __init__(
|
11
|
-
self,
|
12
|
-
url: str = "https://api.usso.io",
|
13
|
-
api_key: str = None,
|
14
|
-
refresh_token: str = None,
|
15
|
-
):
|
16
|
-
if url and not url.startswith("http"):
|
17
|
-
url = f"https://{url}"
|
18
|
-
url = url.rstrip("/")
|
19
|
-
self.url = url
|
20
|
-
assert (
|
21
|
-
api_key or refresh_token
|
22
|
-
), "Either api_key or refresh_token must be provided"
|
23
|
-
self.api_key = api_key
|
24
|
-
self.refresh_token = refresh_token
|
25
|
-
self.access_token = None
|
26
|
-
|
27
|
-
async def _refresh(self, **kwargs):
|
28
|
-
if not self.refresh_token:
|
29
|
-
return
|
30
|
-
|
31
|
-
url = f"{self.url}/auth/refresh"
|
32
|
-
headers = {
|
33
|
-
"Authorization": f"Bearer {self.refresh_token}",
|
34
|
-
"Content-Type": "application/json",
|
35
|
-
}
|
36
|
-
|
37
|
-
async with httpx.AsyncClient() as client:
|
38
|
-
resp = await client.post(url, headers=headers)
|
39
|
-
if kwargs.get("raise_exception", True):
|
40
|
-
resp.raise_for_status()
|
41
|
-
self.access_token = resp.json().get("access_token")
|
42
|
-
|
43
|
-
def _access_valid(self) -> bool:
|
44
|
-
if not self.access_token:
|
45
|
-
return False
|
46
|
-
|
47
|
-
user_data = Usso(
|
48
|
-
jwks_url=f"{self.url}/website/jwks.json?"
|
49
|
-
).user_data_from_token(self.access_token)
|
50
|
-
return bool(user_data)
|
51
|
-
|
52
|
-
async def _request(
|
53
|
-
self,
|
54
|
-
method="get",
|
55
|
-
endpoint: str = "",
|
56
|
-
data: dict = None,
|
57
|
-
**kwargs,
|
58
|
-
) -> dict:
|
59
|
-
url = f"{self.url}/{endpoint}"
|
60
|
-
headers = {"Content-Type": "application/json"}
|
61
|
-
if self.api_key:
|
62
|
-
headers["x-api-key"] = self.api_key
|
63
|
-
elif self.refresh_token:
|
64
|
-
if not self.access_token:
|
65
|
-
await self._refresh()
|
66
|
-
headers["Authorization"] = f"Bearer {self.access_token}"
|
67
|
-
|
68
|
-
async with httpx.AsyncClient() as client:
|
69
|
-
try:
|
70
|
-
resp = await client.request(
|
71
|
-
method,
|
72
|
-
url,
|
73
|
-
headers=headers,
|
74
|
-
json=data,
|
75
|
-
)
|
76
|
-
resp.raise_for_status()
|
77
|
-
return resp.json()
|
78
|
-
except httpx.HTTPStatusError as e:
|
79
|
-
logging.error(f"HTTP error: {e.response.status_code} {e.response.text}")
|
80
|
-
raise e
|
81
|
-
except Exception as e:
|
82
|
-
logging.error(f"Unexpected error: {e}")
|
83
|
-
raise e
|
84
|
-
|
85
|
-
async def get_users(self, **kwargs) -> list[UserData]:
|
86
|
-
users_dict = await self._request(endpoint="website/users", **kwargs)
|
87
|
-
return [UserData(user_id=user.get("uid"), **user) for user in users_dict]
|
88
|
-
|
89
|
-
async def get_user(self, user_id: str, **kwargs) -> UserData:
|
90
|
-
user_dict = await self._request(endpoint=f"website/users/{user_id}", **kwargs)
|
91
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
92
|
-
|
93
|
-
async def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
|
94
|
-
user_dict = await self._request(
|
95
|
-
endpoint="website/users/credentials", data=credentials, **kwargs
|
96
|
-
)
|
97
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
98
|
-
|
99
|
-
async def create_user(self, user_data: dict, **kwargs) -> UserData:
|
100
|
-
user_dict = await self._request(
|
101
|
-
method="post", endpoint="website/users", data=user_data, **kwargs
|
102
|
-
)
|
103
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
104
|
-
|
105
|
-
async def create_user_credentials(
|
106
|
-
self, user_id: str, credentials: dict, **kwargs
|
107
|
-
) -> UserData:
|
108
|
-
user_dict = await self._request(
|
109
|
-
method="post",
|
110
|
-
endpoint=f"website/users/{user_id}/credentials",
|
111
|
-
data=credentials,
|
112
|
-
**kwargs,
|
113
|
-
)
|
114
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
115
|
-
|
116
|
-
async def create_user_by_credentials(
|
117
|
-
self,
|
118
|
-
user_data: dict | None = None,
|
119
|
-
credentials: dict | None = None,
|
120
|
-
**kwargs,
|
121
|
-
) -> UserData:
|
122
|
-
user_data = user_data or {}
|
123
|
-
if credentials:
|
124
|
-
user_data["authenticators"] = [credentials]
|
125
|
-
user_dict = await self._request(
|
126
|
-
method="post", endpoint="website/users", data=credentials, **kwargs
|
127
|
-
)
|
128
|
-
return UserData(user_id=user_dict.get("uid"), **user_dict)
|
129
|
-
|
130
|
-
async def get_user_payload(self, user_id: str, **kwargs) -> dict:
|
131
|
-
return await self._request(
|
132
|
-
endpoint=f"website/users/{user_id}/payload", **kwargs
|
133
|
-
)
|
134
|
-
|
135
|
-
async def update_user_payload(
|
136
|
-
self,
|
137
|
-
user_id: str,
|
138
|
-
payload: dict,
|
139
|
-
**kwargs,
|
140
|
-
) -> dict:
|
141
|
-
return await self._request(
|
142
|
-
method="patch",
|
143
|
-
endpoint=f"website/users/{user_id}/payload",
|
144
|
-
data=payload,
|
145
|
-
**kwargs,
|
146
|
-
)
|
147
|
-
|
148
|
-
async def set_user_payload(
|
149
|
-
self,
|
150
|
-
user_id: str,
|
151
|
-
payload: dict,
|
152
|
-
**kwargs,
|
153
|
-
) -> dict:
|
154
|
-
return await self._request(
|
155
|
-
method="put",
|
156
|
-
endpoint=f"website/users/{user_id}/payload",
|
157
|
-
data=payload,
|
158
|
-
**kwargs,
|
159
|
-
)
|
usso/core.py
DELETED
@@ -1,160 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
import logging
|
3
|
-
import os
|
4
|
-
from datetime import datetime, timedelta
|
5
|
-
from urllib.parse import urlparse
|
6
|
-
|
7
|
-
import cachetools.func
|
8
|
-
import httpx
|
9
|
-
import jwt
|
10
|
-
|
11
|
-
from .exceptions import USSOException
|
12
|
-
from .schemas import JWTConfig, UserData
|
13
|
-
|
14
|
-
logger = logging.getLogger("usso")
|
15
|
-
|
16
|
-
|
17
|
-
def get_authorization_scheme_param(
|
18
|
-
authorization_header_value: str | None,
|
19
|
-
) -> tuple[str, str]:
|
20
|
-
if not authorization_header_value:
|
21
|
-
return "", ""
|
22
|
-
scheme, _, param = authorization_header_value.partition(" ")
|
23
|
-
return scheme, param
|
24
|
-
|
25
|
-
|
26
|
-
def decode_token(key, token: str, algorithms=["RS256"], **kwargs) -> dict:
|
27
|
-
"""Decode a JWT token."""
|
28
|
-
try:
|
29
|
-
decoded = jwt.decode(token, key, algorithms=algorithms)
|
30
|
-
decoded.update({"data": decoded, "token": token})
|
31
|
-
return UserData(**decoded)
|
32
|
-
except jwt.ExpiredSignatureError:
|
33
|
-
_handle_exception("expired_signature", **kwargs)
|
34
|
-
except jwt.InvalidSignatureError:
|
35
|
-
_handle_exception("invalid_signature", **kwargs)
|
36
|
-
except jwt.InvalidAlgorithmError:
|
37
|
-
_handle_exception("invalid_algorithm", **kwargs)
|
38
|
-
except jwt.InvalidIssuedAtError:
|
39
|
-
_handle_exception("invalid_issued_at", **kwargs)
|
40
|
-
except jwt.InvalidTokenError:
|
41
|
-
_handle_exception("invalid_token", **kwargs)
|
42
|
-
except jwt.InvalidKeyError:
|
43
|
-
_handle_exception("invalid_key", **kwargs)
|
44
|
-
except Exception as e:
|
45
|
-
_handle_exception("error", message=str(e), **kwargs)
|
46
|
-
|
47
|
-
|
48
|
-
def _handle_exception(error_type: str, **kwargs):
|
49
|
-
"""Handle JWT-related exceptions."""
|
50
|
-
if kwargs.get("raise_exception", True):
|
51
|
-
raise USSOException(
|
52
|
-
status_code=401, error=error_type, message=kwargs.get("message")
|
53
|
-
)
|
54
|
-
logger.error(kwargs.get("message") or error_type)
|
55
|
-
|
56
|
-
|
57
|
-
def is_expired(token: str, **kwargs) -> bool:
|
58
|
-
now = datetime.now()
|
59
|
-
decoded_token: dict = jwt.decode(token, options={"verify_signature": False})
|
60
|
-
exp = decoded_token.get("exp", (now + timedelta(days=1)).timestamp())
|
61
|
-
exp = datetime.fromtimestamp(exp)
|
62
|
-
return exp < now
|
63
|
-
|
64
|
-
|
65
|
-
@cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
|
66
|
-
def get_jwk_keys(jwk_url: str) -> jwt.PyJWKClient:
|
67
|
-
return jwt.PyJWKClient(jwk_url, headers={"User-Agent": "usso-python"})
|
68
|
-
|
69
|
-
|
70
|
-
def decode_token_with_jwk(jwk_url: str, token: str, **kwargs) -> UserData | None:
|
71
|
-
"""Return the user associated with a token value."""
|
72
|
-
try:
|
73
|
-
jwk_client = get_jwk_keys(jwk_url)
|
74
|
-
signing_key = jwk_client.get_signing_key_from_jwt(token)
|
75
|
-
return decode_token(signing_key.key, token, **kwargs)
|
76
|
-
except Exception as e:
|
77
|
-
_handle_exception("error", message=str(e), **kwargs)
|
78
|
-
|
79
|
-
|
80
|
-
@cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
|
81
|
-
def fetch_api_key_data(jwk_url: str, api_key: str):
|
82
|
-
try:
|
83
|
-
parsed = urlparse(jwk_url)
|
84
|
-
url = f"{parsed.scheme}://{parsed.netloc}/api_key/verify"
|
85
|
-
response = httpx.post(url, json={"api_key": api_key})
|
86
|
-
response.raise_for_status()
|
87
|
-
return UserData(**response.json())
|
88
|
-
except Exception as e:
|
89
|
-
_handle_exception("error", message=str(e))
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
class Usso:
|
94
|
-
def __init__(
|
95
|
-
self,
|
96
|
-
*,
|
97
|
-
jwt_config: (
|
98
|
-
str | dict | JWTConfig | list[str] | list[dict] | list[JWTConfig] | None
|
99
|
-
) = None,
|
100
|
-
jwk_url: str | None = None,
|
101
|
-
secret: str | None = None,
|
102
|
-
):
|
103
|
-
self.jwt_configs = self._initialize_configs(jwt_config, jwk_url, secret)
|
104
|
-
|
105
|
-
def _initialize_configs(
|
106
|
-
self,
|
107
|
-
jwt_config: (
|
108
|
-
str | dict | JWTConfig | list[str] | list[dict] | list[JWTConfig] | None
|
109
|
-
) = None,
|
110
|
-
jwk_url: str | None = None,
|
111
|
-
secret: str | None = None,
|
112
|
-
):
|
113
|
-
"""Initialize JWT configurations."""
|
114
|
-
if jwt_config is None:
|
115
|
-
jwt_config = os.getenv("USSO_JWT_CONFIG")
|
116
|
-
|
117
|
-
if jwt_config is None:
|
118
|
-
jwk_url = jwk_url or os.getenv("USSO_JWK_URL") or os.getenv("USSO_JWKS_URL")
|
119
|
-
secret = secret or os.getenv("USSO_SECRET")
|
120
|
-
if jwk_url:
|
121
|
-
return [JWTConfig(jwk_url=jwk_url)]
|
122
|
-
if secret:
|
123
|
-
return [JWTConfig(secret=secret)]
|
124
|
-
raise ValueError(
|
125
|
-
"Provide jwt_config, jwk_url, or secret, or set the appropriate environment variables."
|
126
|
-
)
|
127
|
-
|
128
|
-
if isinstance(jwt_config, (str, dict, JWTConfig)):
|
129
|
-
return [self._parse_config(jwt_config)]
|
130
|
-
if isinstance(jwt_config, list):
|
131
|
-
return [self._parse_config(config) for config in jwt_config]
|
132
|
-
raise ValueError("Invalid jwt_config format")
|
133
|
-
|
134
|
-
def _parse_config(self, config):
|
135
|
-
"""Parse a single JWT configuration."""
|
136
|
-
if isinstance(config, str):
|
137
|
-
config = json.loads(config)
|
138
|
-
if isinstance(config, dict):
|
139
|
-
return JWTConfig(**config)
|
140
|
-
return config
|
141
|
-
|
142
|
-
def user_data_from_token(self, token: str, **kwargs) -> UserData | None:
|
143
|
-
"""Return the user associated with a token value."""
|
144
|
-
exp = None
|
145
|
-
for jwk_config in self.jwt_configs:
|
146
|
-
try:
|
147
|
-
user_data = jwk_config.decode(token)
|
148
|
-
if user_data.token_type.lower() != kwargs.get("token_type", "access"):
|
149
|
-
_handle_exception("invalid_token_type", **kwargs)
|
150
|
-
return user_data
|
151
|
-
except USSOException as e:
|
152
|
-
exp = e
|
153
|
-
|
154
|
-
if kwargs.get("raise_exception", True):
|
155
|
-
if exp:
|
156
|
-
_handle_exception(exp.error, message=str(exp), **kwargs)
|
157
|
-
_handle_exception("unauthorized", **kwargs)
|
158
|
-
|
159
|
-
def user_data_from_api_key(self, api_key: str):
|
160
|
-
return fetch_api_key_data(self.jwt_configs[0].jwk_url, api_key)
|
usso/fastapi/__init__.py
DELETED
usso/fastapi/integration.py
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
import logging
|
2
|
-
|
3
|
-
from fastapi import Request, WebSocket
|
4
|
-
from fastapi.responses import JSONResponse
|
5
|
-
from starlette.status import HTTP_401_UNAUTHORIZED
|
6
|
-
|
7
|
-
from usso.exceptions import USSOException
|
8
|
-
|
9
|
-
from ..core import UserData, Usso, get_authorization_scheme_param
|
10
|
-
|
11
|
-
logger = logging.getLogger("usso")
|
12
|
-
|
13
|
-
|
14
|
-
def get_request_token(request: Request | WebSocket) -> UserData | None:
|
15
|
-
authorization = request.headers.get("Authorization")
|
16
|
-
token = None
|
17
|
-
|
18
|
-
if authorization:
|
19
|
-
scheme, credentials = get_authorization_scheme_param(authorization)
|
20
|
-
if scheme.lower() == "bearer":
|
21
|
-
token = credentials
|
22
|
-
|
23
|
-
else:
|
24
|
-
cookie_token = request.cookies.get("usso_access_token")
|
25
|
-
if cookie_token:
|
26
|
-
token = cookie_token
|
27
|
-
|
28
|
-
return token
|
29
|
-
|
30
|
-
|
31
|
-
def jwt_access_security_None(request: Request, jwt_config=None) -> UserData | None:
|
32
|
-
"""Return the user associated with a token value."""
|
33
|
-
api_key = request.headers.get("x-api-key")
|
34
|
-
if api_key:
|
35
|
-
return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
|
36
|
-
|
37
|
-
token = get_request_token(request)
|
38
|
-
if not token:
|
39
|
-
return None
|
40
|
-
return Usso(jwt_config=jwt_config).user_data_from_token(
|
41
|
-
token, raise_exception=False
|
42
|
-
)
|
43
|
-
|
44
|
-
|
45
|
-
def jwt_access_security(request: Request, jwt_config=None) -> UserData | None:
|
46
|
-
"""Return the user associated with a token value."""
|
47
|
-
api_key = request.headers.get("x-api-key")
|
48
|
-
if api_key:
|
49
|
-
return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
|
50
|
-
|
51
|
-
token = get_request_token(request)
|
52
|
-
if not token:
|
53
|
-
raise USSOException(
|
54
|
-
status_code=HTTP_401_UNAUTHORIZED,
|
55
|
-
error="unauthorized",
|
56
|
-
message="No token provided",
|
57
|
-
)
|
58
|
-
|
59
|
-
return Usso(jwt_config=jwt_config).user_data_from_token(token)
|
60
|
-
|
61
|
-
|
62
|
-
def jwt_access_security_ws(websocket: WebSocket, jwt_config=None) -> UserData | None:
|
63
|
-
"""Return the user associated with a token value."""
|
64
|
-
api_key = websocket.headers.get("x-api-key")
|
65
|
-
if api_key:
|
66
|
-
return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
|
67
|
-
|
68
|
-
token = get_request_token(websocket)
|
69
|
-
if not token:
|
70
|
-
raise USSOException(
|
71
|
-
status_code=HTTP_401_UNAUTHORIZED,
|
72
|
-
error="unauthorized",
|
73
|
-
message="No token provided",
|
74
|
-
)
|
75
|
-
|
76
|
-
return Usso(jwt_config=jwt_config).user_data_from_token(token)
|
77
|
-
|
78
|
-
|
79
|
-
async def usso_exception_handler(request: Request, exc: USSOException):
|
80
|
-
return JSONResponse(
|
81
|
-
status_code=exc.status_code,
|
82
|
-
content={"message": exc.message, "error": exc.error},
|
83
|
-
)
|
84
|
-
|
85
|
-
|
86
|
-
EXCEPTION_HANDLERS = {
|
87
|
-
USSOException: usso_exception_handler,
|
88
|
-
}
|
usso/schemas.py
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
import uuid
|
2
|
-
|
3
|
-
import cachetools.func
|
4
|
-
from pydantic import BaseModel, model_validator
|
5
|
-
|
6
|
-
from . import b64tools
|
7
|
-
|
8
|
-
|
9
|
-
class UserData(BaseModel):
|
10
|
-
user_id: str
|
11
|
-
workspace_id: str | None = None
|
12
|
-
workspace_ids: list[str] = []
|
13
|
-
token_type: str = "access"
|
14
|
-
|
15
|
-
email: str | None = None
|
16
|
-
phone: str | None = None
|
17
|
-
username: str | None = None
|
18
|
-
|
19
|
-
authentication_method: str | None = None
|
20
|
-
is_active: bool = False
|
21
|
-
|
22
|
-
jti: str | None = None
|
23
|
-
data: dict | None = None
|
24
|
-
|
25
|
-
token: str | None = None
|
26
|
-
|
27
|
-
@property
|
28
|
-
def uid(self) -> uuid.UUID:
|
29
|
-
user_id = self.user_id
|
30
|
-
|
31
|
-
if user_id.startswith("u_"):
|
32
|
-
user_id = user_id[2:]
|
33
|
-
if 22 <= len(user_id) <= 24:
|
34
|
-
user_id = b64tools.b64_decode_uuid(user_id)
|
35
|
-
|
36
|
-
return uuid.UUID(user_id)
|
37
|
-
|
38
|
-
@property
|
39
|
-
def b64id(self) -> uuid.UUID:
|
40
|
-
return b64tools.b64_encode_uuid_strip(self.uid)
|
41
|
-
|
42
|
-
|
43
|
-
class JWTConfig(BaseModel):
|
44
|
-
"""Configuration for JWT processing."""
|
45
|
-
|
46
|
-
jwk_url: str | None = None
|
47
|
-
secret: str | None = None
|
48
|
-
algorithm: str = "RS256"
|
49
|
-
header: dict[str, str] = {"type": "Cookie", "name": "usso_access_token"}
|
50
|
-
|
51
|
-
def __hash__(self):
|
52
|
-
return hash(self.model_dump_json())
|
53
|
-
|
54
|
-
@model_validator(mode="before")
|
55
|
-
def validate_config(cls, data: dict):
|
56
|
-
if not data.get("jwk_url") and not data.get("secret"):
|
57
|
-
raise ValueError("Either jwk_url or secret must be provided")
|
58
|
-
return data
|
59
|
-
|
60
|
-
@cachetools.func.ttl_cache(maxsize=128, ttl=600)
|
61
|
-
def decode(self, token: str):
|
62
|
-
"""Decode a token using the configured method."""
|
63
|
-
from .core import decode_token, decode_token_with_jwk
|
64
|
-
|
65
|
-
if self.jwk_url:
|
66
|
-
return decode_token_with_jwk(self.jwk_url, token)
|
67
|
-
return decode_token(self.secret, token, algorithms=[self.algorithm])
|