Users can be saved and loaded
This commit is contained in:
parent
7a975a5782
commit
d097acaecf
@ -3,7 +3,7 @@ Here are the headers that every .py in this folder should implement.
|
||||
Each file is free to define its own set of config options that will be passed in AFTER the file is imported, under the name "config". It is generally assumed that this will be a dict, but there is no strict requirement, other than "it must be a valid json value".
|
||||
The init() function will be called with no arguments after config is passed in.
|
||||
the *token functions are about access tokens, as distinct from invite tokens.
|
||||
The backend will also have access to utils.py, under the name utils.
|
||||
The backend will also have access to utils.py, under the name utils and models.py under the name models.
|
||||
"""
|
||||
def init(): pass
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from os import path
|
||||
from os import path,rename
|
||||
def init():
|
||||
if not path.exists(config['path']): raise Exception(f"Backend directory doesn't exist: {config['path']}")
|
||||
for n in ['users','invite_tokens','access_tokens']:
|
||||
@ -8,7 +8,7 @@ def init():
|
||||
if path.isdir(p): raise Exception(f"Backend file is a directory: {p}")
|
||||
try: open(p).close()
|
||||
except PermissionError: raise Exception(f"Backend file can't be read: {p}")
|
||||
try: open(p,'w').close()
|
||||
try: open(p,'a').close()
|
||||
except PermissionError: raise Exception(f"Backend file can't be written: {p}")
|
||||
|
||||
def load_user(username):
|
||||
@ -21,7 +21,11 @@ def save_user(user):
|
||||
split=line.split(',')
|
||||
if split[0]==user.username: update(users,{gen.send(True):user.csv}); break
|
||||
else: update(users,{-1:user.csv})
|
||||
def delete_user(user): pass
|
||||
def delete_user(user):
|
||||
gen=linegen(users)
|
||||
for line in gen:
|
||||
split=line.split(',')
|
||||
if split[0]==user.username: remove(users,[gen.send(True)]); break
|
||||
|
||||
def load_tokens(user): pass
|
||||
def save_token(token): pass
|
||||
@ -43,15 +47,28 @@ def linegen(path):
|
||||
file.close()
|
||||
|
||||
def update(path,lines):
|
||||
def _action(file,i,line):
|
||||
if i in lines: file.write(lines[i]+'\n')
|
||||
else: file.write(line)
|
||||
def _post(file):
|
||||
if -1 in lines: file.write(lines[-1]+'\n')
|
||||
writefile(path,_action,_post)
|
||||
|
||||
def remove(path,lines):
|
||||
def _action(file,i,line):
|
||||
if i not in lines: file.write(line)
|
||||
writefile(path,_action)
|
||||
|
||||
def writefile(path,action,post=None):
|
||||
i=0
|
||||
file=open(path)
|
||||
write=open(f'{path}.tmp','w')
|
||||
line=file.readline()
|
||||
while line:
|
||||
i+=1
|
||||
if i in lines: write.write(lines[i]+'\n')
|
||||
else: write.write(line)
|
||||
action(write,i,line)
|
||||
line=file.readline()
|
||||
if -1 in lines: write.write(lines[-1])
|
||||
if post is not None: post(write)
|
||||
file.close()
|
||||
write.close()
|
||||
write.close()
|
||||
rename(f'{path}.tmp',path)
|
6
main.py
6
main.py
@ -2,7 +2,9 @@ from importlib import import_module
|
||||
from . import utils, models
|
||||
from .config import config
|
||||
|
||||
globals()['backend']=import_module('backends.'+config['backend']['type'])
|
||||
globals()['backend']=import_module('.backends.'+config['backend']['type'],'auth')
|
||||
backend.config=config['backend']['options']
|
||||
backend.utils=utils
|
||||
models.backend=backend
|
||||
backend.models=models
|
||||
models.backend=backend
|
||||
backend.init()
|
84
models.py
84
models.py
@ -9,42 +9,12 @@ def phash(pw,salt=None):
|
||||
|
||||
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=None # 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,invite:InviteToken,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),invite.owner,email)
|
||||
u.save()
|
||||
return u
|
||||
# And on User.register (InviteToken)
|
||||
|
||||
@dataclass
|
||||
class Token():
|
||||
value: str
|
||||
owner: User
|
||||
owner: AbstractUser
|
||||
def __init__(self,value,owner=None):
|
||||
if owner is None:
|
||||
owner=value
|
||||
@ -57,15 +27,59 @@ class Token():
|
||||
|
||||
@dataclass
|
||||
class InviteToken(Token):
|
||||
uses: int
|
||||
_uses: int=0
|
||||
_max_uses: int=-1
|
||||
_expires: datetime=None
|
||||
def __init__(self,*args,**kwargs):
|
||||
return super().__init__(*args,**kwargs)
|
||||
@property
|
||||
def uses(self): return self._uses
|
||||
@uses.setter
|
||||
def uses(self,val):
|
||||
if -1<self.max_uses<=val: self.revoke()
|
||||
self._uses=val
|
||||
@property
|
||||
def max_uses(self): return self._max_uses
|
||||
@max_uses.setter
|
||||
def max_uses(self,val):
|
||||
if val==0: self.revoke()
|
||||
if -1<val<=self.uses: self.revoke()
|
||||
self._max_uses=val
|
||||
|
||||
@dataclass
|
||||
class AccessToken(Token): pass
|
||||
class AccessToken(Token):
|
||||
def __init__(self,*args,**kwargs):
|
||||
return super().__init__(*args,**kwargs)
|
||||
|
||||
@dataclass
|
||||
class User(AbstractUser):
|
||||
username: str
|
||||
password_hash: str
|
||||
salt: str
|
||||
invited_by: AbstractUser=None # Root node will just reference itself
|
||||
email: str=''
|
||||
@property
|
||||
def csv(self):
|
||||
return ','.join([self.username,self.password_hash,str(self.salt),self.invited_by.username,self.email])
|
||||
def create_inv_token(self,*args,**kwargs):
|
||||
tok=InviteToken(self,*args,**kwargs)
|
||||
backend.save_token(tok)
|
||||
return tok
|
||||
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=backend.load_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,invite:InviteToken,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),invite.owner,email)
|
||||
u.save()
|
||||
invite.uses+=1
|
||||
return u
|
Loading…
Reference in New Issue
Block a user