71 lines
1.9 KiB
Python
71 lines
1.9 KiB
Python
|
from dataclasses import dataclass
|
||
|
from datetime import datetime
|
||
|
from hashlib import shake_256
|
||
|
import uuid
|
||
|
|
||
|
def phash(pw,salt=None):
|
||
|
if salt is None: salt=uuid.uuid64()
|
||
|
return shake_256(f'{pw}{salt}'.encode('utf-8')).hexdigest(),salt
|
||
|
|
||
|
class AbstractUser():
|
||
|
pass # It fixes circular dep on user.invited_by
|
||
|
|
||
|
@dataclass
|
||
|
class User(AbstractUser):
|
||
|
username: str
|
||
|
password_hash: str
|
||
|
salt: str
|
||
|
invited_by: AbstractUser # Root node will just reference itself
|
||
|
email: str=''
|
||
|
@property
|
||
|
def csv(self):
|
||
|
return ','.join([self.username,self.password_hash,self.salt,self.invited_by.username,self.email])
|
||
|
def create_inv_token(self,*args,**kwargs):
|
||
|
return InviteToken(*args,**kwargs)
|
||
|
def change_password(self,old_pw:str|None,new_pw:str): pass
|
||
|
def save(self):
|
||
|
backend.save_user(self)
|
||
|
|
||
|
@classmethod
|
||
|
def login(cls,username:str,password:str):
|
||
|
u=lookup_user(username)
|
||
|
if u is None: raise Exception("User doesn't exist")
|
||
|
if phash(password,u.salt)[0]==u.password_hash: return AccessToken(u)
|
||
|
raise Exception("Incorrect password")
|
||
|
@classmethod
|
||
|
def register(cls,username:str,password:str,email:str|None):
|
||
|
if set([chr(n) for n in range(32)]+[','])&set(username): raise Exception('Invalid username')
|
||
|
u=backend.load_user(username)
|
||
|
if u is not None: raise Exception("User already exists")
|
||
|
u=User(username,*phash(password),email)
|
||
|
u.save()
|
||
|
return u
|
||
|
|
||
|
@dataclass
|
||
|
class Token():
|
||
|
value: str
|
||
|
owner: User
|
||
|
def __init__(self,value,owner=None):
|
||
|
if owner is None:
|
||
|
owner=value
|
||
|
self.value=uuid.uuid4()
|
||
|
else:
|
||
|
self.value=uuid.UUID(value)
|
||
|
self.owner=owner
|
||
|
def revoke(self):
|
||
|
backend.delete_token(self)
|
||
|
|
||
|
@dataclass
|
||
|
class InviteToken(Token):
|
||
|
uses: int
|
||
|
_max_uses: int=-1
|
||
|
_expires: datetime=None
|
||
|
@property
|
||
|
def max_uses(self): return self._max_uses
|
||
|
@max_uses.setter
|
||
|
def max_uses(self,val):
|
||
|
if val==0: self.revoke()
|
||
|
self._max_uses=val
|
||
|
|
||
|
@dataclass
|
||
|
class AccessToken(Token): pass
|