From 84a81b5e030498017ceff8e4b931d99cba881829 Mon Sep 17 00:00:00 2001 From: ainsausti Date: Thu, 8 Jan 2026 15:48:25 +0100 Subject: [PATCH] :arrow_up: Upgraded JWT --- pyproject.toml | 8 ++++---- pyverless/crypto.py | 44 ++++++++++++++++++++++++++------------------ 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3fd2265..24a17b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "pyverless" -version = "0.0.56" -authors = ["rperez "] +version = "0.0.57" +authors = ["foqum "] description="A mini-framework providing tools to help you make complex APIs with serverless" readme="README.md" license="BSD" @@ -10,7 +10,7 @@ classifiers=[ # 3 - Alpha # 4 - Beta # 5 - Production/Stable - "Development Status :: 3 - Alpha", + "Development Status :: 4 - Beta", # Indicate who your project is intended for "Intended Audience :: Developers", "Topic :: Software Development :: Build Tools", @@ -29,7 +29,7 @@ repository = "https://github.com/QuantumBA/pyverless" [tool.poetry.dependencies] python = "^3.8" -PyJWT = "<2.0.0" +PyJWT = ">2.0.0" PyYAML = ">=5.1" sentry-sdk = ">=0.5.1" python-json-logger = ">=2.0.7" diff --git a/pyverless/crypto.py b/pyverless/crypto.py index 8aa338b..56d004b 100644 --- a/pyverless/crypto.py +++ b/pyverless/crypto.py @@ -1,14 +1,15 @@ import base64 -from calendar import timegm import hashlib import hmac -import jwt import random import time +from calendar import timegm from datetime import datetime -from pyverless.exceptions import Unauthorized +import jwt + from pyverless.config import settings +from pyverless.exceptions import Unauthorized def get_json_web_token(payload, expires=True, expiry=settings.JWT_EXPIRY): @@ -19,9 +20,9 @@ def get_json_web_token(payload, expires=True, expiry=settings.JWT_EXPIRY): # This is how PyJwt computes now, check: # https://github.com/jpadilla/pyjwt/blob/master/jwt/api_jwt.py#L153 now = timegm(datetime.utcnow().utctimetuple()) - payload['exp'] = now + int(expiry) + payload["exp"] = now + int(expiry) - return jwt.encode(payload, settings.SECRET_KEY, settings.JWT_ALGORITHM).decode('utf-8') + return jwt.encode(payload, settings.SECRET_KEY, settings.JWT_ALGORITHM) def decode_json_web_token(token, leeway=settings.JWT_LEEWAY): @@ -29,7 +30,12 @@ def decode_json_web_token(token, leeway=settings.JWT_LEEWAY): Decode a JWT. Leeway time may be provided. """ try: - decoded = jwt.decode(token, settings.SECRET_KEY, leeway=leeway, algorithms=[settings.JWT_ALGORITHM]) + decoded = jwt.decode( + token, + settings.SECRET_KEY, + leeway=leeway, + algorithms=[settings.JWT_ALGORITHM], + ) except jwt.exceptions.DecodeError: raise Unauthorized() except jwt.exceptions.ExpiredSignatureError: @@ -52,9 +58,10 @@ def is_expired(expiry): # and it can be found here: # https://github.com/django/django/blob/master/django/contrib/auth/hashers.py # https://github.com/django/django/blob/master/django/utils/crypto.py -def get_random_string(length=12, - allowed_chars='abcdefghijklmnopqrstuvwxyz' - 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): +def get_random_string( + length=12, + allowed_chars="abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +): """ Return a securely generated random string. The default length of 12 with the a-z, A-Z, 0-9 character set returns @@ -62,16 +69,16 @@ def get_random_string(length=12, """ random.seed( hashlib.sha256( - ('%s%s%s' % (random.getstate(), time.time(), settings.SECRET_KEY)).encode() + ("%s%s%s" % (random.getstate(), time.time(), settings.SECRET_KEY)).encode() ).digest() ) - return ''.join(random.choice(allowed_chars) for i in range(length)) + return "".join(random.choice(allowed_chars) for i in range(length)) def constant_time_compare(val1, val2): """Return True if the two strings are equal, False otherwise.""" - val1 = val1.encode('utf-8', 'strict') - val2 = val2.encode('utf-8', 'strict') + val1 = val1.encode("utf-8", "strict") + val2 = val2.encode("utf-8", "strict") return hmac.compare_digest(val1, val2) @@ -81,18 +88,19 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None): digest = hashlib.sha256 if not dklen: dklen = None - password = password.encode('utf-8', 'strict') - salt = salt.encode('utf-8', 'strict') + password = password.encode("utf-8", "strict") + salt = salt.encode("utf-8", "strict") return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen) -class PBKDF2PasswordHasher(): +class PBKDF2PasswordHasher: """ Secure password hashing using the PBKDF2 algorithm (recommended) Configured to use PBKDF2 + HMAC + SHA256. The result is a 64 byte binary string. Iterations may be changed safely but you must rename the algorithm if you change SHA256. """ + algorithm = "pbkdf2_sha256" iterations = 100000 digest = hashlib.sha256 @@ -105,11 +113,11 @@ def encode(self, password, salt=None, iterations=None): iterations = self.iterations hash = pbkdf2(password, salt, iterations, digest=self.digest) - hash = base64.b64encode(hash).decode('ascii').strip() + hash = base64.b64encode(hash).decode("ascii").strip() return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash) def verify(self, password, encoded): - algorithm, iterations, salt, hash = encoded.split('$', 3) + algorithm, iterations, salt, hash = encoded.split("$", 3) assert algorithm == self.algorithm encoded_2 = self.encode(password, salt, int(iterations)) return constant_time_compare(encoded, encoded_2)