jamlib 3.0.0a10__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.
- jam/__base__.py +451 -0
- jam/__base_encoder__.py +20 -0
- jam/__deprecated__.py +27 -0
- jam/__init__.py +17 -0
- jam/aio/__init__.py +10 -0
- jam/aio/instance.py +371 -0
- jam/aio/jwt/__init__.py +1 -0
- jam/aio/jwt/lists/__init__.py +1 -0
- jam/aio/jwt/lists/json.py +88 -0
- jam/aio/jwt/lists/redis.py +88 -0
- jam/aio/jwt/tools.py +88 -0
- jam/aio/oauth2/__init__.py +61 -0
- jam/aio/oauth2/builtin/__init__.py +3 -0
- jam/aio/oauth2/builtin/github.py +28 -0
- jam/aio/oauth2/builtin/gitlab.py +28 -0
- jam/aio/oauth2/builtin/google.py +28 -0
- jam/aio/oauth2/builtin/yandex.py +31 -0
- jam/aio/oauth2/client.py +151 -0
- jam/aio/sessions/__init__.py +87 -0
- jam/aio/sessions/json.py +85 -0
- jam/aio/sessions/redis.py +243 -0
- jam/encoders.py +47 -0
- jam/exceptions/__init__.py +19 -0
- jam/exceptions/jwt.py +39 -0
- jam/exceptions/oauth2.py +11 -0
- jam/exceptions/sessions.py +11 -0
- jam/ext/__init__.py +3 -0
- jam/ext/fastapi/__init__.py +11 -0
- jam/ext/flask/__init__.py +11 -0
- jam/ext/flask/extensions.py +161 -0
- jam/ext/litestar/__init__.py +13 -0
- jam/ext/litestar/middlewares.py +113 -0
- jam/ext/litestar/plugins.py +121 -0
- jam/ext/litestar/value.py +28 -0
- jam/ext/starlette/__init__.py +12 -0
- jam/ext/starlette/auth_backends.py +134 -0
- jam/ext/starlette/value.py +18 -0
- jam/instance.py +367 -0
- jam/jwt/__algorithms__.py +466 -0
- jam/jwt/__base__.py +38 -0
- jam/jwt/__init__.py +61 -0
- jam/jwt/__types__.py +15 -0
- jam/jwt/lists/__abc_list_repo__.py +27 -0
- jam/jwt/lists/__init__.py +11 -0
- jam/jwt/lists/json.py +110 -0
- jam/jwt/lists/redis.py +103 -0
- jam/jwt/module.py +228 -0
- jam/jwt/utils.py +32 -0
- jam/logger.py +76 -0
- jam/modules.py +29 -0
- jam/oauth2/__base__.py +113 -0
- jam/oauth2/__init__.py +69 -0
- jam/oauth2/builtin/__init__.py +3 -0
- jam/oauth2/builtin/github.py +28 -0
- jam/oauth2/builtin/gitlab.py +28 -0
- jam/oauth2/builtin/google.py +28 -0
- jam/oauth2/builtin/yandex.py +31 -0
- jam/oauth2/client.py +149 -0
- jam/otp/__base__.py +109 -0
- jam/otp/__init__.py +35 -0
- jam/otp/hotp.py +36 -0
- jam/otp/totp.py +73 -0
- jam/paseto/__base__.py +162 -0
- jam/paseto/__init__.py +57 -0
- jam/paseto/utils.py +99 -0
- jam/paseto/v1.py +314 -0
- jam/paseto/v2.py +235 -0
- jam/paseto/v3.py +361 -0
- jam/paseto/v4.py +276 -0
- jam/sessions/__base__.py +202 -0
- jam/sessions/__init__.py +89 -0
- jam/sessions/json.py +192 -0
- jam/sessions/redis.py +247 -0
- jam/tests/__init__.py +5 -0
- jam/tests/clients.py +914 -0
- jam/tests/fakers.py +39 -0
- jam/utils/__init__.py +35 -0
- jam/utils/aes.py +8 -0
- jam/utils/await_maybe.py +22 -0
- jam/utils/basic_auth.py +47 -0
- jam/utils/config_maker.py +236 -0
- jam/utils/ed.py +50 -0
- jam/utils/otp_keys.py +48 -0
- jam/utils/rsa.py +46 -0
- jam/utils/salt_hash.py +114 -0
- jam/utils/symmetric.py +17 -0
- jam/utils/xchacha20poly1305.py +61 -0
- jam/utils/xor.py +35 -0
- jamlib-3.0.0a10.dist-info/METADATA +117 -0
- jamlib-3.0.0a10.dist-info/RECORD +93 -0
- jamlib-3.0.0a10.dist-info/WHEEL +5 -0
- jamlib-3.0.0a10.dist-info/licenses/LICENSE.md +173 -0
- jamlib-3.0.0a10.dist-info/top_level.txt +1 -0
jam/aio/instance.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from this import d
|
|
5
|
+
from typing import Any, Optional, Union, Literal
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
from jam.__base__ import BaseJam
|
|
9
|
+
from jam.logger import BaseLogger, JamLogger
|
|
10
|
+
from jam.encoders import BaseEncoder, JsonEncoder
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Jam(BaseJam):
|
|
14
|
+
"""Main instance for aio."""
|
|
15
|
+
|
|
16
|
+
MODULES: dict[str, str] = {}
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
config: Union[str, dict[str, Any]] = "pyproject.toml",
|
|
21
|
+
pointer: str = "jam",
|
|
22
|
+
*,
|
|
23
|
+
logger: type(BaseLogger) = JamLogger,
|
|
24
|
+
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
|
|
25
|
+
serializer: Union[BaseEncoder, type[BaseEncoder]] = JsonEncoder,
|
|
26
|
+
) -> None:
|
|
27
|
+
super().__init__(config, pointer, logger, log_level, serializer)
|
|
28
|
+
self.__logger.warn("AIO VERSION NOT WORKING IN unstable VERSION")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def jwt_make_payload(
|
|
34
|
+
self, exp: Optional[int], data: dict[str, Any]
|
|
35
|
+
) -> dict[str, Any]:
|
|
36
|
+
"""Make JWT-specific payload.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
exp (int | None): Token expire, if None -> use default
|
|
40
|
+
data (dict[str, Any]): Data to payload
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
dict[str, Any]: Payload
|
|
44
|
+
"""
|
|
45
|
+
payload = {
|
|
46
|
+
"iat": datetime.now().timestamp(),
|
|
47
|
+
"exp": (datetime.now().timestamp() + exp) if exp else None,
|
|
48
|
+
"jti": str(uuid.uuid4()),
|
|
49
|
+
}
|
|
50
|
+
payload = payload | data
|
|
51
|
+
return payload
|
|
52
|
+
|
|
53
|
+
async def jwt_create_token(self, payload: dict[str, Any]) -> str:
|
|
54
|
+
"""Create JWT token.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
payload (dict[str, Any]): Data payload
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
str: New token
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None
|
|
64
|
+
EmtpyPrivateKey: If RSA algorithm is selected, but private key None
|
|
65
|
+
"""
|
|
66
|
+
return self.jwt.encode(payload=payload)
|
|
67
|
+
|
|
68
|
+
async def jwt_verify_token(
|
|
69
|
+
self, token: str, check_exp: bool = True, check_list: bool = True
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
"""Verify and decode JWT token.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
token (str): JWT token
|
|
75
|
+
check_exp (bool): Check expire
|
|
76
|
+
check_list (bool): Check white/black list. Docs: https://jam.makridenko.ru/jwt/lists/what/
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
dict[str, Any]: Decoded payload
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: If the token is invalid.
|
|
83
|
+
EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None.
|
|
84
|
+
EmtpyPublicKey: If RSA algorithm is selected, but public key None.
|
|
85
|
+
NotFoundSomeInPayload: If 'exp' not found in payload.
|
|
86
|
+
TokenLifeTimeExpired: If token has expired.
|
|
87
|
+
TokenNotInWhiteList: If the list type is white, but the token is not there
|
|
88
|
+
TokenInBlackList: If the list type is black and the token is there
|
|
89
|
+
"""
|
|
90
|
+
return self.jwt.decode(token)
|
|
91
|
+
|
|
92
|
+
async def session_create(
|
|
93
|
+
self, session_key: str, data: dict[str, Any]
|
|
94
|
+
) -> str:
|
|
95
|
+
"""Create new session.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
session_key (str): Key for session
|
|
99
|
+
data (dict[str, Any]): Session data
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
str: New session ID
|
|
103
|
+
"""
|
|
104
|
+
return await self.session.create(session_key, data)
|
|
105
|
+
|
|
106
|
+
async def session_get(self, session_id: str) -> Optional[dict[str, Any]]:
|
|
107
|
+
"""Get data from session.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
session_id (str): Session ID
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
dict[str, Any] | None: Session data if exist
|
|
114
|
+
"""
|
|
115
|
+
return await self.session.get(session_id)
|
|
116
|
+
|
|
117
|
+
async def session_delete(self, session_id: str) -> None:
|
|
118
|
+
"""Delete session.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
session_id (str): Session ID
|
|
122
|
+
"""
|
|
123
|
+
return await self.session.delete(session_id)
|
|
124
|
+
|
|
125
|
+
async def session_update(
|
|
126
|
+
self, session_id: str, data: dict[str, Any]
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Update session data.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
session_id (str): Session ID
|
|
132
|
+
data (dict[str, Any]): New data
|
|
133
|
+
"""
|
|
134
|
+
return await self.session.update(session_id, data)
|
|
135
|
+
|
|
136
|
+
async def session_clear(self, session_key: str) -> None:
|
|
137
|
+
"""Delete all sessions by key.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
session_key (str): Key of session
|
|
141
|
+
"""
|
|
142
|
+
return await self.session.clear(session_key)
|
|
143
|
+
|
|
144
|
+
async def session_rework(self, old_session_id: str) -> str:
|
|
145
|
+
"""Rework session.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
old_session_id (str): Old session id
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
str: New session id
|
|
152
|
+
"""
|
|
153
|
+
return await self.session.rework(old_session_id)
|
|
154
|
+
|
|
155
|
+
def otp_code(
|
|
156
|
+
self, secret: Union[str, bytes], factor: Optional[int] = None
|
|
157
|
+
) -> str:
|
|
158
|
+
"""Generates an OTP.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
secret (str | bytes): User secret key.
|
|
162
|
+
factor (int | None, optional): Unixtime for TOTP(if none, use now time) / Counter for HOTP.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
str: OTP code (fixed-length string).
|
|
166
|
+
"""
|
|
167
|
+
self._otp_checker()
|
|
168
|
+
return self._otp_module(
|
|
169
|
+
secret=secret, digits=self._otp.digits, digest=self._otp.digest
|
|
170
|
+
).at(factor)
|
|
171
|
+
|
|
172
|
+
def otp_uri(
|
|
173
|
+
self,
|
|
174
|
+
secret: str,
|
|
175
|
+
name: Optional[str] = None,
|
|
176
|
+
issuer: Optional[str] = None,
|
|
177
|
+
counter: Optional[int] = None,
|
|
178
|
+
) -> str:
|
|
179
|
+
"""Generates an otpauth:// URI for Google Authenticator.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
secret (str): User secret key.
|
|
183
|
+
name (str): Account name (e.g., email).
|
|
184
|
+
issuer (str): Service name (e.g., "GitHub").
|
|
185
|
+
counter (int | None, optional): Counter (for HOTP). Default is None.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
str: A string of the form "otpauth://..."
|
|
189
|
+
"""
|
|
190
|
+
self._otp_checker()
|
|
191
|
+
return self._otp_module(
|
|
192
|
+
secret=secret, digits=self._otp.digits, digest=self._otp.digest
|
|
193
|
+
).provisioning_uri(
|
|
194
|
+
name=name, issuer=issuer, type_=self._otp.type, counter=counter
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def otp_verify_code(
|
|
198
|
+
self,
|
|
199
|
+
secret: Union[str, bytes],
|
|
200
|
+
code: str,
|
|
201
|
+
factor: Optional[int] = None,
|
|
202
|
+
look_ahead: Optional[int] = 1,
|
|
203
|
+
) -> bool:
|
|
204
|
+
"""Checks the OTP code, taking into account the acceptable window.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
secret (str | bytes): User secret key.
|
|
208
|
+
code (str): The code entered.
|
|
209
|
+
factor (int | None, optional): Unixtime for TOTP(if none, use now time) / Counter for HOTP.
|
|
210
|
+
look_ahead (int, optional): Acceptable deviation in intervals (±window(totp) / ±look ahead(hotp)). Default is 1.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
bool: True if the code matches, otherwise False.
|
|
214
|
+
"""
|
|
215
|
+
self._otp_checker()
|
|
216
|
+
return self._otp_module(
|
|
217
|
+
secret=secret, digits=self._otp.digits, digest=self._otp.digest
|
|
218
|
+
).verify(code=code, factor=factor, look_ahead=look_ahead)
|
|
219
|
+
|
|
220
|
+
async def oauth2_get_authorized_url(
|
|
221
|
+
self, provider: str, scope: list[str], **extra_params: Any
|
|
222
|
+
) -> str:
|
|
223
|
+
"""Generate full OAuth2 authorization URL.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
provider (str): Provider name
|
|
227
|
+
scope (list[str]): Auth scope
|
|
228
|
+
extra_params (Any): Extra ath params
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
str: Authorization url
|
|
232
|
+
"""
|
|
233
|
+
from jam.exceptions import ProviderNotConfigurError
|
|
234
|
+
|
|
235
|
+
if provider not in self.oauth2:
|
|
236
|
+
raise ProviderNotConfigurError(
|
|
237
|
+
f"Provider {provider} not configured"
|
|
238
|
+
)
|
|
239
|
+
return await self.oauth2[provider].get_authorization_url(
|
|
240
|
+
scope, **extra_params
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
async def oauth2_fetch_token(
|
|
244
|
+
self,
|
|
245
|
+
provider: str,
|
|
246
|
+
code: str,
|
|
247
|
+
grant_type: str = "authorization_code",
|
|
248
|
+
**extra_params: Any,
|
|
249
|
+
) -> dict[str, Any]:
|
|
250
|
+
"""Exchange authorization code for access token.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
provider (str): Provider name
|
|
254
|
+
code (str): OAuth2 code
|
|
255
|
+
grant_type (str): Type of oauth2 grant
|
|
256
|
+
extra_params (Any): Extra auth params if needed
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
dict: OAuth2 token
|
|
260
|
+
"""
|
|
261
|
+
from jam.exceptions import ProviderNotConfigurError
|
|
262
|
+
|
|
263
|
+
if provider not in self.oauth2:
|
|
264
|
+
raise ProviderNotConfigurError(
|
|
265
|
+
f"Provider {provider} not configured"
|
|
266
|
+
)
|
|
267
|
+
return await self.oauth2[provider].fetch_token(
|
|
268
|
+
code, grant_type, **extra_params
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
async def oauth2_refresh_token(
|
|
272
|
+
self,
|
|
273
|
+
provider: str,
|
|
274
|
+
refresh_token: str,
|
|
275
|
+
grant_type: str = "refresh_token",
|
|
276
|
+
**extra_params: Any,
|
|
277
|
+
) -> dict[str, Any]:
|
|
278
|
+
"""Use refresh token to obtain a new access token.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
provider (str): Provider name
|
|
282
|
+
refresh_token (str): Refresh token
|
|
283
|
+
grant_type (str): Grant type
|
|
284
|
+
extra_params (Any): Extra auth params if needed
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
dict: Refresh token
|
|
288
|
+
"""
|
|
289
|
+
from jam.exceptions import ProviderNotConfigurError
|
|
290
|
+
|
|
291
|
+
if provider not in self.oauth2:
|
|
292
|
+
raise ProviderNotConfigurError(
|
|
293
|
+
f"Provider {provider} not configured"
|
|
294
|
+
)
|
|
295
|
+
return await self.oauth2[provider].refresh_token(
|
|
296
|
+
refresh_token, grant_type, **extra_params
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
async def oauth2_client_credentials_flow(
|
|
300
|
+
self,
|
|
301
|
+
provider: str,
|
|
302
|
+
scope: Optional[list[str]] = None,
|
|
303
|
+
**extra_params: Any,
|
|
304
|
+
) -> dict[str, Any]:
|
|
305
|
+
"""Obtain access token using client credentials flow (no user interaction).
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
provider (str): Provider name
|
|
309
|
+
scope (list[str] | None): Auth scope
|
|
310
|
+
extra_params (Any): Extra auth params if needed
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
dict: JSON with access token
|
|
314
|
+
"""
|
|
315
|
+
from jam.exceptions import ProviderNotConfigurError
|
|
316
|
+
|
|
317
|
+
if provider not in self.oauth2:
|
|
318
|
+
raise ProviderNotConfigurError(
|
|
319
|
+
f"Provider {provider} not configured"
|
|
320
|
+
)
|
|
321
|
+
return await self.oauth2[provider].client_credentials_flow(
|
|
322
|
+
scope, **extra_params
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
async def paseto_make_payload(
|
|
326
|
+
self, exp: Optional[int] = None, **data: dict[str, Any]
|
|
327
|
+
) -> dict[str, Any]:
|
|
328
|
+
"""Generate payload for PASETO.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
exp (int | None): Token expire
|
|
332
|
+
data (dict[str, Any]): Custom data
|
|
333
|
+
|
|
334
|
+
Returns:
|
|
335
|
+
dict: Payload
|
|
336
|
+
"""
|
|
337
|
+
from jam.paseto.utils import payload_maker
|
|
338
|
+
|
|
339
|
+
return payload_maker(expire=exp, data=data)
|
|
340
|
+
|
|
341
|
+
async def paseto_create(
|
|
342
|
+
self,
|
|
343
|
+
payload: dict[str, Any],
|
|
344
|
+
footer: Optional[Union[dict[str, Any], str]],
|
|
345
|
+
) -> str:
|
|
346
|
+
"""Create new PASETO.
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
payload (dict[str, Any]): Payload
|
|
350
|
+
footer (dict | str | None): Footer
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
str: New token
|
|
354
|
+
"""
|
|
355
|
+
return self.paseto.encode(payload=payload, footer=footer)
|
|
356
|
+
|
|
357
|
+
async def paseto_decode(
|
|
358
|
+
self, token: str, check_exp: bool = True, check_list: bool = True
|
|
359
|
+
) -> dict[str, Union[dict, Union[str, dict, None]]]:
|
|
360
|
+
"""Decode PASETO.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
token (str): Token
|
|
364
|
+
check_exp (bool): Check exp in payload
|
|
365
|
+
check_list (bool): Check token in list
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
dict: {'payload' PAYLOAD, 'footer': FOOTER}
|
|
369
|
+
"""
|
|
370
|
+
payload, footer = self.paseto.decode(token)
|
|
371
|
+
return {"payload": payload, "footer": footer}
|
jam/aio/jwt/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from asyncio import to_thread
|
|
4
|
+
import datetime
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from jam.jwt.lists.__abc_list_repo__ import BaseJWTList
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from tinydb import Query, TinyDB
|
|
12
|
+
except ImportError:
|
|
13
|
+
raise ImportError(
|
|
14
|
+
"""
|
|
15
|
+
No required packages found, looks like you didn't install them:
|
|
16
|
+
`pip install "jamlib[json]"`
|
|
17
|
+
"""
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class JSONList(BaseJWTList):
|
|
22
|
+
"""Black/White list in JSON format, not recommended for blacklists because it is not convenient to control token lifetime.
|
|
23
|
+
|
|
24
|
+
Dependency required:
|
|
25
|
+
`pip install jamlib[json]`
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
__list__ (TinyDB): TinyDB instance
|
|
29
|
+
|
|
30
|
+
Methods:
|
|
31
|
+
add: adding token to list
|
|
32
|
+
check: check token in list
|
|
33
|
+
delete: removing token from list
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self, type: Literal["white", "black"], json_path: str = "whitelist.json"
|
|
38
|
+
) -> None:
|
|
39
|
+
"""Class constructor.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
type (Literal["white", "black"]): Type of list
|
|
43
|
+
json_path (str): Path to .json file
|
|
44
|
+
"""
|
|
45
|
+
super().__init__(list_type=type)
|
|
46
|
+
self.__list__ = TinyDB(json_path)
|
|
47
|
+
|
|
48
|
+
async def add(self, token: str) -> None:
|
|
49
|
+
"""Method for adding token to list.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
token (str): Your JWT token
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
(None)
|
|
56
|
+
"""
|
|
57
|
+
_doc = {
|
|
58
|
+
"token": token,
|
|
59
|
+
"timestamp": datetime.datetime.now().timestamp(),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await to_thread(self.__list__.insert, _doc)
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
async def check(self, token: str) -> bool:
|
|
66
|
+
"""Method for checking if a token is present in list.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
token (str): Your jwt token
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
(bool)
|
|
73
|
+
"""
|
|
74
|
+
cond = Query()
|
|
75
|
+
_token = await to_thread(self.__list__.search, cond.token == token)
|
|
76
|
+
return bool(_token)
|
|
77
|
+
|
|
78
|
+
async def delete(self, token: str) -> None:
|
|
79
|
+
"""Method for removing token from list.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
token (str): Your jwt token
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
(None)
|
|
86
|
+
"""
|
|
87
|
+
cond = Query()
|
|
88
|
+
await to_thread(self.__list__.remove, cond.token == token)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import Literal, Optional, Union
|
|
5
|
+
|
|
6
|
+
from jam.jwt.lists.__abc_list_repo__ import BaseJWTList
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from redis.asyncio import Redis
|
|
11
|
+
except ImportError:
|
|
12
|
+
raise ImportError(
|
|
13
|
+
"""
|
|
14
|
+
No required packages found, looks like you didn't install them:
|
|
15
|
+
`pip install "jamlib[redis]"`
|
|
16
|
+
"""
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RedisList(BaseJWTList):
|
|
21
|
+
"""Black/White lists in Redis, most optimal format.
|
|
22
|
+
|
|
23
|
+
Dependency required: `pip install jamlib[redis]`
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
__list__ (Redis): Redis instance
|
|
27
|
+
exp (int | None): Token lifetime
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
type: Literal["white", "black"],
|
|
33
|
+
redis_uri: Union[str, Redis],
|
|
34
|
+
in_list_life_time: Optional[int] = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Class constructor.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
type (Literal["white", "black"]): Type of list
|
|
40
|
+
redis_uri (str): Uri to redis connect
|
|
41
|
+
in_list_life_time (int | None): The lifetime of a token in the list
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(list_type=type)
|
|
44
|
+
if isinstance(redis_uri, str):
|
|
45
|
+
self.__list__ = Redis.from_url(redis_uri, decode_responses=True)
|
|
46
|
+
else:
|
|
47
|
+
self.__list__ = redis_uri
|
|
48
|
+
self.exp = in_list_life_time
|
|
49
|
+
|
|
50
|
+
async def add(self, token: str) -> None:
|
|
51
|
+
"""Method for adding token to list.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
token (str): Your JWT token
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
(None)
|
|
58
|
+
"""
|
|
59
|
+
await self.__list__.set(
|
|
60
|
+
name=token, value=str(datetime.datetime.now()), ex=self.exp
|
|
61
|
+
)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
async def check(self, token: str) -> bool:
|
|
65
|
+
"""Method for checking if a token is present in the list.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
token (str): Your JWT token
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
(bool)
|
|
72
|
+
"""
|
|
73
|
+
_token = await self.__list__.get(name=token)
|
|
74
|
+
if _token:
|
|
75
|
+
return True
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
async def delete(self, token: str) -> None:
|
|
79
|
+
"""Method for removing a token from a list.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
token (str): Your JWT token
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
None
|
|
86
|
+
"""
|
|
87
|
+
await self.__list__.delete(token)
|
|
88
|
+
return None
|
jam/aio/jwt/tools.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from asyncio import to_thread
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from jam.jwt.tools import __gen_jwt__, __payload_maker__, __validate_jwt__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def __gen_jwt_async__(
|
|
10
|
+
header: dict[str, Any],
|
|
11
|
+
payload: dict[str, Any],
|
|
12
|
+
secret: Optional[str] = None,
|
|
13
|
+
private_key: Optional[str] = None,
|
|
14
|
+
) -> str:
|
|
15
|
+
"""Method for generating JWT token with different algorithms.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
```python
|
|
19
|
+
token = await __gen_jwt_async__(
|
|
20
|
+
header={
|
|
21
|
+
"alg": "HS256",
|
|
22
|
+
"type": "jwt"
|
|
23
|
+
},
|
|
24
|
+
payload={
|
|
25
|
+
"id": 1
|
|
26
|
+
},
|
|
27
|
+
secret="SUPER_SECRET"
|
|
28
|
+
)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
header (dict[str, str]): Dict with JWT headers
|
|
33
|
+
payload (dict[str, Any]): Custom JWT payload
|
|
34
|
+
secret (str | None): Secret key for HMAC algorithms
|
|
35
|
+
private_key (str | None): Private key for RSA algorithms
|
|
36
|
+
|
|
37
|
+
Raises:
|
|
38
|
+
EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None
|
|
39
|
+
EmtpyPrivateKey: If RSA algorithm is selected, but private key None
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
(str): Access/refresh token
|
|
43
|
+
"""
|
|
44
|
+
return await to_thread(__gen_jwt__, header, payload, secret, private_key)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
async def __validate_jwt_async__(
|
|
48
|
+
token: str,
|
|
49
|
+
check_exp: bool = False,
|
|
50
|
+
secret: Optional[str] = None,
|
|
51
|
+
public_key: Optional[str] = None,
|
|
52
|
+
) -> dict[str, Any]:
|
|
53
|
+
"""Validate a JWT token and return the payload if valid.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
token (str): The JWT token to validate.
|
|
57
|
+
check_exp (bool): true to check token lifetime.
|
|
58
|
+
secret (str | None): Secret key for HMAC algorithms.
|
|
59
|
+
public_key (str | None): Public key for RSA algorithms.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
(dict[str, Any]): The payload if the token is valid.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If the token is invalid.
|
|
66
|
+
EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None.
|
|
67
|
+
EmtpyPublicKey: If RSA algorithm is selected, but public key None.
|
|
68
|
+
NotFoundSomeInPayload: If 'exp' not found in payload.
|
|
69
|
+
TokenLifeTimeExpired: If token has expired.
|
|
70
|
+
"""
|
|
71
|
+
return await to_thread(
|
|
72
|
+
__validate_jwt__, token, check_exp, secret, public_key
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def __payload_maker_async__(
|
|
77
|
+
exp: Optional[int] = None, **data: Any
|
|
78
|
+
) -> dict[str, Any]:
|
|
79
|
+
"""Tool for making base payload.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
exp (int | None): Token expire
|
|
83
|
+
**data: Data for payload
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
(dict[str, Any])
|
|
87
|
+
"""
|
|
88
|
+
return await to_thread(__payload_maker__, exp, **data)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""Async OAuth2 modules."""
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .builtin.github import GitHubOAuth2Client
|
|
8
|
+
from .builtin.gitlab import GitLabOAuth2Client
|
|
9
|
+
from .builtin.google import GoogleOAuth2Client
|
|
10
|
+
from .builtin.yandex import YandexOAuth2Client
|
|
11
|
+
from jam.oauth2.__abc_oauth2_repo__ import BaseOAuth2Client
|
|
12
|
+
from jam.logger import BaseLogger, logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
BUILTIN_PROVIDERS = {
|
|
16
|
+
"github": "jam.aio.oauth2.builtin.github.GitHubOAuth2Client",
|
|
17
|
+
"gitlab": "jam.aio.oauth2.builtin.gitlab.GitLabOAuth2Client",
|
|
18
|
+
"google": "jam.aio.oauth2.builtin.google.GoogleOAuth2Client",
|
|
19
|
+
"yandex": "jam.aio.oauth2.builtin.yandex.YandexOAuth2Client",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_instance(
|
|
24
|
+
providers: dict[str, dict],
|
|
25
|
+
logger: BaseLogger = logger,
|
|
26
|
+
**kwargs: Any
|
|
27
|
+
) -> dict[str, BaseOAuth2Client]:
|
|
28
|
+
"""Create async OAuth2 clients for configured providers.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
providers: {provider_name: {client_id, client_secret, redirect_uri, ...}}
|
|
32
|
+
logger: Logger instance
|
|
33
|
+
**kwargs: Additional params
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
dict: {provider_name: OAuth2Client instance}
|
|
37
|
+
"""
|
|
38
|
+
from jam.utils.config_maker import __module_loader__
|
|
39
|
+
|
|
40
|
+
result = {}
|
|
41
|
+
for name, cfg in providers.items():
|
|
42
|
+
cfg = cfg.copy() # Don't modify original config
|
|
43
|
+
|
|
44
|
+
if "custom_module" in cfg:
|
|
45
|
+
module_cls = __module_loader__(cfg.pop("custom_module"))
|
|
46
|
+
else:
|
|
47
|
+
module_path = BUILTIN_PROVIDERS.get(name, "jam.aio.oauth2.client.OAuth2Client")
|
|
48
|
+
module_cls = __module_loader__(module_path)
|
|
49
|
+
|
|
50
|
+
result[name] = module_cls(**cfg)
|
|
51
|
+
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"GitLabOAuth2Client",
|
|
57
|
+
"GitHubOAuth2Client",
|
|
58
|
+
"GoogleOAuth2Client",
|
|
59
|
+
"YandexOAuth2Client",
|
|
60
|
+
"create_instance",
|
|
61
|
+
]
|