from base64 import b64decode, b64encode from dataclasses import dataclass import json from random import randbytes from typing import Optional, Any, Self from bson.objectid import ObjectId import scrypt import jwt from roc_fnb.util.base64 import base64_encode, base64_decode with open('private-key.pem') as file: PRIVATE_KEY = file.read() with open('public-key.pem') as file: PUBLIC_KEY = file.read() @dataclass class JwtUser: _id: ObjectId email: str name: str moderator: bool admin: bool @classmethod def from_json(cls, data: dict) -> Self: _id = ObjectId(base64_decode(data.pop('_id'))) return cls(_id=_id, **data) @dataclass class User: _id: Optional[ObjectId] email: str name: str password_hash: bytes salt: bytes moderator: bool admin: bool @classmethod def create(cls, email: str, name: str, password: str|bytes, moderator: bool = False, admin: bool = False): """Alternate constructor which hashes a given password""" salt = randbytes(32) password_hash = scrypt.hash(password, salt) return cls(_id=None, email=email, name=name, password_hash=password_hash, salt=salt, moderator=moderator, admin=admin) @property def document(self): doc = { "email": self.email, "name": self.name, "password_hash": self.password_hash, "salt": self.salt, "moderator": self.moderator, "admin": self.admin, } if self._id is not None: doc['_id'] = self._id return doc @property def public_fields(self): """ Session data is visible to client scripts. This is a feature, not a bug; client scripts may need to gather login info. """ return { '_id': base64_encode(self._id.binary), "email": self.email, "name": self.name, "moderator": self.moderator, "admin": self.admin, } def check_password(self, password: str) -> bool: return self.password_hash == scrypt.hash(password, self.salt)