97 lines
2.3 KiB
Python
97 lines
2.3 KiB
Python
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)
|