Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool.poetry]
name = "pyverless"
version = "0.0.56"
authors = ["rperez <rperez@op2aim.io>"]
version = "0.0.57"
authors = ["foqum <info@foqum.io>"]
description="A mini-framework providing tools to help you make complex APIs with serverless"
readme="README.md"
license="BSD"
Expand All @@ -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",
Expand All @@ -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"
Expand Down
44 changes: 26 additions & 18 deletions pyverless/crypto.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -19,17 +20,22 @@ 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):
"""
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:
Expand All @@ -52,26 +58,27 @@ 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
a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
"""
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)


Expand All @@ -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
Expand All @@ -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)