I'm not losing main.py twice -.-

This commit is contained in:
Zergling_man 2023-12-25 00:27:23 +11:00
commit e6e98bcbd9
7 changed files with 156 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
__pycache__

20
backends/README.py Normal file
View File

@ -0,0 +1,20 @@
"""
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.
"""
def init(): pass
def load_user(username): pass
def save_user(user): pass
def delete_user(user): pass
def load_tokens(user): pass
def save_token(token): pass
def delete_token(token): pass
def load_invites(user): pass
def save_invite(token): pass
def delete_invite(token): pass

55
backends/file.py Normal file
View File

@ -0,0 +1,55 @@
from os import path
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']:
p=path.join(config['path'],n+'.csv')
globals()[n]=p
if not path.exists(p): open(p,'w').write('\n')
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()
except PermissionError: raise Exception(f"Backend file can't be written: {p}")
def load_user(username):
for line in linegen(users):
split=line.split(',')
if split[0]==username: return models.User(*split)
def save_user(user):
gen=linegen(users)
for line in gen:
split=line.split(',')
if split[0]==user.username: update(users,{gen.send(True):user.csv})
def delete_user(user): pass
def load_tokens(user): pass
def save_token(token): pass
def delete_token(token): pass
def load_invites(user): pass
def save_invite(token): pass
def delete_invite(token): pass
def linegen(path):
i=0
file=open(path)
line=file.readline()
while line:
i+=1
response=yield line
if response is not None: yield i
line=file.readline()
file.close()
def update(path,lines):
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)
line=file.readline()
file.close()
write.close()

5
config.py Normal file
View File

@ -0,0 +1,5 @@
import os
import json
file=os.environ.get('AUTH_CONFIG_FILE')
if not file: raise Exception('AUTH_CONFIG_FILE not set. (It should point to a json file.) Exiting.')
config=json.load(open(file))

4
main.py Normal file
View File

@ -0,0 +1,4 @@
from importlib import import_module
from . import utils, models
from .config import config

71
models.py Normal file
View File

@ -0,0 +1,71 @@
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

0
utils.py Normal file
View File