pypomes-jwt 0.9.8__py3-none-any.whl → 0.9.9__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.
Potentially problematic release.
This version of pypomes-jwt might be problematic. Click here for more details.
- pypomes_jwt/jwt_pomes.py +63 -56
- pypomes_jwt/jwt_registry.py +10 -9
- {pypomes_jwt-0.9.8.dist-info → pypomes_jwt-0.9.9.dist-info}/METADATA +3 -3
- pypomes_jwt-0.9.9.dist-info/RECORD +8 -0
- pypomes_jwt-0.9.8.dist-info/RECORD +0 -8
- {pypomes_jwt-0.9.8.dist-info → pypomes_jwt-0.9.9.dist-info}/WHEEL +0 -0
- {pypomes_jwt-0.9.8.dist-info → pypomes_jwt-0.9.9.dist-info}/licenses/LICENSE +0 -0
pypomes_jwt/jwt_pomes.py
CHANGED
|
@@ -161,69 +161,76 @@ def jwt_validate_token(errors: list[str] | None,
|
|
|
161
161
|
"""
|
|
162
162
|
# initialize the return variable
|
|
163
163
|
result: dict[str, Any] | None = None
|
|
164
|
+
|
|
164
165
|
if logger:
|
|
165
166
|
logger.debug(msg="Validate JWT token")
|
|
167
|
+
op_errors: list[str] = []
|
|
166
168
|
|
|
167
169
|
# extract needed data from token header
|
|
168
|
-
token_header: dict[str, Any] =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
token_header: dict[str, Any] | None = None
|
|
171
|
+
try:
|
|
172
|
+
token_header: dict[str, Any] = jwt.get_unverified_header(jwt=token)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
op_errors.append(str(e))
|
|
173
175
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
# token
|
|
180
|
-
|
|
181
|
-
if account_id:
|
|
182
|
-
where_data[JWT_DB_COL_ACCOUNT] = account_id
|
|
183
|
-
recs: list[tuple[str]] = db_select(errors=op_errors,
|
|
184
|
-
sel_stmt=f"SELECT {JWT_DB_COL_ALGORITHM}, {JWT_DB_COL_DECODER} "
|
|
185
|
-
f"FROM {JWT_DB_TABLE}",
|
|
186
|
-
where_data=where_data,
|
|
187
|
-
logger=logger)
|
|
188
|
-
if recs:
|
|
189
|
-
token_alg = recs[0][0]
|
|
190
|
-
token_decoder = urlsafe_b64decode(recs[0][1])
|
|
191
|
-
else:
|
|
176
|
+
if not op_errors:
|
|
177
|
+
token_kid: str = token_header.get("kid")
|
|
178
|
+
token_alg: str | None = None
|
|
179
|
+
token_decoder: bytes | None = None
|
|
180
|
+
|
|
181
|
+
# retrieve token data from database
|
|
182
|
+
if nature and not (token_kid and token_kid[0:1] == nature):
|
|
192
183
|
op_errors.append("Invalid token")
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
184
|
+
elif token_kid and len(token_kid) > 1 and \
|
|
185
|
+
token_kid[0:1] in ["A", "R"] and token_kid[1:].isdigit():
|
|
186
|
+
# token was likely issued locally
|
|
187
|
+
where_data: dict[str, Any] = {JWT_DB_COL_KID: int(token_kid[1:])}
|
|
188
|
+
if account_id:
|
|
189
|
+
where_data[JWT_DB_COL_ACCOUNT] = account_id
|
|
190
|
+
recs: list[tuple[str]] = db_select(errors=op_errors,
|
|
191
|
+
sel_stmt=f"SELECT {JWT_DB_COL_ALGORITHM}, {JWT_DB_COL_DECODER} "
|
|
192
|
+
f"FROM {JWT_DB_TABLE}",
|
|
193
|
+
where_data=where_data,
|
|
194
|
+
logger=logger)
|
|
195
|
+
if recs:
|
|
196
|
+
token_alg = recs[0][0]
|
|
197
|
+
token_decoder = urlsafe_b64decode(recs[0][1])
|
|
198
|
+
else:
|
|
199
|
+
op_errors.append("Invalid token")
|
|
200
|
+
else:
|
|
201
|
+
token_alg = JWT_DEFAULT_ALGORITHM
|
|
202
|
+
token_decoder = JWT_DECODING_KEY
|
|
196
203
|
|
|
197
204
|
# validate the token
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
205
|
+
if not op_errors:
|
|
206
|
+
try:
|
|
207
|
+
# raises:
|
|
208
|
+
# InvalidTokenError: token is invalid
|
|
209
|
+
# InvalidKeyError: authentication key is not in the proper format
|
|
210
|
+
# ExpiredSignatureError: token and refresh period have expired
|
|
211
|
+
# InvalidSignatureError: signature does not match the one provided as part of the token
|
|
212
|
+
# ImmatureSignatureError: 'nbf' or 'iat' claim represents a timestamp in the future
|
|
213
|
+
# InvalidAlgorithmError: the specified algorithm is not recognized
|
|
214
|
+
# InvalidIssuedAtError: 'iat' claim is non-numeric
|
|
215
|
+
# MissingRequiredClaimError: a required claim is not contained in the claimset
|
|
216
|
+
payload: dict[str, Any] = jwt.decode(jwt=token,
|
|
217
|
+
options={
|
|
218
|
+
"verify_signature": True,
|
|
219
|
+
"verify_exp": True,
|
|
220
|
+
"verify_nbf": True
|
|
221
|
+
},
|
|
222
|
+
key=token_decoder,
|
|
223
|
+
require=["iat", "iss", "exp", "sub"],
|
|
224
|
+
algorithms=token_alg)
|
|
225
|
+
if account_id and payload.get("sub") != account_id:
|
|
226
|
+
op_errors.append("Token does not belong to account")
|
|
227
|
+
else:
|
|
228
|
+
result = {
|
|
229
|
+
"header": token_header,
|
|
230
|
+
"payload": payload
|
|
231
|
+
}
|
|
232
|
+
except Exception as e:
|
|
233
|
+
op_errors.append(str(e))
|
|
227
234
|
|
|
228
235
|
if op_errors:
|
|
229
236
|
err_msg: str = "; ".join(op_errors)
|
|
@@ -256,7 +263,7 @@ def jwt_revoke_token(errors: list[str] | None,
|
|
|
256
263
|
result: bool = False
|
|
257
264
|
|
|
258
265
|
if logger:
|
|
259
|
-
logger.debug(msg=f"Revoking
|
|
266
|
+
logger.debug(msg=f"Revoking token of account '{account_id}'")
|
|
260
267
|
|
|
261
268
|
op_errors: list[str] = []
|
|
262
269
|
token_claims: dict[str, Any] = jwt_validate_token(errors=op_errors,
|
pypomes_jwt/jwt_registry.py
CHANGED
|
@@ -379,12 +379,13 @@ def _jwt_persist_token(errors: list[str],
|
|
|
379
379
|
if logger:
|
|
380
380
|
logger.debug(msg=f"Read {len(recs)} token from storage for account '{account_id}'")
|
|
381
381
|
# remove the expired tokens
|
|
382
|
-
|
|
383
|
-
|
|
382
|
+
just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
|
|
383
|
+
oldest_ts: int = sys.maxsize
|
|
384
|
+
oldest_id: int | None = None
|
|
384
385
|
expired: list[int] = []
|
|
385
386
|
for rec in recs:
|
|
386
387
|
token: str = rec[1]
|
|
387
|
-
|
|
388
|
+
token_id: int = rec[0]
|
|
388
389
|
token_claims: dict[str, Any] = jwt_get_claims(errors=errors,
|
|
389
390
|
token=token,
|
|
390
391
|
logger=logger)
|
|
@@ -393,14 +394,14 @@ def _jwt_persist_token(errors: list[str],
|
|
|
393
394
|
|
|
394
395
|
# find expired tokens
|
|
395
396
|
exp: int = token_claims["payload"].get("exp", sys.maxsize)
|
|
396
|
-
if exp <
|
|
397
|
-
expired.append(
|
|
397
|
+
if exp < just_now:
|
|
398
|
+
expired.append(token_id)
|
|
398
399
|
|
|
399
400
|
# find oldest token
|
|
400
401
|
iat: int = token_claims["payload"].get("iat", sys.maxsize)
|
|
401
|
-
if iat <
|
|
402
|
-
|
|
403
|
-
|
|
402
|
+
if iat < oldest_ts:
|
|
403
|
+
oldest_ts = exp
|
|
404
|
+
oldest_id = token_id
|
|
404
405
|
|
|
405
406
|
# remove expired tokens from persistence
|
|
406
407
|
if expired:
|
|
@@ -419,7 +420,7 @@ def _jwt_persist_token(errors: list[str],
|
|
|
419
420
|
# delete the oldest token to make way for the new one
|
|
420
421
|
db_delete(errors=errors,
|
|
421
422
|
delete_stmt=f"DELETE FROM {JWT_DB_TABLE}",
|
|
422
|
-
where_data={JWT_DB_COL_KID:
|
|
423
|
+
where_data={JWT_DB_COL_KID: oldest_id},
|
|
423
424
|
connection=db_conn,
|
|
424
425
|
logger=logger)
|
|
425
426
|
if errors:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.9
|
|
4
4
|
Summary: A collection of Python pomes, penyeach (JWT module)
|
|
5
5
|
Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-JWT
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-JWT/issues
|
|
@@ -12,5 +12,5 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Requires-Dist: cryptography>=44.0.2
|
|
14
14
|
Requires-Dist: pyjwt>=2.10.1
|
|
15
|
-
Requires-Dist: pypomes-core>=1.8.
|
|
16
|
-
Requires-Dist: pypomes-db>=1.9.
|
|
15
|
+
Requires-Dist: pypomes-core>=1.8.6
|
|
16
|
+
Requires-Dist: pypomes-db>=1.9.8
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pypomes_jwt/__init__.py,sha256=fLr_M8yXlcmSTNPMdJOJQlMmtaiK5YKh0vKjOp3z2E4,1446
|
|
2
|
+
pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
|
|
3
|
+
pypomes_jwt/jwt_pomes.py,sha256=SI_lAGIqhCNm5DneyFN4KSKomCQPaZ2HoKCgmBdZL5I,20435
|
|
4
|
+
pypomes_jwt/jwt_registry.py,sha256=5L6n4ue7YEkGT60U69WvoobKR9ziHkqdIgeOmYEn2gU,21472
|
|
5
|
+
pypomes_jwt-0.9.9.dist-info/METADATA,sha256=cFLCxrL91S8J1VAHd31XiMRgVSNIlBEiV4DKkTbSEtY,632
|
|
6
|
+
pypomes_jwt-0.9.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
pypomes_jwt-0.9.9.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
+
pypomes_jwt-0.9.9.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pypomes_jwt/__init__.py,sha256=fLr_M8yXlcmSTNPMdJOJQlMmtaiK5YKh0vKjOp3z2E4,1446
|
|
2
|
-
pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
|
|
3
|
-
pypomes_jwt/jwt_pomes.py,sha256=_aK1TjV4cJ96ERa3VzloU9VJZVktbee2QdBTMYxR1DE,20064
|
|
4
|
-
pypomes_jwt/jwt_registry.py,sha256=mASF2NxHK4jWNswqfDXPGGuv8RsSIvLh1MzldaBxdeA,21425
|
|
5
|
-
pypomes_jwt-0.9.8.dist-info/METADATA,sha256=q37-GtefddZ2WqtcHMh8wGtK8usua2to3g7w5aO5-Jc,632
|
|
6
|
-
pypomes_jwt-0.9.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
pypomes_jwt-0.9.8.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
-
pypomes_jwt-0.9.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|