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.
Files changed (93) hide show
  1. jam/__base__.py +451 -0
  2. jam/__base_encoder__.py +20 -0
  3. jam/__deprecated__.py +27 -0
  4. jam/__init__.py +17 -0
  5. jam/aio/__init__.py +10 -0
  6. jam/aio/instance.py +371 -0
  7. jam/aio/jwt/__init__.py +1 -0
  8. jam/aio/jwt/lists/__init__.py +1 -0
  9. jam/aio/jwt/lists/json.py +88 -0
  10. jam/aio/jwt/lists/redis.py +88 -0
  11. jam/aio/jwt/tools.py +88 -0
  12. jam/aio/oauth2/__init__.py +61 -0
  13. jam/aio/oauth2/builtin/__init__.py +3 -0
  14. jam/aio/oauth2/builtin/github.py +28 -0
  15. jam/aio/oauth2/builtin/gitlab.py +28 -0
  16. jam/aio/oauth2/builtin/google.py +28 -0
  17. jam/aio/oauth2/builtin/yandex.py +31 -0
  18. jam/aio/oauth2/client.py +151 -0
  19. jam/aio/sessions/__init__.py +87 -0
  20. jam/aio/sessions/json.py +85 -0
  21. jam/aio/sessions/redis.py +243 -0
  22. jam/encoders.py +47 -0
  23. jam/exceptions/__init__.py +19 -0
  24. jam/exceptions/jwt.py +39 -0
  25. jam/exceptions/oauth2.py +11 -0
  26. jam/exceptions/sessions.py +11 -0
  27. jam/ext/__init__.py +3 -0
  28. jam/ext/fastapi/__init__.py +11 -0
  29. jam/ext/flask/__init__.py +11 -0
  30. jam/ext/flask/extensions.py +161 -0
  31. jam/ext/litestar/__init__.py +13 -0
  32. jam/ext/litestar/middlewares.py +113 -0
  33. jam/ext/litestar/plugins.py +121 -0
  34. jam/ext/litestar/value.py +28 -0
  35. jam/ext/starlette/__init__.py +12 -0
  36. jam/ext/starlette/auth_backends.py +134 -0
  37. jam/ext/starlette/value.py +18 -0
  38. jam/instance.py +367 -0
  39. jam/jwt/__algorithms__.py +466 -0
  40. jam/jwt/__base__.py +38 -0
  41. jam/jwt/__init__.py +61 -0
  42. jam/jwt/__types__.py +15 -0
  43. jam/jwt/lists/__abc_list_repo__.py +27 -0
  44. jam/jwt/lists/__init__.py +11 -0
  45. jam/jwt/lists/json.py +110 -0
  46. jam/jwt/lists/redis.py +103 -0
  47. jam/jwt/module.py +228 -0
  48. jam/jwt/utils.py +32 -0
  49. jam/logger.py +76 -0
  50. jam/modules.py +29 -0
  51. jam/oauth2/__base__.py +113 -0
  52. jam/oauth2/__init__.py +69 -0
  53. jam/oauth2/builtin/__init__.py +3 -0
  54. jam/oauth2/builtin/github.py +28 -0
  55. jam/oauth2/builtin/gitlab.py +28 -0
  56. jam/oauth2/builtin/google.py +28 -0
  57. jam/oauth2/builtin/yandex.py +31 -0
  58. jam/oauth2/client.py +149 -0
  59. jam/otp/__base__.py +109 -0
  60. jam/otp/__init__.py +35 -0
  61. jam/otp/hotp.py +36 -0
  62. jam/otp/totp.py +73 -0
  63. jam/paseto/__base__.py +162 -0
  64. jam/paseto/__init__.py +57 -0
  65. jam/paseto/utils.py +99 -0
  66. jam/paseto/v1.py +314 -0
  67. jam/paseto/v2.py +235 -0
  68. jam/paseto/v3.py +361 -0
  69. jam/paseto/v4.py +276 -0
  70. jam/sessions/__base__.py +202 -0
  71. jam/sessions/__init__.py +89 -0
  72. jam/sessions/json.py +192 -0
  73. jam/sessions/redis.py +247 -0
  74. jam/tests/__init__.py +5 -0
  75. jam/tests/clients.py +914 -0
  76. jam/tests/fakers.py +39 -0
  77. jam/utils/__init__.py +35 -0
  78. jam/utils/aes.py +8 -0
  79. jam/utils/await_maybe.py +22 -0
  80. jam/utils/basic_auth.py +47 -0
  81. jam/utils/config_maker.py +236 -0
  82. jam/utils/ed.py +50 -0
  83. jam/utils/otp_keys.py +48 -0
  84. jam/utils/rsa.py +46 -0
  85. jam/utils/salt_hash.py +114 -0
  86. jam/utils/symmetric.py +17 -0
  87. jam/utils/xchacha20poly1305.py +61 -0
  88. jam/utils/xor.py +35 -0
  89. jamlib-3.0.0a10.dist-info/METADATA +117 -0
  90. jamlib-3.0.0a10.dist-info/RECORD +93 -0
  91. jamlib-3.0.0a10.dist-info/WHEEL +5 -0
  92. jamlib-3.0.0a10.dist-info/licenses/LICENSE.md +173 -0
  93. jamlib-3.0.0a10.dist-info/top_level.txt +1 -0
jam/__base__.py ADDED
@@ -0,0 +1,451 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from abc import ABC, abstractmethod
4
+ import gc
5
+ from typing import Any, Literal, Optional, Union
6
+
7
+ from jam.encoders import BaseEncoder, JsonEncoder
8
+ from jam.jwt.__base__ import BaseJWT
9
+ from jam.logger import BaseLogger, JamLogger
10
+ from jam.oauth2.__base__ import BaseOAuth2Client
11
+ from jam.sessions.__base__ import BaseSessionModule
12
+ from jam.utils.config_maker import __config_maker__, __module_loader__
13
+
14
+
15
+ class BaseJam(ABC):
16
+ """Base jam instance."""
17
+
18
+ MODULES: dict[str, str] = {}
19
+
20
+ def __init__(
21
+ self,
22
+ config: Union[str, dict[str, Any]] = "pyproject.toml",
23
+ pointer: str = "jam",
24
+ *,
25
+ logger: type(BaseLogger) = JamLogger,
26
+ log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
27
+ serializer: Union[BaseEncoder, type[BaseEncoder]] = JsonEncoder,
28
+ ) -> None:
29
+ """Initialize instance.
30
+
31
+ Args:
32
+ config (Union[str, dict[str, Any]]): Configuration
33
+ pointer (str): Pointer
34
+ logger (BaseLogger): Logger
35
+ serializer (Union[BaseEncoder, type[BaseBrowser]]): Serializer
36
+
37
+ Returns:
38
+ None
39
+ """
40
+ config = __config_maker__(config, pointer)
41
+ main_config = self.__build_main_config(config, logger, log_level, serializer)
42
+
43
+ logger = main_config["logger"]
44
+ log_level = main_config["log_level"]
45
+ serializer = main_config["serializer"]
46
+
47
+ self.__logger = logger(log_level)
48
+ self._serializer = serializer
49
+ self.jwt: Optional[BaseJWT] = None
50
+ self.session: Optional[BaseSessionModule] = None
51
+ self.oauth2: Optional[BaseOAuth2Client] = None
52
+
53
+ self.__logger.debug(f"Initializing BaseJam with log_level={log_level}, serializer={serializer}")
54
+ self.__build_instance(config)
55
+ self.__logger.debug(f"BaseJam initialization complete. Modules loaded: jwt={self.jwt is not None}, session={self.session is not None}, oauth2={self.oauth2 is not None}")
56
+ gc.collect()
57
+
58
+
59
+ def __build_main_config(
60
+ self,
61
+ config: dict[str, Any],
62
+ default_logger: type[BaseLogger],
63
+ default_log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
64
+ default_serializer: Union[BaseEncoder, type[BaseEncoder]],
65
+ ) -> dict[str, Any]:
66
+ """Build main params from config like logger, loglevel, etc.
67
+
68
+ Args:
69
+ config (dict[str, Any]): Configuration dictionary
70
+ default_logger (type[BaseLogger]): Default logger class
71
+ default_log_level (Literal): Default log level
72
+ default_serializer (Union[BaseEncoder, type[BaseEncoder]]): Default serializer
73
+
74
+ Returns:
75
+ dict[str, Any]: Dictionary with logger, log_level, and serializer
76
+ """
77
+ logger = default_logger
78
+ log_level = default_log_level
79
+ serializer = default_serializer
80
+
81
+ # Read logger from config
82
+ if "logger" in config:
83
+ logger_cfg = config["logger"]
84
+ if isinstance(logger_cfg, str):
85
+ logger = __module_loader__(logger_cfg)
86
+ elif isinstance(logger_cfg, type) and issubclass(logger_cfg, BaseLogger):
87
+ logger = logger_cfg
88
+
89
+ if "log_level" in config:
90
+ log_level_cfg = config["log_level"]
91
+ if isinstance(log_level_cfg, str) and log_level_cfg.upper() in (
92
+ "DEBUG",
93
+ "INFO",
94
+ "WARNING",
95
+ "ERROR",
96
+ "CRITICAL",
97
+ ):
98
+ log_level = log_level_cfg.upper()
99
+
100
+ if "serializer" in config:
101
+ serializer_cfg = config["serializer"]
102
+ if isinstance(serializer_cfg, str):
103
+ serializer = __module_loader__(serializer_cfg)
104
+ elif isinstance(serializer_cfg, type) and issubclass(
105
+ serializer_cfg, BaseEncoder
106
+ ):
107
+ serializer = serializer_cfg
108
+ elif isinstance(serializer_cfg, BaseEncoder):
109
+ serializer = serializer_cfg
110
+
111
+ return {
112
+ "logger": logger,
113
+ "log_level": log_level,
114
+ "serializer": serializer,
115
+ }
116
+
117
+
118
+
119
+ def __build_instance(self, config: dict[str, Any]) -> None:
120
+ """Build instance.
121
+
122
+ Load modules from configuration and initialize them.
123
+
124
+ Args:
125
+ config (dict[str, Any]): Configuration
126
+
127
+ Returns:
128
+ None
129
+ """
130
+ for name, path in self.MODULES.items():
131
+ if name not in config:
132
+ self.__logger.debug(f"Missing configuration for module {name}")
133
+ continue
134
+
135
+ try:
136
+ module_cls = __module_loader__(path)
137
+ self.__logger.debug(f"Loading module {name} from {path}")
138
+ params = config.get(name, {})
139
+ self.__logger.debug(f"Module {name} config params: {list(params.keys())}")
140
+ params["logger"] = self.__logger
141
+ params["serializer"] = self._serializer
142
+ module_instance = module_cls(**params)
143
+ self.__setattr__(name, module_instance)
144
+ self.__logger.debug(f"Module {name} initialized successfully")
145
+
146
+ except Exception as e:
147
+ self.__logger.error(
148
+ f"Failed to load module {name} from {path}: {e}",
149
+ exc_info=True
150
+ )
151
+
152
+ @abstractmethod
153
+ def jwt_make_payload(
154
+ self, exp: Optional[int], data: dict[str, Any]
155
+ ) -> dict[str, Any]:
156
+ """Make JWT-specific payload.
157
+
158
+ Args:
159
+ exp (int | None): Token expire, if None -> use default
160
+ data (dict[str, Any]): Data to payload
161
+
162
+ Returns:
163
+ dict[str, Any]: Payload
164
+ """
165
+ raise NotImplementedError
166
+
167
+ @abstractmethod
168
+ def jwt_create_token(self, payload: dict[str, Any]) -> str:
169
+ """Create JWT token.
170
+
171
+ Args:
172
+ payload (dict[str, Any]): Data payload
173
+
174
+ Returns:
175
+ str: New token
176
+
177
+ Raises:
178
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None
179
+ EmtpyPrivateKey: If RSA algorithm is selected, but private key None
180
+ """
181
+ raise NotImplementedError
182
+
183
+ @abstractmethod
184
+ def jwt_verify_token(
185
+ self, token: str, check_exp: bool = True, check_list: bool = True
186
+ ) -> dict[str, Any]:
187
+ """Verify and decode JWT token.
188
+
189
+ Args:
190
+ token (str): JWT token
191
+ check_exp (bool): Check expire
192
+ check_list (bool): Check white/black list. Docs: https://jam.makridenko.ru/jwt/lists/what/
193
+
194
+ Returns:
195
+ dict[str, Any]: Decoded payload
196
+
197
+ Raises:
198
+ ValueError: If the token is invalid.
199
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None.
200
+ EmtpyPublicKey: If RSA algorithm is selected, but public key None.
201
+ NotFoundSomeInPayload: If 'exp' not found in payload.
202
+ TokenLifeTimeExpired: If token has expired.
203
+ TokenNotInWhiteList: If the list type is white, but the token is not there
204
+ TokenInBlackList: If the list type is black and the token is there
205
+ """
206
+ raise NotImplementedError
207
+
208
+ @abstractmethod
209
+ def session_create(self, session_key: str, data: dict[str, Any]) -> str:
210
+ """Create new session.
211
+
212
+ Args:
213
+ session_key (str): Key for session
214
+ data (dict[str, Any]): Session data
215
+
216
+ Returns:
217
+ str: New session ID
218
+ """
219
+ raise NotImplementedError
220
+
221
+ @abstractmethod
222
+ def session_get(self, session_id: str) -> Optional[dict[str, Any]]:
223
+ """Get data from session.
224
+
225
+ Args:
226
+ session_id (str): Session ID
227
+
228
+ Returns:
229
+ dict[str, Any] | None: Session data if exist
230
+ """
231
+ raise NotImplementedError
232
+
233
+ @abstractmethod
234
+ def session_delete(self, session_id: str) -> None:
235
+ """Delete session.
236
+
237
+ Args:
238
+ session_id (str): Session ID
239
+ """
240
+ raise NotImplementedError
241
+
242
+ @abstractmethod
243
+ def session_update(self, session_id: str, data: dict[str, Any]) -> None:
244
+ """Update session data.
245
+
246
+ Args:
247
+ session_id (str): Session ID
248
+ data (dict[str, Any]): New data
249
+ """
250
+ raise NotImplementedError
251
+
252
+ @abstractmethod
253
+ def session_clear(self, session_key: str) -> None:
254
+ """Delete all sessions by key.
255
+
256
+ Args:
257
+ session_key (str): Key of session
258
+ """
259
+ raise NotImplementedError
260
+
261
+ @abstractmethod
262
+ def session_rework(self, old_session_id: str) -> str:
263
+ """Rework session.
264
+
265
+ Args:
266
+ old_session_id (str): Old session id
267
+
268
+ Returns:
269
+ str: New session id
270
+ """
271
+ raise NotImplementedError
272
+
273
+ @abstractmethod
274
+ def otp_code(
275
+ self, secret: Union[str, bytes], factor: Optional[int] = None
276
+ ) -> str:
277
+ """Generates an OTP.
278
+
279
+ Args:
280
+ secret (str | bytes): User secret key.
281
+ factor (int | None, optional): Unixtime for TOTP(if none, use now time) / Counter for HOTP.
282
+
283
+ Returns:
284
+ str: OTP code (fixed-length string).
285
+ """
286
+ raise NotImplementedError
287
+
288
+ @abstractmethod
289
+ def otp_uri(
290
+ self,
291
+ secret: str,
292
+ name: Optional[str] = None,
293
+ issuer: Optional[str] = None,
294
+ counter: Optional[int] = None,
295
+ ) -> str:
296
+ """Generates an otpauth:// URI for Google Authenticator.
297
+
298
+ Args:
299
+ secret (str): User secret key.
300
+ name (str): Account name (e.g., email).
301
+ issuer (str): Service name (e.g., "GitHub").
302
+ counter (int | None, optional): Counter (for HOTP). Default is None.
303
+
304
+ Returns:
305
+ str: A string of the form "otpauth://..."
306
+ """
307
+ raise NotImplementedError
308
+
309
+ @abstractmethod
310
+ def otp_verify_code(
311
+ self,
312
+ secret: Union[str, bytes],
313
+ code: str,
314
+ factor: Optional[int] = None,
315
+ look_ahead: Optional[int] = 1,
316
+ ) -> bool:
317
+ """Checks the OTP code, taking into account the acceptable window.
318
+
319
+ Args:
320
+ secret (str | bytes): User secret key.
321
+ code (str): The code entered.
322
+ factor (int | None, optional): Unixtime for TOTP(if none, use now time) / Counter for HOTP.
323
+ look_ahead (int, optional): Acceptable deviation in intervals (±window(totp) / ±look ahead(hotp)). Default is 1.
324
+
325
+ Returns:
326
+ bool: True if the code matches, otherwise False.
327
+ """
328
+ raise NotImplementedError
329
+
330
+ @abstractmethod
331
+ def oauth2_get_authorized_url(
332
+ self, provider: str, scope: list[str], **extra_params: Any
333
+ ) -> str:
334
+ """Generate full OAuth2 authorization URL.
335
+
336
+ Args:
337
+ provider (str): Provider name
338
+ scope (list[str]): Auth scope
339
+ extra_params (Any): Extra ath params
340
+
341
+ Returns:
342
+ str: Authorization url
343
+ """
344
+ raise NotImplementedError
345
+
346
+ @abstractmethod
347
+ def oauth2_fetch_token(
348
+ self,
349
+ provider: str,
350
+ code: str,
351
+ grant_type: str = "authorization_code",
352
+ **extra_params: Any,
353
+ ) -> dict[str, Any]:
354
+ """Exchange authorization code for access token.
355
+
356
+ Args:
357
+ provider (str): Provider name
358
+ code (str): OAuth2 code
359
+ grant_type (str): Type of oauth2 grant
360
+ extra_params (Any): Extra auth params if needed
361
+
362
+ Returns:
363
+ dict: OAuth2 token
364
+ """
365
+ raise NotImplementedError
366
+
367
+ @abstractmethod
368
+ def oauth2_refresh_token(
369
+ self,
370
+ provider: str,
371
+ refresh_token: str,
372
+ grant_type: str = "refresh_token",
373
+ **extra_params: Any,
374
+ ) -> dict[str, Any]:
375
+ """Use refresh token to obtain a new access token.
376
+
377
+ Args:
378
+ provider (str): Provider name
379
+ refresh_token (str): Refresh token
380
+ grant_type (str): Grant type
381
+ extra_params (Any): Extra auth params if needed
382
+
383
+ Returns:
384
+ dict: Refresh token
385
+ """
386
+ raise NotImplementedError
387
+
388
+ @abstractmethod
389
+ def oauth2_client_credentials_flow(
390
+ self,
391
+ provider: str,
392
+ scope: Optional[list[str]] = None,
393
+ **extra_params: Any,
394
+ ) -> dict[str, Any]:
395
+ """Obtain access token using client credentials flow (no user interaction).
396
+
397
+ Args:
398
+ provider (str): Provider name
399
+ scope (list[str] | None): Auth scope
400
+ extra_params (Any): Extra auth params if needed
401
+
402
+ Returns:
403
+ dict: JSON with access token
404
+ """
405
+ raise NotImplementedError
406
+
407
+ @abstractmethod
408
+ def paseto_make_payload(
409
+ self, exp: Optional[int] = None, **data: dict[str, Any]
410
+ ) -> dict[str, Any]:
411
+ """Generate payload for PASETO.
412
+
413
+ Args:
414
+ exp (int | None): Custom expire if needed
415
+ data (dict[str, Any]): Data in payload
416
+
417
+ Returns:
418
+ dict[str, Any]: New payload
419
+ """
420
+ raise NotImplementedError
421
+
422
+ @abstractmethod
423
+ def paseto_create(
424
+ self,
425
+ payload: dict[str, Any],
426
+ footer: Optional[Union[dict[str, Any], str]],
427
+ ) -> str:
428
+ """Create new PASETO.
429
+
430
+ Args:
431
+ payload (dict[str, Any]): Payload
432
+ footer (dict[str, Any] | str | None): Payload if needed
433
+
434
+ Returns:
435
+ str: PASETO
436
+ """
437
+ raise NotImplementedError
438
+
439
+ @abstractmethod
440
+ def paseto_decode( # mb refactoring this
441
+ self, token: str
442
+ ) -> dict[str, Union[dict, Union[str, dict, None]]]:
443
+ """Decode PASETO and return payload and footer.
444
+
445
+ Args:
446
+ token (str): PASETO
447
+
448
+ Returns:
449
+ dict: {`payload`: PAYLOAD, `footer`: FOOTER}
450
+ """
451
+ raise NotImplementedError
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Union
5
+
6
+
7
+ class BaseEncoder(ABC):
8
+ """Base encoder instance."""
9
+
10
+ @classmethod
11
+ @abstractmethod
12
+ def dumps(cls, var: dict[str, Any]) -> bytes:
13
+ """Dump dict."""
14
+ raise NotImplementedError
15
+
16
+ @classmethod
17
+ @abstractmethod
18
+ def loads(cls, var: Union[str, bytes]) -> dict[str, Any]:
19
+ """Load json."""
20
+ raise NotImplementedError
jam/__deprecated__.py ADDED
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import functools
4
+ from typing import Optional
5
+ import warnings
6
+
7
+
8
+ def deprecated(replacement: Optional[str] = None):
9
+ """Mark funcs are deprecated."""
10
+
11
+ def decorator(func):
12
+ msg = f"Function {func.__name__}() is deprecated."
13
+ if replacement:
14
+ msg += f" {replacement}"
15
+
16
+ @functools.wraps(func)
17
+ def wrapper(*args, **kwargs):
18
+ warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
19
+ return func(*args, **kwargs)
20
+
21
+ wrapper.__deprecated__ = True
22
+ wrapper.__doc__ = (
23
+ func.__doc__ or ""
24
+ ) + f"\n\n⚠️ Deprecated: {replacement or ''}".strip()
25
+ return wrapper
26
+
27
+ return decorator
jam/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ JAM - Universal auth* library
5
+
6
+ Source code: https://github.com/lyaguxafrog/jam
7
+ Documentation: https://jam.makridenko.ru
8
+ """
9
+
10
+ from jam.__base_encoder__ import BaseEncoder
11
+ from jam.__base__ import BaseJam
12
+ from jam.encoders import JsonEncoder
13
+ from jam.instance import Jam
14
+
15
+
16
+ __version__ = "3.0.0a10"
17
+ __all__ = ["Jam", "JsonEncoder", "BaseJam", "BaseEncoder"]
jam/aio/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ Async jam version.
5
+ """
6
+
7
+ from jam.aio.instance import Jam
8
+
9
+
10
+ __all__ = ["Jam"]