Flask-SqlAlchemy, Bcrypt, Postgres issue with encoding - postgresql

I'm writing my first API from scratch and have a /login endpoint that errors when verifying a users password with bcrypt but only when using Postgres as my DB, works correctly when using SQLite3.
Also, any assistance in better ways to structure anything in my models or route is always welcome, this is my first API in Flask / Python so I'm still learning.
Thanks in advance!
Error:
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
[2021-06-22 12:06:14,415] ERROR in app: Exception on /api/v1/login [POST]
Traceback (most recent call last):
File "C:\Users\x4c8\Projects\money_api\venv\lib\site-packages\flask\app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "C:\Users\x4c8\Projects\money_api\venv\lib\site-packages\flask\app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Users\x4c8\Projects\money_api\venv\lib\site-packages\flask\app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Users\x4c8\Projects\money_api\venv\lib\site-packages\flask\app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "C:\Users\x4c8\Projects\money_api\routes.py", line 47, in token_get
check = user.verify_password(password)
File "C:\Users\x4c8\Projects\money_api\models.py", line 40, in verify_password
return bcrypt.checkpw(enc_pw, self.password_hash)
File "C:\Users\x4c8\Projects\money_api\venv\lib\site-packages\bcrypt\__init__.py", line 120, in checkpw
raise TypeError("Unicode-objects must be encoded before checking")
TypeError: Unicode-objects must be encoded before checking
127.0.0.1 - - [22/Jun/2021 12:06:14] "POST /api/v1/login HTTP/1.1" 500 -
User class in Models.py:
class User(db.Model, Serializer):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(15), unique=False, nullable=True)
last_name = db.Column(db.String(20), unique=False, nullable=True)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(255), unique=False, nullable=False)
country = db.Column(db.String(2), unique=False, nullable=True)
subscription_level = db.Column(db.Integer, default=0)
subscription_purchase_date = db.Column(db.DateTime(), unique=False, nullable=True)
last_login = db.Column(db.DateTime(), unique=False, default=datetime.utcnow)
modified_at = db.Column(db.DateTime(), unique=False, default=datetime.utcnow)
created_at = db.Column(db.DateTime(), unique=False, default=datetime.utcnow)
# relationships
portfolios = db.relationship('StockPortfolio', foreign_keys='StockPortfolio.fk_user', backref='user',
lazy='dynamic', cascade='all, delete-orphan')
#property
def password(self):
raise AttributeError('password not readable')
#password.setter
def password(self, password):
enc_pw = password.encode('utf-8')
self.password_hash = bcrypt.hashpw(enc_pw, bcrypt.gensalt()).decode('utf-8')
def verify_password(self, password):
enc_pw = password.encode('utf-8')
return bcrypt.checkpw(enc_pw, self.password_hash)
def serialize(self):
d = Serializer.serialize(self)
del d['password_hash']
del d['modified_at']
del d['created_at']
del d['last_login']
return d
/login from routes.py
# POST /login
#routes.route(api_v1 + 'login', methods=['POST'])
def token_get():
if request.method == 'POST':
body = request.get_json()
# fail on missing params
if body.get('email') is None:
return jsonify(msg='email parameter is missing'), 422
if body.get('password') is None:
return jsonify(msg='password parameter is missing'), 422
# fail on email not in use
user = User.query.filter_by(email=body.get('email')).first()
if user is None:
return jsonify(msg='Email is not in use'), 404
else:
password = body.get('password')
check = user.verify_password(password)
if check:
# record last login
user.last_login = datetime.utcnow()
# prep and return tokens
access_token = create_access_token(identity=user.id)
refresh_token = create_refresh_token(identity=user.id)
return jsonify(msg='login successful', access_token=access_token, refresh_token=refresh_token), 200
else:
return jsonify(msg='incorrect email or password'), 409

You need just change this part of the code to convert password_hash to bytes:
def verify_password(self, password):
enc_pw = password.encode('utf-8')
return bcrypt.checkpw(enc_pw, bytes(self.password_hash, 'utf-8'))

Related

Unable to login with Flask Login when deployed to Heroku using PostgreSQL

I am building a simple authentication page that uses Flask and Flask SQLAlchemy deployed on Heroku with PostgreSQL.
I was able to register users but was unable to log in.
Code for Log in:
`
#indexbp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
flash("You have already logged in!", 'success')
return redirect('/')
form = LoginForm()
if form.validate_on_submit():
try:
user = User.query.filter_by(username=form.username.data).first()
if bcrypt.check_password_hash(user.hashed_password, form.password.data):
login_user(user)
flash('You are logged in!', 'success')
return redirect('/')
except AttributeError:
db.session.rollback()
flash('Wrong username, password or email. Please try again!', 'info')
return redirect('/login')
except Exception as e:
print(e)
db.session.rollback()
flash('Something went wrong inside our systems, please try again later. If this problem persists, please contact our admin team.', 'danger')
return redirect('/login')
return render_template('login.html', form=form)
`
Code for Register:
`
#indexbp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
flash("You have already logged in!", 'success')
return redirect('/')
form = RegisterForm()
if form.validate_on_submit():
try:
new_user = User(username=form.username.data, hashed_password=bcrypt.generate_password_hash(form.password.data), user_id=str(uuid.uuid1()))
db.session.add(new_user)
db.session.commit()
return redirect('/login')
except Exception as e:
print(str(e))
db.session.rollback()
flash('Something went wrong inside our systems, please try again later. If this problem persists, please contact our admin team.', 'danger')
return redirect('/register')
return render_template('register.html', form=form)
`
User table:
`
#User table
class User(db.Model, UserMixin):
id = db.Column(db.Integer(), primary_key=True, unique=True, nullable=False)
username = db.Column(db.String(), unique=True, nullable=False)
hashed_password = db.Column(db.String(), unique=False, nullable=False)
user_id = db.Column(db.String(), unique=True, nullable=False)
`
Flask Configs:
`
app.config['SQLALCHEMY_DATABASE_URI'] = "link-to-database-provided-by-heroku"
app.config['SECRET_KEY'] = "g3AbKqKhxhCACvFNlNgLv802LUBwXQKr4X4Z9J9d"
`
I have tried to migrate the database, rechecking it multiple times.
I have also attempted to run it locally but only registering works.
I installed psycopg2 as well as reconfiguring the User table.
Problem was Flask-Bcrypt not working properly. Changed my hashing function to md5 and it worked.

SQL Database Operational Error - no such table : users

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users
[SQL: SELECT users.id AS users_id, users.username AS users_username, users.password AS users_password
FROM users
WHERE users.username = ?]
the above is the error im getting.
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(24), unique=True, nullable=False)
password = db.Column(db.String(), nullable=False)
this is my User code.
#app.route('/', methods=['GET', 'POST'])
def index():
reg_form = Registeration()
if reg_form.validate_on_submit():
username = reg_form.username.data
password = reg_form.password.data
# for checking if someone has taken the username already
user_object = User.query.filter_by(username=username).all()
if user_object:
return "This username has been taken. Try another one.."
#registering in the database and commiting and etc
user = User(username=username, password=password)
db.session.add(user)
db.session.commit()
return 'Registered into the database!'
return db.create_all()
the application code.
I'm unable to figure out.. any help would be greatly appreciated. Btw im trying to make a registration system where the username and the pass get stored in Postgres Database.

Flask BCrypt check_password_hash return False after saving via MongoEngine

I run into a weird situation, which is similar to this . This is my model:
class User(db.Document):
email = db.EmailField(required=True, unique=True)
username = db.StringField(max_length=50, required=True, unique=True )
password = db.StringField(required=True)
first_name = db.StringField(max_length=100, required=False)
last_name = db.StringField(max_length=100, required=False)
role = db.IntField(required = True, choices = role_choices, default = 5)
status = db.IntField(required = True, choices = status_choices, default = 1)
last_login = db.DateTimeField(required=False)
#property
def is_authenticated(self):
return True
#property
def is_active(self):
return True
#property
def is_anonymous(self):
return False
def __unicode__(self):
return self.username
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password)
def __init__(self, *args, **kwargs):
username = kwargs.pop('username', None)
password = kwargs.pop('password', None)
email = kwargs.pop('email', None)
super(User, self).__init__(*args, **kwargs)
self.username = username
self.set_password(password)
self.email = email
meta = {
'allow_inheritance': False,
'indexes': ['-username'],
'ordering': ['-username']
}
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
When I first create an instance of this User class, everything works as expected:
an = User(username="annguyen", first_name="An", last_name="Nguyen", password="janet78", email="an#gmail.com")
>>> an.password
'$2b$12$Ho9Q0/n4FPERytHKxA3szu8gzRZE4J9FxuZots8FFxJUKP6ULmqpe'
>>> an.save()
<User: annguyen>
>>> len(an.password)
60
>>> an.check_password('janet78')
True
However, when I retrieve this user from the database, the check_password method always return False.
>>> an_new = User.objects.get(username='annguyen')
>>> an_new.password
'$2b$12$j9VfNiySMKN19cYIEjuAseiamREUmGbB2ZFM4faoLJySB6uZfaCj2'
>>> len(an.password)
60
>>> len(an_new.password)
60
>>> an_new.check_password('janet78')
False
It is very clear that the password retrieved from the database is very different from what it was before being saved to the database, and therefore it always returns False. I spent a whole day trying to figure out what's wrong but couldn't come up with any clue. Can someone help point out how I can fix it.
I am developing on Windows 10 with Python 2.7, Flask 0.10.1, MongoDB 3.2, flask-mongoengine==0.7.5, mongoengine==0.10.6
Thanks a lot.

AttributeError 'IdLookup' object has no attribute 'rel'

I try to use the django REST-framework's tutorial http://django-rest-framework.org/#django-rest-framework to administrate users. (I also use the Neo4j database and the neo4django mapper https://github.com/scholrly/neo4django to acces data via python.)
Whatever, wen I call localhost:8000/users an AttributeError appears.
models.py
from django.utils import timezone
from django.conf import settings
from django.contrib.auth import models as django_auth_models
from ..db import models
from ..db.models.manager import NodeModelManager
from ..decorators import borrows_methods
class UserManager(NodeModelManager, django_auth_models.UserManager):
pass
# all non-overriden methods of DjangoUser are called this way instead.
# inheritance would be preferred, but isn't an option because of conflicting
# metaclasses and weird class side-effects
USER_PASSTHROUGH_METHODS = (
"__unicode__", "natural_key", "get_absolute_url",
"is_anonymous", "is_authenticated", "get_full_name", "set_password",
"check_password", "set_unusable_password", "has_usable_password",
"get_group_permissions", "get_all_permissions", "has_perm", "has_perms",
"has_module_perms", "email_user", 'get_profile','get_username')
#borrows_methods(django_auth_models.User, USER_PASSTHROUGH_METHODS)
class User(models.NodeModel):
objects = UserManager()
username = models.StringProperty(indexed=True, unique=True)
first_name = models.StringProperty()
last_name = models.StringProperty()
email = models.EmailProperty(indexed=True)
password = models.StringProperty()
is_staff = models.BooleanProperty(default=False)
is_active = models.BooleanProperty(default=False)
is_superuser = models.BooleanProperty(default=False)
last_login = models.DateTimeProperty(default=timezone.now())
date_joined = models.DateTimeProperty(default=timezone.now())
USERNAME_FIELD = 'username'
REQUIRED_FIELDS=['email']
serializers.py
from neo4django.graph_auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ('url', 'username', 'email')
views.py
from neo4django.graph_auth.models import User
from rest_framework import viewsets
from api.serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
I get this:
Environment:
Request Method: GET
Request URL: http://localhost:8000/users/
Django Version: 1.5.3
Python Version: 2.7.3
Installed Applications:
('core.models',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'neo4django.graph_auth',
'rest_framework')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware')
Traceback:
File "/opt/phaidra/env/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
115. response = callback(request, *callback_args, **callback_kwargs)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/viewsets.py" in view
78. return self.dispatch(request, *args, **kwargs)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
77. return view_func(*args, **kwargs)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
399. response = self.handle_exception(exc)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
396. response = handler(request, *args, **kwargs)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/mixins.py" in list
92. serializer = self.get_pagination_serializer(page)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/generics.py" in get_pagination_serializer
113. return pagination_serializer_class(instance=page, context=context)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/pagination.py" in __init__
85. self.fields[results_field] = object_serializer(source='object_list', **context_kwarg)
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/serializers.py" in __init__
162. self.fields = self.get_fields()
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/serializers.py" in get_fields
198. default_fields = self.get_default_fields()
File "/opt/phaidra/env/local/lib/python2.7/site-packages/rest_framework/serializers.py" in get_default_fields
599. while pk_field.rel and pk_field.rel.parent_link:
Exception Type: AttributeError at /users/
Exception Value: 'IdLookup' object has no attribute 'rel'
I am rather new on the Python/Django/REST service area. I hope someone could help. Thanks in advance.
Firstly I would recommend to either use Tastypie - which is out of the box supported by neo4django - instead of Django Rest Framework - or use Django Rest Framework Serializer instead of the ModelSerializer or (worst choice in my opinion) HyperlinkedModelSerializer.
As for the error you get, the problem is that neo4django does not return the record id, therefore you get the error.
One solution is to override the restore_object function, like this, to include the ID.
# User Serializer
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField()
username = serializers.CharField(max_length=30)
first_name = serializers.CharField(max_length=30)
last_name = serializers.CharField(max_length=30)
email = serializers.EmailField()
password = serializers.CharField(max_length=128)
is_staff = serializers.BooleanField()
is_active = serializers.BooleanField()
is_superuser = serializers.BooleanField()
last_login = serializers.DateTimeField()
date_joined = serializers.DateTimeField()
def restore_object(self, attrs, instance=None):
"""
Given a dictionary of deserialized field values, either update
an existing model instance, or create a new model instance.
"""
if instance is not None:
instance.id = attrs.get('ID', instance.pk)
instance.username = attrs.get('Username', instance.username)
instance.first_name = attrs.get('First Name', instance.first_name)
instance.last_name = attrs.get('First Name', instance.last_name)
instance.email = attrs.get('email', instance.email)
instance.password = attrs.get('Password', instance.password)
instance.is_staff = attrs.get('Staff', instance.is_staff)
instance.is_active = attrs.get('Active', instance.is_active)
instance.is_superuser = attrs.get('Superusers', instance.is_superuser)
instance.last_login = attrs.get('Last Seen', instance.last_login)
instance.date_joined = attrs.get('Joined', instance.date_joined)
return instance
return User(**attrs)
But I still think it's better to use Tastypie with ModelResource.
Here's a gist by Matt Luongo (or Lukas Martini ?) https://gist.github.com/mhluongo/5789513
TIP. Where Serializer in Django Rest Framework, is Resource in Tastypie and always make sure you are using the github version of neo4django (pip install -e git+https://github.com/scholrly/neo4django/#egg=neo4django)

About interacting and testing with django tastypie

I read many tutorial about django testing but I don't know how can I test for function insde resource , for example this User resource with signin function and obj_create. I really appreciate any helps because I can not figure out how to test them. k thanks.
class UserResource(ModelResource):
school = fields.ToOneField('frittie.app.api.api.LocationResource', 'user')
class Meta:
queryset = UserProfile.objects.all()
resource_name = 'User'
allowed_methods = ['get','post']
serializer = Serializer(formats=['json', 'plist'])
authorization= Authorization()
#models.signals.post_save.connect(create_api_key, sender=User)
#fields = ['username', 'email']
def obj_create(self, bundle, request=None, **kwargs):
if not request.method == "POST":
raise BadRequest('Object not found or not allowed to create a new one.')
username, email, password = bundle.data['username'], bundle.data['password'], bundle.data['password'],
try:
bundle.obj = User.objects.create_user(username, email, password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
def signin(self, request, **kwargs):
self.method_check(request, allowed=['post'])
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return self.create_response(request, {'success': True})
else:
# Return a 'disabled account' error message
return self.create_response(request, {'success': False})
else:
# Return an 'invalid login' error message.
return self.create_response(request, {'success': False})
Tastypie has a descent testing documentation - ResourceTestCase API Reference