Browse Source

Add a password hash field

Getty Ritter 1 year ago
parent
commit
e8e608a1d9
5 changed files with 53 additions and 5 deletions
  1. 10 1
      lc/model.py
  2. 1 0
      lc/request.py
  3. 19 1
      poetry.lock
  4. 2 0
      pyproject.toml
  5. 21 3
      tests/model.py

+ 10 - 1
lc/model.py

@@ -1,4 +1,5 @@
 import datetime
+from passlib.apps import custom_app_context as pwd
 import peewee
 import typing
 
@@ -18,10 +19,18 @@ class User(Model):
     """
 
     name = peewee.TextField()
+    passhash = peewee.TextField()
 
     @staticmethod
     def from_request(user: r.User) -> "User":
-        return User.create(name=user.name)
+        passhash = pwd.hash(user.password)
+        return User.create(
+            name=user.name,
+            passhash=passhash,
+        )
+
+    def authenticate(self, password: str) -> bool:
+        return pwd.verify(password, self.passhash)
 
     @staticmethod
     def by_slug(slug: str) -> "User":

+ 1 - 0
lc/request.py

@@ -7,6 +7,7 @@ from typing import List
 @dataclass
 class User:
     name: str
+    password: str
 
 
 @dataclass_json

+ 19 - 1
poetry.lock

@@ -233,6 +233,20 @@ version = "20.3"
 pyparsing = ">=2.0.2"
 six = "*"
 
+[[package]]
+category = "main"
+description = "comprehensive password hashing framework supporting over 30 schemes"
+name = "passlib"
+optional = false
+python-versions = "*"
+version = "1.7.2"
+
+[package.extras]
+argon2 = ["argon2-cffi (>=18.2.0)"]
+bcrypt = ["bcrypt (>=3.1.0)"]
+build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.0)"]
+totp = ["cryptography"]
+
 [[package]]
 category = "dev"
 description = "Utility library for gitignore style pattern matching of file paths."
@@ -418,7 +432,7 @@ dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-
 watchdog = ["watchdog"]
 
 [metadata]
-content-hash = "5fcc7150bc562ce07e6d72d39f335ab06e576ddf8c95c1f623fa16c2fde4ca32"
+content-hash = "ad6bfc0a1ba5e7235af3805fba4e6abf8aad3713fb963f4ef5d6387c85d121a0"
 python-versions = "^3.8"
 
 [metadata.files]
@@ -550,6 +564,10 @@ packaging = [
     {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"},
     {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"},
 ]
+passlib = [
+    {file = "passlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177"},
+    {file = "passlib-1.7.2.tar.gz", hash = "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"},
+]
 pathspec = [
     {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"},
     {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"},

+ 2 - 0
pyproject.toml

@@ -10,6 +10,8 @@ flask = "^1.1.2"
 peewee = "^3.13.2"
 pystache = "^0.5.4"
 dataclasses-json = "^0.4.2"
+passlib = "^1.7.2"
+itsdangerous = "^1.1.0"
 
 [tool.poetry.dev-dependencies]
 pytest = "^5.4.1"

+ 21 - 3
tests/model.py

@@ -1,4 +1,5 @@
 import lc.config as c
+import lc.request as r
 import lc.model as m
 
 
@@ -10,9 +11,15 @@ class TestDB:
     def teardown_method(self, _):
         c.DB.close()
 
+    def mk_user(self, name="gdritter", password="foo") -> m.User:
+        return m.User.from_request(r.User(
+            name=name,
+            password=password,
+        ))
+
     def test_create_user(self):
         name = "gdritter"
-        u = m.User.create(name=name)
+        u = self.mk_user(name=name)
 
         # it should be the only thing in the db
         all_users = m.User.select()
@@ -25,9 +32,20 @@ class TestDB:
         assert named_user.id == u.id
         assert named_user.name == name
 
+    def test_user_passwords(self):
+        name = "gdritter"
+        password = "foo"
+
+        u = self.mk_user(name=name, password=password)
+        print(u.name, u.passhash)
+
+        assert u.authenticate(password)
+        assert u.authenticate("wrong password") is False
+
     def test_get_or_create_tag(self):
+        u = self.mk_user()
+
         tag_name = "food"
-        u = m.User.create(name="gdritter")
         t = m.Tag.get_or_create_tag(u, tag_name)
 
         # we should be able to find the tag with the given name
@@ -39,7 +57,7 @@ class TestDB:
         assert t.id == t2.id
 
     def test_find_hierarchy(self):
-        u = m.User.create(name="gdritter")
+        u = self.mk_user()
         t = m.Tag.get_or_create_tag(u, "food/bread/rye")
 
         # this should have created three DB rows: for 'food', for