1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
from dataclasses import dataclass
from sqlalchemy import Text, TypeDecorator
from werkzeug.security import check_password_hash, generate_password_hash
@dataclass(eq=False)
class PasswordHash:
"""
Generate hash for password storage. It wraps arround the werkzeug.security
module.
"""
hash: str
def __eq__(self, candidate) -> bool:
return check_password_hash(self.hash, candidate)
@classmethod
def new(cls, password: str, method: str = "pbkdf2", salt_length: int = 16):
"""Wrapper over generate_password"""
hashed_password = generate_password_hash(password, method, salt_length)
return cls(hashed_password)
class Password(TypeDecorator):
"""The SQLAlchemy Addapter for PasswordHash"""
impl = Text
def process_bind_param(self, value, dialect) -> str | None:
if value is not None:
if isinstance(value, PasswordHash):
return value.hash
raise ValueError("Invalid value type. Expected PasswordHash object.")
return None
def process_result_value(self, value: str, dialect) -> PasswordHash | None:
if value is not None:
return PasswordHash(value)
return None
def coerce_compared_value(self, op, value):
if isinstance(value, PasswordHash):
return value.hash
return super().coerce_compared_value(op, value)
|