In Eve, how can you safely store a user's password?
If you put the debugger in the startup file, you will see that the user's password is hashed, but when you look in the mongo collection, the user's password is stored in plain text. How do I save a user's password as a hash?
Here are my files:
run.py:
from eve import Eve
from eve.auth import BasicAuth
import bcrypt
class BCryptAuth(BasicAuth):
def check_auth(self, username, password, allowed_roles, resource, method):
# use Eve own db driver; no additional connections/resources are used
accounts = app.data.driver.db["accounts"]
account = accounts.find_one({"username": username})
return account and \
bcrypt.hashpw(password, account['password']) == account['password']
def create_user(*arguments, **keywords):
password = arguments[0][0]['password']
username = arguments[0][0]['username']
user = {
"password": bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()),
"username": username,
}
return post_internal("accounts", user)
app = Eve(auth=BCryptAuth)
app.on_insert_accounts += create_user
if __name__ == '__main__':
app.run()
settings.py:
API_NAME = "gametest"
CACHE_CONTROL = "max-age=20"
CACHE_EXPIRES = 20
MONGO_DBNAME = "gametest"
MONGO_HOST = "localhost"
MONGO_PORT = 27017
PUBLIC_ITEM_METHODS = ["GET"]
RESOURCE_METHODS = ["GET"]
accounts_schema = {
"username": {
"type": "string",
"required": True,
"unique": True,
},
"password": {
"type": "string",
"required": True,
},
}
accounts = {
# the standard account entry point is defined as
# '/accounts/<ObjectId>'. We define an additional read-only entry
# point accessible at '/accounts/<username>'.
"additional_lookup": {
"url": "regex('[\w]+')",
"field": "username",
},
# We also disable endpoint caching as we don't want client apps to
# cache account data.
"cache_control": "",
"cache_expires": 0,
# Finally, let add the schema definition for this endpoint.
"schema": accounts_schema,
"public_methods": ["POST"],
"resource_methods": ["POST"],
}
games_schema = {
"game_id": {
"type": "objectid",
"required": True
},
"title": {
"type": "string",
"required": True
},
}
games = {
"item_title": "game",
"schema": games_schema,
}
orders = {
"schema": {
"game": {
"type": "objectid",
"required": True,
},
},
"resource_methods": ["GET", "POST"],
}
DOMAIN = {
"accounts", accounts,
"orders": orders,
"games": game,
}
+1
source to share
1 answer
There run.py
were several important things in yours that prevented you from authenticating:
- In the trick,
create_user
you generated the salt withbcrypt.gensalt()
, but you didn't store the salt anywhere. Salts are useful for preventing rainbow table , but you need to store them so that when you re-hash the password, you get the same result. - You use an event binding
on_insert_accounts
to modify the document before publishing it, but then return itpost_internal
instead of letting the event cursor fire your course. It might work, but I feel like you should just use the event hook as intended.
Here's the modified one run.py
:
from eve import Eve
from eve.auth import BasicAuth
import bcrypt
class BCryptAuth(BasicAuth):
def check_auth(self, username, password, allowed_roles, resource, method):
# use Eve own db driver; no additional connections/resources are used
accounts = app.data.driver.db["accounts"]
account = accounts.find_one({"username": username})
return account and \
bcrypt.hashpw(password.encode('utf-8'), account['salt'].encode('utf-8')) == account['password']
def create_user(documents):
for document in documents:
document['salt'] = bcrypt.gensalt().encode('utf-8')
password = document['password'].encode('utf-8')
document['password'] = bcrypt.hashpw(password, document['salt'])
app = Eve(auth=BCryptAuth)
app.on_insert_accounts += create_user
if __name__ == '__main__':
app.run()
Yours settings.py
had a few typos, so I'm using the working version for a good estimate:
API_NAME = "gametest"
CACHE_CONTROL = "max-age=20"
CACHE_EXPIRES = 20
MONGO_DBNAME = "gametest"
MONGO_HOST = "localhost"
MONGO_PORT = 27017
PUBLIC_ITEM_METHODS = ["GET"]
RESOURCE_METHODS = ["GET"]
accounts_schema = {
"username": {
"type": "string",
"required": True,
"unique": True
},
"password": {
"type": "string",
"required": True
}
}
accounts = {
# the standard account entry point is defined as
# '/accounts/<ObjectId>'. We define an additional read-only entry
# point accessible at '/accounts/<username>'.
"additional_lookup": {
"url": "regex('[\w]+')",
"field": "username",
},
# We also disable endpoint caching as we don't want client apps to
# cache account data.
"cache_control": "",
"cache_expires": 0,
# Finally, let add the schema definition for this endpoint.
"schema": accounts_schema,
"public_methods": ["POST"],
"resource_methods": ["POST"]
}
games_schema = {
"game_id": {
"type": "objectid",
"required": True
},
"title": {
"type": "string",
"required": True
}
}
games = {
"item_title": "game",
"schema": games_schema
}
orders = {
"schema": {
"game": {
"type": "objectid",
"required": True,
}
},
"resource_methods": ["GET", "POST"]
}
DOMAIN = {
"accounts": accounts,
"orders": orders,
"games": games
}
+3
source to share