pypomes-jwt 0.9.1__py3-none-any.whl → 0.9.3__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.
- pypomes_jwt/__init__.py +4 -4
- pypomes_jwt/jwt_pomes.py +89 -40
- pypomes_jwt/jwt_registry.py +22 -16
- {pypomes_jwt-0.9.1.dist-info → pypomes_jwt-0.9.3.dist-info}/METADATA +1 -1
- pypomes_jwt-0.9.3.dist-info/RECORD +8 -0
- pypomes_jwt-0.9.1.dist-info/RECORD +0 -8
- {pypomes_jwt-0.9.1.dist-info → pypomes_jwt-0.9.3.dist-info}/WHEEL +0 -0
- {pypomes_jwt-0.9.1.dist-info → pypomes_jwt-0.9.3.dist-info}/licenses/LICENSE +0 -0
pypomes_jwt/__init__.py
CHANGED
|
@@ -9,8 +9,8 @@ from .jwt_constants import (
|
|
|
9
9
|
from .jwt_pomes import (
|
|
10
10
|
jwt_needed, jwt_verify_request,
|
|
11
11
|
jwt_assert_account, jwt_set_account, jwt_remove_account,
|
|
12
|
-
jwt_issue_token, jwt_issue_tokens,
|
|
13
|
-
jwt_validate_token, jwt_revoke_token
|
|
12
|
+
jwt_issue_token, jwt_issue_tokens, jwt_refresh_tokens,
|
|
13
|
+
jwt_get_claims, jwt_validate_token, jwt_revoke_token
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
@@ -24,8 +24,8 @@ __all__ = [
|
|
|
24
24
|
# jwt_pomes
|
|
25
25
|
"jwt_needed", "jwt_verify_request",
|
|
26
26
|
"jwt_assert_account", "jwt_set_account", "jwt_remove_account",
|
|
27
|
-
"jwt_issue_token", "jwt_issue_tokens", "
|
|
28
|
-
"jwt_validate_token", "jwt_revoke_token"
|
|
27
|
+
"jwt_issue_token", "jwt_issue_tokens", "jwt_refresh_tokens",
|
|
28
|
+
"jwt_get_claims", "jwt_validate_token", "jwt_revoke_token"
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
from importlib.metadata import version
|
pypomes_jwt/jwt_pomes.py
CHANGED
|
@@ -61,7 +61,7 @@ def jwt_verify_request(request: Request,
|
|
|
61
61
|
logger.debug(msg="Bearer token was retrieved")
|
|
62
62
|
errors: list[str] = []
|
|
63
63
|
jwt_validate_token(errors=errors,
|
|
64
|
-
|
|
64
|
+
natures=["A"],
|
|
65
65
|
token=token)
|
|
66
66
|
if errors:
|
|
67
67
|
err_msg = "; ".join(errors)
|
|
@@ -157,20 +157,20 @@ def jwt_remove_account(account_id: str,
|
|
|
157
157
|
|
|
158
158
|
def jwt_validate_token(errors: list[str] | None,
|
|
159
159
|
token: str,
|
|
160
|
-
|
|
160
|
+
natures: list[str] = None,
|
|
161
161
|
account_id: str = None,
|
|
162
162
|
logger: Logger = None) -> dict[str, Any] | None:
|
|
163
163
|
"""
|
|
164
164
|
Verify if *token* ia a valid JWT token.
|
|
165
165
|
|
|
166
166
|
Raise an appropriate exception if validation failed.
|
|
167
|
-
if *nature* is provided,
|
|
168
|
-
A token issued locally has the header claim *kid* starting with
|
|
169
|
-
followed by its id in the token database
|
|
167
|
+
if *nature* is provided, verify whether *token* has been locally issued and is of a appropriate nature.
|
|
168
|
+
A token issued locally has the header claim *kid* starting with *A* (for *Access*) or *R* (for *Refresh*),
|
|
169
|
+
followed by its id in the token database, or as a single letter in the range *[B-Z]*, less *R*.
|
|
170
170
|
|
|
171
171
|
:param errors: incidental error messages
|
|
172
172
|
:param token: the token to be validated
|
|
173
|
-
:param
|
|
173
|
+
:param natures: one or more prefixes identifying the nature of locally issued tokens
|
|
174
174
|
:param account_id: optionally, validate the token's account owner
|
|
175
175
|
:param logger: optional logger
|
|
176
176
|
:return: The token's claims (header and payload) if if is valid, *None* otherwise
|
|
@@ -188,7 +188,7 @@ def jwt_validate_token(errors: list[str] | None,
|
|
|
188
188
|
op_errors: list[str] = []
|
|
189
189
|
|
|
190
190
|
# retrieve token data from database
|
|
191
|
-
if
|
|
191
|
+
if natures and not (token_kid and token_kid[0:1] in natures):
|
|
192
192
|
op_errors.append("Invalid token")
|
|
193
193
|
elif token_kid and len(token_kid) > 1 and token_kid[0:1].isupper() and token[1:].isdigit():
|
|
194
194
|
# token was likely issued locally
|
|
@@ -263,7 +263,7 @@ def jwt_revoke_token(errors: list[str] | None,
|
|
|
263
263
|
|
|
264
264
|
:param errors: incidental error messages
|
|
265
265
|
:param account_id: the account identification
|
|
266
|
-
:param refresh_token: the token to be
|
|
266
|
+
:param refresh_token: the token to be revoked
|
|
267
267
|
:param logger: optional logger
|
|
268
268
|
:return: *True* if operation could be performed, *False* otherwise
|
|
269
269
|
"""
|
|
@@ -276,7 +276,7 @@ def jwt_revoke_token(errors: list[str] | None,
|
|
|
276
276
|
op_errors: list[str] = []
|
|
277
277
|
token_claims: dict[str, Any] = jwt_validate_token(errors=op_errors,
|
|
278
278
|
token=refresh_token,
|
|
279
|
-
|
|
279
|
+
natures=["A", "R"],
|
|
280
280
|
account_id=account_id,
|
|
281
281
|
logger=logger)
|
|
282
282
|
if not op_errors:
|
|
@@ -303,22 +303,22 @@ def jwt_issue_token(errors: list[str] | None,
|
|
|
303
303
|
account_id: str,
|
|
304
304
|
nature: str,
|
|
305
305
|
duration: int,
|
|
306
|
-
claims: dict[str, Any],
|
|
307
306
|
grace_interval: int = None,
|
|
307
|
+
claims: dict[str, Any] = None,
|
|
308
308
|
logger: Logger = None) -> str:
|
|
309
309
|
"""
|
|
310
310
|
Issue or refresh, and return, a JWT token associated with *account_id*, of the specified *nature*.
|
|
311
311
|
|
|
312
|
-
The parameter *nature* must be a single
|
|
313
|
-
(
|
|
312
|
+
The parameter *nature* must be a single letter in the range *[B-Z]*, less *R*
|
|
313
|
+
(*A* is reserved for *access* tokens, and *R* for *refresh* tokens).
|
|
314
314
|
The parameter *duration* specifies the token's validity interval (at least 60 seconds).
|
|
315
|
-
|
|
315
|
+
These claims are ignored, if specified in *claims*: *iat*, *iss*, *exp*, *jti*, *nbf*, and *sub*.
|
|
316
316
|
|
|
317
317
|
:param errors: incidental error messages
|
|
318
318
|
:param account_id: the account identification
|
|
319
|
-
:param nature: the token's nature
|
|
319
|
+
:param nature: the token's nature, must be a single letter in the range *[B-Z]*, less *R*
|
|
320
320
|
:param duration: the number of seconds for the token to remain valid (at least 60 seconds)
|
|
321
|
-
:param claims:
|
|
321
|
+
:param claims: optional token's claims
|
|
322
322
|
:param grace_interval: optional interval for the token to become active (in seconds)
|
|
323
323
|
:param logger: optional logger
|
|
324
324
|
:return: the JWT token data, or *None* if error
|
|
@@ -327,7 +327,7 @@ def jwt_issue_token(errors: list[str] | None,
|
|
|
327
327
|
result: str | None = None
|
|
328
328
|
|
|
329
329
|
if logger:
|
|
330
|
-
logger.debug(msg=f"
|
|
330
|
+
logger.debug(msg=f"Issuing a JWT token for '{account_id}'")
|
|
331
331
|
op_errors: list[str] = []
|
|
332
332
|
|
|
333
333
|
try:
|
|
@@ -355,13 +355,14 @@ def jwt_issue_token(errors: list[str] | None,
|
|
|
355
355
|
def jwt_issue_tokens(errors: list[str] | None,
|
|
356
356
|
account_id: str,
|
|
357
357
|
account_claims: dict[str, Any] = None,
|
|
358
|
-
refresh_token: str = None,
|
|
359
358
|
logger: Logger = None) -> dict[str, Any]:
|
|
360
359
|
"""
|
|
361
360
|
Issue the JWT tokens associated with *account_id*, for access and refresh operations.
|
|
362
361
|
|
|
363
|
-
If *refresh_token* is provided, its claims are used on issuing the new tokens,
|
|
364
|
-
|
|
362
|
+
If *refresh_token* is provided, its claims are used on issuing the new tokens, and
|
|
363
|
+
claims in *account_claims*, if any, are ignored. Furthermore, these claims are ignored,
|
|
364
|
+
if provided in *account_claims*: *iat*, *iss*, *exp*, *jti*, *nbf*, and *sub*.
|
|
365
|
+
Other claims specified therein may supercede registered account-related claims.
|
|
365
366
|
|
|
366
367
|
Structure of the return data:
|
|
367
368
|
{
|
|
@@ -374,7 +375,6 @@ def jwt_issue_tokens(errors: list[str] | None,
|
|
|
374
375
|
:param errors: incidental error messages
|
|
375
376
|
:param account_id: the account identification
|
|
376
377
|
:param account_claims: if provided, may supercede registered claims
|
|
377
|
-
:param refresh_token: if provided, defines a token refresh operation
|
|
378
378
|
:param logger: optional logger
|
|
379
379
|
:return: the JWT token data, or *None* if error
|
|
380
380
|
"""
|
|
@@ -382,33 +382,82 @@ def jwt_issue_tokens(errors: list[str] | None,
|
|
|
382
382
|
result: dict[str, Any] | None = None
|
|
383
383
|
|
|
384
384
|
if logger:
|
|
385
|
-
logger.debug(msg=f"
|
|
385
|
+
logger.debug(msg=f"Issuing a pair of JWT tokens for '{account_id}'")
|
|
386
|
+
op_errors: list[str] = []
|
|
387
|
+
|
|
388
|
+
try:
|
|
389
|
+
result = __jwt_registry.issue_tokens(account_id=account_id,
|
|
390
|
+
account_claims=account_claims,
|
|
391
|
+
logger=logger)
|
|
392
|
+
if logger:
|
|
393
|
+
logger.debug(msg=f"Token data is '{result}'")
|
|
394
|
+
except Exception as e:
|
|
395
|
+
# token issuing failed
|
|
396
|
+
op_errors.append(str(e))
|
|
397
|
+
|
|
398
|
+
if op_errors:
|
|
399
|
+
if logger:
|
|
400
|
+
logger.error("; ".join(op_errors))
|
|
401
|
+
if isinstance(errors, list):
|
|
402
|
+
errors.extend(op_errors)
|
|
403
|
+
|
|
404
|
+
return result
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def jwt_refresh_tokens(errors: list[str] | None,
|
|
408
|
+
account_id: str,
|
|
409
|
+
refresh_token: str = None,
|
|
410
|
+
logger: Logger = None) -> dict[str, Any]:
|
|
411
|
+
"""
|
|
412
|
+
Issue the JWT tokens associated with *account_id*, for access and refresh operations.
|
|
413
|
+
|
|
414
|
+
The claims in *refresh-token* are used on issuing the new tokens.
|
|
415
|
+
|
|
416
|
+
Structure of the return data:
|
|
417
|
+
{
|
|
418
|
+
"access_token": <jwt-token>,
|
|
419
|
+
"created_in": <timestamp>,
|
|
420
|
+
"expires_in": <seconds-to-expiration>,
|
|
421
|
+
"refresh_token": <jwt-token>
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
:param errors: incidental error messages
|
|
425
|
+
:param account_id: the account identification
|
|
426
|
+
:param refresh_token: the base refresh token
|
|
427
|
+
:param logger: optional logger
|
|
428
|
+
:return: the JWT token data, or *None* if error
|
|
429
|
+
"""
|
|
430
|
+
# inicialize the return variable
|
|
431
|
+
result: dict[str, Any] | None = None
|
|
432
|
+
|
|
433
|
+
if logger:
|
|
434
|
+
logger.debug(msg=f"Refreshing a pair of JWT tokens for '{account_id}'")
|
|
386
435
|
op_errors: list[str] = []
|
|
387
436
|
|
|
388
437
|
# verify whether this refresh token is legitimate
|
|
389
438
|
if refresh_token:
|
|
390
|
-
account_claims = (jwt_validate_token(errors=op_errors,
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
439
|
+
account_claims: dict[str, Any] = (jwt_validate_token(errors=op_errors,
|
|
440
|
+
token=refresh_token,
|
|
441
|
+
natures=["R"],
|
|
442
|
+
account_id=account_id,
|
|
443
|
+
logger=logger) or {}).get("payload")
|
|
444
|
+
# revoke current refresh token
|
|
445
|
+
if account_claims and jwt_revoke_token(errors=errors,
|
|
446
|
+
account_id=account_id,
|
|
447
|
+
refresh_token=refresh_token,
|
|
448
|
+
logger=logger):
|
|
449
|
+
account_claims.pop("exp", None)
|
|
396
450
|
account_claims.pop("iat", None)
|
|
397
|
-
account_claims.pop("jti", None)
|
|
398
451
|
account_claims.pop("iss", None)
|
|
399
|
-
account_claims.pop("
|
|
452
|
+
account_claims.pop("jti", None)
|
|
400
453
|
account_claims.pop("nbt", None)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
logger.debug(msg=f"Token data is '{result}'")
|
|
409
|
-
except Exception as e:
|
|
410
|
-
# token issuing failed
|
|
411
|
-
op_errors.append(str(e))
|
|
454
|
+
account_claims.pop("sub", None)
|
|
455
|
+
# issue tokens
|
|
456
|
+
result = jwt_issue_tokens(errors=errors,
|
|
457
|
+
account_id=account_id,
|
|
458
|
+
account_claims=account_claims)
|
|
459
|
+
else:
|
|
460
|
+
op_errors.append("Refresh token was not provided")
|
|
412
461
|
|
|
413
462
|
if op_errors:
|
|
414
463
|
if logger:
|
pypomes_jwt/jwt_registry.py
CHANGED
|
@@ -81,12 +81,12 @@ class JwtRegistry:
|
|
|
81
81
|
"nonce": <string> # value used to associate a client session with a token
|
|
82
82
|
|
|
83
83
|
The token header has these items:
|
|
84
|
-
"alg": <string> # the algorithm used to sign the token (one of
|
|
85
|
-
"typ": <string> # the token type (fixed to
|
|
84
|
+
"alg": <string> # the algorithm used to sign the token (one of *HS256*, *HS51*', *RSA256*, *RSA512*)
|
|
85
|
+
"typ": <string> # the token type (fixed to *JWT*
|
|
86
86
|
"kid": <string> # a reference to the token type and the key to its location in the token database
|
|
87
87
|
|
|
88
|
-
If issued by the local server, "kid" holds the key to the corresponding record in the token database
|
|
89
|
-
|
|
88
|
+
If issued by the local server, "kid" holds the key to the corresponding record in the token database,
|
|
89
|
+
if starting with *A* for (*Access*) or *R* (for *Refresh*), followed an integer.
|
|
90
90
|
"""
|
|
91
91
|
def __init__(self) -> None:
|
|
92
92
|
"""
|
|
@@ -111,7 +111,7 @@ class JwtRegistry:
|
|
|
111
111
|
Add to storage the parameters needed to produce and validate JWT tokens for *account_id*.
|
|
112
112
|
|
|
113
113
|
The parameter *claims* may contain account-related claims, only. Ideally, it should contain,
|
|
114
|
-
at a minimum,
|
|
114
|
+
at a minimum, *birthdate*, *email*, *gender*, *name*, and *roles*.
|
|
115
115
|
If the token provider is local, then the token-related claims are created at token issuing time.
|
|
116
116
|
If the token provider is remote, all claims are sent to it at token request time.
|
|
117
117
|
|
|
@@ -178,21 +178,21 @@ class JwtRegistry:
|
|
|
178
178
|
account_id: str,
|
|
179
179
|
nature: str,
|
|
180
180
|
duration: int,
|
|
181
|
-
claims: dict[str, Any],
|
|
182
181
|
grace_interval: int = None,
|
|
182
|
+
claims: dict[str, Any] = None,
|
|
183
183
|
logger: Logger = None) -> str:
|
|
184
184
|
"""
|
|
185
185
|
Issue an return a JWT token associated with *account_id*.
|
|
186
186
|
|
|
187
|
-
The parameter *nature* must be a single
|
|
188
|
-
(
|
|
187
|
+
The parameter *nature* must be a single letter in the range *[B-Z]*, less *R*
|
|
188
|
+
(*A* is reserved for *access* tokens, and *R* for *refresh* tokens).
|
|
189
189
|
The parameter *duration* specifies the token's validity interval (at least 60 seconds).
|
|
190
|
-
|
|
190
|
+
These claims are ignored, if specified in *claims*: *iat*, *iss*, *exp*, *jti*, *nbf*, and *sub*.
|
|
191
191
|
|
|
192
192
|
:param account_id: the account identification
|
|
193
|
-
:param nature: the token's nature
|
|
193
|
+
:param nature: the token's nature, must be a single letter in the range *[B-Z]*, less *R*
|
|
194
194
|
:param duration: the number of seconds for the token to remain valid (at least 60 seconds)
|
|
195
|
-
:param claims:
|
|
195
|
+
:param claims: optional token's claims
|
|
196
196
|
:param grace_interval: optional interval for the token to become active (in seconds)
|
|
197
197
|
:param logger: optional logger
|
|
198
198
|
:return: the JWT token
|
|
@@ -212,13 +212,16 @@ class JwtRegistry:
|
|
|
212
212
|
logger.error(err_msg)
|
|
213
213
|
raise RuntimeError(err_msg)
|
|
214
214
|
|
|
215
|
-
#
|
|
216
|
-
self.__get_account_data(account_id=account_id,
|
|
217
|
-
|
|
215
|
+
# obtain the account data in storage (may raise an exception)
|
|
216
|
+
account_data: dict[str, Any] = self.__get_account_data(account_id=account_id,
|
|
217
|
+
logger=logger)
|
|
218
218
|
# issue the token
|
|
219
|
-
current_claims: dict[str, Any] =
|
|
219
|
+
current_claims: dict[str, Any] = {}
|
|
220
|
+
if claims:
|
|
221
|
+
current_claims.update(claims)
|
|
220
222
|
current_claims["jti"] = str_random(size=32,
|
|
221
223
|
chars=string.ascii_letters + string.digits)
|
|
224
|
+
current_claims["iss"] = account_data.get("reference-url")
|
|
222
225
|
current_claims["sub"] = account_id
|
|
223
226
|
just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
|
|
224
227
|
current_claims["iat"] = just_now
|
|
@@ -245,6 +248,9 @@ class JwtRegistry:
|
|
|
245
248
|
"""
|
|
246
249
|
Issue and return the JWT access and refresh tokens for *account_id*.
|
|
247
250
|
|
|
251
|
+
These claims are ignored, if specified in *account_claims*: *iat*, *iss*, *exp*, *jti*, *nbf*, and *sub*.
|
|
252
|
+
Other claims specified therein may supercede registered account-related claims.
|
|
253
|
+
|
|
248
254
|
Structure of the return data:
|
|
249
255
|
{
|
|
250
256
|
"access_token": <jwt-token>,
|
|
@@ -262,7 +268,7 @@ class JwtRegistry:
|
|
|
262
268
|
# initialize the return variable
|
|
263
269
|
result: dict[str, Any] | None = None
|
|
264
270
|
|
|
265
|
-
# process the data in storage
|
|
271
|
+
# process the account data in storage
|
|
266
272
|
with (self.access_lock):
|
|
267
273
|
account_data: dict[str, Any] = self.__get_account_data(account_id=account_id,
|
|
268
274
|
logger=logger)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
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=t6TzpvttDuLMaKSGuBicOf9cZU4Y0N9mtby3ThS4lt8,1398
|
|
2
|
+
pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
|
|
3
|
+
pypomes_jwt/jwt_pomes.py,sha256=UnEkOUN0vovlenyb8ROvpM96Qf0Mx-JRle-EooyTy7k,21734
|
|
4
|
+
pypomes_jwt/jwt_registry.py,sha256=TANRyMGxoO7sR2EwO_bgVzIMjM3OHAr7olvnSmMtwCQ,26020
|
|
5
|
+
pypomes_jwt-0.9.3.dist-info/METADATA,sha256=DrB3ku89ZNBM3FBpnnnmDv-Rvi4F70sIXj0M5zJnQOM,632
|
|
6
|
+
pypomes_jwt-0.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
pypomes_jwt-0.9.3.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
+
pypomes_jwt-0.9.3.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pypomes_jwt/__init__.py,sha256=6AISZOs7JP695PnMSsYyKfoiMXvSzXffkv3nKw7qA1A,1356
|
|
2
|
-
pypomes_jwt/jwt_constants.py,sha256=IQV39AiZKGuU8XxZBgJ-KJZQZ_mmnxyOnRZeuxlqDRk,4045
|
|
3
|
-
pypomes_jwt/jwt_pomes.py,sha256=qm8COn0vPts-I0n5pnA_5phm-5wkV82dicAf3bvQ8R8,19734
|
|
4
|
-
pypomes_jwt/jwt_registry.py,sha256=MWJy1bxXdgVySmBYJCdK_721U6tJXk3BHrYVChLjCR8,25613
|
|
5
|
-
pypomes_jwt-0.9.1.dist-info/METADATA,sha256=b5EpwtK-c0JCi3QjRt6W_QoQXg8nYkUjJHbD0pTGgF4,632
|
|
6
|
-
pypomes_jwt-0.9.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
pypomes_jwt-0.9.1.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
-
pypomes_jwt-0.9.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|