Compare commits

..

No commits in common. "10586b480a4740d4ed70288d53981c12189df0f7" and "620f780113cacea4c6dbc0f3a9322c3a26c385e1" have entirely different histories.

4 changed files with 28 additions and 61 deletions

View File

@ -15,7 +15,7 @@ def _load(name,pos,file,obj):
for line in linegen(file): for line in linegen(file):
split=line.split(',') split=line.split(',')
if split[pos]==name: return obj(*split) if split[pos]==name: return obj(*split)
return None raise Exception('No such',obj.__name__,'with identifier',name)
def _load_multi(name,pos,file,obj): def _load_multi(name,pos,file,obj):
out=[] out=[]
for line in linegen(file): for line in linegen(file):
@ -46,56 +46,53 @@ def load_token(tokeid):
def load_tokens(user): def load_tokens(user):
return _load_multi(user.username,1,access_tokens,models.AccessToken) return _load_multi(user.username,1,access_tokens,models.AccessToken)
def save_token(token): def save_token(token):
return _save(token,0,access_tokens,str(token)) return _save(token,0,access_tokens,token.value)
def delete_token(token): def delete_token(token):
return _delete(token,0,access_tokens,str(token)) return _delete(token,0,access_tokens,token.value)
def load_invite(tokeid): def load_invite(tokeid):
return _load(tokeid,0,invite_tokens,models.InviteToken) return _load(tokeid,0,invite_tokens,models.InviteToken)
def load_invites(user): def load_invites(user):
return _load_multi(user.username,1,invite_tokens,models.InviteToken) return _load_multi(user.username,1,invite_tokens,models.InviteToken)
def save_invite(token): def save_invite(token):
return _save(token,0,invite_tokens,str(token)) return _save(token,0,invite_tokens,token.value)
def delete_invite(token): def delete_invite(token):
return _delete(token,0,invite_tokens,str(token)) return _delete(token,0,invite_tokens,token.value)
def linegen(path): def linegen(path):
i=0 i=0
file=open(path) file=open(path)
line=_rl(file) line=file.readline()
while line: while line:
i+=1 i+=1
response=yield line response=yield line
if response is not None: yield i if response is not None: yield i
line=_rl(file) line=file.readline()
file.close() file.close()
def update(path,lines): def update(path,lines):
def _action(file,i,line): def _action(file,i,line):
if i in lines: file.write(lines[i]+'\n') if i in lines: file.write(lines[i]+'\n')
else: file.write(line+'\n') else: file.write(line)
def _post(file): def _post(file):
if -1 in lines: file.write(lines[-1]+'\n') if -1 in lines: file.write(lines[-1]+'\n')
writefile(path,_action,_post) writefile(path,_action,_post)
def remove(path,lines): def remove(path,lines):
def _action(file,i,line): def _action(file,i,line):
if i not in lines: file.write(line+'\n') if i not in lines: file.write(line)
writefile(path,_action) writefile(path,_action)
def writefile(path,action,post=None): def writefile(path,action,post=None):
i=0 i=0
file=open(path) file=open(path)
write=open(f'{path}.tmp','w') write=open(f'{path}.tmp','w')
line=_rl(file) line=file.readline()
while line: while line:
i+=1 i+=1
action(write,i,line) action(write,i,line)
line=_rl(file) line=file.readline()
if post is not None: post(write) if post is not None: post(write)
file.close() file.close()
write.close() write.close()
rename(f'{path}.tmp',path) rename(f'{path}.tmp',path)
def _rl(file):
return file.readline().rstrip('\n') # Maybe sometimes there won't be one.

View File

@ -6,6 +6,5 @@ globals()['backend']=import_module('.backends.'+config['backend']['type'],'auth'
backend.config=config['backend']['options'] backend.config=config['backend']['options']
backend.utils=utils backend.utils=utils
backend.models=models backend.models=models
models.utils=utils
models.backend=backend models.backend=backend
backend.init() backend.init()

View File

@ -1,15 +1,19 @@
from dataclasses import dataclass from dataclasses import dataclass
from pydantic.v1 import validate_arguments
from datetime import datetime from datetime import datetime
from hashlib import shake_256
import uuid import uuid
def phash(pw,salt=None):
if salt is None: salt=uuid.uuid4()
return shake_256(f'{pw}{salt}'.encode('utf-8')).hexdigest(256),salt
class AbstractUser(): class AbstractUser():
pass # It fixes circular dep on user.invited_by pass # It fixes circular dep on user.invited_by
# And on User.register (InviteToken) # And on User.register (InviteToken)
@dataclass @dataclass
class Token(): class Token():
value: uuid.UUID value: str
owner: AbstractUser owner: AbstractUser
def __init__(self,value,owner=None): def __init__(self,value,owner=None):
if owner is None: if owner is None:
@ -20,20 +24,14 @@ class Token():
self.owner=owner self.owner=owner
def revoke(self): def revoke(self):
backend.delete_token(self) backend.delete_token(self)
@property
def serialise(self):
return ','.join(map(str,[self,self.owner]))
def __str__(self): return str(self.value)
@dataclass @dataclass
class InviteToken(Token): class InviteToken(Token):
_uses: int=0 _uses: int=0
_max_uses: int=-1 _max_uses: int=-1
expires: datetime=None _expires: datetime=None
@validate_arguments def __init__(self,*args,**kwargs):
def __init__(self,value,owner,uses:int,maxuses:int,expiry:datetime|None): return super().__init__(*args,**kwargs)
self.uses,self.max_uses,self.expires=uses,maxuses,expiry
return super().__init__(value,owner)
@property @property
def uses(self): return self._uses def uses(self): return self._uses
@uses.setter @uses.setter
@ -46,9 +44,6 @@ class InviteToken(Token):
def max_uses(self,val): def max_uses(self,val):
if -1<val<=self.uses: self.revoke() if -1<val<=self.uses: self.revoke()
self._max_uses=val self._max_uses=val
@property
def serialise(self):
return super().serialise+','+','.join(map(str,[self.uses,self.max_uses,self.expires]))
@dataclass @dataclass
class AccessToken(Token): class AccessToken(Token):
@ -60,49 +55,31 @@ class User(AbstractUser):
username: str username: str
password_hash: str password_hash: str
salt: str salt: str
_invited_by: AbstractUser|str # Root node will just reference itself invited_by: AbstractUser=None # Root node will just reference itself
email: str='' email: str=''
def _load_invite(self):
if self._invited_by==self.username: return self # Sanity-check to prevent infinite recursion.
return backend.load_user(self._invited_by)
@property
def invited_by(self):
if isinstance(self._invited_by,str):
self._invited_by=self._load_invite()
return self._invited_by
@property @property
def serialise(self): def serialise(self):
return ','.join([self.username,self.password_hash,str(self.salt),self.invited_by.username,self.email or '']) return ','.join([self.username,self.password_hash,str(self.salt),self.invited_by.username,self.email])
def create_inv_token(self,*args,**kwargs): def create_inv_token(self,*args,**kwargs):
tok=InviteToken(self,*args,**kwargs) tok=InviteToken(self,*args,**kwargs)
backend.save_invite(tok) backend.save_token(tok)
return tok return tok
def auth(self,pw): def change_password(self,old_pw:str|None,new_pw:str): pass
return utils.phash(pw,self.salt)[0]==self.password_hash
def change_password(self,old_pw:str|None,new_pw:str):
if self.auth(old_pw):
self.password_hash,self.salt=utils.phash(new_pw)
self.save()
def save(self): def save(self):
backend.save_user(self) backend.save_user(self)
def __str__(self): return self.username
@classmethod @classmethod
def login(cls,username:str,password:str): def login(cls,username:str,password:str):
u=backend.load_user(username) u=backend.load_user(username)
if u is None: raise Exception("User doesn't exist") if u is None: raise Exception("User doesn't exist")
if u.auth(password): if phash(password,u.salt)[0]==u.password_hash: return AccessToken(u)
a=AccessToken(u)
backend.save_token(a)
return a
raise Exception("Incorrect password") raise Exception("Incorrect password")
@classmethod @classmethod
def register(cls,username:str,password:str,invite:InviteToken,email:str=''): 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') if set([chr(n) for n in range(32)]+[','])&set(username): raise Exception('Invalid username')
u=backend.load_user(username) u=backend.load_user(username)
if u is not None: raise Exception("User already exists") if u is not None: raise Exception("User already exists")
u=User(username,*utils.phash(password),invite.owner,email) u=User(username,*phash(password),invite.owner,email)
u.save() u.save()
invite.uses+=1 invite.uses+=1
backend.save_invite(invite)
return u return u

View File

@ -1,6 +0,0 @@
from hashlib import shake_256
import uuid
def phash(pw,salt=None):
if salt is None: salt=uuid.uuid4()
return shake_256(f'{pw}{salt}'.encode('utf-8')).hexdigest(256),salt