Add Password field
This commit is contained in:
parent
cb71005a0e
commit
7eb06b150e
|
@ -1,4 +1,4 @@
|
||||||
from grung.types import BackReference, Collection, Field, Integer, Record
|
from grung.types import BackReference, Collection, Field, Integer, Password, Record
|
||||||
|
|
||||||
|
|
||||||
class User(Record):
|
class User(Record):
|
||||||
|
@ -9,6 +9,7 @@ class User(Record):
|
||||||
Field("name"),
|
Field("name"),
|
||||||
Integer("number", default=0),
|
Integer("number", default=0),
|
||||||
Field("email", unique=True),
|
Field("email", unique=True),
|
||||||
|
Password("password"),
|
||||||
BackReference("groups", Group),
|
BackReference("groups", Group),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import os
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
@ -46,6 +48,34 @@ class Integer(Field):
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Password(Field):
|
||||||
|
value_type = str
|
||||||
|
default: str = None
|
||||||
|
|
||||||
|
# Relatively weak. Consider using stronger initial values in production applications.
|
||||||
|
salt_size = 4
|
||||||
|
digest_size = 16
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_digest(cls, passwd: str, salt: bytes = None):
|
||||||
|
if not salt:
|
||||||
|
salt = os.urandom(cls.salt_size)
|
||||||
|
digest = hashlib.blake2b(passwd.encode(), digest_size=cls.digest_size, salt=salt).hexdigest()
|
||||||
|
return digest, salt.hex()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def compare(cls, passwd: value_type, stored: value_type):
|
||||||
|
stored_salt, stored_digest = stored.split(":")
|
||||||
|
input_digest, input_salt = cls.get_digest(passwd, bytes.fromhex(stored_salt))
|
||||||
|
return input_digest == stored_digest
|
||||||
|
|
||||||
|
def before_insert(self, value: value_type, db: TinyDB, record: Record) -> None:
|
||||||
|
if value:
|
||||||
|
digest, salt = self.__class__.get_digest(value)
|
||||||
|
record[self.name] = f"{salt}:{digest}"
|
||||||
|
|
||||||
|
|
||||||
class Record(Dict[(str, Field)]):
|
class Record(Dict[(str, Field)]):
|
||||||
"""
|
"""
|
||||||
Base type for a single database record.
|
Base type for a single database record.
|
||||||
|
@ -72,7 +102,7 @@ class Record(Dict[(str, Field)]):
|
||||||
|
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
"""
|
"""
|
||||||
Serialie every field on the record
|
Serialize every field on the record
|
||||||
"""
|
"""
|
||||||
rec = {}
|
rec = {}
|
||||||
for name, _field in self._metadata.fields.items():
|
for name, _field in self._metadata.fields.items():
|
||||||
|
|
|
@ -118,3 +118,15 @@ def test_search(db):
|
||||||
Group = Query()
|
Group = Query()
|
||||||
crew = db.Group.search(Group.name == "Crew", recurse=False)
|
crew = db.Group.search(Group.name == "Crew", recurse=False)
|
||||||
assert kirk.reference in crew[0].members
|
assert kirk.reference in crew[0].members
|
||||||
|
|
||||||
|
|
||||||
|
def test_password(db):
|
||||||
|
user = db.save(examples.User(name="john", email="john@foo", password="fnord"))
|
||||||
|
|
||||||
|
assert ":" in user.password
|
||||||
|
assert user.password != "fnord"
|
||||||
|
|
||||||
|
check = user._metadata.fields["password"].compare
|
||||||
|
assert check("fnord", user.password)
|
||||||
|
assert not check("wrong password", user.password)
|
||||||
|
assert not check("", user.password)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user