Encryptors 2.62__tar.gz → 2.64__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.
- {encryptors-2.62 → encryptors-2.64}/PKG-INFO +2 -2
- {encryptors-2.62 → encryptors-2.64}/setup.py +2 -2
- {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/PKG-INFO +2 -2
- {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/requires.txt +1 -1
- encryptors-2.64/src/Osdental/Helpers/JwtTokenHelper.py +179 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/JwtAuthTokenService.py +25 -0
- encryptors-2.62/src/Osdental/Helpers/JwtTokenHelper.py +0 -171
- {encryptors-2.62 → encryptors-2.64}/README.md +0 -0
- {encryptors-2.62 → encryptors-2.64}/setup.cfg +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/SOURCES.txt +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/dependency_links.txt +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/entry_points.txt +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Encryptors.egg-info/top_level.txt +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Cache/Redis.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Cache/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Cli/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Constants/Constant.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Constants/Message.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Constants/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Context/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Database/BaseRepository.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Database/Connection.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Database/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/Grpc.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/Retry.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/SecureResolver.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Decorators/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Aes.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Argon2.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Bcrypt.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Jwt.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Rsa.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/Sha512.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Encryptor/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/AuditType.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/ErrorSource.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/FileType.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/GrahpqlOperation.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/Profile.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/ResultType.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/StatusCode.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Enums/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Exception/ControlledException.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Exception/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AuditHelper.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AuditQueue.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AuthTokenProcessor.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/AzureClassifier.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/GrpcConnection.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/Resilience.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/ResponseDecryptor.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Helpers/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Http/APIClient.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Http/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/AzureServiceBus.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/Kafka.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/RabbitMQ.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Messaging/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/ApiResponse.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/AuditContext.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Graphql.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Notification.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Response.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/Token.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/TokenClaims.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Models/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Secrets/AzureKeyVaultProvider.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Secrets/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/ServiceBusAuditEmitter.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/WebsocketClient.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Services/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Storage/AzureBlobStorage.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Storage/S3Storage.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Storage/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/CaseConverter.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/CodeGenerator.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/DataNormalizer.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/DataUtils.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/DateUtils.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/FileMetaData.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/HashValidator.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/Mapper.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/PasswordGenerator.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/QueryGenerator.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/RsaUtils.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/TextProcessor.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/Utils/__init__.py +0 -0
- {encryptors-2.62 → encryptors-2.64}/src/Osdental/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Encryptors
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.64
|
|
4
4
|
Summary: End-to-end algorithm library
|
|
5
5
|
Author: OSDental LLC
|
|
6
6
|
Author-email: support@osdental.ai
|
|
@@ -57,7 +57,7 @@ Requires-Dist: bcrypt==4.3.0
|
|
|
57
57
|
Requires-Dist: azure-monitor-opentelemetry==1.8.1
|
|
58
58
|
Requires-Dist: uvicorn==0.37.0
|
|
59
59
|
Requires-Dist: gunicorn==23.0.0
|
|
60
|
-
Requires-Dist:
|
|
60
|
+
Requires-Dist: joserfc==1.5.0
|
|
61
61
|
Dynamic: author
|
|
62
62
|
Dynamic: author-email
|
|
63
63
|
Dynamic: classifier
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
# ANDERSON REVISAR EL CACHE LOCAL DEL KEYVAULT PARA VALIDAR SI FUNCIONA
|
|
3
3
|
setup(
|
|
4
4
|
name="Encryptors",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.64",
|
|
6
6
|
author="OSDental LLC",
|
|
7
7
|
author_email="support@osdental.ai",
|
|
8
8
|
description="End-to-end algorithm library",
|
|
@@ -65,7 +65,7 @@ setup(
|
|
|
65
65
|
"azure-monitor-opentelemetry==1.8.1",
|
|
66
66
|
"uvicorn==0.37.0",
|
|
67
67
|
"gunicorn==23.0.0",
|
|
68
|
-
"
|
|
68
|
+
"joserfc==1.5.0",
|
|
69
69
|
],
|
|
70
70
|
entry_points={
|
|
71
71
|
'console_scripts': [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Encryptors
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.64
|
|
4
4
|
Summary: End-to-end algorithm library
|
|
5
5
|
Author: OSDental LLC
|
|
6
6
|
Author-email: support@osdental.ai
|
|
@@ -57,7 +57,7 @@ Requires-Dist: bcrypt==4.3.0
|
|
|
57
57
|
Requires-Dist: azure-monitor-opentelemetry==1.8.1
|
|
58
58
|
Requires-Dist: uvicorn==0.37.0
|
|
59
59
|
Requires-Dist: gunicorn==23.0.0
|
|
60
|
-
Requires-Dist:
|
|
60
|
+
Requires-Dist: joserfc==1.5.0
|
|
61
61
|
Dynamic: author
|
|
62
62
|
Dynamic: author-email
|
|
63
63
|
Dynamic: classifier
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from datetime import datetime, timedelta, timezone
|
|
3
|
+
from joserfc import jwe, jwt
|
|
4
|
+
from joserfc.jwk import RSAKey
|
|
5
|
+
from Osdental.Models.TokenClaims import TokenValidationOptions
|
|
6
|
+
from Osdental.Exception.ControlledException import UnauthorizedException
|
|
7
|
+
from Osdental.Enums.StatusCode import StatusCode
|
|
8
|
+
|
|
9
|
+
class JwtTokenHelper:
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
private_key_pem: str,
|
|
14
|
+
public_key_pem: str,
|
|
15
|
+
):
|
|
16
|
+
self._private_key = RSAKey.import_key(
|
|
17
|
+
private_key_pem.encode()
|
|
18
|
+
)
|
|
19
|
+
self._public_key = RSAKey.import_key(
|
|
20
|
+
public_key_pem.encode()
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
self._jwe_algorithms = ["RSA-OAEP", "A256CBC-HS512"]
|
|
24
|
+
|
|
25
|
+
def generate_token(
|
|
26
|
+
self,
|
|
27
|
+
claims: dict,
|
|
28
|
+
expiration_minutes: int,
|
|
29
|
+
) -> str:
|
|
30
|
+
|
|
31
|
+
now = datetime.now(timezone.utc)
|
|
32
|
+
|
|
33
|
+
payload = {
|
|
34
|
+
**claims,
|
|
35
|
+
"iat": int(now.timestamp()),
|
|
36
|
+
"nbf": int(now.timestamp()),
|
|
37
|
+
"exp": int(
|
|
38
|
+
(
|
|
39
|
+
now
|
|
40
|
+
+ timedelta(
|
|
41
|
+
minutes=expiration_minutes
|
|
42
|
+
)
|
|
43
|
+
).timestamp()
|
|
44
|
+
),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# 1. Firmar → JWS (inner JWT)
|
|
48
|
+
signed: str = jwt.encode(
|
|
49
|
+
header={"alg": "RS256", "typ": "JWT"},
|
|
50
|
+
claims=payload,
|
|
51
|
+
key=self._private_key,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# 2. Encriptar → JWE (outer token)
|
|
55
|
+
encrypted: str = jwe.encrypt_compact(
|
|
56
|
+
protected={"alg": "RSA-OAEP", "enc": "A256CBC-HS512", "cty": "JWT"},
|
|
57
|
+
plaintext=signed.encode(), # el JWT firmado es el payload del JWE
|
|
58
|
+
public_key=self._public_key,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return encrypted
|
|
62
|
+
|
|
63
|
+
def get_claims_ignore_expiration(
|
|
64
|
+
self,
|
|
65
|
+
token: str,
|
|
66
|
+
) -> dict:
|
|
67
|
+
"""
|
|
68
|
+
Desencripta y verifica FIRMA únicamente.
|
|
69
|
+
|
|
70
|
+
NO valida: exp, nbf, iat, iss, aud.
|
|
71
|
+
|
|
72
|
+
Usar exclusivamente en el flujo de refresh token,
|
|
73
|
+
donde el token puede estar vencido pero la firma debe ser válida.
|
|
74
|
+
"""
|
|
75
|
+
return self._decode(token, validate_exp=False)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def validate_token(
|
|
79
|
+
self,
|
|
80
|
+
token: str,
|
|
81
|
+
options: TokenValidationOptions | None = None,
|
|
82
|
+
) -> dict:
|
|
83
|
+
"""
|
|
84
|
+
Desencripta, verifica firma, valida exp y,
|
|
85
|
+
opcionalmente, iss y aud según el TokenValidationOptions recibido.
|
|
86
|
+
"""
|
|
87
|
+
options = options or TokenValidationOptions()
|
|
88
|
+
|
|
89
|
+
claims = self._decode(token, validate_exp=True)
|
|
90
|
+
|
|
91
|
+
self._validate_registered_claims(claims, options)
|
|
92
|
+
|
|
93
|
+
return claims
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _decode(
|
|
97
|
+
self,
|
|
98
|
+
token: str,
|
|
99
|
+
validate_exp: bool,
|
|
100
|
+
) -> dict:
|
|
101
|
+
"""
|
|
102
|
+
Núcleo del proceso:
|
|
103
|
+
1. Desencripta el JWE → valida criptografía outer
|
|
104
|
+
2. Verifica la firma del JWT inner → valida criptografía inner
|
|
105
|
+
3. Valida exp solo si validate_exp=True
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
# --- Paso 1: desencriptar JWE ---
|
|
109
|
+
decrypted = jwe.decrypt_compact(
|
|
110
|
+
value=token.encode(),
|
|
111
|
+
private_key=self._private_key,
|
|
112
|
+
algorithms=self._jwe_algorithms,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
inner_token: str = decrypted.plaintext.decode()
|
|
116
|
+
|
|
117
|
+
# --- Paso 2: verificar firma (decode NO valida claims) ---
|
|
118
|
+
token_obj = jwt.decode(
|
|
119
|
+
value=inner_token,
|
|
120
|
+
key=self._public_key,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
claims: dict = token_obj.claims
|
|
124
|
+
|
|
125
|
+
# --- Paso 3: validar exp solo si se pide ---
|
|
126
|
+
if validate_exp:
|
|
127
|
+
now = datetime.now(timezone.utc)
|
|
128
|
+
exp = claims.get("exp")
|
|
129
|
+
|
|
130
|
+
if exp is None:
|
|
131
|
+
raise UnauthorizedException(
|
|
132
|
+
message="Token missing exp claim",
|
|
133
|
+
status_code=StatusCode.UNAUTHORIZED,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
if int(now.timestamp()) > exp:
|
|
137
|
+
raise UnauthorizedException(
|
|
138
|
+
message="Token expired",
|
|
139
|
+
status_code=StatusCode.UNAUTHORIZED,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return claims
|
|
143
|
+
|
|
144
|
+
except UnauthorizedException:
|
|
145
|
+
raise
|
|
146
|
+
|
|
147
|
+
except Exception as exc:
|
|
148
|
+
raise UnauthorizedException(
|
|
149
|
+
message="Invalid token",
|
|
150
|
+
status_code=StatusCode.UNAUTHORIZED,
|
|
151
|
+
) from exc
|
|
152
|
+
|
|
153
|
+
def _validate_registered_claims(
|
|
154
|
+
self,
|
|
155
|
+
claims: dict,
|
|
156
|
+
options: TokenValidationOptions,
|
|
157
|
+
) -> None:
|
|
158
|
+
|
|
159
|
+
if options.validate_issuer:
|
|
160
|
+
|
|
161
|
+
if not options.issuer:
|
|
162
|
+
raise ValueError("issuer is required in options")
|
|
163
|
+
|
|
164
|
+
if claims.get("iss") != options.issuer:
|
|
165
|
+
raise UnauthorizedException(
|
|
166
|
+
message="Invalid issuer",
|
|
167
|
+
status_code=StatusCode.UNAUTHORIZED,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if options.validate_audience:
|
|
171
|
+
|
|
172
|
+
if not options.audience:
|
|
173
|
+
raise ValueError("audience is required in options")
|
|
174
|
+
|
|
175
|
+
if claims.get("aud") != options.audience:
|
|
176
|
+
raise UnauthorizedException(
|
|
177
|
+
message="Invalid audience",
|
|
178
|
+
status_code=StatusCode.UNAUTHORIZED,
|
|
179
|
+
)
|
|
@@ -130,6 +130,31 @@ class JwtAuthTokenService(IAuthTokenService):
|
|
|
130
130
|
)
|
|
131
131
|
)
|
|
132
132
|
|
|
133
|
+
return UserTokenClaims(
|
|
134
|
+
id_legacy=claims["id_legacy"],
|
|
135
|
+
id_enterprise=claims["id_enterprise"],
|
|
136
|
+
id_token=claims["id_token"],
|
|
137
|
+
id_user=claims["id_user"],
|
|
138
|
+
id_external_enterprise=claims["id_external_enterprise"],
|
|
139
|
+
id_profile=claims["id_profile"],
|
|
140
|
+
user_full_name=claims["user_full_name"],
|
|
141
|
+
abbreviation=claims["abbreviation"],
|
|
142
|
+
aes_key_auth=claims["aes_key_auth"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def validate_token_for_refresh(
|
|
147
|
+
self,
|
|
148
|
+
token: str,
|
|
149
|
+
) -> UserTokenClaims:
|
|
150
|
+
"""
|
|
151
|
+
Valida firma pero ignora exp.
|
|
152
|
+
Usar exclusivamente para emitir un nuevo token cuando el anterior venció.
|
|
153
|
+
"""
|
|
154
|
+
await self.__get_token_signatures()
|
|
155
|
+
|
|
156
|
+
claims = self._jwt.get_claims_ignore_expiration(token)
|
|
157
|
+
|
|
133
158
|
return UserTokenClaims(
|
|
134
159
|
id_legacy=claims["id_legacy"],
|
|
135
160
|
id_enterprise=claims["id_enterprise"],
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from datetime import datetime, timedelta, timezone
|
|
3
|
-
from jwcrypto import jwk
|
|
4
|
-
from jwcrypto import jwt
|
|
5
|
-
from jwcrypto import jwe
|
|
6
|
-
from jwcrypto.jwt import JWTExpired
|
|
7
|
-
from Osdental.Models.TokenClaims import TokenValidationOptions
|
|
8
|
-
from Osdental.Exception.ControlledException import UnauthorizedException
|
|
9
|
-
from Osdental.Enums.StatusCode import StatusCode
|
|
10
|
-
|
|
11
|
-
class JwtTokenHelper:
|
|
12
|
-
|
|
13
|
-
def __init__(
|
|
14
|
-
self,
|
|
15
|
-
private_key_pem: str,
|
|
16
|
-
public_key_pem: str,
|
|
17
|
-
):
|
|
18
|
-
self._private_key = jwk.JWK.from_pem(
|
|
19
|
-
private_key_pem.encode()
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
self._public_key = jwk.JWK.from_pem(
|
|
23
|
-
public_key_pem.encode()
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
def generate_token(
|
|
27
|
-
self,
|
|
28
|
-
claims: dict,
|
|
29
|
-
expiration_minutes: int,
|
|
30
|
-
) -> str:
|
|
31
|
-
|
|
32
|
-
now = datetime.now(timezone.utc)
|
|
33
|
-
|
|
34
|
-
payload = {
|
|
35
|
-
**claims,
|
|
36
|
-
"iat": int(now.timestamp()),
|
|
37
|
-
"nbf": int(now.timestamp()),
|
|
38
|
-
"exp": int(
|
|
39
|
-
(
|
|
40
|
-
now
|
|
41
|
-
+ timedelta(
|
|
42
|
-
minutes=expiration_minutes
|
|
43
|
-
)
|
|
44
|
-
).timestamp()
|
|
45
|
-
),
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
signed_token = jwt.JWT(
|
|
49
|
-
header={
|
|
50
|
-
"alg": "RS256",
|
|
51
|
-
"typ": "JWT",
|
|
52
|
-
},
|
|
53
|
-
claims=payload,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
signed_token.make_signed_token(
|
|
57
|
-
self._private_key
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
encrypted_token = jwt.JWT(
|
|
61
|
-
header={
|
|
62
|
-
"alg": "RSA-OAEP",
|
|
63
|
-
"enc": "A256CBC-HS512",
|
|
64
|
-
"cty": "JWT",
|
|
65
|
-
},
|
|
66
|
-
claims=signed_token.serialize(),
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
encrypted_token.make_encrypted_token(
|
|
70
|
-
self._public_key
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
return encrypted_token.serialize()
|
|
74
|
-
|
|
75
|
-
def get_claims(
|
|
76
|
-
self,
|
|
77
|
-
token: str,
|
|
78
|
-
) -> dict:
|
|
79
|
-
"""
|
|
80
|
-
Desencripta y verifica firma.
|
|
81
|
-
|
|
82
|
-
NO valida:
|
|
83
|
-
- issuer
|
|
84
|
-
- audience
|
|
85
|
-
|
|
86
|
-
Retorna claims incluso si el token está expirado.
|
|
87
|
-
"""
|
|
88
|
-
|
|
89
|
-
try:
|
|
90
|
-
encrypted = jwe.JWE()
|
|
91
|
-
|
|
92
|
-
encrypted.deserialize(
|
|
93
|
-
token
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
encrypted.decrypt(
|
|
97
|
-
self._private_key
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
inner_token = (
|
|
101
|
-
encrypted.payload.decode()
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
verified = jwt.JWT()
|
|
105
|
-
|
|
106
|
-
verified.deserialize(
|
|
107
|
-
jwt=inner_token,
|
|
108
|
-
key=self._public_key,
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
return json.loads(
|
|
112
|
-
verified.claims
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
except JWTExpired:
|
|
116
|
-
raise UnauthorizedException(
|
|
117
|
-
message="Token expired",
|
|
118
|
-
status_code=StatusCode.UNAUTHORIZED
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
def validate_claims(
|
|
122
|
-
self,
|
|
123
|
-
claims: dict,
|
|
124
|
-
options: TokenValidationOptions,
|
|
125
|
-
) -> None:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if options.validate_issuer:
|
|
129
|
-
|
|
130
|
-
if not options.issuer:
|
|
131
|
-
raise ValueError(
|
|
132
|
-
"issuer is required"
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
if claims.get("iss") != options.issuer:
|
|
136
|
-
raise ValueError(
|
|
137
|
-
"Invalid issuer"
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if options.validate_audience:
|
|
142
|
-
|
|
143
|
-
if not options.audience:
|
|
144
|
-
raise ValueError(
|
|
145
|
-
"audience is required"
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
if claims.get("aud") != options.audience:
|
|
149
|
-
raise ValueError(
|
|
150
|
-
"Invalid audience"
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def validate_token(
|
|
155
|
-
self,
|
|
156
|
-
token: str,
|
|
157
|
-
options: TokenValidationOptions | None = None,
|
|
158
|
-
) -> dict:
|
|
159
|
-
|
|
160
|
-
options = options or TokenValidationOptions()
|
|
161
|
-
|
|
162
|
-
claims = self.get_claims(
|
|
163
|
-
token
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
self.validate_claims(
|
|
167
|
-
claims=claims,
|
|
168
|
-
options=options,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
return claims
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|