Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Argon2id hashed passwords in userdb #2102

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
46 changes: 41 additions & 5 deletions server/fishtest/userdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
import threading
import time
from datetime import datetime, timezone

from functools import lru_cache

from argon2 import PasswordHasher
from argon2.exceptions import (
HashingError,
InvalidHash,
VerificationError,
VerifyMismatchError,
)
from fishtest.schemas import user_schema
from pymongo import ASCENDING
from vtjson import ValidationError, validate
Expand Down Expand Up @@ -47,16 +55,44 @@ def clear_cache(self):
with self.user_lock:
self.cache.clear()

def hash_password(
self,
password,
time_cost: int = 3,
memory_cost: int = 12288,
parallelism: int = 1,
):
return PasswordHasher(time_cost, memory_cost, parallelism).hash(password)

@lru_cache(maxsize=128)
def check_password(self, hashed_password, password):
try:
return PasswordHasher().verify(hashed_password, password)
except InvalidHash as e:
print("InvalidHash:", e, sep="\n")
except VerifyMismatchError as e:
print("VerifyMismatchError:", e, sep="\n")
except HashingError as e:
print("HashingError:", e, sep="\n")
except VerificationError as e:
print("VerificationError:", e, sep="\n")
except Exception as e:
print("Exception:", e, sep="\n")
return False

def authenticate(self, username, password):
user = self.get_user(username)
if not user or user["password"] != password:
sys.stderr.write("Invalid login: '{}' '{}'\n".format(username, password))
if not user:
sys.stderr.write("Invalid username: '{}'\n".format(username))
return {"error": "Invalid username: {}".format(username)}
if not self.check_password(user["password"], password):
sys.stderr.write("Invalid login: '{}'\n".format(username))
return {"error": "Invalid password for user: {}".format(username)}
if "blocked" in user and user["blocked"]:
sys.stderr.write("Blocked account: '{}' '{}'\n".format(username, password))
sys.stderr.write("Blocked account: '{}'\n".format(username))
return {"error": "Account blocked for user: {}".format(username)}
if "pending" in user and user["pending"]:
sys.stderr.write("Pending account: '{}' '{}'\n".format(username, password))
sys.stderr.write("Pending account: '{}'\n".format(username))
return {"error": "Account pending for user: {}".format(username)}

return {"username": username, "authenticated": True}
Expand Down
9 changes: 5 additions & 4 deletions server/fishtest/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def signup(request):

result = request.userdb.create_user(
username=signup_username,
password=signup_password,
password=request.userdb.hash_password(signup_password),
email=validated_email,
tests_repo=tests_repo,
)
Expand Down Expand Up @@ -621,8 +621,7 @@ def user(request):
new_email = request.params.get("email").strip()
tests_repo = request.params.get("tests_repo").strip()

# Temporary comparison until passwords are hashed.
if old_password != user_data["password"].strip():
if not request.userdb.check_password(user["password"], old_password):
request.session.flash("Invalid password!", "error")
return home(request)

Expand All @@ -635,7 +634,9 @@ def user(request):
(new_email if len(new_email) > 0 else None),
)
if strong_password:
user_data["password"] = new_password
user_data["password"] = request.userdb.hash_password(
new_password
)
request.session.flash("Success! Password updated")
else:
request.session.flash(
Expand Down
1 change: 1 addition & 0 deletions server/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"awscli",
"zxcvbn",
"email_validator",
"argon2-cffi",
"vtjson",
]

Expand Down
4 changes: 2 additions & 2 deletions server/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def setUpClass(cls):
}
cls.rundb.userdb.create_user(
cls.username,
cls.password,
"$argon2id$v=19$m=12288,t=3,p=1$CBZVlx94y2hWHPm1D6Vo3A$19cJ5J4prNe6aObbgFBHpwzyeg1DwiuWHWXG6srMq7w",
"[email protected]",
"https://github.com/official-stockfish/Stockfish",
)
Expand Down Expand Up @@ -526,7 +526,7 @@ def setUpClass(cls):
}
cls.rundb.userdb.create_user(
cls.username,
cls.password,
"$argon2id$v=19$m=12288,t=3,p=1$CBZVlx94y2hWHPm1D6Vo3A$19cJ5J4prNe6aObbgFBHpwzyeg1DwiuWHWXG6srMq7w",
"[email protected]",
"https://github.com/official-stockfish/Stockfish",
)
Expand Down
37 changes: 34 additions & 3 deletions server/tests/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def setUp(self):
self.rundb = util.get_rundb()
self.rundb.userdb.create_user(
"JoeUser",
"secret",
"$argon2id$v=19$m=12288,t=3,p=1$CBZVlx94y2hWHPm1D6Vo3A$19cJ5J4prNe6aObbgFBHpwzyeg1DwiuWHWXG6srMq7w",
"[email protected]",
"https://github.com/official-stockfish/Stockfish",
)
Expand All @@ -55,6 +55,7 @@ def tearDown(self):
testing.tearDown()

def test_login(self):
# Pending user, wrong password
request = testing.DummyRequest(
userdb=self.rundb.userdb,
method="POST",
Expand All @@ -65,21 +66,51 @@ def test_login(self):
"Invalid password for user: JoeUser" in request.session.pop_flash("error")
)

# Correct password, but still pending from logging in
# Pending user, correct password
request.params["password"] = "secret"
login(request)
self.assertTrue(
"Account pending for user: JoeUser" in request.session.pop_flash("error")[0]
)

# Unblock, then user can log in successfully
# Approved user, wrong password
user = self.rundb.userdb.get_user("JoeUser")
user["pending"] = False
self.rundb.userdb.save_user(user)
request.params["password"] = "badsecret"
response = login(request)
self.assertTrue(
"Invalid password for user: JoeUser" in request.session.pop_flash("error")
)

# Approved user, correct password
request.params["password"] = "secret"
response = login(request)
self.assertEqual(response.code, 302)
self.assertTrue("The resource was found at" in str(response))

# User is blocked, correct password
user["blocked"] = True
self.rundb.userdb.save_user(user)
response = login(request)
self.assertTrue(
"Account blocked for user: JoeUser" in request.session.pop_flash("error")[0]
)

# User is unblocked, correct password
user["blocked"] = False
self.rundb.userdb.save_user(user)
response = login(request)
self.assertEqual(response.code, 302)
self.assertTrue("The resource was found at" in str(response))

# Invalid username, correct password
request.params["username"] = "UserJoe"
response = login(request)
self.assertTrue(
"Invalid username: UserJoe" in request.session.pop_flash("error")[0]
)


class Create90APITest(unittest.TestCase):
def setUp(self):
Expand Down
Loading