Hi there,
I've just run into this problem also - I got the same error message. But I was able to verify via the sandbox that the new user node was created.
Here's my auth.py code:
import bcrypt
import jwt
from datetime import datetime
from flask import current_app
from api.exceptions.badrequest import BadRequestException
from api.exceptions.validation import ValidationException
from neo4j.exceptions import ConstraintError
class AuthDAO:
"""
The constructor expects an instance of the Neo4j Driver, which will be
used to interact with Neo4j.
"""
def init(self, driver, jwt_secret):
self.driver = driver
self.jwt_secret = jwt_secret
"""
This method should create a new User node in the database with the email and name
provided, along with an encrypted version of the password and a `userId` property
generated by the server.
The properties also be used to generate a JWT `token` which should be included
with the returned user.
"""
# tag::register[]
def register(self, email, plain_password, name):
encrypted = bcrypt.hashpw(plain_password.encode("utf8"), bcrypt.gensalt()).decode('utf8')
def create_user(tx, email, encrypted, name):
return tx.run(""" // (1)
CREATE (u:User {
userId: randomUuid(),
email: $email,
password: $encrypted,
name: $name
})
RETURN u
""",
email=email, encrypted=encrypted, name=name # (2)
).single() # (3)
try:
with self.driver.session() as session:
result = session.execute_write(create_user, email, encrypted, name)
user = result['u']
payload = {
"userId": user["userId"],
"email": user["email"],
"name": user["name"],
}
payload["token"] = self._generate_token(payload)
return payload
except ConstraintError as err:
# Pass error details through to a ValidationException
raise ValidationException(err.message, {
"email": err.message
})
# end::register[]
"""
This method should attempt to find a user by the email address provided
and attempt to verify the password.
If a user is not found or the passwords do not match, a `false` value should
be returned. Otherwise, the users properties should be returned along with
an encoded JWT token with a set of 'claims'.
{
userId: 'some-random-uuid',
email: 'graphacademy@neo4j.com',
name: 'GraphAcademy User',
token: '...'
}
"""
# tag::authenticate[]
def authenticate(self, email, plain_password):
# TODO: Implement Login functionality
if email == "graphacademy@neo4j.com" and plain_password == "letmein":
# Build a set of claims
payload = {
"userId": "00000000-0000-0000-0000-000000000000",
"email": email,
"name": "GraphAcademy User",
}
# Generate Token
payload["token"] = self._generate_token(payload)
return payload
else:
return False
# end::authenticate[]
"""
This method should take the claims encoded into a JWT token and return
the information needed to authenticate this user against the database.
"""
# tag::generate[]
def _generate_token(self, payload):
iat = datetime.utcnow()
payload["sub"] = payload["userId"]
payload["iat"] = iat
payload["nbf"] = iat
payload["exp"] = iat + current_app.config.get('JWT_EXPIRATION_DELTA')
return jwt.encode(
payload,
self.jwt_secret,
algorithm='HS256'
)
# end::generate[]
"""
This method will attemp to decode a JWT token
"""
# tag::decode[]
def decode_token(auth_token, jwt_secret):
try:
payload = jwt.decode(auth_token, jwt_secret)
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
# end::decode[]
Here's the terminal:
pod /workspace/app-python (main) $ pytest tests/03_registering_a_user__test.py
=============================================================== test session starts ================================================================
platform linux -- Python 3.11.9, pytest-7.1.3, pluggy-1.0.0
rootdir: /workspace/app-python
collected 1 item
tests/03_registering_a_user__test.py F [100%]
===================================================================== FAILURES =====================================================================
________________________________________________________________ test_register_user ________________________________________________________________
app = <Flask 'api'>
def test_register_user(app):
with app.app_context():
driver = get_driver()
dao = AuthDAO(driver, os.environ.get('JWT_SECRET'))
user = dao.register(email, password, name)
tests/03_registering_a_user__test.py:31:
api/dao/auth.py:58: in register
payload["token"] = self._generate_token(payload)
api/dao/auth.py:115: in _generate_token
return jwt.encode(
../.pyenv_mirror/user/3.11.9/lib/python3.11/site-packages/jwt/api_jwt.py:67: in encode
return api_jws.encode(json_payload, key, algorithm, headers, json_encoder)
../.pyenv_mirror/user/3.11.9/lib/python3.11/site-packages/jwt/api_jws.py:153: in encode
key = alg_obj.prepare_key(key)
../.pyenv_mirror/user/3.11.9/lib/python3.11/site-packages/jwt/algorithms.py:186: in prepare_key
key = force_bytes(key)
value = None
def force_bytes(value: Union[str, bytes]) -> bytes:
if isinstance(value, str):
return value.encode("utf-8")
elif isinstance(value, bytes):
return value
else:
raise TypeError("Expected a string value")
E TypeError: Expected a string value
../.pyenv_mirror/user/3.11.9/lib/python3.11/site-packages/jwt/utils.py:22: TypeError
============================================================= short test summary info ==============================================================
FAILED tests/03_registering_a_user__test.py::test_register_user - TypeError: Expected a string value
================================================================ 1 failed in 1.24s =================================================================
gitpod /workspace/app-python (main) $