auth0-server-python 1.0.0b1__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.
utils/helpers.py ADDED
@@ -0,0 +1,225 @@
1
+ import base64
2
+ import hashlib
3
+ import secrets
4
+ import string
5
+ import time
6
+ from typing import Dict, Any, Optional
7
+ from urllib.parse import urlencode, urlparse, parse_qs
8
+
9
+ class PKCE:
10
+ @classmethod
11
+ def generate_random_string(cls, length: int = 64) -> str:
12
+ """
13
+ Generate a cryptographically secure random string.
14
+ """
15
+ alphabet = string.ascii_letters + string.digits
16
+ return ''.join(secrets.choice(alphabet) for _ in range(length))
17
+
18
+ @classmethod
19
+ def generate_code_verifier(cls, length: int = 64) -> str:
20
+ """
21
+ Generate a PKCE code verifier.
22
+ """
23
+ return cls.generate_random_string(length)
24
+
25
+ @classmethod
26
+ def generate_code_challenge(cls, code_verifier: str) -> str:
27
+ """
28
+ Generate a PKCE code challenge from a code verifier.
29
+ """
30
+ digest = hashlib.sha256(code_verifier.encode()).digest()
31
+ challenge = base64.urlsafe_b64encode(digest).decode('utf-8')
32
+ return challenge.rstrip('=')
33
+
34
+
35
+ class State:
36
+ @classmethod
37
+ def update_state_data(
38
+ cls,
39
+ audience: str,
40
+ state_data: Optional[Dict[str, Any]],
41
+ token_endpoint_response: Dict[str, Any]
42
+ ) -> Dict[str, Any]:
43
+ """
44
+ Utility function to update the state with a new response from the token endpoint
45
+
46
+ Args:
47
+ audience: The audience of the token endpoint response
48
+ state_data: The existing state data to update, or None if no state data available
49
+ token_endpoint_response: The response from the token endpoint
50
+
51
+ Returns:
52
+ Updated state data
53
+ """
54
+ current_time = int(time.time())
55
+
56
+ if state_data and hasattr(state_data, "dict") and callable(state_data.dict):
57
+ state_data_dict = state_data.dict()
58
+ else:
59
+ state_data_dict = state_data or {}
60
+
61
+ if state_data_dict:
62
+ # Check if we need to add a new token set or update an existing one
63
+ is_new_token_set = True
64
+ token_sets = state_data_dict.get("token_sets", [])
65
+
66
+ for token_set in token_sets:
67
+ if (token_set.get("audience") == audience and
68
+ token_set.get("scope") == token_endpoint_response.get("scope")):
69
+ is_new_token_set = False
70
+ break
71
+
72
+ # Create the updated token set
73
+ updated_token_set = {
74
+ "audience": audience,
75
+ "access_token": token_endpoint_response.get("access_token"),
76
+ "scope": token_endpoint_response.get("scope"),
77
+ "expires_at": current_time + int(token_endpoint_response.get("expires_in", 0))
78
+ }
79
+
80
+ # Update or add the token set
81
+ if is_new_token_set:
82
+ token_sets = token_sets + [updated_token_set]
83
+ else:
84
+ token_sets = [
85
+ updated_token_set if (ts.get("audience") == audience and
86
+ ts.get("scope") == token_endpoint_response.get("scope"))
87
+ else ts
88
+ for ts in token_sets
89
+ ]
90
+ # Return updated state data
91
+ return {
92
+ **state_data_dict,
93
+ "id_token": token_endpoint_response.get("id_token"),
94
+ "refresh_token": token_endpoint_response.get("refresh_token") or state_data_dict.get("refresh_token"),
95
+ "token_sets": token_sets
96
+ }
97
+ else:
98
+ # Create completely new state data
99
+ user = token_endpoint_response.get("claims", {})
100
+ return {
101
+ "user": user,
102
+ "id_token": token_endpoint_response.get("id_token"),
103
+ "refresh_token": token_endpoint_response.get("refresh_token"),
104
+ "token_sets": [
105
+ {
106
+ "audience": audience,
107
+ "access_token": token_endpoint_response.get("access_token"),
108
+ "scope": token_endpoint_response.get("scope"),
109
+ "expires_at": current_time + int(token_endpoint_response.get("expires_in", 0))
110
+ }
111
+ ],
112
+ "internal": {
113
+ "sid": user.get("sid", ""),
114
+ "created_at": current_time
115
+ }
116
+ }
117
+
118
+
119
+ @classmethod
120
+ def update_state_data_for_connection_token_set(
121
+ cls,
122
+ options: Dict[str, Any],
123
+ state_data: Dict[str, Any],
124
+ token_endpoint_response: Dict[str, Any]
125
+ ) -> Dict[str, Any]:
126
+ """
127
+ Update state data with connection token set information
128
+
129
+ Args:
130
+ options: Options containing connection details
131
+ state_data: Existing state data
132
+ token_endpoint_response: Response from token endpoint
133
+
134
+ Returns:
135
+ Updated state data
136
+ """
137
+ # Initialize connection_token_sets if it doesn't exist
138
+ connection_token_sets = state_data.get("connection_token_sets", [])
139
+
140
+ # Check if we need to add a new token set or update an existing one
141
+ is_new_token_set = True
142
+
143
+ for token_set in connection_token_sets:
144
+ if (token_set.get("connection") == options.get("connection") and
145
+ (not options.get("login_hint") or token_set.get("login_hint") == options.get("login_hint"))):
146
+ is_new_token_set = False
147
+ break
148
+
149
+ # Create the connection token set
150
+ connection_token_set = {
151
+ "connection": options.get("connection"),
152
+ "login_hint": options.get("login_hint"),
153
+ "access_token": token_endpoint_response.get("access_token"),
154
+ "scope": token_endpoint_response.get("scope"),
155
+ "expires_at": int(time.time()) + int(token_endpoint_response.get("expires_in", 0))
156
+ }
157
+
158
+ # Update or add the token set
159
+ if is_new_token_set:
160
+ connection_token_sets = connection_token_sets + [connection_token_set]
161
+ else:
162
+ connection_token_sets = [
163
+ connection_token_set if (ts.get("connection") == options.get("connection") and
164
+ (not options.get("login_hint") or
165
+ ts.get("login_hint") == options.get("login_hint")))
166
+ else ts
167
+ for ts in connection_token_sets
168
+ ]
169
+
170
+ # Return updated state data
171
+ return {
172
+ **state_data,
173
+ "connection_token_sets": connection_token_sets
174
+ }
175
+
176
+ class URL:
177
+ @staticmethod
178
+ def build_url(base_url: str, params: Dict[str, Any]) -> str:
179
+ """
180
+ Build a complete URL by appending query parameters to a base URL.
181
+
182
+ Args:
183
+ base_url: The base URL without query parameters.
184
+ params: A dictionary of query parameters to add.
185
+
186
+ Returns:
187
+ The complete URL with the query parameters appended.
188
+ """
189
+ query_string = urlencode(params)
190
+ separator = '?' if '?' not in base_url else '&'
191
+ return f"{base_url}{separator}{query_string}" if query_string else base_url
192
+
193
+ @staticmethod
194
+ def parse_url_params(url: str) -> Dict[str, str]:
195
+ """
196
+ Parse the query parameters from a URL.
197
+
198
+ Args:
199
+ url: The URL to parse.
200
+
201
+ Returns:
202
+ A dictionary of query parameters, converting list values to a single string.
203
+ """
204
+ parsed_url = urlparse(url)
205
+ query_params = parse_qs(parsed_url.query)
206
+ return {k: v[0] if v and len(v) > 0 else '' for k, v in query_params.items()}
207
+
208
+ @staticmethod
209
+ def create_logout_url(domain: str, client_id: str, return_to: Optional[str] = None) -> str:
210
+ """
211
+ Create an Auth0 logout URL.
212
+
213
+ Args:
214
+ domain: Auth0 domain.
215
+ client_id: Auth0 client ID.
216
+ return_to: Optional URL to redirect to after logout.
217
+
218
+ Returns:
219
+ The complete logout URL.
220
+ """
221
+ base_url = f"https://{domain}/v2/logout"
222
+ params = {"client_id": client_id}
223
+ if return_to:
224
+ params["returnTo"] = return_to
225
+ return URL.build_url(base_url, params)