pypomes-jwt 0.7.8__py3-none-any.whl → 0.7.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/__init__.py +2 -2
- pypomes_jwt/jwt_constants.py +1 -3
- pypomes_jwt/jwt_data.py +107 -116
- pypomes_jwt/jwt_pomes.py +45 -32
- {pypomes_jwt-0.7.8.dist-info → pypomes_jwt-0.7.9.dist-info}/METADATA +1 -1
- pypomes_jwt-0.7.9.dist-info/RECORD +8 -0
- pypomes_jwt-0.7.8.dist-info/RECORD +0 -8
- {pypomes_jwt-0.7.8.dist-info → pypomes_jwt-0.7.9.dist-info}/WHEEL +0 -0
- {pypomes_jwt-0.7.8.dist-info → pypomes_jwt-0.7.9.dist-info}/licenses/LICENSE +0 -0
pypomes_jwt/__init__.py
CHANGED
|
@@ -8,7 +8,7 @@ from .jwt_constants import (
|
|
|
8
8
|
from .jwt_pomes import (
|
|
9
9
|
jwt_needed, jwt_verify_request,
|
|
10
10
|
jwt_get_tokens, jwt_get_claims, jwt_validate_token,
|
|
11
|
-
jwt_assert_access, jwt_set_access,
|
|
11
|
+
jwt_assert_access, jwt_set_access, jwt_remove_account, jwt_revoke_token
|
|
12
12
|
)
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
@@ -21,7 +21,7 @@ __all__ = [
|
|
|
21
21
|
# jwt_pomes
|
|
22
22
|
"jwt_needed", "jwt_verify_request",
|
|
23
23
|
"jwt_get_tokens", "jwt_get_claims", "jwt_validate_token",
|
|
24
|
-
"jwt_assert_access", "jwt_set_access", "
|
|
24
|
+
"jwt_assert_access", "jwt_set_access", "jwt_remove_account", "jwt_revoke_token"
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
from importlib.metadata import version
|
pypomes_jwt/jwt_constants.py
CHANGED
|
@@ -3,7 +3,7 @@ from cryptography.hazmat.primitives.asymmetric import rsa
|
|
|
3
3
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey
|
|
4
4
|
from pypomes_core import (
|
|
5
5
|
APP_PREFIX,
|
|
6
|
-
env_get_str, env_get_bytes, env_get_int
|
|
6
|
+
env_get_str, env_get_bytes, env_get_int
|
|
7
7
|
)
|
|
8
8
|
from secrets import token_bytes
|
|
9
9
|
from typing import Final
|
|
@@ -54,8 +54,6 @@ JWT_ACCESS_MAX_AGE: Final[int] = env_get_int(key=f"{APP_PREFIX}_JWT_ACCESS_MAX_A
|
|
|
54
54
|
# recommended: at least 2 hours (set to 24 hours)
|
|
55
55
|
JWT_REFRESH_MAX_AGE: Final[int] = env_get_int(key=f"{APP_PREFIX}_JWT_REFRESH_MAX_AGE",
|
|
56
56
|
def_value=86400)
|
|
57
|
-
JWT_ROTATE_TOKENS: Final[bool] = env_get_bool(key=f"{APP_PREFIX}_JWT_ROTATE_TOKENS",
|
|
58
|
-
def_value=True)
|
|
59
57
|
JWT_ACCOUNT_LIMIT: Final[int] = env_get_int(key=f"{APP_PREFIX}_JWT_ACCOUNT_LIMIT")
|
|
60
58
|
|
|
61
59
|
# recommended: allow the encode and decode keys to be generated anew when app starts
|
pypomes_jwt/jwt_data.py
CHANGED
|
@@ -10,9 +10,8 @@ from threading import Lock
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
from .jwt_constants import (
|
|
13
|
-
JWT_DEFAULT_ALGORITHM, JWT_ENCODING_KEY, JWT_DECODING_KEY,
|
|
14
|
-
|
|
15
|
-
JWT_DB_TABLE, JWT_DB_COL_ACCOUNT, JWT_DB_COL_HASH, JWT_DB_COL_TOKEN
|
|
13
|
+
JWT_DEFAULT_ALGORITHM, JWT_ENCODING_KEY, JWT_DECODING_KEY, JWT_ACCOUNT_LIMIT,
|
|
14
|
+
JWT_DB_ENGINE, JWT_DB_TABLE, JWT_DB_COL_ACCOUNT, JWT_DB_COL_HASH, JWT_DB_COL_TOKEN
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
|
|
@@ -21,30 +20,30 @@ class JwtData:
|
|
|
21
20
|
Shared JWT data for security token access.
|
|
22
21
|
|
|
23
22
|
Instance variables:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
23
|
+
- access_lock: lock for safe multi-threading access
|
|
24
|
+
- access_data: dictionary holding the JWT token data, organized by account id:
|
|
25
|
+
{
|
|
26
|
+
<account-id>: {
|
|
27
|
+
"reference-url": # the reference URL
|
|
28
|
+
"remote-provider": <bool>, # whether the JWT provider is a remote server
|
|
29
|
+
"request-timeout": <int>, # in seconds - defaults to no timeout
|
|
30
|
+
"access-max-age": <int>, # in seconds - defaults to JWT_ACCESS_MAX_AGE
|
|
31
|
+
"refresh-max-age": <int>, # in seconds - defaults to JWT_REFRESH_MAX_AGE
|
|
32
|
+
"grace-interval": <int> # time to wait for token to be valid, in seconds
|
|
33
|
+
"token-audience": <string> # the audience the token is intended for
|
|
34
|
+
"token_nonce": <string> # value used to associate a client session with a token
|
|
35
|
+
"claims": {
|
|
36
|
+
"birthdate": <string>, # subject's birth date
|
|
37
|
+
"email": <string>, # subject's email
|
|
38
|
+
"gender": <string>, # subject's gender
|
|
39
|
+
"name": <string>, # subject's name
|
|
40
|
+
"roles": <List[str]>, # subject roles
|
|
41
|
+
"nonce": <string>, # value used to associate a Client session with a token
|
|
42
|
+
...
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
...
|
|
46
|
+
}
|
|
48
47
|
|
|
49
48
|
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between
|
|
50
49
|
two parties. It is fully described in the RFC 7519, issued by the Internet Engineering Task Force
|
|
@@ -53,24 +52,30 @@ class JwtData:
|
|
|
53
52
|
as token-related and account-related. All times are UTC.
|
|
54
53
|
|
|
55
54
|
Token-related claims are mostly required claims, and convey information about the token itself:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
"exp": <timestamp> # expiration time
|
|
56
|
+
"iat": <timestamp> # issued at
|
|
57
|
+
"iss": <string> # issuer (for remote providers, URL to obtain and validate the access tokens)
|
|
58
|
+
"jti": <string> # JWT id
|
|
59
|
+
"sub": <string> # subject (the account identification)
|
|
60
|
+
"nat": <string> # nature of token (A: access; R: refresh) - locally issued tokens, only
|
|
61
|
+
# optional:
|
|
62
|
+
"aud": <string> # token audience
|
|
63
|
+
"nbt": <timestamp> # not before time
|
|
65
64
|
|
|
66
65
|
Account-related claims are optional claims, and convey information about the registered account they belong to.
|
|
67
66
|
Alhough they can be freely specified, these are some of the most commonly used claims:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
"birthdate": <string> # subject's birth date
|
|
68
|
+
"email": <string> # subject's email
|
|
69
|
+
"gender": <string> # subject's gender
|
|
70
|
+
"name": <string> # subject's name
|
|
71
|
+
"roles": <List[str]> # subject roles
|
|
72
|
+
"nonce": <string> # value used to associate a client session with a token
|
|
73
|
+
|
|
74
|
+
The token header has these items:
|
|
75
|
+
"alg": <string> # the algorithm used to sign the token (one of 'HS256', 'HS512', 'RSA256', 'RSA512')
|
|
76
|
+
"typ": <string> # the token type (fixed to 'JWT'
|
|
77
|
+
"kid": <string> # a reference to the encoding/decoding keys used
|
|
78
|
+
# (if issued by the local server, holds the public key, if assimetric keys were used)
|
|
74
79
|
"""
|
|
75
80
|
def __init__(self) -> None:
|
|
76
81
|
"""
|
|
@@ -79,18 +84,18 @@ class JwtData:
|
|
|
79
84
|
self.access_lock: Lock = Lock()
|
|
80
85
|
self.access_data: dict[str, Any] = {}
|
|
81
86
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
def add_account(self,
|
|
88
|
+
account_id: str,
|
|
89
|
+
reference_url: str,
|
|
90
|
+
claims: dict[str, Any],
|
|
91
|
+
access_max_age: int,
|
|
92
|
+
refresh_max_age: int,
|
|
93
|
+
grace_interval: int,
|
|
94
|
+
token_audience: str,
|
|
95
|
+
token_nonce: str,
|
|
96
|
+
request_timeout: int,
|
|
97
|
+
remote_provider: bool,
|
|
98
|
+
logger: Logger = None) -> None:
|
|
94
99
|
"""
|
|
95
100
|
Add to storage the parameters needed to produce and validate JWT tokens for *account_id*.
|
|
96
101
|
|
|
@@ -130,9 +135,9 @@ class JwtData:
|
|
|
130
135
|
elif logger:
|
|
131
136
|
logger.warning(f"JWT data already exists for '{account_id}'")
|
|
132
137
|
|
|
133
|
-
def
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
def remove_account(self,
|
|
139
|
+
account_id: str,
|
|
140
|
+
logger: Logger) -> bool:
|
|
136
141
|
"""
|
|
137
142
|
Remove from storage the access data for *account_id*.
|
|
138
143
|
|
|
@@ -144,6 +149,12 @@ class JwtData:
|
|
|
144
149
|
with self.access_lock:
|
|
145
150
|
account_data = self.access_data.pop(account_id, None)
|
|
146
151
|
|
|
152
|
+
if account_data and JWT_DB_ENGINE:
|
|
153
|
+
from pypomes_db import db_delete
|
|
154
|
+
db_delete(errors=None,
|
|
155
|
+
delete_stmt=f"DELETE FROM {JWT_DB_TABLE}",
|
|
156
|
+
where_data={JWT_DB_COL_ACCOUNT: account_id},
|
|
157
|
+
logger=logger)
|
|
147
158
|
if logger:
|
|
148
159
|
if account_data:
|
|
149
160
|
logger.debug(f"Removed JWT data for '{account_id}'")
|
|
@@ -227,55 +238,36 @@ class JwtData:
|
|
|
227
238
|
# JWT service is being provided locally
|
|
228
239
|
just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
|
|
229
240
|
current_claims["iat"] = just_now
|
|
230
|
-
|
|
231
|
-
|
|
241
|
+
token_header: dict[str, Any] = None \
|
|
242
|
+
if JWT_DEFAULT_ALGORITHM not in ["RSA256", "RSA512"] \
|
|
243
|
+
else {"kid": JWT_DECODING_KEY}
|
|
232
244
|
|
|
233
|
-
#
|
|
234
|
-
refresh_token: str | None = None
|
|
235
|
-
if JWT_DB_ENGINE:
|
|
236
|
-
from pypomes_db import db_select, db_delete
|
|
237
|
-
if JWT_ROTATE_TOKENS:
|
|
238
|
-
db_delete(errors=errors,
|
|
239
|
-
delete_stmt=f"DELETE FROM {JWT_DB_TABLE} "
|
|
240
|
-
f"WHERE {JWT_DB_COL_ACCOUNT} = '{account_id}'",
|
|
241
|
-
logger=logger)
|
|
242
|
-
else:
|
|
243
|
-
recs: list[tuple[str]] = \
|
|
244
|
-
db_select(errors=errors,
|
|
245
|
-
sel_stmt=f"SELECT token FROM {JWT_DB_TABLE} "
|
|
246
|
-
f"WHERE {JWT_DB_COL_ACCOUNT} = '{account_id}'",
|
|
247
|
-
max_count=1,
|
|
248
|
-
logger=logger)
|
|
249
|
-
if recs:
|
|
250
|
-
refresh_token = recs[0][0]
|
|
251
|
-
if errors:
|
|
252
|
-
raise RuntimeError(" - ".join(errors))
|
|
253
|
-
|
|
254
|
-
# was it obtained ?
|
|
255
|
-
if not refresh_token:
|
|
256
|
-
# no, issue a new one
|
|
257
|
-
current_claims["exp"] = just_now + account_data.get("refresh-max-age")
|
|
258
|
-
current_claims["nat"] = "R"
|
|
259
|
-
# may raise an exception
|
|
260
|
-
refresh_token: str = jwt.encode(payload=current_claims,
|
|
261
|
-
key=JWT_ENCODING_KEY,
|
|
262
|
-
algorithm=JWT_DEFAULT_ALGORITHM)
|
|
263
|
-
# persist the new refresh token
|
|
264
|
-
if JWT_DB_ENGINE:
|
|
265
|
-
# persist the refresh token
|
|
266
|
-
_jwt_persist_token(account_id=account_id,
|
|
267
|
-
jwt_token=refresh_token,
|
|
268
|
-
logger=logger)
|
|
269
|
-
if errors:
|
|
270
|
-
raise RuntimeError(" - ".join(errors))
|
|
271
|
-
|
|
272
|
-
# issue the access token
|
|
245
|
+
# issue the access token first
|
|
273
246
|
current_claims["nat"] = "A"
|
|
274
247
|
current_claims["exp"] = just_now + account_data.get("access-max-age")
|
|
275
248
|
# may raise an exception
|
|
276
249
|
access_token: str = jwt.encode(payload=current_claims,
|
|
277
250
|
key=JWT_ENCODING_KEY,
|
|
278
|
-
algorithm=JWT_DEFAULT_ALGORITHM
|
|
251
|
+
algorithm=JWT_DEFAULT_ALGORITHM,
|
|
252
|
+
headers=token_header)
|
|
253
|
+
|
|
254
|
+
# then issue the refresh token
|
|
255
|
+
current_claims["exp"] = just_now + account_data.get("refresh-max-age")
|
|
256
|
+
current_claims["nat"] = "R"
|
|
257
|
+
# may raise an exception
|
|
258
|
+
refresh_token: str = jwt.encode(payload=current_claims,
|
|
259
|
+
key=JWT_ENCODING_KEY,
|
|
260
|
+
algorithm=JWT_DEFAULT_ALGORITHM,
|
|
261
|
+
headers=token_header)
|
|
262
|
+
if JWT_DB_ENGINE:
|
|
263
|
+
# persist the refresh token
|
|
264
|
+
_jwt_persist_token(errors=errors,
|
|
265
|
+
account_id=account_id,
|
|
266
|
+
jwt_token=refresh_token,
|
|
267
|
+
logger=logger)
|
|
268
|
+
if errors:
|
|
269
|
+
raise RuntimeError("; ".join(errors))
|
|
270
|
+
|
|
279
271
|
# return the token data
|
|
280
272
|
result = {
|
|
281
273
|
"access_token": access_token,
|
|
@@ -304,7 +296,9 @@ def _jwt_request_token(errors: list[str],
|
|
|
304
296
|
Expected structure of the return data:
|
|
305
297
|
{
|
|
306
298
|
"access_token": <jwt-token>,
|
|
307
|
-
"
|
|
299
|
+
"created_in": <timestamp>,
|
|
300
|
+
"expires_in": <seconds-to-expiration>,
|
|
301
|
+
"refresh_token": <token>
|
|
308
302
|
}
|
|
309
303
|
It is up to the invoker to make sure that the *claims* data conform to the requirements
|
|
310
304
|
of the provider issuing the JWT token.
|
|
@@ -345,24 +339,25 @@ def _jwt_request_token(errors: list[str],
|
|
|
345
339
|
return result
|
|
346
340
|
|
|
347
341
|
|
|
348
|
-
def _jwt_persist_token(
|
|
342
|
+
def _jwt_persist_token(errors: list[str],
|
|
343
|
+
account_id: str,
|
|
349
344
|
jwt_token: str,
|
|
350
|
-
logger: Logger = None) ->
|
|
345
|
+
logger: Logger = None) -> None:
|
|
351
346
|
"""
|
|
352
347
|
Persist the given token, making sure that the account limit is adhered to.
|
|
353
348
|
|
|
354
|
-
:param
|
|
349
|
+
:param errors: incidental errors
|
|
355
350
|
:param account_id: the account identification
|
|
356
|
-
:
|
|
351
|
+
:param jwt_token: the JWT token to persist
|
|
352
|
+
:param logger: optional logger
|
|
357
353
|
"""
|
|
358
354
|
from pypomes_db import db_select, db_insert, db_delete
|
|
359
355
|
from .jwt_pomes import jwt_get_claims
|
|
360
356
|
|
|
361
357
|
# retrieve the account's tokens
|
|
362
|
-
errors: list[str] = []
|
|
363
358
|
recs: list[tuple[str]] = db_select(errors=errors,
|
|
364
359
|
sel_stmt=f"SELECT {JWT_DB_COL_HASH}, {JWT_DB_COL_TOKEN} FROM {JWT_DB_TABLE} ",
|
|
365
|
-
where_data={JWT_DB_COL_ACCOUNT:
|
|
360
|
+
where_data={JWT_DB_COL_ACCOUNT: account_id})
|
|
366
361
|
if not errors:
|
|
367
362
|
if logger:
|
|
368
363
|
logger.debug(msg=f"Read {len(recs)} token from storage for account '{account_id}'")
|
|
@@ -371,14 +366,12 @@ def _jwt_persist_token(account_id: str,
|
|
|
371
366
|
for rec in recs:
|
|
372
367
|
token: str = rec[1]
|
|
373
368
|
token_hash: str = rec[0]
|
|
374
|
-
|
|
375
|
-
token_claims: dict[str, Any] = jwt_get_claims(errors=op_errors,
|
|
369
|
+
token_claims: dict[str, Any] = jwt_get_claims(errors=errors,
|
|
376
370
|
token=token,
|
|
377
371
|
validate=False,
|
|
378
372
|
logger=logger)
|
|
379
|
-
if
|
|
373
|
+
if errors:
|
|
380
374
|
break
|
|
381
|
-
|
|
382
375
|
exp: int = token_claims["payload"]["exp"]
|
|
383
376
|
if exp < datetime.now(tz=timezone.utc).timestamp():
|
|
384
377
|
expired.append(token_hash)
|
|
@@ -390,7 +383,7 @@ def _jwt_persist_token(account_id: str,
|
|
|
390
383
|
if db_delete(errors=errors,
|
|
391
384
|
delete_stmt=f"DELETE FROM {JWT_DB_TABLE}",
|
|
392
385
|
where_data={
|
|
393
|
-
JWT_DB_COL_ACCOUNT:
|
|
386
|
+
JWT_DB_COL_ACCOUNT: account_id,
|
|
394
387
|
JWT_DB_COL_HASH: expired
|
|
395
388
|
},
|
|
396
389
|
logger=logger) is not None:
|
|
@@ -402,12 +395,10 @@ def _jwt_persist_token(account_id: str,
|
|
|
402
395
|
# persist token
|
|
403
396
|
if not errors:
|
|
404
397
|
# ruff: noqa: S324
|
|
405
|
-
hasher = hashlib.new(name="md5"
|
|
406
|
-
|
|
398
|
+
hasher = hashlib.new(name="md5",
|
|
399
|
+
data=jwt_token.encode())
|
|
407
400
|
token_hash: str = hasher.digest().decode()
|
|
408
401
|
db_insert(errors=errors,
|
|
409
402
|
insert_stmt=f"INSERT INTO {JWT_DB_TABLE}",
|
|
410
403
|
insert_data={"ds_hash": token_hash,
|
|
411
404
|
"ds_token": jwt_token})
|
|
412
|
-
|
|
413
|
-
return len(errors) == 0
|
pypomes_jwt/jwt_pomes.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import hashlib
|
|
1
2
|
import jwt
|
|
2
3
|
from flask import Request, Response, request
|
|
3
4
|
from logging import Logger
|
|
@@ -113,7 +114,7 @@ def jwt_set_access(account_id: str,
|
|
|
113
114
|
:param logger: optional logger
|
|
114
115
|
"""
|
|
115
116
|
if logger:
|
|
116
|
-
logger.debug(msg=f"Register
|
|
117
|
+
logger.debug(msg=f"Register account data for '{account_id}'")
|
|
117
118
|
|
|
118
119
|
# extract the claims provided in the reference URL's query string
|
|
119
120
|
pos: int = reference_url.find("?")
|
|
@@ -124,21 +125,21 @@ def jwt_set_access(account_id: str,
|
|
|
124
125
|
reference_url = reference_url[:pos]
|
|
125
126
|
|
|
126
127
|
# register the JWT service
|
|
127
|
-
__jwt_data.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def
|
|
141
|
-
|
|
128
|
+
__jwt_data.add_account(account_id=account_id,
|
|
129
|
+
reference_url=reference_url,
|
|
130
|
+
claims=claims,
|
|
131
|
+
access_max_age=access_max_age,
|
|
132
|
+
refresh_max_age=refresh_max_age,
|
|
133
|
+
grace_interval=grace_interval,
|
|
134
|
+
token_audience=token_audience,
|
|
135
|
+
token_nonce=token_nonce,
|
|
136
|
+
request_timeout=request_timeout,
|
|
137
|
+
remote_provider=remote_provider,
|
|
138
|
+
logger=logger)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def jwt_remove_account(account_id: str,
|
|
142
|
+
logger: Logger = None) -> bool:
|
|
142
143
|
"""
|
|
143
144
|
Remove from storage the JWT access data for *account_id*.
|
|
144
145
|
|
|
@@ -149,8 +150,8 @@ def jwt_remove_access(account_id: str,
|
|
|
149
150
|
if logger:
|
|
150
151
|
logger.debug(msg=f"Remove access data for '{account_id}'")
|
|
151
152
|
|
|
152
|
-
return __jwt_data.
|
|
153
|
-
|
|
153
|
+
return __jwt_data.remove_account(account_id=account_id,
|
|
154
|
+
logger=logger)
|
|
154
155
|
|
|
155
156
|
|
|
156
157
|
def jwt_validate_token(errors: list[str] | None,
|
|
@@ -181,7 +182,7 @@ def jwt_validate_token(errors: list[str] | None,
|
|
|
181
182
|
claims: dict[str, Any] = jwt.decode(jwt=token,
|
|
182
183
|
key=JWT_DECODING_KEY,
|
|
183
184
|
algorithms=[JWT_DEFAULT_ALGORITHM])
|
|
184
|
-
if nature and
|
|
185
|
+
if nature and nature != claims.get("nat"):
|
|
185
186
|
nat: str = "an access" if nature == "A" else "a refresh"
|
|
186
187
|
err_msg = f"Token is not {nat} token"
|
|
187
188
|
except Exception as e:
|
|
@@ -198,16 +199,18 @@ def jwt_validate_token(errors: list[str] | None,
|
|
|
198
199
|
return err_msg is None
|
|
199
200
|
|
|
200
201
|
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
def jwt_revoke_token(errors: list[str] | None,
|
|
203
|
+
account_id: str,
|
|
204
|
+
refresh_token: str,
|
|
205
|
+
logger: Logger = None) -> bool:
|
|
204
206
|
"""
|
|
205
|
-
Revoke
|
|
207
|
+
Revoke the *refresh_token* associated with *account_id*.
|
|
206
208
|
|
|
207
209
|
Revoke operations require access to a database table defined by *JWT_DB_TABLE*.
|
|
208
210
|
|
|
209
211
|
:param errors: incidental error messages
|
|
210
212
|
:param account_id: the account identification
|
|
213
|
+
:param refresh_token: the token to be revolked
|
|
211
214
|
:param logger: optional logger
|
|
212
215
|
:return: *True* if operation could be performed, *False* otherwise
|
|
213
216
|
"""
|
|
@@ -215,16 +218,25 @@ def jwt_revoke_tokens(errors: list[str] | None,
|
|
|
215
218
|
result: bool = False
|
|
216
219
|
|
|
217
220
|
if logger:
|
|
218
|
-
logger.debug(msg=f"Revoking refresh
|
|
221
|
+
logger.debug(msg=f"Revoking refresh token of '{account_id}'")
|
|
219
222
|
|
|
220
223
|
op_errors: list[str] = []
|
|
221
224
|
if JWT_DB_ENGINE:
|
|
222
|
-
from pypomes_db import db_delete
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
from pypomes_db import db_exists, db_delete
|
|
226
|
+
# ruff: noqa: S324
|
|
227
|
+
hasher = hashlib.new(name="md5",
|
|
228
|
+
data=refresh_token.encode())
|
|
229
|
+
token_hash: str = hasher.digest().decode()
|
|
230
|
+
if db_exists(errors=op_errors,
|
|
231
|
+
table=JWT_DB_TABLE,
|
|
232
|
+
where_data={"ds_hash": token_hash},
|
|
233
|
+
logger=logger):
|
|
234
|
+
db_delete(errors=errors,
|
|
235
|
+
delete_stmt=f"DELETE FROM {JWT_DB_TABLE}",
|
|
236
|
+
where_data={"ds_hash": token_hash},
|
|
237
|
+
logger=logger)
|
|
238
|
+
elif not op_errors:
|
|
239
|
+
op_errors.append("Token was not found")
|
|
228
240
|
else:
|
|
229
241
|
op_errors.append("Database access for token revocation has not been specified")
|
|
230
242
|
|
|
@@ -278,7 +290,7 @@ def jwt_get_tokens(errors: list[str] | None,
|
|
|
278
290
|
recs: list[tuple[str]] = db_select(errors=op_errors,
|
|
279
291
|
sel_stmt=f"SELECT {JWT_DB_COL_TOKEN} "
|
|
280
292
|
f"FROM {JWT_DB_TABLE}",
|
|
281
|
-
where_data={JWT_DB_COL_ACCOUNT:
|
|
293
|
+
where_data={JWT_DB_COL_ACCOUNT: account_id},
|
|
282
294
|
logger=logger)
|
|
283
295
|
if not op_errors and \
|
|
284
296
|
(len(recs) == 0 or recs[0][0] != refresh_token):
|
|
@@ -325,7 +337,8 @@ def jwt_get_claims(errors: list[str] | None,
|
|
|
325
337
|
{
|
|
326
338
|
"header": {
|
|
327
339
|
"alg": "HS256",
|
|
328
|
-
"typ": "JWT"
|
|
340
|
+
"typ": "JWT",
|
|
341
|
+
"kid": "rt466ytRTYH64577uydhDFGHDYJH2341"
|
|
329
342
|
},
|
|
330
343
|
"payload": {
|
|
331
344
|
"birthdate": "1980-01-01",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_jwt
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pypomes_jwt/__init__.py,sha256=xUDd_xphRQFHuTxrvlQxO-mHIXgTqZjHWHMgp5gRrXU,1130
|
|
2
|
+
pypomes_jwt/jwt_constants.py,sha256=6-Jw4ORgf32hRWnaGyVISXMJMtTBk7LdKl3RrDy7Ll0,4328
|
|
3
|
+
pypomes_jwt/jwt_data.py,sha256=gyhGquSQbHevOKIoXmAmjMSwCjXB7pYbI2sY-7sGGO8,19158
|
|
4
|
+
pypomes_jwt/jwt_pomes.py,sha256=xNBlHhvrOH07WP6hKE0PyDl4fKSl0R1Xg7AQO_1b1uo,15201
|
|
5
|
+
pypomes_jwt-0.7.9.dist-info/METADATA,sha256=rXu25F1ufUL_U7frUaV1DMAj27pH8gvPX-TIA0qLlrg,599
|
|
6
|
+
pypomes_jwt-0.7.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
+
pypomes_jwt-0.7.9.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
+
pypomes_jwt-0.7.9.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pypomes_jwt/__init__.py,sha256=dkWeYPNwypjwFuTjx4YtC8QV9ihykF4xcJJ7x86Wc5g,1130
|
|
2
|
-
pypomes_jwt/jwt_constants.py,sha256=8QYpYbpuNfeq15Gwkww2VtEXHFOIkZ1pPS-GU_hifs4,4491
|
|
3
|
-
pypomes_jwt/jwt_data.py,sha256=aZCb6SAgZEz5dFhLWcinr1mc9HZndYQ1gSw5z-5cnCA,19622
|
|
4
|
-
pypomes_jwt/jwt_pomes.py,sha256=nrvu7zIAp0Blq3wAHbvtKLKOVBGlqCttTDOu-LsZxrE,14650
|
|
5
|
-
pypomes_jwt-0.7.8.dist-info/METADATA,sha256=k_n1-ruFPdSXdEHhrAvLDNFoxeXlMAuHZh7Qg5X7Dy0,599
|
|
6
|
-
pypomes_jwt-0.7.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
7
|
-
pypomes_jwt-0.7.8.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
|
|
8
|
-
pypomes_jwt-0.7.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|