pypomes-jwt 1.2.8__tar.gz → 1.3.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.

Potentially problematic release.


This version of pypomes-jwt might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_jwt
3
- Version: 1.2.8
3
+ Version: 1.3.0
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
@@ -14,4 +14,4 @@ Requires-Dist: cryptography>=45.0.6
14
14
  Requires-Dist: flask>=3.1.2
15
15
  Requires-Dist: pyjwt>=2.10.1
16
16
  Requires-Dist: pypomes-core>=2.7.0
17
- Requires-Dist: pypomes-db>=2.5.8
17
+ Requires-Dist: pypomes-db>=2.5.9
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "pypomes_jwt"
9
- version = "1.2.8"
9
+ version = "1.3.0"
10
10
  authors = [
11
11
  { name="GT Nunes", email="wisecoder01@gmail.com" }
12
12
  ]
@@ -23,7 +23,7 @@ dependencies = [
23
23
  "Flask>=3.1.2",
24
24
  "PyJWT>=2.10.1",
25
25
  "pypomes_core>=2.7.0",
26
- "pypomes_db>=2.5.8"
26
+ "pypomes_db>=2.5.9"
27
27
  ]
28
28
 
29
29
  [project.urls]
@@ -52,14 +52,19 @@ class JwtConfig(Enum):
52
52
  def_value=300)
53
53
  ACCOUNT_LIMIT: int = env_get_int(key=f"{APP_PREFIX}_JWT_ACCOUNT_LIMIT",
54
54
  def_value=5)
55
+ DEFAULT_ALGORITHM: _default_algorithm
55
56
  ENCODING_KEY: bytes = _encoding_key
56
57
  DECODING_KEY: bytes = _decoding_key
57
- DEFAULT_ALGORITHM: str = _default_algorithm
58
58
  # recommended: at least 2 hours (set to 24 hours)
59
59
  REFRESH_MAX_AGE: int = env_get_int(key=f"{APP_PREFIX}_JWT_REFRESH_MAX_AGE",
60
60
  def_value=86400)
61
61
 
62
62
 
63
+ del _decoding_key
64
+ del _encoding_key
65
+ del _default_algorithm
66
+
67
+
63
68
  class JwtDbConfig(StrEnum):
64
69
  """
65
70
  Parameters for JWT database connection.
@@ -158,7 +158,10 @@ def jwt_validate_token(token: str,
158
158
 
159
159
  if logger:
160
160
  logger.debug(msg="Validate JWT token")
161
- op_errors: list[str] = []
161
+
162
+ # make sure to have an errors list
163
+ if not isinstance(errors, list):
164
+ errors = []
162
165
 
163
166
  # extract needed data from token header
164
167
  token_header: dict[str, Any] | None = None
@@ -169,9 +172,9 @@ def jwt_validate_token(token: str,
169
172
  exc_info=sys.exc_info())
170
173
  if logger:
171
174
  logger.error(msg=f"Error retrieving the token's header: {exc_err}")
172
- op_errors.append(exc_err)
175
+ errors.append(exc_err)
173
176
 
174
- if not op_errors:
177
+ if not errors:
175
178
  token_kid: str = token_header.get("kid")
176
179
  token_alg: str | None = None
177
180
  token_decoder: bytes | None = None
@@ -180,7 +183,7 @@ def jwt_validate_token(token: str,
180
183
  if nature and not (token_kid and token_kid[0:1] == nature):
181
184
  if logger:
182
185
  logger.error(f"Nature of token's 'kid' ('{token_kid}') not '{nature}'")
183
- op_errors.append("Invalid token")
186
+ errors.append("Invalid token")
184
187
  elif token_kid and len(token_kid) > 1 and \
185
188
  token_kid[0:1] in ["A", "R"] and token_kid[1:].isdigit():
186
189
  # token was likely issued locally
@@ -192,24 +195,24 @@ def jwt_validate_token(token: str,
192
195
  f"FROM {JwtDbConfig.TABLE}",
193
196
  where_data=where_data,
194
197
  engine=DbEngine(JwtDbConfig.ENGINE),
195
- errors=op_errors,
198
+ errors=errors,
196
199
  logger=logger)
197
200
  if recs:
198
201
  token_alg = recs[0][0]
199
202
  token_decoder = b64decode(recs[0][1])
200
- elif op_errors:
203
+ elif errors:
201
204
  if logger:
202
- logger.error(msg=f"Error retrieving the token's decoder: {'; '.join(op_errors)}")
205
+ logger.error(msg=f"Error retrieving the token's decoder: {'; '.join(errors)}")
203
206
  else:
204
207
  if logger:
205
208
  logger.error(msg="Token not in the database")
206
- op_errors.append("Invalid token")
209
+ errors.append("Invalid token")
207
210
  else:
208
211
  token_alg = JwtConfig.DEFAULT_ALGORITHM.value
209
212
  token_decoder = JwtConfig.DECODING_KEY.value
210
213
 
211
214
  # validate the token
212
- if not op_errors:
215
+ if not errors:
213
216
  try:
214
217
  # raises:
215
218
  # InvalidTokenError: token is invalid
@@ -232,7 +235,7 @@ def jwt_validate_token(token: str,
232
235
  if account_id and payload.get("sub") != account_id:
233
236
  if logger:
234
237
  logger.error(msg=f"Token does not belong to account '{account_id}'")
235
- op_errors.append("Invalid token")
238
+ errors.append("Invalid token")
236
239
  else:
237
240
  result = {
238
241
  "header": token_header,
@@ -243,12 +246,9 @@ def jwt_validate_token(token: str,
243
246
  exc_info=sys.exc_info())
244
247
  if logger:
245
248
  logger.error(msg=f"Error decoding the token: {exc_err}")
246
- op_errors.append(exc_err)
249
+ errors.append(exc_err)
247
250
 
248
- if op_errors:
249
- if isinstance(errors, list):
250
- errors.extend(op_errors)
251
- elif logger:
251
+ if not errors and logger:
252
252
  logger.debug(msg="Token is valid")
253
253
 
254
254
  return result
@@ -275,15 +275,18 @@ def jwt_revoke_token(account_id: str,
275
275
  if logger:
276
276
  logger.debug(msg=f"Revoking token of account '{account_id}'")
277
277
 
278
- op_errors: list[str] = []
278
+ # make sure to have an errors list
279
+ if not isinstance(errors, list):
280
+ errors = []
281
+
279
282
  token_claims: dict[str, Any] = jwt_validate_token(token=token,
280
283
  account_id=account_id,
281
- errors=op_errors,
284
+ errors=errors,
282
285
  logger=logger)
283
- if not op_errors:
286
+ if not errors:
284
287
  token_kid: str = token_claims["header"].get("kid")
285
288
  if token_kid[0:1] not in ["A", "R"]:
286
- op_errors.append("Invalid token")
289
+ errors.append("Invalid token")
287
290
  else:
288
291
  db_delete(delete_stmt=f"DELETE FROM {JwtDbConfig.TABLE}",
289
292
  where_data={
@@ -291,15 +294,12 @@ def jwt_revoke_token(account_id: str,
291
294
  JwtDbConfig.COL_ACCOUNT: account_id
292
295
  },
293
296
  engine=DbEngine(JwtDbConfig.ENGINE),
294
- errors=op_errors,
297
+ errors=errors,
295
298
  logger=logger)
296
- if op_errors:
297
- if logger:
298
- logger.error(msg="; ".join(op_errors))
299
- if isinstance(errors, list):
300
- errors.extend(op_errors)
301
- else:
299
+ if not errors:
302
300
  result = True
301
+ elif logger:
302
+ logger.error(msg="; ".join(errors))
303
303
 
304
304
  return result
305
305
 
@@ -333,7 +333,6 @@ def jwt_issue_token(account_id: str,
333
333
 
334
334
  if logger:
335
335
  logger.debug(msg=f"Issuing a JWT token for '{account_id}'")
336
- op_errors: list[str] = []
337
336
 
338
337
  try:
339
338
  result = __jwt_registry.issue_token(account_id=account_id,
@@ -350,10 +349,8 @@ def jwt_issue_token(account_id: str,
350
349
  exc_info=sys.exc_info())
351
350
  if logger:
352
351
  logger.error(msg=f"Error issuing the token: {exc_err}")
353
- op_errors.append(exc_err)
354
-
355
- if op_errors and isinstance(errors, list):
356
- errors.extend(op_errors)
352
+ if isinstance(errors, list):
353
+ errors.append(exc_err)
357
354
 
358
355
  return result
359
356
 
@@ -387,7 +384,6 @@ def jwt_issue_tokens(account_id: str,
387
384
 
388
385
  if logger:
389
386
  logger.debug(msg=f"Issuing a JWT token pair for '{account_id}'")
390
- op_errors: list[str] = []
391
387
 
392
388
  try:
393
389
  result = __jwt_registry.issue_tokens(account_id=account_id,
@@ -401,13 +397,8 @@ def jwt_issue_tokens(account_id: str,
401
397
  exc_info=sys.exc_info())
402
398
  if logger:
403
399
  logger.error(msg=f"Error issuing the token pair: {exc_err}")
404
- op_errors.append(exc_err)
405
-
406
- if op_errors:
407
- if logger:
408
- logger.error("; ".join(op_errors))
409
400
  if isinstance(errors, list):
410
- errors.extend(op_errors)
401
+ errors.append(exc_err)
411
402
 
412
403
  return result
413
404
 
@@ -440,7 +431,10 @@ def jwt_refresh_tokens(account_id: str,
440
431
 
441
432
  if logger:
442
433
  logger.debug(msg=f"Refreshing a JWT token pair for '{account_id}'")
443
- op_errors: list[str] = []
434
+
435
+ # make sure to have an errors list
436
+ if not isinstance(errors, list):
437
+ errors = []
444
438
 
445
439
  # assert the refresh token
446
440
  if refresh_token:
@@ -448,7 +442,7 @@ def jwt_refresh_tokens(account_id: str,
448
442
  token_claims: dict[str, Any] = jwt_validate_token(token=refresh_token,
449
443
  nature="R",
450
444
  account_id=account_id,
451
- errors=op_errors,
445
+ errors=errors,
452
446
  logger=logger)
453
447
  if token_claims:
454
448
  # yes, proceed
@@ -457,7 +451,7 @@ def jwt_refresh_tokens(account_id: str,
457
451
  # start the database transaction
458
452
  db_conn: Any = db_connect(autocommit=False,
459
453
  engine=DbEngine(JwtDbConfig.ENGINE),
460
- errors=op_errors,
454
+ errors=errors,
461
455
  logger=logger)
462
456
  if db_conn:
463
457
  # delete current refresh token
@@ -469,11 +463,11 @@ def jwt_refresh_tokens(account_id: str,
469
463
  engine=DbEngine(JwtDbConfig.ENGINE),
470
464
  connection=db_conn,
471
465
  committable=False,
472
- errors=op_errors,
466
+ errors=errors,
473
467
  logger=logger)
474
468
 
475
469
  # issue the token pair
476
- if not op_errors:
470
+ if not errors:
477
471
  try:
478
472
  result = __jwt_registry.issue_tokens(account_id=account_id,
479
473
  account_claims=token_claims.get("payload"),
@@ -487,27 +481,24 @@ def jwt_refresh_tokens(account_id: str,
487
481
  exc_info=sys.exc_info())
488
482
  if logger:
489
483
  logger.error(msg=f"Error refreshing the token pair: {exc_err}")
490
- op_errors.append(exc_err)
484
+ errors.append(exc_err)
491
485
 
492
486
  # wrap-up the transaction
493
- if op_errors:
487
+ if errors:
494
488
  db_rollback(connection=db_conn,
495
489
  logger=logger)
496
490
  else:
497
491
  db_commit(connection=db_conn,
498
- errors=op_errors,
492
+ errors=errors,
499
493
  logger=logger)
500
494
  db_close(connection=db_conn,
501
495
  logger=logger)
502
496
  else:
503
497
  # refresh token not found
504
- op_errors.append("Refresh token was not provided")
498
+ errors.append("Refresh token was not provided")
505
499
 
506
- if op_errors:
507
- if logger:
508
- logger.error("; ".join(op_errors))
509
- if isinstance(errors, list):
510
- errors.extend(op_errors)
500
+ if errors and logger:
501
+ logger.error(msg="; ".join(errors))
511
502
 
512
503
  return result
513
504
 
@@ -62,14 +62,14 @@ def provider_register(provider_id: str,
62
62
  }
63
63
 
64
64
 
65
- def provider_get_token(errors: list[str] | None,
66
- provider_id: str,
65
+ def provider_get_token(provider_id: str,
66
+ errors: list[str] | None,
67
67
  logger: Logger = None) -> str | None:
68
68
  """
69
69
  Obtain an authentication token from the external provider *provider_id*.
70
70
 
71
- :param errors: incidental error messages
72
71
  :param provider_id: the provider's identification
72
+ :param errors: incidental error messages
73
73
  :param logger: optional logger
74
74
  """
75
75
  # initialize the return variable
@@ -6,7 +6,7 @@ from datetime import datetime, UTC
6
6
  from logging import Logger
7
7
  from pypomes_core import str_random
8
8
  from pypomes_db import (
9
- DbEngine, db_connect, db_commit, db_rollback,
9
+ DbEngine, db_connect, db_commit, db_rollback, db_close,
10
10
  db_select, db_insert, db_update, db_delete
11
11
  )
12
12
  from threading import Lock
@@ -296,6 +296,8 @@ class JwtRegistry:
296
296
  db_commit(connection=curr_conn,
297
297
  errors=errors,
298
298
  logger=logger)
299
+ db_close(connection=curr_conn)
300
+
299
301
  if errors:
300
302
  raise RuntimeError("; ".join(errors))
301
303
 
@@ -359,102 +361,100 @@ class JwtRegistry:
359
361
  """
360
362
  from .jwt_pomes import jwt_get_claims
361
363
 
362
- # retrieve the account's tokens
363
- errors: list[str] = []
364
- # noinspection PyTypeChecker
365
- recs: list[tuple[int, str, str, str]] = \
366
- db_select(sel_stmt=f"SELECT {JwtDbConfig.COL_KID}, {JwtDbConfig.COL_TOKEN} "
367
- f"FROM {JwtDbConfig.TABLE}",
368
- where_data={JwtDbConfig.COL_ACCOUNT: account_id},
369
- engine=DbEngine(JwtDbConfig.ENGINE),
370
- connection=db_conn,
371
- errors=errors,
372
- logger=logger)
373
- if errors:
374
- raise RuntimeError("; ".join(errors))
364
+ # initialize the return variable
365
+ result: int | None = None
375
366
 
376
- if logger:
377
- logger.debug(msg=f"Retrieved {len(recs)} tokens from storage for account '{account_id}'")
378
- # remove the expired tokens
379
- just_now: int = int(datetime.now(tz=UTC).timestamp())
380
- oldest_ts: int = sys.maxsize
381
- oldest_id: int | None = None
382
- expired: list[int] = []
383
- for rec in recs:
384
- token: str = rec[1]
385
- token_id: int = rec[0]
386
- token_payload: dict[str, Any] = (jwt_get_claims(token=token,
387
- errors=errors,
388
- logger=logger) or {}).get("payload")
389
- if errors:
390
- raise RuntimeError("; ".join(errors))
391
-
392
- # find expired tokens
393
- exp: int = token_payload.get("exp", sys.maxsize)
394
- if exp < just_now:
395
- expired.append(token_id)
396
-
397
- # find oldest token
398
- iat: int = token_payload.get("iat", sys.maxsize)
399
- if iat < oldest_ts:
400
- oldest_ts = iat
401
- oldest_id = token_id
402
-
403
- # remove expired tokens from persistence
404
- if expired:
405
- db_delete(delete_stmt=f"DELETE FROM {JwtDbConfig.TABLE}",
406
- where_data={JwtDbConfig.COL_KID: expired},
407
- engine=DbEngine(JwtDbConfig.ENGINE),
408
- connection=db_conn,
409
- errors=errors,
410
- logger=logger)
411
- if errors:
412
- raise RuntimeError("; ".join(errors))
413
- if logger:
414
- logger.debug(msg=f"{len(expired)} tokens of account "
415
- f"'{account_id}' removed from storage")
416
-
417
- if 0 < JwtConfig.ACCOUNT_LIMIT.value <= len(recs) - len(expired):
418
- # delete the oldest token to make way for the new one
419
- db_delete(delete_stmt=f"DELETE FROM {JwtDbConfig.TABLE}",
420
- where_data={JwtDbConfig.COL_KID: oldest_id},
421
- engine=DbEngine(JwtDbConfig.ENGINE),
422
- connection=db_conn,
423
- errors=errors,
424
- logger=logger)
367
+ # make sure to have a database connection
368
+ errors: list[str] = []
369
+ curr_conn: Any = db_conn or db_connect(autocommit=False,
370
+ engine=DbEngine(JwtDbConfig.ENGINE),
371
+ errors=errors,
372
+ logger=logger)
373
+ if not errors:
374
+
375
+ # retrieve the account's tokens
376
+ # noinspection PyTypeChecker
377
+ recs: list[tuple[int, str, str, str]] = \
378
+ db_select(sel_stmt=f"SELECT {JwtDbConfig.COL_KID}, {JwtDbConfig.COL_TOKEN} "
379
+ f"FROM {JwtDbConfig.TABLE}",
380
+ where_data={JwtDbConfig.COL_ACCOUNT: account_id},
381
+ engine=DbEngine(JwtDbConfig.ENGINE),
382
+ connection=curr_conn,
383
+ errors=errors,
384
+ logger=logger)
385
+ if not errors:
386
+ if logger:
387
+ logger.debug(msg=f"Retrieved {len(recs)} tokens from storage for account '{account_id}'")
388
+ # remove the expired tokens
389
+ just_now: int = int(datetime.now(tz=UTC).timestamp())
390
+ oldest_ts: int = sys.maxsize
391
+ oldest_id: int | None = None
392
+ expired: list[int] = []
393
+ for rec in recs:
394
+ token: str = rec[1]
395
+ token_id: int = rec[0]
396
+ token_payload: dict[str, Any] = (jwt_get_claims(token=token,
397
+ errors=errors,
398
+ logger=logger) or {}).get("payload")
399
+ if errors:
400
+ break
401
+ # find expired tokens
402
+ exp: int = token_payload.get("exp", sys.maxsize)
403
+ if exp < just_now:
404
+ expired.append(token_id)
405
+
406
+ # find oldest token
407
+ iat: int = token_payload.get("iat", sys.maxsize)
408
+ if iat < oldest_ts:
409
+ oldest_ts = iat
410
+ oldest_id = token_id
411
+
412
+ # remove expired tokens from persistence
413
+ if not errors and expired:
414
+ db_delete(delete_stmt=f"DELETE FROM {JwtDbConfig.TABLE}",
415
+ where_data={JwtDbConfig.COL_KID: expired},
416
+ engine=DbEngine(JwtDbConfig.ENGINE),
417
+ connection=curr_conn,
418
+ errors=errors,
419
+ logger=logger)
420
+ if not errors and logger:
421
+ logger.debug(msg=f"{len(expired)} tokens of account "
422
+ f"'{account_id}' removed from storage")
423
+
424
+ if not errors and 0 < JwtConfig.ACCOUNT_LIMIT.value <= len(recs) - len(expired):
425
+ # delete the oldest token to make way for the new one
426
+ db_delete(delete_stmt=f"DELETE FROM {JwtDbConfig.TABLE}",
427
+ where_data={JwtDbConfig.COL_KID: oldest_id},
428
+ engine=DbEngine(JwtDbConfig.ENGINE),
429
+ connection=curr_conn,
430
+ errors=errors,
431
+ logger=logger)
432
+ if not errors and logger:
433
+ logger.debug(msg="Oldest active token of account "
434
+ f"'{account_id}' removed from storage")
435
+ # persist token
436
+ if not errors:
437
+ result = db_insert(insert_stmt=f"INSERT INTO {JwtDbConfig.TABLE}",
438
+ insert_data={
439
+ JwtDbConfig.COL_ACCOUNT: account_id,
440
+ JwtDbConfig.COL_TOKEN: jwt_token,
441
+ JwtDbConfig.COL_ALGORITHM: JwtConfig.DEFAULT_ALGORITHM.value,
442
+ JwtDbConfig.COL_DECODER: b64encode(s=JwtConfig.DECODING_KEY.value).decode()
443
+ },
444
+ return_cols={JwtDbConfig.COL_KID: int},
445
+ engine=DbEngine(JwtDbConfig.ENGINE),
446
+ connection=curr_conn,
447
+ errors=errors,
448
+ logger=logger)
449
+ # finish the operation
450
+ if not db_conn:
425
451
  if errors:
426
- raise RuntimeError("; ".join(errors))
427
- if logger:
428
- logger.debug(msg="Oldest active token of account "
429
- f"'{account_id}' removed from storage")
430
- # persist token
431
- col_kid: int = db_insert(insert_stmt=f"INSERT INTO {JwtDbConfig.TABLE}",
432
- insert_data={
433
- JwtDbConfig.COL_ACCOUNT: account_id,
434
- JwtDbConfig.COL_TOKEN: jwt_token,
435
- JwtDbConfig.COL_ALGORITHM: JwtConfig.DEFAULT_ALGORITHM.value,
436
- JwtDbConfig.COL_DECODER: b64encode(s=JwtConfig.DECODING_KEY.value).decode()
437
- },
438
- return_cols={JwtDbConfig.COL_KID: int},
439
- engine=DbEngine(JwtDbConfig.ENGINE),
440
- connection=db_conn,
441
- errors=errors,
442
- logger=logger)
443
- if errors:
444
- raise RuntimeError("; ".join(errors))
452
+ db_rollback(connection=curr_conn)
453
+ else:
454
+ db_commit(connection=curr_conn)
455
+ db_close(connection=curr_conn)
445
456
 
446
- # obtain and return the token's storage id
447
- reply: list[tuple[int]] = db_select(sel_stmt=f"SELECT {JwtDbConfig.COL_KID} "
448
- f"FROM {JwtDbConfig.TABLE}",
449
- where_data={JwtDbConfig.COL_KID: col_kid},
450
- min_count=1,
451
- max_count=1,
452
- engine=DbEngine(JwtDbConfig.ENGINE),
453
- connection=db_conn,
454
- committable=False,
455
- errors=errors,
456
- logger=logger)
457
457
  if errors:
458
458
  raise RuntimeError("; ".join(errors))
459
459
 
460
- return reply[0][0]
460
+ return result
File without changes
File without changes
File without changes