pypomes-jwt 0.7.1__py3-none-any.whl → 0.7.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.

Potentially problematic release.


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

pypomes_jwt/__init__.py CHANGED
@@ -1,25 +1,27 @@
1
1
  from .jwt_constants import (
2
2
  JWT_DB_ENGINE, JWT_DB_HOST, JWT_DB_NAME,
3
3
  JWT_DB_PORT, JWT_DB_USER, JWT_DB_PWD,
4
+ JWT_DB_TABLE, JWT_DB_COL_ACCOUNT, JWT_DB_COL_TOKEN,
4
5
  JWT_ACCESS_MAX_AGE, JWT_REFRESH_MAX_AGE,
5
6
  JWT_ENCODING_KEY, JWT_DECODING_KEY
6
7
  )
7
8
  from .jwt_pomes import (
8
- jwt_needed, jwt_verify_request, jwt_claims, jwt_tokens,
9
+ jwt_needed, jwt_verify_request,
9
10
  jwt_get_tokens, jwt_get_claims, jwt_validate_token,
10
- jwt_assert_access, jwt_set_access, jwt_remove_access
11
+ jwt_assert_access, jwt_set_access, jwt_remove_access, jwt_revoke_tokens
11
12
  )
12
13
 
13
14
  __all__ = [
14
15
  # jwt_constants
15
16
  "JWT_DB_ENGINE", "JWT_DB_HOST", "JWT_DB_NAME",
16
17
  "JWT_DB_PORT", "JWT_DB_USER", "JWT_DB_PWD",
18
+ "JWT_DB_TABLE", "JWT_DB_COL_ACCOUNT", "JWT_DB_COL_TOKEN",
17
19
  "JWT_ACCESS_MAX_AGE", "JWT_REFRESH_MAX_AGE",
18
20
  "JWT_ENCODING_KEY", "JWT_DECODING_KEY",
19
21
  # jwt_pomes
20
- "jwt_needed", "jwt_verify_request", "jwt_claims", "jwt_tokens",
22
+ "jwt_needed", "jwt_verify_request",
21
23
  "jwt_get_tokens", "jwt_get_claims", "jwt_validate_token",
22
- "jwt_assert_access", "jwt_set_access", "jwt_remove_access"
24
+ "jwt_assert_access", "jwt_set_access", "jwt_remove_access", "jwt_revoke_tokens"
23
25
  ]
24
26
 
25
27
  from importlib.metadata import version
@@ -9,16 +9,43 @@ from secrets import token_bytes
9
9
  from typing import Final
10
10
 
11
11
  # database specs for token persistence
12
- JWT_DB_ENGINE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_ENGINE")
13
12
  JWT_DB_HOST: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_HOST")
14
13
  JWT_DB_NAME: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_NAME")
15
14
  JWT_DB_PORT: Final[int] = env_get_int(key=f"{APP_PREFIX}_JWT_DB_PORT")
16
15
  JWT_DB_USER: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_USER")
17
16
  JWT_DB_PWD: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_PWD")
18
-
19
- JWT_ROTATE_TOKENS: Final[bool] = False \
20
- if JWT_DB_ENGINE is None else env_get_bool(key=f"{APP_PREFIX}_JWT_ROTATE_TOKENS",
21
- def_value=False)
17
+ JWT_DB_CLIENT: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_CLIENT") # for Oracle, only
18
+ JWT_DB_DRIVER: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_DRIVER") # for SQLServer, only
19
+ JWT_DB_TABLE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_TABLE",
20
+ def_value="jwt_token")
21
+ JWT_DB_COL_ACCOUNT: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_COL_ACCOUNT",
22
+ def_value="account_id")
23
+ JWT_DB_COL_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DB_COL_TOKEN",
24
+ def_value="token")
25
+ # define the database engine
26
+ __db_engine: str | None = env_get_str(key=f"{APP_PREFIX}_JWT_DB_ENGINE")
27
+ if __db_engine:
28
+ from pypomes_db import DbEngine, db_setup, db_assert_access, db_delete
29
+ from sys import stderr
30
+ if db_setup(engine=DbEngine(__db_engine),
31
+ db_name=JWT_DB_NAME,
32
+ db_user=JWT_DB_USER,
33
+ db_pwd=JWT_DB_PWD,
34
+ db_host=JWT_DB_HOST,
35
+ db_port=JWT_DB_PORT,
36
+ db_client=JWT_DB_CLIENT,
37
+ db_driver=JWT_DB_DRIVER):
38
+ __errors: list[str] = []
39
+ if not db_assert_access(errors=__errors) or \
40
+ db_delete(errors=__errors,
41
+ delete_stmt=f"DELETE FROM {JWT_DB_TABLE}") is None:
42
+ stderr.write(f"{'; '.join(__errors)}\n")
43
+ __db_engine = None
44
+ else:
45
+ stderr.write("Invalid database parameters\n")
46
+ __db_engine = None
47
+ # if set to 'None', no further attempt will be made to access the database
48
+ JWT_DB_ENGINE: Final[DbEngine] = DbEngine(__db_engine) if __db_engine else None
22
49
 
23
50
  # one of HS256, HS512, RSA256, RSA512
24
51
  JWT_DEFAULT_ALGORITHM: Final[str] = env_get_str(key=f"{APP_PREFIX}_JWT_DEFAULT_ALGORITHM",
@@ -29,6 +56,8 @@ JWT_ACCESS_MAX_AGE: Final[int] = env_get_int(key=f"{APP_PREFIX}_JWT_ACCESS_MAX_A
29
56
  # recommended: at least 2 hours (set to 24 hours)
30
57
  JWT_REFRESH_MAX_AGE: Final[int] = env_get_int(key=f"{APP_PREFIX}_JWT_REFRESH_MAX_AGE",
31
58
  def_value=86400)
59
+ JWT_ROTATE_TOKENS: Final[bool] = env_get_bool(key=f"{APP_PREFIX}_JWT_ROTATE_TOKENS",
60
+ def_value=True)
32
61
 
33
62
  # recommended: allow the encode and decode keys to be generated anew when app starts
34
63
  __encoding_key: bytes = env_get_bytes(key=f"{APP_PREFIX}_JWT_ENCODE_KEY")
pypomes_jwt/jwt_data.py CHANGED
@@ -9,7 +9,8 @@ from threading import Lock
9
9
  from typing import Any
10
10
 
11
11
  from .jwt_constants import (
12
- JWT_DEFAULT_ALGORITHM, JWT_ENCODING_KEY
12
+ JWT_DEFAULT_ALGORITHM, JWT_ENCODING_KEY, JWT_ROTATE_TOKENS,
13
+ JWT_DB_ENGINE, JWT_DB_TABLE, JWT_DB_COL_ACCOUNT, JWT_DB_COL_TOKEN
13
14
  )
14
15
 
15
16
 
@@ -19,7 +20,7 @@ class JwtData:
19
20
 
20
21
  Instance variables:
21
22
  - access_lock: lock for safe multi-threading access
22
- - access_data: list with dictionaries holding the JWT token data, organized by account ids:
23
+ - access_data: dictionary holding the JWT token data, organized by account id:
23
24
  {
24
25
  <account-id>: {
25
26
  "reference-url": # the reference URL
@@ -178,7 +179,7 @@ class JwtData:
178
179
  :raises InvalidIssuerError: 'iss' claim does not match the expected issuer
179
180
  :raises InvalidIssuedAtError: 'iat' claim is non-numeric
180
181
  :raises MissingRequiredClaimError: a required claim is not contained in the claimset
181
- :raises RuntimeError: access data not found for the given *account_id*, or
182
+ :raises RuntimeError: error accessing the revocation database, or
182
183
  the remote JWT provider failed to return a token
183
184
  """
184
185
  # initialize the return variable
@@ -191,9 +192,10 @@ class JwtData:
191
192
  # was the JWT data obtained ?
192
193
  if account_data:
193
194
  # yes, proceed
195
+ errors: list[str] = []
194
196
  current_claims: dict[str, Any] = account_data.get("claims").copy()
195
197
  if account_claims:
196
- current_claims.update(current_claims)
198
+ current_claims.update(account_claims)
197
199
 
198
200
  # obtain new tokens
199
201
  current_claims["jti"] = str_random(size=32,
@@ -203,7 +205,6 @@ class JwtData:
203
205
  # where is the JWT service provider ?
204
206
  if account_data.get("remote-provider"):
205
207
  # JWT service is being provided by a remote server
206
- errors: list[str] = []
207
208
  # Structure of the return data:
208
209
  # {
209
210
  # "access_token": <jwt-token>,
@@ -221,15 +222,53 @@ class JwtData:
221
222
  raise RuntimeError(" - ".join(errors))
222
223
  else:
223
224
  # JWT service is being provided locally
224
- just_now: float = datetime.now(tz=timezone.utc).timestamp()
225
+ just_now: int = int(datetime.now(tz=timezone.utc).timestamp())
225
226
  current_claims["iat"] = just_now
226
- current_claims["exp"] = just_now + account_data.get("access-max-age")
227
- current_claims["nat"] = "R"
228
- # may raise an exception
229
- refresh_token: str = jwt.encode(payload=current_claims,
230
- key=JWT_ENCODING_KEY,
231
- algorithm=JWT_DEFAULT_ALGORITHM)
227
+
228
+ # retrieve the refresh token associated with the account id
229
+ refresh_token: str | None = None
230
+ if JWT_DB_ENGINE:
231
+ from pypomes_db import db_select, db_delete
232
+ if JWT_ROTATE_TOKENS:
233
+ db_delete(errors=errors,
234
+ delete_stmt=f"DELETE FROM {JWT_DB_TABLE} "
235
+ f"WHERE {JWT_DB_COL_ACCOUNT} = '{account_id}'",
236
+ logger=logger)
237
+ else:
238
+ recs: list[tuple[str]] = \
239
+ db_select(errors=errors,
240
+ sel_stmt=f"SELECT token FROM {JWT_DB_TABLE} "
241
+ f"WHERE {JWT_DB_COL_ACCOUNT} = '{account_id}'",
242
+ max_count=1,
243
+ logger=logger)
244
+ if recs:
245
+ refresh_token = recs[0][0]
246
+ if errors:
247
+ raise RuntimeError(" - ".join(errors))
248
+
249
+ # was it obtained ?
250
+ if not refresh_token:
251
+ # no, issue a new one
252
+ current_claims["exp"] = just_now + account_data.get("refresh-max-age")
253
+ current_claims["nat"] = "R"
254
+ # may raise an exception
255
+ refresh_token: str = jwt.encode(payload=current_claims,
256
+ key=JWT_ENCODING_KEY,
257
+ algorithm=JWT_DEFAULT_ALGORITHM)
258
+ # persist the new refresh token
259
+ if JWT_DB_ENGINE:
260
+ from pypomes_db import db_insert
261
+ db_insert(errors=errors,
262
+ insert_stmt=f"INSERT INTO {JWT_DB_TABLE}",
263
+ insert_data={JWT_DB_COL_ACCOUNT: account_id,
264
+ JWT_DB_COL_TOKEN: refresh_token},
265
+ logger=logger)
266
+ if errors:
267
+ raise RuntimeError(" - ".join(errors))
268
+
269
+ # issue the access token
232
270
  current_claims["nat"] = "A"
271
+ current_claims["exp"] = just_now + account_data.get("access-max-age")
233
272
  # may raise an exception
234
273
  access_token: str = jwt.encode(payload=current_claims,
235
274
  key=JWT_ENCODING_KEY,
@@ -237,8 +276,8 @@ class JwtData:
237
276
  # return the token data
238
277
  result = {
239
278
  "access_token": access_token,
240
- "created_in": account_claims.get("iat"),
241
- "expires_in": account_claims.get("exp"),
279
+ "created_in": current_claims.get("iat"),
280
+ "expires_in": current_claims.get("exp"),
242
281
  "refresh_token": refresh_token
243
282
  }
244
283
  else:
pypomes_jwt/jwt_pomes.py CHANGED
@@ -1,12 +1,12 @@
1
- import contextlib
2
1
  import jwt
3
- from flask import Request, Response, request, jsonify
2
+ from flask import Request, Response, request
4
3
  from logging import Logger
5
4
  from typing import Any, Literal
6
5
 
7
6
  from .jwt_constants import (
8
7
  JWT_ACCESS_MAX_AGE, JWT_REFRESH_MAX_AGE,
9
- JWT_DEFAULT_ALGORITHM, JWT_DECODING_KEY
8
+ JWT_DEFAULT_ALGORITHM, JWT_DECODING_KEY,
9
+ JWT_DB_ENGINE, JWT_DB_TABLE, JWT_DB_COL_ACCOUNT
10
10
  )
11
11
  from .jwt_data import JwtData
12
12
 
@@ -153,12 +153,57 @@ def jwt_validate_token(errors: list[str] | None,
153
153
  return err_msg is None
154
154
 
155
155
 
156
+ def jwt_revoke_tokens(errors: list[str] | None,
157
+ account_id: str,
158
+ logger: Logger = None) -> bool:
159
+ """
160
+ Revoke all refresh tokens associated with *account_id*.
161
+
162
+ Revoke operations require access to a database table defined by *JWT_DB_TABLE*.
163
+
164
+ :param errors: incidental error messages
165
+ :param account_id: the account identification
166
+ :param logger: optional logger
167
+ :return: *True* if operation could be performed, *False* otherwise
168
+ """
169
+ # initialize the return variable
170
+ result: bool = False
171
+
172
+ if logger:
173
+ logger.debug(msg=f"Revoking refresh tokens of '{account_id}'")
174
+
175
+ op_errors: list[str] = []
176
+ if JWT_DB_ENGINE:
177
+ from pypomes_db import db_delete
178
+ delete_stmt: str = (f"DELETE FROM {JWT_DB_TABLE} "
179
+ f"WHERE {JWT_DB_COL_ACCOUNT} = '{account_id}'")
180
+ db_delete(errors=op_errors,
181
+ delete_stmt=delete_stmt,
182
+ logger=logger)
183
+ else:
184
+ op_errors.append("Database access for token revocation has not been specified")
185
+
186
+ if op_errors:
187
+ if logger:
188
+ logger.error(msg="; ".join(op_errors))
189
+ if isinstance(errors, list):
190
+ errors.extend(op_errors)
191
+ else:
192
+ result = True
193
+
194
+ return result
195
+
196
+
156
197
  def jwt_get_tokens(errors: list[str] | None,
157
198
  account_id: str,
158
199
  account_claims: dict[str, Any] = None,
200
+ refresh_token: str = None,
159
201
  logger: Logger = None) -> dict[str, Any]:
160
202
  """
161
- Issue and return the JWT token data associated with *account_id*.
203
+ Issue or refresh, and return, the JWT token data associated with *account_id*.
204
+
205
+ If *refresh_token* is provided, its claims are used on issuing the new tokens,
206
+ and claims in *account_claims*, if any, are ignored.
162
207
 
163
208
  Structure of the return data:
164
209
  {
@@ -171,6 +216,7 @@ def jwt_get_tokens(errors: list[str] | None,
171
216
  :param errors: incidental error messages
172
217
  :param account_id: the account identification
173
218
  :param account_claims: if provided, may supercede registered custom claims
219
+ :param refresh_token: if provided, defines a token refresh operation
174
220
  :param logger: optional logger
175
221
  :return: the JWT token data, or *None* if error
176
222
  """
@@ -179,17 +225,28 @@ def jwt_get_tokens(errors: list[str] | None,
179
225
 
180
226
  if logger:
181
227
  logger.debug(msg=f"Retrieve JWT token data for '{account_id}'")
182
- try:
183
- result = __jwt_data.issue_tokens(account_id=account_id,
184
- account_claims=account_claims,
185
- logger=logger)
186
- if logger:
187
- logger.debug(msg=f"Data is '{result}'")
188
- except Exception as e:
228
+ op_errors: list[str] = []
229
+ if refresh_token:
230
+ account_claims = jwt_get_claims(errors=op_errors,
231
+ token=refresh_token)
232
+ if not op_errors and account_claims.get("nat") != "R":
233
+ op_errors.extend("Invalid parameters")
234
+
235
+ if not op_errors:
236
+ try:
237
+ result = __jwt_data.issue_tokens(account_id=account_id,
238
+ account_claims=account_claims)
239
+ if logger:
240
+ logger.debug(msg=f"Data is '{result}'")
241
+ except Exception as e:
242
+ # token issuing failed
243
+ op_errors.append(str(e))
244
+
245
+ if op_errors:
189
246
  if logger:
190
- logger.error(msg=str(e))
247
+ logger.error("; ".join(op_errors))
191
248
  if isinstance(errors, list):
192
- errors.append(str(e))
249
+ errors.extend(op_errors)
193
250
 
194
251
  return result
195
252
 
@@ -212,14 +269,14 @@ def jwt_get_claims(errors: list[str] | None,
212
269
  logger.debug(msg=f"Retrieve claims for token '{token}'")
213
270
 
214
271
  try:
215
- reply: dict[str, Any] = jwt.decode(jwt=token,
216
- options={"verify_signature": False})
217
- if reply.get("nat") in ["A", "R"]:
272
+ claims: dict[str, Any] = jwt.decode(jwt=token,
273
+ options={"verify_signature": False})
274
+ if claims.get("nat") in ["A", "R"]:
218
275
  result = jwt.decode(jwt=token,
219
276
  key=JWT_DECODING_KEY,
220
277
  algorithms=[JWT_DEFAULT_ALGORITHM])
221
278
  else:
222
- result = reply
279
+ result = claims
223
280
  except Exception as e:
224
281
  if logger:
225
282
  logger.error(msg=str(e))
@@ -250,12 +307,13 @@ def jwt_verify_request(request: Request,
250
307
 
251
308
  # was a 'Bearer' authorization obtained ?
252
309
  if auth_header and auth_header.startswith("Bearer "):
253
- # yes, extract and validate the JWT token
310
+ # yes, extract and validate the JWT access token
254
311
  token: str = auth_header.split(" ")[1]
255
312
  if logger:
256
313
  logger.debug(msg=f"Token is '{token}'")
257
314
  errors: list[str] = []
258
315
  jwt_validate_token(errors=errors,
316
+ nature="A",
259
317
  token=token)
260
318
  if errors:
261
319
  err_msg = "; ".join(errors)
@@ -271,122 +329,3 @@ def jwt_verify_request(request: Request,
271
329
  status=401)
272
330
 
273
331
  return result
274
-
275
-
276
- def jwt_claims(token: str = None) -> Response:
277
- """
278
- REST service entry point for retrieving the claims of a JWT token.
279
-
280
- Structure of the return data:
281
- {
282
- "<claim-1>": <value-of-claim-1>,
283
- ...
284
- "<claim-n>": <value-of-claim-n>
285
- }
286
-
287
- :param token: the JWT token
288
- :return: a *Response* containing the requested JWT token claims, or reporting an error
289
- """
290
- # declare the return variable
291
- result: Response
292
-
293
- # retrieve the token
294
- # noinspection PyUnusedLocal
295
- if not token:
296
- token = request.values.get("token")
297
- if not token:
298
- with contextlib.suppress(Exception):
299
- token = request.get_json().get("token")
300
-
301
- # has the token been obtained ?
302
- if token:
303
- # yes, obtain the token data
304
- errors: list[str] = []
305
- token_claims: dict[str, Any] = jwt_get_claims(errors=errors,
306
- token=token)
307
- if errors:
308
- result = Response(response=errors,
309
- status=400)
310
- else:
311
- result = jsonify(token_claims)
312
- else:
313
- # no, report the problem
314
- result = Response(response="Invalid parameters",
315
- status=400)
316
-
317
- return result
318
-
319
-
320
- def jwt_tokens(service_params: dict[str, Any] = None) -> Response:
321
- """
322
- REST service entry point for obtaining or refreshing JWT tokens.
323
-
324
- The requester must send, as parameter *service_params* or in the body of the request:
325
- {
326
- "account-id": "<string>" - required account identification
327
- "refresh_token": <string> - if refresh is being requested
328
- "<account-claim-key-1>": "<account-claim-value-1>", - optional superceding account claims
329
- ...
330
- "<account-claim-key-n>": "<account-claim-value-n>"
331
- }
332
- if provided, the refresh token will cause a token refresh operation to be carried out.
333
- Otherwise, a regular token issue operation is carried out, with the optional superceding
334
- account claims being used (claims currently registered for the account may be overridden).
335
-
336
- Structure of the return data:
337
- {
338
- "access_token": <jwt-token>,
339
- "created_in": <timestamp>,
340
- "expires_in": <seconds-to-expiration>,
341
- "refresh_token": <jwt-token>
342
- }
343
-
344
- :param service_params: the optional JSON containing the request parameters (defaults to JSON in body)
345
- :return: a *Response* containing the requested JWT token data, or reporting an error
346
- """
347
- # declare the return variable
348
- result: Response
349
-
350
- # retrieve the parameters
351
- # noinspection PyUnusedLocal
352
- params: dict[str, Any] = service_params or {}
353
- if not params:
354
- with contextlib.suppress(Exception):
355
- params = request.get_json()
356
- account_id: str | None = params.pop("account-id", None)
357
- refresh_token: str | None = params.pop("refresh-token", None)
358
- err_msg: str | None = None
359
- token_data: dict[str, Any] | None = None
360
-
361
- # has the account been identified ?
362
- if account_id:
363
- # yes, proceed
364
- if refresh_token:
365
- errors: list[str] = []
366
- claims: dict[str, Any] = jwt_get_claims(errors=errors,
367
- token=refresh_token)
368
- if errors:
369
- err_msg = "; ".join(errors)
370
- elif claims.get("nat") != "R":
371
- err_msg = "Invalid parameters"
372
- else:
373
- params = claims
374
-
375
- if not err_msg:
376
- try:
377
- token_data = __jwt_data.issue_tokens(account_id=account_id,
378
- account_claims=params)
379
- except Exception as e:
380
- # token issuing failed
381
- err_msg = str(e)
382
- else:
383
- # no, report the problem
384
- err_msg = "Invalid parameters"
385
-
386
- if err_msg:
387
- result = Response(response=err_msg,
388
- status=401)
389
- else:
390
- result = jsonify(token_data)
391
-
392
- return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_jwt
3
- Version: 0.7.1
3
+ Version: 0.7.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=dkWeYPNwypjwFuTjx4YtC8QV9ihykF4xcJJ7x86Wc5g,1130
2
+ pypomes_jwt/jwt_constants.py,sha256=zuU-JvVojYuNeEWfvPC62_IL2KZYshppYLZ3MnSpp4c,4523
3
+ pypomes_jwt/jwt_data.py,sha256=gKXL-gRm2ABfSD_X42bSzBciv5nclO2uiqOSvUUgE3k,16419
4
+ pypomes_jwt/jwt_pomes.py,sha256=zFU1TyeR3BhK9ToC1Vs1wh67uE2DGvzH9FltiQxI0EM,12160
5
+ pypomes_jwt-0.7.3.dist-info/METADATA,sha256=FRPBH-XDlmst_zCKAAaRzSZa4LJvEZIcXe0Rw2GDprU,599
6
+ pypomes_jwt-0.7.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ pypomes_jwt-0.7.3.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
8
+ pypomes_jwt-0.7.3.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- pypomes_jwt/__init__.py,sha256=4dBUGZTw1c-TeZukYjwAM7VWFzoFBSEyAN26jgjQRtM,1022
2
- pypomes_jwt/jwt_constants.py,sha256=k1PFqBF7KI2Ie8ErOW1zw9IWTgqjkxvU4m2eKGr_1EA,2927
3
- pypomes_jwt/jwt_data.py,sha256=npKZqHZbftvvOGCRAl-2yovUbqCQW0FvcQvyuYGXA_U,14189
4
- pypomes_jwt/jwt_pomes.py,sha256=w2sgJUF4CZibBCxU_-PZvrQyMm1saP0FBnf1yCCUSak,14247
5
- pypomes_jwt-0.7.1.dist-info/METADATA,sha256=S092HRvlJYZVEgHbzJyZ3o1bAqlIqUwEri37srho9jA,599
6
- pypomes_jwt-0.7.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- pypomes_jwt-0.7.1.dist-info/licenses/LICENSE,sha256=NdakochSXm_H_-DSL_x2JlRCkYikj3snYYvTwgR5d_c,1086
8
- pypomes_jwt-0.7.1.dist-info/RECORD,,