aboutsummaryrefslogtreecommitdiffstats
path: root/src/expense/auth/password.py
blob: 0c7090c0e5f1bc21bc7009520f2dc4361059d9d4 (plain) (blame)
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)