Compare commits
2 Commits
620f780113
...
10586b480a
Author | SHA1 | Date | |
---|---|---|---|
10586b480a | |||
43c4ff2d75 |
|
@ -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)
|
||||||
raise Exception('No such',obj.__name__,'with identifier',name)
|
return None
|
||||||
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,53 +46,56 @@ 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,token.value)
|
return _save(token,0,access_tokens,str(token))
|
||||||
def delete_token(token):
|
def delete_token(token):
|
||||||
return _delete(token,0,access_tokens,token.value)
|
return _delete(token,0,access_tokens,str(token))
|
||||||
|
|
||||||
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,token.value)
|
return _save(token,0,invite_tokens,str(token))
|
||||||
def delete_invite(token):
|
def delete_invite(token):
|
||||||
return _delete(token,0,invite_tokens,token.value)
|
return _delete(token,0,invite_tokens,str(token))
|
||||||
|
|
||||||
def linegen(path):
|
def linegen(path):
|
||||||
i=0
|
i=0
|
||||||
file=open(path)
|
file=open(path)
|
||||||
line=file.readline()
|
line=_rl(file)
|
||||||
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=file.readline()
|
line=_rl(file)
|
||||||
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)
|
else: file.write(line+'\n')
|
||||||
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)
|
if i not in lines: file.write(line+'\n')
|
||||||
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=file.readline()
|
line=_rl(file)
|
||||||
while line:
|
while line:
|
||||||
i+=1
|
i+=1
|
||||||
action(write,i,line)
|
action(write,i,line)
|
||||||
line=file.readline()
|
line=_rl(file)
|
||||||
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.
|
1
main.py
1
main.py
|
@ -6,5 +6,6 @@ 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()
|
55
models.py
55
models.py
|
@ -1,19 +1,15 @@
|
||||||
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: str
|
value: uuid.UUID
|
||||||
owner: AbstractUser
|
owner: AbstractUser
|
||||||
def __init__(self,value,owner=None):
|
def __init__(self,value,owner=None):
|
||||||
if owner is None:
|
if owner is None:
|
||||||
|
@ -24,14 +20,20 @@ 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
|
||||||
def __init__(self,*args,**kwargs):
|
@validate_arguments
|
||||||
return super().__init__(*args,**kwargs)
|
def __init__(self,value,owner,uses:int,maxuses:int,expiry:datetime|None):
|
||||||
|
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
|
||||||
|
@ -44,6 +46,9 @@ 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):
|
||||||
|
@ -55,31 +60,49 @@ class User(AbstractUser):
|
||||||
username: str
|
username: str
|
||||||
password_hash: str
|
password_hash: str
|
||||||
salt: str
|
salt: str
|
||||||
invited_by: AbstractUser=None # Root node will just reference itself
|
_invited_by: AbstractUser|str # 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])
|
return ','.join([self.username,self.password_hash,str(self.salt),self.invited_by.username,self.email or ''])
|
||||||
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_token(tok)
|
backend.save_invite(tok)
|
||||||
return tok
|
return tok
|
||||||
def change_password(self,old_pw:str|None,new_pw:str): pass
|
def auth(self,pw):
|
||||||
|
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 phash(password,u.salt)[0]==u.password_hash: return AccessToken(u)
|
if u.auth(password):
|
||||||
|
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|None):
|
def register(cls,username:str,password:str,invite:InviteToken,email:str=''):
|
||||||
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,*phash(password),invite.owner,email)
|
u=User(username,*utils.phash(password),invite.owner,email)
|
||||||
u.save()
|
u.save()
|
||||||
invite.uses+=1
|
invite.uses+=1
|
||||||
|
backend.save_invite(invite)
|
||||||
return u
|
return u
|
Loading…
Reference in New Issue
Block a user