pypomes-jwt 1.0.2__tar.gz → 1.0.4__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.
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/PKG-INFO +3 -3
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/pyproject.toml +3 -3
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/src/pypomes_jwt/jwt_pomes.py +55 -16
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/src/pypomes_jwt/jwt_registry.py +49 -34
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/.gitignore +0 -0
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/LICENSE +0 -0
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/README.md +0 -0
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/src/pypomes_jwt/__init__.py +0 -0
- {pypomes_jwt-1.0.2 → pypomes_jwt-1.0.4}/src/pypomes_jwt/jwt_constants.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
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>=
|
|
15
|
+
Requires-Dist: pypomes-core>=1.8.7
|
|
16
|
+
Requires-Dist: pypomes-db>=2.0.0
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pypomes_jwt"
|
|
9
|
-
version = "1.0.
|
|
9
|
+
version = "1.0.4"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name="GT Nunes", email="wisecoder01@gmail.com" }
|
|
12
12
|
]
|
|
@@ -21,8 +21,8 @@ classifiers = [
|
|
|
21
21
|
dependencies = [
|
|
22
22
|
"PyJWT>=2.10.1",
|
|
23
23
|
"cryptography>=44.0.2",
|
|
24
|
-
"pypomes_core>=1.8.
|
|
25
|
-
"pypomes_db>=
|
|
24
|
+
"pypomes_core>=1.8.7",
|
|
25
|
+
"pypomes_db>=2.0.0"
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
@@ -4,7 +4,9 @@ from base64 import urlsafe_b64decode
|
|
|
4
4
|
from flask import Request, Response, request
|
|
5
5
|
from logging import Logger
|
|
6
6
|
from pypomes_core import exc_format
|
|
7
|
-
from pypomes_db import
|
|
7
|
+
from pypomes_db import (
|
|
8
|
+
db_connect, db_commit, db_rollback, db_select, db_delete
|
|
9
|
+
)
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
12
|
from . import (
|
|
@@ -436,21 +438,57 @@ def jwt_refresh_tokens(errors: list[str] | None,
|
|
|
436
438
|
# assert the refresh token
|
|
437
439
|
if refresh_token:
|
|
438
440
|
# is the refresh token valid ?
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
account_id=account_id,
|
|
452
|
-
account_claims=account_claims,
|
|
441
|
+
token_claims: dict[str, Any] = jwt_validate_token(errors=op_errors,
|
|
442
|
+
token=refresh_token,
|
|
443
|
+
nature="R",
|
|
444
|
+
account_id=account_id,
|
|
445
|
+
logger=logger)
|
|
446
|
+
if token_claims:
|
|
447
|
+
# yes, proceed
|
|
448
|
+
token_kid: str = token_claims["header"].get("kid")
|
|
449
|
+
|
|
450
|
+
# start the database transaction
|
|
451
|
+
db_conn: Any = db_connect(errors=op_errors,
|
|
452
|
+
autocommit=False,
|
|
453
453
|
logger=logger)
|
|
454
|
+
if db_conn:
|
|
455
|
+
# delete current refresh token
|
|
456
|
+
db_delete(errors=op_errors,
|
|
457
|
+
delete_stmt=f"DELETE FROM {JWT_DB_TABLE}",
|
|
458
|
+
where_data={
|
|
459
|
+
JWT_DB_COL_KID: int(token_kid[1:]),
|
|
460
|
+
JWT_DB_COL_ACCOUNT: account_id
|
|
461
|
+
},
|
|
462
|
+
connection=db_conn,
|
|
463
|
+
committable=False,
|
|
464
|
+
logger=logger)
|
|
465
|
+
|
|
466
|
+
# issue the token pair
|
|
467
|
+
if not op_errors:
|
|
468
|
+
try:
|
|
469
|
+
result = __jwt_registry.issue_tokens(account_id=account_id,
|
|
470
|
+
account_claims=token_claims.get("payload"),
|
|
471
|
+
db_conn=db_conn,
|
|
472
|
+
logger=logger)
|
|
473
|
+
if logger:
|
|
474
|
+
logger.debug(msg=f"Token pair was refreshed for account '{account_id}'")
|
|
475
|
+
except Exception as e:
|
|
476
|
+
# token issuing failed
|
|
477
|
+
exc_err: str = exc_format(exc=e,
|
|
478
|
+
exc_info=sys.exc_info())
|
|
479
|
+
if logger:
|
|
480
|
+
logger.error(msg=f"Error refreshing the token pair: {exc_err}")
|
|
481
|
+
op_errors.append(exc_err)
|
|
482
|
+
|
|
483
|
+
# conclude the transaction
|
|
484
|
+
if op_errors:
|
|
485
|
+
db_rollback(errors=op_errors,
|
|
486
|
+
connection=db_conn,
|
|
487
|
+
logger=logger)
|
|
488
|
+
else:
|
|
489
|
+
db_commit(errors=op_errors,
|
|
490
|
+
connection=db_conn,
|
|
491
|
+
logger=logger)
|
|
454
492
|
else:
|
|
455
493
|
# refresh token not found
|
|
456
494
|
op_errors.append("Refresh token was not provided")
|
|
@@ -470,7 +508,8 @@ def jwt_get_claims(errors: list[str] | None,
|
|
|
470
508
|
"""
|
|
471
509
|
Retrieve and return the claims set of a JWT *token*.
|
|
472
510
|
|
|
473
|
-
Any
|
|
511
|
+
Any well-constructed JWT token may be provided in *token*, as this operation is not restricted to
|
|
512
|
+
locally issued tokens. Note that neither the token's signature nor its expiration is verified.
|
|
474
513
|
|
|
475
514
|
Structure of the returned data, for locally issued tokens:
|
|
476
515
|
{
|
|
@@ -6,7 +6,8 @@ from datetime import datetime, timezone
|
|
|
6
6
|
from logging import Logger
|
|
7
7
|
from pypomes_core import str_random
|
|
8
8
|
from pypomes_db import (
|
|
9
|
-
db_connect, db_commit,
|
|
9
|
+
db_connect, db_commit, db_rollback,
|
|
10
|
+
db_select, db_insert, db_update, db_delete
|
|
10
11
|
)
|
|
11
12
|
from threading import Lock
|
|
12
13
|
from typing import Any
|
|
@@ -225,6 +226,7 @@ class JwtRegistry:
|
|
|
225
226
|
def issue_tokens(self,
|
|
226
227
|
account_id: str,
|
|
227
228
|
account_claims: dict[str, Any] = None,
|
|
229
|
+
db_conn: Any = None,
|
|
228
230
|
logger: Logger = None) -> dict[str, Any]:
|
|
229
231
|
"""
|
|
230
232
|
Issue and return a JWT token pair associated with *account_id*.
|
|
@@ -232,6 +234,9 @@ class JwtRegistry:
|
|
|
232
234
|
These claims are ignored, if specified in *account_claims*: *iat*, *exp*, *jti*, *nbf*, and *sub*.
|
|
233
235
|
Other claims specified therein may supercede registered account-related claims.
|
|
234
236
|
|
|
237
|
+
If provided, *db_conn* indicates that this operation is part of a larger database transaction.
|
|
238
|
+
Otherwise, the database transaction's scope is limited to this operation.
|
|
239
|
+
|
|
235
240
|
Structure of the return data:
|
|
236
241
|
{
|
|
237
242
|
"access-token": <jwt-token>,
|
|
@@ -242,6 +247,7 @@ class JwtRegistry:
|
|
|
242
247
|
|
|
243
248
|
:param account_id: the account identification
|
|
244
249
|
:param account_claims: if provided, may supercede registered account-related claims
|
|
250
|
+
:param db_conn: if provided, indicates that this operation is part of a larger database transaction
|
|
245
251
|
:param logger: optional logger
|
|
246
252
|
:return: the JWT token data
|
|
247
253
|
:raises RuntimeError: invalid account id, or error accessing the token database
|
|
@@ -277,31 +283,41 @@ class JwtRegistry:
|
|
|
277
283
|
key=JWT_ENCODING_KEY,
|
|
278
284
|
algorithm=JWT_DEFAULT_ALGORITHM,
|
|
279
285
|
headers={"kid": "R0"})
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
connection=db_conn,
|
|
286
|
+
|
|
287
|
+
# make sure to have a database connection
|
|
288
|
+
curr_conn: Any = db_conn or db_connect(errors=errors,
|
|
289
|
+
autocommit=False,
|
|
290
|
+
logger=logger)
|
|
291
|
+
if curr_conn:
|
|
292
|
+
# persist the candidate token (may raise an exception)
|
|
293
|
+
token_id: int = _jwt_persist_token(account_id=account_id,
|
|
294
|
+
jwt_token=refresh_token,
|
|
295
|
+
db_conn=curr_conn,
|
|
296
|
+
logger=logger)
|
|
297
|
+
# issue the definitive refresh token
|
|
298
|
+
refresh_token = jwt.encode(payload=current_claims,
|
|
299
|
+
key=JWT_ENCODING_KEY,
|
|
300
|
+
algorithm=JWT_DEFAULT_ALGORITHM,
|
|
301
|
+
headers={"kid": f"R{token_id}"})
|
|
302
|
+
# persist it
|
|
303
|
+
db_update(errors=errors,
|
|
304
|
+
update_stmt=f"UPDATE {JWT_DB_TABLE}",
|
|
305
|
+
update_data={JWT_DB_COL_TOKEN: refresh_token},
|
|
306
|
+
where_data={JWT_DB_COL_KID: token_id},
|
|
307
|
+
connection=curr_conn,
|
|
308
|
+
committable=False,
|
|
304
309
|
logger=logger)
|
|
310
|
+
|
|
311
|
+
# conclude the transaction
|
|
312
|
+
if not db_conn:
|
|
313
|
+
if errors:
|
|
314
|
+
db_rollback(errors=errors,
|
|
315
|
+
connection=curr_conn,
|
|
316
|
+
logger=logger)
|
|
317
|
+
else:
|
|
318
|
+
db_commit(errors=errors,
|
|
319
|
+
connection=curr_conn,
|
|
320
|
+
logger=logger)
|
|
305
321
|
if errors:
|
|
306
322
|
raise RuntimeError("; ".join(errors))
|
|
307
323
|
|
|
@@ -343,7 +359,7 @@ class JwtRegistry:
|
|
|
343
359
|
|
|
344
360
|
def _jwt_persist_token(account_id: str,
|
|
345
361
|
jwt_token: str,
|
|
346
|
-
db_conn: Any
|
|
362
|
+
db_conn: Any,
|
|
347
363
|
logger: Logger = None) -> int:
|
|
348
364
|
"""
|
|
349
365
|
Persist the given token, making sure that the account limit is adhered to.
|
|
@@ -352,8 +368,7 @@ def _jwt_persist_token(account_id: str,
|
|
|
352
368
|
If a token's expiration timestamp is in the past, it is removed from storage. If the maximum number
|
|
353
369
|
of active tokens for *account_id* has been reached, the oldest active one is alse removed,
|
|
354
370
|
to make room for the new *jwt_token*.
|
|
355
|
-
|
|
356
|
-
If *db_conn* is provided, then all DB operations will be carried out in the scope of a single transaction.
|
|
371
|
+
The provided database connection *db_conn* indicates that this operation is part of a larger transaction.
|
|
357
372
|
|
|
358
373
|
:param account_id: the account identification
|
|
359
374
|
:param jwt_token: the JWT token to persist
|
|
@@ -388,21 +403,21 @@ def _jwt_persist_token(account_id: str,
|
|
|
388
403
|
for rec in recs:
|
|
389
404
|
token: str = rec[1]
|
|
390
405
|
token_id: int = rec[0]
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
406
|
+
token_payload: dict[str, Any] = (jwt_get_claims(errors=errors,
|
|
407
|
+
token=token,
|
|
408
|
+
logger=logger) or {}).get("payload")
|
|
394
409
|
if errors:
|
|
395
410
|
raise RuntimeError("; ".join(errors))
|
|
396
411
|
|
|
397
412
|
# find expired tokens
|
|
398
|
-
exp: int =
|
|
413
|
+
exp: int = token_payload.get("exp", sys.maxsize)
|
|
399
414
|
if exp < just_now:
|
|
400
415
|
expired.append(token_id)
|
|
401
416
|
|
|
402
417
|
# find oldest token
|
|
403
|
-
iat: int =
|
|
418
|
+
iat: int = token_payload.get("iat", sys.maxsize)
|
|
404
419
|
if iat < oldest_ts:
|
|
405
|
-
oldest_ts =
|
|
420
|
+
oldest_ts = iat
|
|
406
421
|
oldest_id = token_id
|
|
407
422
|
|
|
408
423
|
# remove expired tokens from persistence
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|