pypomes-jwt 1.0.3__py3-none-any.whl → 1.0.5__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 +55 -16
- pypomes_jwt/jwt_registry.py +55 -29
- {pypomes_jwt-1.0.3.dist-info → pypomes_jwt-1.0.5.dist-info}/METADATA +1 -1
- pypomes_jwt-1.0.5.dist-info/RECORD +8 -0
- pypomes_jwt-1.0.3.dist-info/RECORD +0 -8
- {pypomes_jwt-1.0.3.dist-info → pypomes_jwt-1.0.5.dist-info}/WHEEL +0 -0
- {pypomes_jwt-1.0.3.dist-info → pypomes_jwt-1.0.5.dist-info}/licenses/LICENSE +0 -0
pypomes_jwt/jwt_pomes.py
CHANGED
|
@@ -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
|
{
|
pypomes_jwt/jwt_registry.py
CHANGED
|
@@ -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
|
|
@@ -384,6 +399,7 @@ def _jwt_persist_token(account_id: str,
|
|
|
384
399
|
just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
|
|
385
400
|
oldest_ts: int = sys.maxsize
|
|
386
401
|
oldest_id: int | None = None
|
|
402
|
+
existing_ids: list[int] = []
|
|
387
403
|
expired: list[int] = []
|
|
388
404
|
for rec in recs:
|
|
389
405
|
token: str = rec[1]
|
|
@@ -405,6 +421,9 @@ def _jwt_persist_token(account_id: str,
|
|
|
405
421
|
oldest_ts = iat
|
|
406
422
|
oldest_id = token_id
|
|
407
423
|
|
|
424
|
+
# save token id
|
|
425
|
+
existing_ids.append(token_id)
|
|
426
|
+
|
|
408
427
|
# remove expired tokens from persistence
|
|
409
428
|
if expired:
|
|
410
429
|
db_delete(errors=errors,
|
|
@@ -446,10 +465,17 @@ def _jwt_persist_token(account_id: str,
|
|
|
446
465
|
raise RuntimeError("; ".join(errors))
|
|
447
466
|
|
|
448
467
|
# obtain the token's storage id
|
|
468
|
+
# HAZARD: JWT_DB_COL_TOKEN's type might prevent it for being used in a WHERE clause
|
|
469
|
+
where_clause: str | None = None
|
|
470
|
+
if existing_ids:
|
|
471
|
+
where_clause = f"{JWT_DB_COL_KID} NOT IN ({existing_ids})"
|
|
472
|
+
where_clause = where_clause.replace("[", "").replace("]", "")
|
|
449
473
|
reply: list[tuple[int]] = db_select(errors=errors,
|
|
450
474
|
sel_stmt=f"SELECT {JWT_DB_COL_KID} "
|
|
451
475
|
f"FROM {JWT_DB_TABLE}",
|
|
452
|
-
|
|
476
|
+
where_clause=where_clause,
|
|
477
|
+
where_data={JWT_DB_COL_ACCOUNT: account_id},
|
|
478
|
+
require_count=1,
|
|
453
479
|
connection=db_conn,
|
|
454
480
|
committable=False,
|
|
455
481
|
logger=logger)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
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
|
|
@@ -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=2LyjMMVkdPXeC7hMD52637e-LJoGbhKTb0Wqj6QXbTg,23049
|
|
4
|
+
pypomes_jwt/jwt_registry.py,sha256=OgL4qH2WJdg0awtTRTk3jLP4oXB21PbEfDRzADTEhYI,22979
|
|
5
|
+
pypomes_jwt-1.0.5.dist-info/METADATA,sha256=yRdOfyKQEeiDHaHjLmtpwiInVnl30f8228Gj2RoSP_0,632
|
|
6
|
+
pypomes_jwt-1.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
pypomes_jwt-1.0.5.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
+
pypomes_jwt-1.0.5.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=0JvWQaVgGXMfvvck_ZI6EW5diech7DabBFYskPU7jmE,21215
|
|
4
|
-
pypomes_jwt/jwt_registry.py,sha256=t2adiHGg04RRwrLvX2XUgRwOtwtH8p2Ir2uAbsc2CoQ,21593
|
|
5
|
-
pypomes_jwt-1.0.3.dist-info/METADATA,sha256=ayr8d8ZmUyAHGDHKMeXNW0-zbhyU62HBZwdIj69nlM0,632
|
|
6
|
-
pypomes_jwt-1.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
pypomes_jwt-1.0.3.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
-
pypomes_jwt-1.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|