Add Password field

This commit is contained in:
evilchili 2025-10-04 12:01:59 -07:00
parent cb71005a0e
commit 7eb06b150e
3 changed files with 45 additions and 2 deletions

View File

@ -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):
@ -9,6 +9,7 @@ class User(Record):
Field("name"),
Integer("number", default=0),
Field("email", unique=True),
Password("password"),
BackReference("groups", Group),
]

View File

@ -1,5 +1,7 @@
from __future__ import annotations
import hashlib
import os
from collections import namedtuple
from dataclasses import dataclass, field
from typing import Dict, List
@ -46,6 +48,34 @@ class Integer(Field):
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)]):
"""
Base type for a single database record.
@ -72,7 +102,7 @@ class Record(Dict[(str, Field)]):
def serialize(self):
"""
Serialie every field on the record
Serialize every field on the record
"""
rec = {}
for name, _field in self._metadata.fields.items():

View File

@ -118,3 +118,15 @@ def test_search(db):
Group = Query()
crew = db.Group.search(Group.name == "Crew", recurse=False)
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)