jamlib 0.0.0__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.
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Adrian Makridenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
22
+
23
+
jamlib-0.0.0/PKG-INFO ADDED
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: jamlib
3
+ Version: 0.0.0
4
+ Summary: Simple and univirsal library for authorization.
5
+ Author-email: Makridenko Adrian <adrianmakridenko@duck.com>
6
+ License: MIT License
7
+ Project-URL: Homepage, https://github.com/lyaguxafrog/jam
8
+ Project-URL: Repository, https://github.com/lyaguxafrog/jam
9
+ Project-URL: Issues, https://github.com/lyaguxafrog/jam/issues
10
+ Project-URL: Changelog, https://github.com/lyaguxafrog/jam/releases
11
+ Keywords: auth,backend,jwt
12
+ Requires-Python: >=3.13
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE.md
15
+ Requires-Dist: pycryptodome<4.0.0,>=3.21.0
16
+ Requires-Dist: cryptography<45.0.0,>=44.0.2
17
+ Provides-Extra: json-lists
18
+ Requires-Dist: tinydb>=4.8.2; extra == "json-lists"
19
+ Provides-Extra: redis-lists
20
+ Requires-Dist: redis>=5.2.1; extra == "redis-lists"
21
+ Dynamic: license-file
22
+
23
+ # Jam
24
+
25
+ ![logo](docs/assets/h_logo_n_title.png)
26
+
27
+ ![Static Badge](https://img.shields.io/badge/Python-3.13-blue?logo=python&logoColor=white)
28
+ ![tests](https://github.com/lyaguxafrog/jam/actions/workflows/run-tests.yml/badge.svg)
29
+ ![License](https://img.shields.io/badge/Licese-MIT-grey?link=https%3A%2F%2Fgithub.com%2Flyaguxafrog%2Fjam%2Fblob%2Frelease%2FLICENSE.md)
30
+ ![jam](https://img.shields.io/badge/jam-3.1.0_alpha-white?style=flat&labelColor=red)
31
+
32
+ ## Install
33
+ ```bash
34
+ pip install jamlib
35
+ ```
36
+
37
+ ## Getting start
38
+ ```python
39
+ # -*- coding: utf-8 -*-
40
+
41
+ from typing import Any
42
+
43
+ from jam import Jam
44
+
45
+ config: dict[str, Any] = {
46
+ "jwt_secret_key": "some-secret",
47
+ "expire": 3600
48
+ }
49
+
50
+ data = {
51
+ "user_id": 1,
52
+ "role": "admin"
53
+ }
54
+
55
+ jam = Jam(auth_type="jwt", config=config)
56
+
57
+ payload = jam.make_payload(**data)
58
+ token = jam.gen_jwt_token(**payload)
59
+ ```
60
+
61
+ ## Roadmap
62
+ ![Roadmap](https://github.com/lyaguxafrog/jam/blob/stable/docs/assets/roadmap.png)
63
+
64
+ &copy; [Adrian Makridenko](https://github.com/lyaguxafrog) 2025
jamlib-0.0.0/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # Jam
2
+
3
+ ![logo](docs/assets/h_logo_n_title.png)
4
+
5
+ ![Static Badge](https://img.shields.io/badge/Python-3.13-blue?logo=python&logoColor=white)
6
+ ![tests](https://github.com/lyaguxafrog/jam/actions/workflows/run-tests.yml/badge.svg)
7
+ ![License](https://img.shields.io/badge/Licese-MIT-grey?link=https%3A%2F%2Fgithub.com%2Flyaguxafrog%2Fjam%2Fblob%2Frelease%2FLICENSE.md)
8
+ ![jam](https://img.shields.io/badge/jam-3.1.0_alpha-white?style=flat&labelColor=red)
9
+
10
+ ## Install
11
+ ```bash
12
+ pip install jamlib
13
+ ```
14
+
15
+ ## Getting start
16
+ ```python
17
+ # -*- coding: utf-8 -*-
18
+
19
+ from typing import Any
20
+
21
+ from jam import Jam
22
+
23
+ config: dict[str, Any] = {
24
+ "jwt_secret_key": "some-secret",
25
+ "expire": 3600
26
+ }
27
+
28
+ data = {
29
+ "user_id": 1,
30
+ "role": "admin"
31
+ }
32
+
33
+ jam = Jam(auth_type="jwt", config=config)
34
+
35
+ payload = jam.make_payload(**data)
36
+ token = jam.gen_jwt_token(**payload)
37
+ ```
38
+
39
+ ## Roadmap
40
+ ![Roadmap](https://github.com/lyaguxafrog/jam/blob/stable/docs/assets/roadmap.png)
41
+
42
+ &copy; [Adrian Makridenko](https://github.com/lyaguxafrog) 2025
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+
7
+ class __AbstractInstance(ABC):
8
+ """Abstract Instance object."""
9
+
10
+ @abstractmethod
11
+ def gen_jwt_token(self, payload) -> str:
12
+ """Generate new JWT token."""
13
+ raise NotImplementedError
14
+
15
+ @abstractmethod
16
+ def verify_jwt_token(
17
+ self, token: str, check_exp: bool, check_list: bool
18
+ ) -> Any:
19
+ """Verify JWT token."""
20
+ raise NotImplementedError
21
+
22
+ @abstractmethod
23
+ def make_payload(self, **payload) -> Any:
24
+ """Generate new template."""
25
+ raise NotImplementedError
@@ -0,0 +1,14 @@
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.instance import Jam
11
+
12
+
13
+ __version__ = "1.0.0"
14
+ __all__ = ["Jam"]
@@ -0,0 +1,15 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ All Jam exceptions
5
+ """
6
+
7
+ from .jwt import (
8
+ EmptyPublicKey,
9
+ EmptySecretKey,
10
+ EmtpyPrivateKey,
11
+ NotFoundSomeInPayload,
12
+ TokenInBlackList,
13
+ TokenLifeTimeExpired,
14
+ TokenNotInWhiteList,
15
+ )
@@ -0,0 +1,38 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class EmptySecretKey(Exception):
8
+ message: str | Exception = "Secret key cannot be NoneType"
9
+
10
+
11
+ @dataclass
12
+ class EmtpyPrivateKey(Exception):
13
+ message: str | Exception = "Private key cannot be NoneType"
14
+
15
+
16
+ @dataclass
17
+ class EmptyPublicKey(Exception):
18
+ message: str | Exception = "Public key cannot be NoneType"
19
+
20
+
21
+ @dataclass
22
+ class TokenLifeTimeExpired(Exception):
23
+ message: str | Exception = "Token lifetime has expired."
24
+
25
+
26
+ class NotFoundSomeInPayload(Exception):
27
+ def __inti__(self, message: str | Exception) -> None:
28
+ self.message: str | Exception = message
29
+
30
+
31
+ @dataclass
32
+ class TokenNotInWhiteList(Exception):
33
+ message: str | Exception = "Token not found on white list."
34
+
35
+
36
+ @dataclass
37
+ class TokenInBlackList(Exception):
38
+ message: str | Exception = "The token is blacklisted."
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from typing import Any, Literal
4
+
5
+ from jam.__abc_instances__ import __AbstractInstance
6
+ from jam.modules import JWTModule
7
+
8
+
9
+ class Jam(__AbstractInstance):
10
+ """Main instance."""
11
+
12
+ def __init__(
13
+ self,
14
+ auth_type: Literal["jwt"],
15
+ config: dict[str, Any],
16
+ ) -> None:
17
+ """Class construcotr.
18
+
19
+ Args:
20
+ auth_type (Literal["jwt"]): Type of auth*
21
+ config (dict[str, Any]): Config for Jam, can use `jam.utils.config_maker`
22
+ """
23
+ self.type = auth_type
24
+ if self.type == "jwt":
25
+ self.module = JWTModule(
26
+ alg=config["alg"],
27
+ secret_key=config["secret_key"],
28
+ public_key=config["public_key"],
29
+ private_key=config["private_key"],
30
+ expire=config["expire"],
31
+ list=config["list"],
32
+ )
33
+ else:
34
+ raise NotImplementedError
35
+
36
+ def gen_jwt_token(self, payload: dict[str, Any]) -> str:
37
+ """Creating a new token.
38
+
39
+ Args:
40
+ payload (dict[str, Any]): Payload with information
41
+
42
+ Raises:
43
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None
44
+ EmtpyPrivateKey: If RSA algorithm is selected, but private key None
45
+ """
46
+ return self.module.gen_token(**payload)
47
+
48
+ def verify_jwt_token(
49
+ self, token: str, check_exp: bool = True, check_list: bool = True
50
+ ) -> dict[str, Any]:
51
+ """A method for verifying a token.
52
+
53
+ Args:
54
+ token (str): The token to check
55
+ check_exp (bool): Check for expiration?
56
+ check_list (bool): Check if there is a black/white list
57
+
58
+ Raises:
59
+ ValueError: If the token is invalid.
60
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None.
61
+ EmtpyPublicKey: If RSA algorithm is selected, but public key None.
62
+ NotFoundSomeInPayload: If 'exp' not found in payload.
63
+ TokenLifeTimeExpired: If token has expired.
64
+ TokenNotInWhiteList: If the list type is white, but the token is not there
65
+ TokenInBlackList: If the list type is black and the token is there
66
+
67
+ Returns:
68
+ (dict[str, Any]): Payload from token
69
+ """
70
+ return self.module.validate_payload(
71
+ token=token, check_exp=check_exp, check_list=check_list
72
+ )
73
+
74
+ def make_payload(self, exp: int | None = None, **data) -> dict[str, Any]:
75
+ """Payload maker tool.
76
+
77
+ Args:
78
+ exp (int | None): If none exp = JWTModule.exp
79
+ **data: Custom data
80
+ """
81
+ return self.module.make_payload(exp=exp, **data)
@@ -0,0 +1 @@
1
+ # -*- coding: utf-8 -*-
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import base64
4
+
5
+
6
+ def __base64url_encode__(data: bytes) -> str:
7
+ """Encodes data using URL-safe Base64 encoding.
8
+
9
+ Removes padding characters ('=') typically added in standard Base64 encoding.
10
+
11
+ Args:
12
+ data (bytes): The data to encode.
13
+
14
+ Returns:
15
+ str: A URL-safe Base64 encoded string without padding.
16
+ """
17
+ return base64.urlsafe_b64encode(data).rstrip(b"=").decode("utf-8")
18
+
19
+
20
+ def __base64url_decode__(data: str) -> bytes:
21
+ """Decodes a URL-safe Base64 encoded string back to bytes.
22
+
23
+ Automatically adds the necessary padding characters ('=') before decoding.
24
+
25
+ Args:
26
+ data (str): The Base64url encoded string to decode.
27
+
28
+ Returns:
29
+ bytes: The decoded byte data.
30
+ """
31
+ padding = "=" * (4 - len(data) % 4)
32
+ return base64.urlsafe_b64decode(data + padding)
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Literal, Protocol
5
+
6
+
7
+ class ABCList(ABC):
8
+ """Abstrac class for lists manipulation."""
9
+
10
+ def __init__(self, list_type: Literal["white", "black"]) -> None:
11
+ """Class constructor."""
12
+ self.__list_type__ = list_type
13
+
14
+ @abstractmethod
15
+ def add(self, token: str) -> None:
16
+ """Method for adding token to list."""
17
+ raise NotImplementedError
18
+
19
+ @abstractmethod
20
+ def check(self, token: str) -> bool:
21
+ """Method for checking if a token is present in the list."""
22
+ raise NotImplementedError
23
+
24
+ @abstractmethod
25
+ def delete(self, token: str) -> None:
26
+ """Method for removing a token from a list."""
27
+ raise NotImplementedError
28
+
29
+
30
+ class JWTList(Protocol):
31
+ """Protocol class for lists."""
32
+
33
+ __list_type__: Literal["black", "white"]
34
+
35
+ def add(self, token: str) -> None:
36
+ """Method for adding token to list."""
37
+ pass
38
+
39
+ def check(self, token: str) -> bool:
40
+ """Method for checking if a token is present in the list."""
41
+ pass
42
+
43
+ def delete(self, token: str) -> None:
44
+ """Method for removing a token from a list."""
45
+ pass
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .__abc_list_repo__ import ABCList
4
+ from .list_manipulations import JSONList, RedisList
5
+
6
+
7
+ __all__ = ["JSONList", "RedisList", "ABCList"]
@@ -0,0 +1,151 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import datetime
4
+ from typing import Literal
5
+
6
+ from redis import Redis
7
+ from tinydb import Query, TinyDB
8
+
9
+ from jam.jwt.lists.__abc_list_repo__ import ABCList
10
+
11
+
12
+ class JSONList(ABCList):
13
+ """Black/White list in JSON format, not recommended for blacklists because it is not convenient to control token lifetime.
14
+
15
+ Dependency required:
16
+ `pip install jamlib[json-lists]`
17
+
18
+ Attributes:
19
+ __list__ (TinyDB): TinyDB instance
20
+
21
+ Methods:
22
+ add: adding token to list
23
+ check: check token in list
24
+ delete: removing token from list
25
+ """
26
+
27
+ def __init__(
28
+ self, type: Literal["white", "black"], json_path: str = "whitelist.json"
29
+ ) -> None:
30
+ """Class constructor.
31
+
32
+ Args:
33
+ type (Literal["white", "black"]): Type of list
34
+ json_path (str): Path to .json file
35
+ """
36
+ super().__init__(list_type=type)
37
+ self.__list__ = TinyDB(json_path)
38
+
39
+ def add(self, token: str) -> None:
40
+ """Method for adding token to list.
41
+
42
+ Args:
43
+ token (str): Your JWT token
44
+
45
+ Returns:
46
+ (None)
47
+ """
48
+ _doc = {
49
+ "token": token,
50
+ "timestamp": datetime.datetime.now().timestamp(),
51
+ }
52
+
53
+ from icecream import ic
54
+
55
+ ic(self.__list__.insert(_doc))
56
+ return None
57
+
58
+ def check(self, token: str) -> bool:
59
+ """Method for checking if a token is present in list.
60
+
61
+ Args:
62
+ token (str): Your jwt token
63
+
64
+ Returns:
65
+ (bool)
66
+ """
67
+ cond = Query()
68
+ _token = self.__list__.search(cond.token == token)
69
+ if _token:
70
+ return True
71
+ else:
72
+ return False
73
+
74
+ def delete(self, token: str) -> None:
75
+ """Method for removing token from list.
76
+
77
+ Args:
78
+ token (str): Your jwt token
79
+
80
+ Returns:
81
+ (None)
82
+ """
83
+ cond = Query()
84
+ self.__list__.remove(cond.token == token)
85
+
86
+
87
+ class RedisList(ABCList):
88
+ """Black/White lists in Redis, most optimal format.
89
+
90
+ Dependency required: `pip install jamlib[redis-lists]`
91
+
92
+ Attributes:
93
+ __list__ (Redis): Redis instance
94
+ exp (int | None): Token lifetime
95
+ """
96
+
97
+ def __init__(
98
+ self,
99
+ type: Literal["white", "black"],
100
+ redis_instance: Redis,
101
+ in_list_life_time: int | None,
102
+ ) -> None:
103
+ """Class constructor.
104
+
105
+ Args:
106
+ type (Literal["white", "black"]): Type og list
107
+ redis_instance (Redis): `redis.Redis`
108
+ in_list_life_time (int | None): The lifetime of a token in the list
109
+ """
110
+ super().__init__(list_type=type)
111
+ self.__list__ = redis_instance
112
+ self.exp = in_list_life_time
113
+
114
+ def add(self, token: str) -> None:
115
+ """Method for adding token to list.
116
+
117
+ Args:
118
+ token (str): Your JWT token
119
+
120
+ Returns:
121
+ (None)
122
+ """
123
+ self.__list__.set(name=token, value="", ex=self.exp)
124
+ return None
125
+
126
+ def check(self, token: str) -> bool:
127
+ """Method for checking if a token is present in the list.
128
+
129
+ Args:
130
+ token (str): Your JWT token
131
+
132
+ Returns:
133
+ (bool)
134
+ """
135
+ _token = self.__list__.get(name=token)
136
+ if not _token:
137
+ return False
138
+ else:
139
+ return True
140
+
141
+ def delete(self, token: str) -> None:
142
+ """Method for removing a token from a list.
143
+
144
+ Args:
145
+ token (str): Your JWT token
146
+
147
+ Returns:
148
+ None
149
+ """
150
+ self.__list__.delete(token)
151
+ return None
@@ -0,0 +1,168 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ from datetime import datetime
7
+ from typing import Any
8
+ from uuid import uuid4
9
+
10
+ from Crypto.Hash import SHA256
11
+ from Crypto.PublicKey import RSA
12
+ from Crypto.Signature import pkcs1_15
13
+
14
+ from jam.exceptions import (
15
+ EmptyPublicKey,
16
+ EmptySecretKey,
17
+ EmtpyPrivateKey,
18
+ NotFoundSomeInPayload,
19
+ TokenLifeTimeExpired,
20
+ )
21
+ from jam.jwt.__utils__ import __base64url_decode__, __base64url_encode__
22
+
23
+
24
+ def __gen_jwt__(
25
+ header: dict[str, Any],
26
+ payload: dict[str, Any],
27
+ secret: str | None = None,
28
+ private_key: str | None = None,
29
+ ) -> str:
30
+ """Method for generating JWT token with different algorithms.
31
+
32
+ Example:
33
+ ```python
34
+ token = __gen_jwt__(
35
+ header={
36
+ "alg": "HS256",
37
+ "type": "jwt"
38
+ },
39
+ payload={
40
+ "id": 1
41
+ },
42
+ secret="SUPER_SECRET"
43
+ )
44
+ ```
45
+
46
+ Args:
47
+ header (Dict[str, str]): Dict with JWT headers
48
+ payload (Dict[str, Any]): Custom JWT payload
49
+ secret (str | None): Secret key for HMAC algorithms
50
+ private_key (str | None): Private key for RSA algorithms
51
+
52
+ Raises:
53
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None
54
+ EmtpyPrivateKey: If RSA algorithm is selected, but private key None
55
+
56
+ Returns:
57
+ (str): Access/refresh token
58
+ """
59
+ header_encoded = __base64url_encode__(json.dumps(header).encode("utf-8"))
60
+ payload_encoded = __base64url_encode__(json.dumps(payload).encode("utf-8"))
61
+
62
+ signature_input = f"{header_encoded}.{payload_encoded}".encode()
63
+
64
+ if header["alg"].startswith("HS"):
65
+ if secret is None:
66
+ raise EmptySecretKey
67
+ signature = hmac.new(
68
+ secret.encode("utf-8"), signature_input, hashlib.sha256
69
+ ).digest()
70
+ elif header["alg"].startswith("RS"):
71
+ if private_key is None:
72
+ raise EmtpyPrivateKey
73
+ rsa_key = RSA.import_key(private_key)
74
+ hash_obj = SHA256.new(signature_input)
75
+ signature = pkcs1_15.new(rsa_key).sign(hash_obj)
76
+ else:
77
+ raise ValueError("Unsupported algorithm")
78
+
79
+ signature_encoded = __base64url_encode__(signature)
80
+
81
+ jwt_token = f"{header_encoded}.{payload_encoded}.{signature_encoded}"
82
+ return jwt_token
83
+
84
+
85
+ def __validate_jwt__(
86
+ token: str,
87
+ check_exp: bool = False,
88
+ secret: str | None = None,
89
+ public_key: str | None = None,
90
+ ) -> dict[str, Any]:
91
+ """Validate a JWT token and return the payload if valid.
92
+
93
+ Args:
94
+ token (str): The JWT token to validate.
95
+ check_exp (bool): true to check token lifetime.
96
+ secret (str | None): Secret key for HMAC algorithms.
97
+ public_key (str | None): Public key for RSA algorithms.
98
+
99
+ Returns:
100
+ (Dict[str, Any]): The payload if the token is valid.
101
+
102
+ Raises:
103
+ ValueError: If the token is invalid.
104
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None.
105
+ EmtpyPublicKey: If RSA algorithm is selected, but public key None.
106
+ NotFoundSomeInPayload: If 'exp' not found in payload.
107
+ TokenLifeTimeExpired: If token has expired.
108
+ """
109
+ try:
110
+ header_encoded, payload_encoded, signature_encoded = token.split(".")
111
+ except ValueError:
112
+ raise ValueError("Invalid token format")
113
+
114
+ header = json.loads(__base64url_decode__(header_encoded).decode("utf-8"))
115
+ payload = json.loads(__base64url_decode__(payload_encoded).decode("utf-8"))
116
+ signature = __base64url_decode__(signature_encoded)
117
+
118
+ signature_input = f"{header_encoded}.{payload_encoded}".encode()
119
+
120
+ if header["alg"].startswith("HS"):
121
+ if secret is None:
122
+ raise EmptySecretKey
123
+ expected_signature = hmac.new(
124
+ secret.encode("utf-8"), signature_input, hashlib.sha256
125
+ ).digest()
126
+ elif header["alg"].startswith("RS"):
127
+ if public_key is None:
128
+ raise EmptyPublicKey
129
+ rsa_key = RSA.import_key(public_key)
130
+ hash_obj = SHA256.new(signature_input)
131
+ try:
132
+ pkcs1_15.new(rsa_key).verify(hash_obj, signature)
133
+ expected_signature = signature
134
+ except (ValueError, TypeError):
135
+ raise ValueError("Invalid signature")
136
+ else:
137
+ raise ValueError("Unsupported algorithm")
138
+
139
+ if expected_signature != signature:
140
+ raise ValueError("Invalid token signature")
141
+
142
+ if check_exp:
143
+ if payload["exp"] is None:
144
+ raise NotFoundSomeInPayload('"exp" not found in payload')
145
+ if payload["exp"] < datetime.today().timestamp():
146
+ raise TokenLifeTimeExpired
147
+
148
+ return payload
149
+
150
+
151
+ def __payload_maker__(exp: int | None, **data) -> dict[str, Any]:
152
+ """Tool for making base payload.
153
+
154
+ Args:
155
+ exp (int | None): Token expire
156
+ **data: Data for payload
157
+
158
+ Returns:
159
+ (Dict[str, Any])
160
+ """
161
+ base_payload: dict = {
162
+ "iat": datetime.now().timestamp(),
163
+ "exp": exp,
164
+ "jti": str(uuid4()),
165
+ }
166
+
167
+ base_payload.update(data)
168
+ return base_payload
@@ -0,0 +1,159 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import datetime
4
+ from typing import Any, Literal
5
+ from uuid import uuid4
6
+
7
+ from jam.exceptions import TokenInBlackList, TokenNotInWhiteList
8
+ from jam.jwt.lists.__abc_list_repo__ import JWTList
9
+ from jam.jwt.tools import __gen_jwt__, __validate_jwt__
10
+
11
+
12
+ class BaseModule:
13
+ """The base module from which all other modules inherit."""
14
+
15
+ def __init__(
16
+ self,
17
+ module_type: Literal[
18
+ "jwt", "session-redis", "session-mongo", "session-custom"
19
+ ],
20
+ ) -> None:
21
+ """Class constructor.
22
+
23
+ Args:
24
+ module_type (Litetal["jwt", "session-redis", "session-mongo", "session-custom"]): Type of module
25
+ """
26
+ self._type = module_type
27
+
28
+ def __get_type(self) -> str:
29
+ return self._type
30
+
31
+
32
+ class JWTModule(BaseModule):
33
+ """Module for JWT auth.
34
+
35
+ Methods:
36
+ make_payload(exp: int | None, **data): Creating a generic payload for a token
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ alg: Literal[
42
+ "HS256",
43
+ "HS384",
44
+ "HS512",
45
+ "RS256",
46
+ "RS384",
47
+ "RS512",
48
+ # "PS256",
49
+ # "PS384",
50
+ # "PS512",
51
+ ] = "HS256",
52
+ secret_key: str | None = None,
53
+ public_key: str | None = None,
54
+ private_key: str | None = None,
55
+ expire: int = 3600,
56
+ list: JWTList | None = None,
57
+ ) -> None:
58
+ """Class constructor.
59
+
60
+ Args:
61
+ alg (Literal["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "PS512", "PS384", "PS512"]): Algorithm for token encryption
62
+ secret_key (str | None): Secret key for HMAC enecryption
63
+ private_key (str | None): Private key for RSA enecryption
64
+ public_key (str | None): Public key for RSA
65
+ expire (int): Token lifetime in seconds
66
+ list (JWTList | None): List module
67
+ """
68
+ super().__init__(module_type="jwt")
69
+ self._secret_key = secret_key
70
+ self.alg = alg
71
+ self._private_key = (private_key,)
72
+ self.public_key = public_key
73
+ self.exp = expire
74
+ self.list = list
75
+
76
+ def make_payload(self, exp: int | None = None, **data) -> dict[str, Any]:
77
+ """Payload maker tool.
78
+
79
+ Args:
80
+ exp (int | None): If none exp = JWTModule.exp
81
+ **data: Custom data
82
+ """
83
+ if not exp:
84
+ _exp = self.exp
85
+ else:
86
+ _exp = exp
87
+ payload = {
88
+ "jti": str(uuid4()),
89
+ "exp": _exp,
90
+ "iat": datetime.datetime.now().timestamp(),
91
+ }
92
+ payload.update(**data)
93
+ return payload
94
+
95
+ def gen_token(self, **payload) -> str:
96
+ """Creating a new token.
97
+
98
+ Args:
99
+ **payload: Payload with information
100
+
101
+ Raises:
102
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None
103
+ EmtpyPrivateKey: If RSA algorithm is selected, but private key None
104
+ """
105
+ header = {"alg": self.alg, "typ": "jwt"}
106
+ token = __gen_jwt__(
107
+ header=header,
108
+ payload=payload,
109
+ secret=self._secret_key,
110
+ private_key=self._private_key, # type: ignore
111
+ )
112
+
113
+ if self.list:
114
+ if self.list.__list_type__ == "white":
115
+ self.list.add(token)
116
+ return token
117
+
118
+ def validate_payload(
119
+ self, token: str, check_exp: bool = False, check_list: bool = True
120
+ ) -> dict[str, Any]:
121
+ """A method for verifying a token.
122
+
123
+ Args:
124
+ token (str): The token to check
125
+ check_exp (bool): Check for expiration?
126
+ check_list (bool): Check if there is a black/white list
127
+
128
+ Raises:
129
+ ValueError: If the token is invalid.
130
+ EmptySecretKey: If the HMAC algorithm is selected, but the secret key is None.
131
+ EmtpyPublicKey: If RSA algorithm is selected, but public key None.
132
+ NotFoundSomeInPayload: If 'exp' not found in payload.
133
+ TokenLifeTimeExpired: If token has expired.
134
+ TokenNotInWhiteList: If the list type is white, but the token is not there
135
+ TokenInBlackList: If the list type is black and the token is there
136
+
137
+ Returns:
138
+ (dict[str, Any]): Payload from token
139
+ """
140
+ if check_list:
141
+ if self.list.__list_type__ == "white": # type: ignore
142
+ if not self.list.check(token): # type: ignore
143
+ raise TokenNotInWhiteList
144
+ else:
145
+ pass
146
+ if self.list.__list_type__ == "black": # type: ignore
147
+ if self.list.check(token): # type: ignore
148
+ raise TokenInBlackList
149
+ else:
150
+ pass
151
+
152
+ payload = __validate_jwt__(
153
+ token=token,
154
+ check_exp=check_exp,
155
+ secret=self._secret_key,
156
+ public_key=self.public_key,
157
+ )
158
+
159
+ return payload
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .config_maker import make_jwt_config
4
+ from .rsa import generate_rsa_key_pair
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from typing import Any, Literal
4
+
5
+ from jam.exceptions.jwt import EmptyPublicKey, EmptySecretKey, EmtpyPrivateKey
6
+ from jam.jwt.lists.__abc_list_repo__ import JWTList
7
+
8
+
9
+ def make_jwt_config(
10
+ alg: Literal[
11
+ "HS256",
12
+ "HS384",
13
+ "HS512",
14
+ "RS256",
15
+ "RS384",
16
+ "RS512",
17
+ # "PS256",
18
+ # "PS384",
19
+ # "PS512",
20
+ ] = "HS256",
21
+ secret_key: str | None = None,
22
+ public_key: str | None = None,
23
+ private_key: str | None = None,
24
+ expire: int = 3600,
25
+ list: JWTList | None = None,
26
+ ) -> dict[str, Any]:
27
+ """Util for making JWT config.
28
+
29
+ Args:
30
+ alg (Literal["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", "PS512", "PS384", "PS512"]): Algorithm for token encryption
31
+ secret_key (str | None): Secret key for HMAC enecryption
32
+ private_key (str | None): Private key for RSA enecryption
33
+ public_key (str | None): Public key for RSA
34
+ expire (int): Token lifetime in seconds
35
+ list (JWTList | None): List module for checking
36
+
37
+ Raises:
38
+ EmptySecretKey: If HS* algorithm is selected, but the secret key is empty
39
+ EmtpyPrivateKey: If RS* algorithm is selected, but the private key is empty
40
+ EmtpyPublicKey: If RS* algorithm is selected, but the public key is empty
41
+ """
42
+ if alg.startswith("HS") and secret_key is None:
43
+ raise EmptySecretKey
44
+
45
+ if alg.startswith("RS") and private_key is None:
46
+ raise EmtpyPrivateKey
47
+
48
+ if alg.startswith("RS") and public_key is None:
49
+ raise EmptyPublicKey
50
+
51
+ return {
52
+ "alg": alg,
53
+ "secret_key": secret_key,
54
+ "private_key": private_key,
55
+ "public_key": public_key,
56
+ "expire": expire,
57
+ "list": list,
58
+ }
@@ -0,0 +1,44 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from cryptography.hazmat.backends import default_backend
4
+ from cryptography.hazmat.primitives import serialization
5
+ from cryptography.hazmat.primitives.asymmetric import rsa
6
+
7
+
8
+ def generate_rsa_key_pair(key_size: int = 2048) -> dict:
9
+ """RSA key generation utility.
10
+
11
+ Args:
12
+ key_size (int): Size of RSA key
13
+
14
+ Returns:
15
+ (dict): with public and private keys in format:
16
+
17
+ ```python
18
+ {
19
+ "public": "some_key",
20
+ "private": "key"
21
+ }
22
+ ```
23
+ """
24
+ private_key = rsa.generate_private_key(
25
+ public_exponent=65537, key_size=key_size, backend=default_backend()
26
+ )
27
+
28
+ public_key = private_key.public_key()
29
+
30
+ pem_private = private_key.private_bytes(
31
+ encoding=serialization.Encoding.PEM,
32
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
33
+ encryption_algorithm=serialization.NoEncryption(),
34
+ )
35
+
36
+ pem_public = public_key.public_bytes(
37
+ encoding=serialization.Encoding.PEM,
38
+ format=serialization.PublicFormat.SubjectPublicKeyInfo,
39
+ )
40
+
41
+ return {
42
+ "public": pem_public.decode("utf-8"),
43
+ "private": pem_private.decode("utf-8"),
44
+ }
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: jamlib
3
+ Version: 0.0.0
4
+ Summary: Simple and univirsal library for authorization.
5
+ Author-email: Makridenko Adrian <adrianmakridenko@duck.com>
6
+ License: MIT License
7
+ Project-URL: Homepage, https://github.com/lyaguxafrog/jam
8
+ Project-URL: Repository, https://github.com/lyaguxafrog/jam
9
+ Project-URL: Issues, https://github.com/lyaguxafrog/jam/issues
10
+ Project-URL: Changelog, https://github.com/lyaguxafrog/jam/releases
11
+ Keywords: auth,backend,jwt
12
+ Requires-Python: >=3.13
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE.md
15
+ Requires-Dist: pycryptodome<4.0.0,>=3.21.0
16
+ Requires-Dist: cryptography<45.0.0,>=44.0.2
17
+ Provides-Extra: json-lists
18
+ Requires-Dist: tinydb>=4.8.2; extra == "json-lists"
19
+ Provides-Extra: redis-lists
20
+ Requires-Dist: redis>=5.2.1; extra == "redis-lists"
21
+ Dynamic: license-file
22
+
23
+ # Jam
24
+
25
+ ![logo](docs/assets/h_logo_n_title.png)
26
+
27
+ ![Static Badge](https://img.shields.io/badge/Python-3.13-blue?logo=python&logoColor=white)
28
+ ![tests](https://github.com/lyaguxafrog/jam/actions/workflows/run-tests.yml/badge.svg)
29
+ ![License](https://img.shields.io/badge/Licese-MIT-grey?link=https%3A%2F%2Fgithub.com%2Flyaguxafrog%2Fjam%2Fblob%2Frelease%2FLICENSE.md)
30
+ ![jam](https://img.shields.io/badge/jam-3.1.0_alpha-white?style=flat&labelColor=red)
31
+
32
+ ## Install
33
+ ```bash
34
+ pip install jamlib
35
+ ```
36
+
37
+ ## Getting start
38
+ ```python
39
+ # -*- coding: utf-8 -*-
40
+
41
+ from typing import Any
42
+
43
+ from jam import Jam
44
+
45
+ config: dict[str, Any] = {
46
+ "jwt_secret_key": "some-secret",
47
+ "expire": 3600
48
+ }
49
+
50
+ data = {
51
+ "user_id": 1,
52
+ "role": "admin"
53
+ }
54
+
55
+ jam = Jam(auth_type="jwt", config=config)
56
+
57
+ payload = jam.make_payload(**data)
58
+ token = jam.gen_jwt_token(**payload)
59
+ ```
60
+
61
+ ## Roadmap
62
+ ![Roadmap](https://github.com/lyaguxafrog/jam/blob/stable/docs/assets/roadmap.png)
63
+
64
+ &copy; [Adrian Makridenko](https://github.com/lyaguxafrog) 2025
@@ -0,0 +1,23 @@
1
+ LICENSE.md
2
+ README.md
3
+ pyproject.toml
4
+ jam/__abc_instances__.py
5
+ jam/__init__.py
6
+ jam/instance.py
7
+ jam/modules.py
8
+ jam/exceptions/__init__.py
9
+ jam/exceptions/jwt.py
10
+ jam/jwt/__init__.py
11
+ jam/jwt/__utils__.py
12
+ jam/jwt/tools.py
13
+ jam/jwt/lists/__abc_list_repo__.py
14
+ jam/jwt/lists/__init__.py
15
+ jam/jwt/lists/list_manipulations.py
16
+ jam/utils/__init__.py
17
+ jam/utils/config_maker.py
18
+ jam/utils/rsa.py
19
+ jamlib.egg-info/PKG-INFO
20
+ jamlib.egg-info/SOURCES.txt
21
+ jamlib.egg-info/dependency_links.txt
22
+ jamlib.egg-info/requires.txt
23
+ jamlib.egg-info/top_level.txt
@@ -0,0 +1,8 @@
1
+ pycryptodome<4.0.0,>=3.21.0
2
+ cryptography<45.0.0,>=44.0.2
3
+
4
+ [json-lists]
5
+ tinydb>=4.8.2
6
+
7
+ [redis-lists]
8
+ redis>=5.2.1
@@ -0,0 +1 @@
1
+ jam
@@ -0,0 +1,86 @@
1
+ [project]
2
+ name = "jamlib"
3
+ dynamic = ["version"]
4
+ description = "Simple and univirsal library for authorization."
5
+ authors = [
6
+ {name = "Makridenko Adrian",email = "adrianmakridenko@duck.com"}
7
+ ]
8
+ license = {text = "MIT License"}
9
+ readme = {file = "README.md", content-type = "text/markdown"}
10
+ keywords = [
11
+ "auth",
12
+ "backend",
13
+ "jwt"
14
+ ]
15
+ requires-python = ">=3.13"
16
+ dependencies = [
17
+ "pycryptodome>=3.21.0,<4.0.0",
18
+ "cryptography (>=44.0.2,<45.0.0)",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://github.com/lyaguxafrog/jam"
23
+ Repository = "https://github.com/lyaguxafrog/jam"
24
+ Issues = "https://github.com/lyaguxafrog/jam/issues"
25
+ Changelog = "https://github.com/lyaguxafrog/jam/releases"
26
+
27
+ [project.optional-dependencies]
28
+ json-lists=["tinydb>=4.8.2"]
29
+ redis-lists=["redis>=5.2.1"]
30
+
31
+ [dependency-groups]
32
+ dev = [
33
+ "black>=25.1.0",
34
+ "icecream>=2.1.4",
35
+ "isort>=6.0.1",
36
+ "mkdocs>=1.6.1",
37
+ "mkdocs-material>=9.6.9",
38
+ "mkdocstrings>=0.29.0",
39
+ "mkdocstrings-python>=1.16.7",
40
+ "mypy>=1.15.0",
41
+ "pre-commit>=4.2.0",
42
+ "pytest>=8.3.5",
43
+ "redis>=5.2.1",
44
+ "ruff>=0.11.2",
45
+ "termynal>=0.13.0",
46
+ "tinydb>=4.8.2",
47
+ ]
48
+
49
+
50
+ [tool.ruff]
51
+ target-version = "py313"
52
+ line-length = 80
53
+ ignore = ["UP009", "D100"]
54
+ exclude = ["*__init__.py", "tests/", "*exceptions/"]
55
+
56
+ [tool.ruff.lint]
57
+ extend-select = ["UP", "D"]
58
+
59
+ [tool.ruff.lint.pydocstyle]
60
+ convention = "google"
61
+
62
+ [tool.isort]
63
+ profile = "black"
64
+ default_section = "THIRDPARTY"
65
+ balanced_wrapping = true
66
+ known_first_party = "src"
67
+ line_length = 80
68
+ lines_after_imports = 2
69
+ lines_between_sections = 1
70
+ multi_line_output = 3
71
+ sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
72
+ skip = ["env", ".env", ".env.example", "migrations/", ".venv/"]
73
+
74
+ [tool.black]
75
+ line-length = 80
76
+ skip-string-normalization = false
77
+
78
+ [tool.mypy]
79
+ disable_error_code = ["no-redef", "import-not-found", "import-untyped", "attr-defined"]
80
+
81
+ [tool.pytest]
82
+ #asyncio_default_fixture_loop_scope = "function"
83
+ python_files = ["test*.py"]
84
+
85
+ [tool.pytest.ini_options]
86
+ addopts = "--capture=no"
jamlib-0.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+