aboutsummaryrefslogtreecommitdiffstats
path: root/src/expense/auth/password.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/expense/auth/password.py')
-rw-r--r--src/expense/auth/password.py45
1 files changed, 45 insertions, 0 deletions
diff --git a/src/expense/auth/password.py b/src/expense/auth/password.py
new file mode 100644
index 0000000..0c7090c
--- /dev/null
+++ b/src/expense/auth/password.py
@@ -0,0 +1,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)