finish up webui

This commit is contained in:
Izalia Mae 2020-02-08 11:40:28 -05:00
parent c0d766c8b8
commit ff42651808
22 changed files with 351 additions and 673 deletions

3
.gitignore vendored
View file

@ -114,4 +114,5 @@ dmypy.json
# Pyre type checker
.pyre/
/data
/data*
/config*

80
database.sql Normal file
View file

@ -0,0 +1,80 @@
CREATE TABLE IF NOT EXISTS config (
id SERIAL PRIMARY KEY,
key TEXT NOT NULL,
value TEXT
);
CREATE TABLE IF NOT EXISTS inboxes (
id SERIAL PRIMARY KEY,
domain TEXT NOT NULL,
inbox TEXT NOT NULL,
actor TEXT NOT NULL,
timestamp float8 NOT NULL
);
CREATE TABLE IF NOT EXISTS retries (
id SERIAL PRIMARY KEY,
msgid TEXT NOT NULL,
inbox TEXT NOT NULL,
data TEXT NOT NULL,
headers TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS requests (
id SERIAL PRIMARY KEY,
followid TEXT NOT NULL,
domain TEXT NOT NULL,
inbox TEXT NOT NULL,
actor TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
handle TEXT NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
timestamp float8 NOT NULL
);
CREATE TABLE IF NOT EXISTS tokens (
id SERIAL PRIMARY KEY,
userid int NOT NULL,
token TEXT NOT NULL,
timestamp float8 NOT NULL
);
CREATE TABLE IF NOT EXISTS whitelist (
id SERIAL PRIMARY KEY,
domain TEXT NOT NULL,
timestamp float8 NOT NULL
);
CREATE TABLE IF NOT EXISTS domainbans (
id SERIAL PRIMARY KEY,
domain TEXT NOT NULL,
reason TEXT,
timestamp float8 NOT NULL
);
CREATE TABLE IF NOT EXISTS userbans (
id SERIAL PRIMARY KEY,
username TEXT NOT NULL,
domain TEXT NOT NULL,
reason TEXT,
timestamp float8 NOT NULL
);
CREATE TABLE IF NOT EXISTS keys (
actor TEXT NOT NULL PRIMARY KEY,
privkey TEXT NOT NULL,
pubkey TEXT NOT NULL
);

View file

@ -42,7 +42,9 @@ for row in table.inbox.all():
'domain': row['domain']
}
put.inbox('add', urls)
timestamp = row.get('timestamp')
put.inbox('add', urls, timestamp=timestamp)
db_actor_key = table.key.get(query.user == 'relay')
@ -103,5 +105,4 @@ if isfile(listfile):
put.ban('add', domain)
for user in config['blocked_users']:
username, domain = user.split('@')
put.ban('add', username, domain)
put.ban('add', user)

View file

@ -1,14 +1,14 @@
git+https://git.barkshark.xyz/izaliamae/jhaml.git#egg=jhaml
pygresql==5.1
dbutils==1.3
sanic==19.12.2
pycryptodome==3.9.1
urllib3==1.25.7
watchdog==0.8.3
markdown==3.1.1
jinja2==2.10.1
jinja2-markdown==0.0.3
hamlpy3==0.84.0
colour==0.1.5
argon2-cffi==19.2.0
passlib==1.7.2
markdown==3.1.1
envbash==1.2.0

View file

@ -6,7 +6,7 @@ from urllib.parse import urlparse
from .log import logging
from .functions import format_urls
from .database import get, put
from .database import get, put, bool_check
from . import messages
@ -64,7 +64,7 @@ def get_userbans():
for user in get.userban(None, 'all'):
users.append({
'user': user['user'],
'user': user['username'],
'domain': user['domain'],
'reason': user['reason']
})
@ -72,17 +72,6 @@ def get_userbans():
return users
def bool_check(value):
if value == True or value.lower() in ['yes', 'true', 'enable']:
return True
elif value in [None, False] or value.lower() in ['no', 'false', 'disable', '']:
return False
else:
return value
def sanitize(data, extras=None):
if extras == 'spaces':
extra = '\s'
@ -121,6 +110,10 @@ def settings(data):
new_data = {}
for setting in ['info', 'rules']:
if not data.get(setting):
put.config({setting: None})
for k, v in data.items():
if k == 'port':
try:
@ -140,7 +133,7 @@ def ban(data):
domain = data['name']
reason = data.get('reason')
if put.ban('add', data, reason=reason):
if put.ban('add', domain, reason=reason):
logging.info(f'Added {domain} to the banlist')
else:
@ -178,14 +171,18 @@ def remove(data):
def accept(data):
row = get.request(data.get('name'))
print(data)
if not row:
print('heck1')
return
if not messages.accept(row['followid'], row):
print('heck')
return
if put.inboxes('add', row):
if put.inbox('add', row):
print('heck2')
put.request('remove', row)
return True
@ -212,6 +209,7 @@ def eject(data):
def run(action, data):
print(action, data)
if action in ['accept', 'deny']:
print(action, data)

View file

@ -1,4 +1,4 @@
import pg, uuid, sys
import pg, uuid, sys, random, string
from os.path import isfile
@ -46,9 +46,9 @@ db = dbconn(dbconfig['name'])
def newtrans(func):
def inner(*arg):
def inner(*arg, **kwargs):
db.begin()
result = func(*arg)
result = func(*arg, **kwargs)
db.end()
return result
return inner
@ -81,14 +81,15 @@ def setup():
for k,v in settings.items():
db.insert('config', key=k, value=v)
logging.info('Database setup finished :3')
logging.info('Database setup finished :3')
def query(table, data, one=True):
def query(table, data, one=True, sort=None):
items = data.items()
k,v = list(items)[0]
SORT = f'ORDER BY {sort} ASC' if sort else ''
row = db.query(f"SELECT * FROM {table} WHERE {k} = '{v}'")
row = db.query(f"SELECT * FROM {table} WHERE {k} = '{v}' {SORT}")
try:
return row.singledict() if one else row.dictresult()
@ -97,8 +98,9 @@ def query(table, data, one=True):
return
def query_all(table):
row = db.query(f"SELECT * FROM {table}")
def query_all(table, sort=None):
SORT = f'ORDER BY {sort} ASC' if sort else ''
row = db.query(f"SELECT * FROM {table} {SORT}")
try:
return row.dictresult()
@ -107,7 +109,7 @@ def query_all(table):
return
def query_or(table, value, keys, one=True):
def query_or(table, value, keys, one=True, sort=None):
if type(keys) != list:
return
@ -119,7 +121,8 @@ def query_or(table, value, keys, one=True):
if keys[-1] != key:
query_str += f' or '
row = db.query(f'SELECT * FROM {table} WHERE {query_str}')
SORT = f'ORDER BY {sort} ASC' if sort else ''
row = db.query(f'SELECT * FROM {table} WHERE {query_str} {SORT}')
try:
return row.singledict() if one else row.dictresult()
@ -128,7 +131,7 @@ def query_or(table, value, keys, one=True):
pass
def query_and(table, data, one=True):
def query_and(table, data, one=True, sort=None):
query_str = ''
items = data.items()
@ -139,7 +142,8 @@ def query_and(table, data, one=True):
if list(items)[-1][0] != k:
query_str += f' and '
row = db.query(f'SELECT * FROM {table} WHERE {query_str}')
SORT = f'ORDER BY {sort} ASC' if sort else ''
row = db.query(f'SELECT * FROM {table} WHERE {query_str} {SORT}')
try:
return row.singledict() if one else row.dictresult()
@ -179,4 +183,18 @@ def randomgen(chars=20):
return ''.join(random.choices(string.ascii_letters + string.digits, k=chars))
def bool_check(value):
if value in [True, False, None]:
return value
if value.lower() in ['yes', 'true', 'enable']:
return True
elif value.lower() in ['no', 'false', 'disable', '']:
return False
else:
return value
__all__ = ['db', 'newtrans', 'HashContext', 'randomgen', 'query', 'query_all', 'query_or', 'query_and']

View file

@ -1,5 +0,0 @@
#!/usr/bin/env python3
from . import newtrans, setup, get
from ..log import logging
setup()

View file

@ -3,6 +3,7 @@ import pg
from Crypto.PublicKey import RSA
from . import *
from . import bool_check as bcheck
from ..log import logging
@ -44,15 +45,15 @@ def config(data, *args):
rows = query_all('config')
for row in rows:
settings.update({row['key']: row['value']})
settings.update({row['key']: bcheck(row['value'])})
else:
for k,v in data.items():
query_data = {'key': k, 'value': v}
query_data = {'key': k, 'value': bcheck(v)}
row = True if data == 'all' else query_and('config', query_data)
if row:
settings.update({key, row['value']})
settings.update({key, bcheck(row['value'])})
else:
settings.update({key: None})
@ -60,33 +61,33 @@ def config(data, *args):
return settings
elif type(data) == str:
row = query('config', {'key': data})
row = query('config', {'key': bcheck(data)})
if row:
return row['value']
return bcheck(row['value'])
elif args:
return args[0]
def inbox(url):
return query_all('inboxes') if url == 'all' else query_or('inboxes', url, ['inbox', 'domain', 'actor'])
return query_all('inboxes', sort='domain') if url == 'all' else query_or('inboxes', url, ['inbox', 'domain', 'actor'], sort='domain')
def domainban(domain):
return query_all('domainbans') if domain == 'all' else query('domainbans', {'domain': domain})
return query_all('domainbans', sort='domain') if domain == 'all' else query('domainbans', {'domain': domain}, sort='domain')
def userban(user, domain):
return query_all('userbans') if domain == 'all' else query_and('userbans', {'username': user, 'domain': domain})
return query_all('userbans', sort='username') if domain == 'all' else query_and('userbans', {'username': user, 'domain': domain}, sort='username')
def whitelist(domain):
return query_all('whitelist') if domain == 'all' else query_and('whitelist', {'domain': domain})
return query_all('whitelist', sort='domain') if domain == 'all' else query_and('whitelist', {'domain': domain}, sort='domain')
def request(domain):
return query_all('inboxes') if domain == 'all' else query_or('requests', domain, ['inbox', 'domain', 'actor'])
return query_all('requests', sort='domain') if domain == 'all' else query_or('requests', domain, ['inbox', 'domain', 'actor'], sort='domain')
def retries(data):
@ -113,24 +114,39 @@ def retries(data):
def user(data):
if not data:
return
if data == 'all':
return query_all('users')
username = data.get('username')
userid = data.get('userid')
if type(data) == int:
return query('users', {'id': data})
querydata = {'id': userid} if userid else {'username': username}
return query('users', querydata)
else:
return query_or('users', data, ['username', 'handle'])
def token(token):
return query('tokens', {'token': token})
def verify_pass(username, password):
user_data = user({'username': username})
def verify_password(username, password):
user_data = user(username)
return Hash.verify(password, user_data['password'])
# generate an auth code if there are no admin users
if len(user('all')) < 1:
auth_code = randomgen()
host = config('host')
if config('setup'):
logging.warn(f'There are no admin users in the database. Please register an account at https://{host}/register?code={auth_code}')
else:
logging.warn(f'The relay is not configured. Please set it up at https://{host}/setup?code={auth_code}')
else:
auth_code = None

View file

@ -17,7 +17,7 @@ def config(data):
configs = get.config('all')
for k,v in data.items():
if configs and configs[k] == v:
if configs and configs.get(k) == v:
return
if not configs or k not in configs:
@ -29,7 +29,12 @@ def config(data):
db.insert('config', data)
else:
db.update('keys', {'value': v}, key=k)
row = query('config', {'key': k})
if not row:
return
db.update('config', {'value': v}, id=row['id'])
@newtrans
@ -37,14 +42,14 @@ def rsa_key(name, keys):
actor_key = get.rsa_key('default')
if not actor_key:
pgdb.update('keys', {
db.update('keys', {
'pubkey': keys['pubkey'],
'privkey': keys['privkey']
}, actor='default')
@newtrans
def inbox(action, urls):
def inbox(action, urls, timestamp=None):
actor, inbox, domain = format_urls(urls)
row = get.inbox(actor)
@ -56,13 +61,13 @@ def inbox(action, urls):
'domain': domain,
'inbox': inbox,
'actor': actor,
'timestamp': datetime.now().timestamp()
'timestamp': datetime.now().timestamp() if not timestamp else timestamp
}
db.insert('inboxes', data)
elif action == 'remove':
db.remove('inboxes', id=row['id'])
db.delete('inboxes', id=row['id'])
else:
return
@ -86,12 +91,12 @@ def request(action, urls, followid=None):
'followid': followid,
'timestamp': datetime.now().timestamp()
}
db.insert('request', data)
db.insert('requests', data)
return True
if action == 'remove':
db.remove('requests', id=row['id'])
db.delete('requests', id=row['id'])
return True
@ -121,14 +126,14 @@ def del_retries(data):
rows = get.retries(data)
for row in rows:
db.remove('requests', id=row['id'])
db.delete('requests', id=row['id'])
@newtrans
def ban(action, data, reason=None):
if '@' in data:
if data.startswith('@'):
data.replace('@', '', 1)
data = data.replace('@', '', 1)
username, domain = data.split('@')
@ -153,19 +158,44 @@ def ban(action, data, reason=None):
data.update({'username': username})
if row:
db.update(bantype, data, id=row['id'])
return True if db.update(bantype, data, id=row['id']) else False
else:
db.insert(bantype, data)
return True if db.insert(bantype, data) else False
if action == 'remove':
db.remove(bantype, id=row['id'])
return True if db.delete(bantype, id=row['id']) else False
@newtrans
def whitelist(action, data, reason=None):
domain = urlparse(data).netloc if data.startswith('https://') else data
row = get.whitelist(domain)
if action == 'remove' and not row:
return True
if action == 'add':
data = {
'domain': domain,
'timestamp': datetime.now().timestamp()
}
if row:
db.update('whitelist', data, id=row['id'])
else:
db.insert('whitelist', data)
if action == 'remove':
db.delete('whitelist', id=row['id'])
@newtrans
def user(handle, password):
username = handle.lower()
timestamp = timedate.now().timestamp()
timestamp = datetime.now().timestamp()
if query('users', {'username': username}):
return
@ -177,22 +207,20 @@ def user(handle, password):
'timestamp': timestamp
}
db.insert('users', data)
return db.insert('users', data)
@newtrans
def token(username):
userdata = user(username)
userdata = get.user(username)
if not userdata:
return
tokendata = {
'userid': userdata['id'],
'token': randomgen(chars=40),
'timestamp': datetime.now()
}
tokendata = {
'userid': userdata['id'],
'token': randomgen(chars=40),
'timestamp': datetime.now().timestamp()
}
db.insert('tokens', tokendata)
return tokendata
return db.insert('tokens', tokendata)

View file

@ -1,489 +0,0 @@
import sys, random, string, json
from json.decoder import JSONDecodeError
from urllib.parse import urlparse
from datetime import datetime
from os.path import isfile
from tinydb import TinyDB, Query
from tinydb_smartcache import SmartCacheTable
from tinyrecord import transaction as trans
from Crypto.PublicKey import RSA
from passlib.context import CryptContext
from .config import stor_path
from .log import logging
try:
db = TinyDB(f'{stor_path}/db.json', ensure_ascii=False, escape_forward_slashes=False, indent=4)
except JSONDecodeError as e:
logging.critical(f'Failed to load DB: {e}')
logging.info(f'Exiting...')
sys.exit()
query = Query()
class table:
config = db.table('config', cache_size=100)
inbox = db.table('inbox', cache_size=100)
retries = db.table('retries', cache_size=100)
requests = db.table('requests', cache_size=100)
users = db.table('users', cache_size=100)
tokens = db.table('tokens', cache_size=100)
whitelist = db.table('whitelist', cache_size=100)
domainbans = db.table('domainbans', cache_size=100)
userbans = db.table('userbans', cache_size=100)
key = db.table('key', cache_size=100)
def setup_db():
rsa_key('default')
defconfig = {
'host': '127.0.0.1',
'address': '0.0.0.0',
'port': 3621,
'name': 'Uncia Relay',
'email': None,
'admin': None,
'show_domainbans': False,
'show_userbans': False,
'whitelist': False,
'block_relays': True,
'require_approval': True,
'notifications': False,
'log_level': 'INFO',
'development': False,
'setup': False
}
set_config(defconfig)
def rsa_key(actor):
actor_key = table.key.get(query.actor == actor)
if not actor_key:
logging.info('No RSA key. Generating one...')
PRIV = RSA.generate(4096)
PUB = PRIV.publickey()
keydata = {
'actor': actor,
'pubkey': PUB.exportKey('PEM').decode('utf-8'),
'privkey': PRIV.exportKey('PEM').decode('utf-8')
}
with trans(table.key) as tr:
tr.insert(keydata)
return keydata
return actor_key
def inboxes(action, urls):
from .functions import format_urls
actor_url, inbox, domain = format_urls(urls)
row = table.inbox.get(query.actor == actor_url)
if (row and action == 'add') or (not row and action == 'remove'):
return True
with trans(table.inbox) as tr:
if action == 'add':
data = {
'domain': domain,
'inbox': inbox,
'actor': actor_url,
'timestamp': datetime.now()
}
tr.insert(data)
elif action == 'remove':
tr.remove(doc_ids=[row.doc_id])
else:
return
return True
def get_inboxes():
return [instance['inbox'] for instance in table.inbox.all()]
def banlist(domain, action, reason=None):
domain = domain.lower()
data = domain.replace('@', '', 1) if domain.startswith('@') else domain
check = ban_check(data)
if (action == 'add' and check) or (action == 'remove' and not check):
return
if '@' in data:
user, domain = data.split('@')
with trans(table.userbans) as tr:
if action == 'add':
data = {
'user': user,
'domain': domain,
'reason': reason,
'timestamp': datetime.now()
}
tr.insert(data)
elif action == 'remove':
tr.remove(doc_ids=[check.doc_id])
else:
if data.startswith('https'):
domain = urlparse(data).netloc
with trans(table.domainbans) as tr:
if action == 'add':
data = {
'domain': domain,
'reason': reason,
'timestamp': datetime.now()
}
tr.insert(data)
elif action == 'remove':
tr.remove(doc_ids=[check.doc_id])
return True
def get_bans(bantype='domain'):
types = {'domain': table.domainbans, 'user': table.userbans}
return types[bantype].all()
def ban_check(name):
if name.startswith('https'):
name = urlparse(name).netloc
if '@' in name:
if name.startswith('@'):
name = name.replace('@', '', 1)
user, domain = name.split('@')
userban = table.userbans.search(query.user == user)
if not userban:
return False
for line in userban:
if line['domain'] in [domain, 'any']:
return line
return False
else:
return table.domainbans.get(query.domain == name)
def get_config(setting):
if setting == 'all':
return {row['key']: row['value'] for row in table.config.all()}
elif type(setting) in [list, tuple]:
return_dict = {}
for k, v in setting:
setdata = table.config.search(query.key == k)
if setdata:
return_dict.update({setdata['key']: setdata['value']})
return setdata
else:
row = table.config.get(query.key == setting)
return row['value'] if row else None
def set_config(settings):
if type(settings) != dict:
logging.error('Unwilling to set config options. Data is not a dict')
return
with trans(table.config) as tr:
for k, v in settings.items():
data = {'key': k, 'value': v}
row = table.config.get(query.key == k)
if row:
tr.update(data, doc_ids=[row.doc_id])
else:
tr.insert(data)
config = get_config('all')
def whitelist(domain, action):
if domain.startswith('https'):
domain = urlparse(domain).netloc
row = table.whitelist.get(query.domain == domain)
if (action == 'add' and row) or (action == 'remove' and not row):
return
with trans(table.whitelist) as tr:
if action == 'add':
data = {
'domain': domain,
'timestamp': datetime.now()
}
tr.insert(data)
elif action == 'remove':
tr.remove(doc_ids=[row.doc_id])
return True
def retries(action, msgid, inbox, data=None, headers=None):
from .functions import fetch
row = table.retries.get(query.msgid == msgid and query.inbox == inbox)
if not fetch(data['actor'], cached=False):
logging.info(f'Failed to fetch actor before retrying: {data["actor"]}')
return
if action == 'add' and None in [data, headers]:
logging.warning(f'Missing data or headers for {msgid}')
return
with trans(table.retries) as tr:
if action == 'add':
if not row:
tr.insert({
'msgid': msgid,
'inbox': inbox,
'data': data,
'headers': headers
})
return True
if action == 'remove':
if row:
tr.remove(doc_ids=[row.doc_id])
return True
def request(action, urls, followid=None):
from .functions import format_urls
actor, inbox, domain = format_urls(urls)
row = table.requests.get(query.domain == domain)
if (row and action == 'add') or (not row and action == 'remove'):
return True
if action == 'add' and followid:
with trans(table.requests) as tr:
tr.insert({
'domain': domain,
'inbox': inbox,
'actor': actor,
'followid': followid,
'timestamp': datetime.now()
})
return True
if action == 'remove':
with trans(table.requests) as tr:
tr.remove(doc_ids=[row.doc_id])
return True
def admin_user(action, data):
username = data.get('user')
password = data.get('pass')
if not username or (action == 'add' and not password):
return
displayname = username
username = username.lower()
user = get_user(username)
if (action == 'add' and user) or (action in ['remove', 'update'] and not user):
return
with trans(table.users) as tr:
if action == 'add':
data = {
'displayname': displayname,
'username': username,
'password': hashes.encrypt(password),
'timestamp': datetime.now()
}
tr.insert(data)
if action == 'remove':
tr.remove(doc_ids=[user.doc_id])
with trans(table.tokens) as tr:
if action == 'add':
return create_token(username)
if action == 'remove':
tr.remove(doc_ids=[row.doc_id for row in table.tokens.search(query.username)])
def get_user(username):
if not username:
return
username = username.lower()
userdata = table.users.get(query.username == username)
return userdata if userdata else None
def create_token(username):
if not username:
return
userdata = get_user(username)
if not userdata:
return
with trans(table.tokens) as tr:
tokendata = {
'username': username,
'token': randomgen(chars=40),
'timestamp': datetime.now()
}
tr.insert(tokendata)
return tokendata
def del_token(token):
if not token:
return
tokendata = table.tokens.get(query.token == token)
if tokendata:
with trans(table.tokens) as tr:
tr.remove(doc_ids=[tokendata.doc_id])
def get_token(token):
if not token:
return
tokendata = table.tokens.get(query.token == token)
if not tokendata:
return
one_day = 60*60*24
two_weeks = one_day*14
token_time = tokendata['timestamp']
current_time = datetime.now().timestamp()
if current_time - two_weeks > token_time:
with trans(table.tokens) as tr:
tr.remove(doc_ids=[tokendata.doc_id])
return
if current_time - one_day > token_time:
with trans(table.tokens) as tr:
tr.update({'timestamp': datetime.now()}, doc_ids=[tokendata.doc_id])
tokendata['timestamp'] = current_time
return tokendata
def check_password(username, password):
user = get_user(username)
if not user:
return
if not hashes.verify(password, user['password']):
return
return True
def randomgen(chars=20):
if type(chars) != int:
logging.warning(f'Invalid character length. Must be an int: {chars}')
chars = 20
return ''.join(random.choices(string.ascii_letters + string.digits, k=chars))
class HashContext:
def __init__(self, schemes=['argon2'], default='argon2', rounds=25):
self.saltfile = f'{stor_path}/salt.txt'
self.salt = None
self.hasher = CryptContext(schemes=schemes, default=default, argon2__default_rounds=rounds)
def encrypt(self, string):
return self.hasher.encrypt(string+self.salt)
def verify(self, string, hashed):
return self.hasher.verify(string+self.salt, hashed)
def set_salt(self):
if not isfile(self.saltfile):
self.salt = randomgen()
with open(self.saltfile, 'w') as newfile:
newfile.write(self.salt)
else:
self.salt = open(self.saltfile).read()
# initialize password hasher
hashes = HashContext()
hashes.set_salt()
# initialize the db
if get_config('setup') == None:
setup_db()
# generate an auth code if there are no admin users
if len(table.users.all()) < 1:
auth_code = randomgen()
host = get_config('host')
if get_config('setup'):
logging.warn(f'There are no admin users in the database. Please register an account at https://{host}/register?code={auth_code}')
else:
logging.warn(f'The relay is not configured. Please set it up at https://{host}/setup?code={auth_code}')
else:
auth_code = None
config = get_config('all')

View file

@ -239,6 +239,10 @@ tr:last-child .col2 {
height: 16em;
}
#network input {
width: calc(100% - 20px);
}
#submit {
text-align: center;
}
@ -248,7 +252,6 @@ tr:last-child .col2 {
}
.settings label {
height: 26px;
display: flex;
align-items: center;
padding-left: 15px;

View file

@ -184,23 +184,25 @@
%label General Info
%textarea{'name': 'info', 'placeholder': 'Relay Info'}<
{{config.info}}
-if config.info
{{config.info}}
%label Relay Rules
%textarea{'name': 'rules', 'placeholder': 'Relay Rules'}<
{{config.rules}}
-if config.rules
{{config.rules}}
%div{'class': 'section settings', 'id': 'settings'}
%p{'class': 'sec-header'} Relay Settings
%div{'class': 'grid-container'}
- if config.admin
- set admin = config.admin
- else
- set admin = 'None'
-if config.admin
-set admin = config.admin
-else
-set admin = 'None'
- if config.email
- set email = config.email
- else
-if config.email
-set email = config.email
-else
-set email = 'None'
%div{'class': 'grid-item col1'}

View file

@ -1,7 +1,6 @@
- set default_open = 'open'
- set config = get.config('all')
- set cookie = get.token(request.cookies.token)
- set user = get.user(cookie.username)
- set user = get.user(cookie.userid)
!!!
%html
%head
@ -61,11 +60,15 @@
@{{config.admin}}
%div{'class': 'grid-item acct', 'style': 'display: inline'}
- if user != None
{{user.displayname}} [<a href='/logout'>logout</a>]
- if config.setup
- if user != None
{{user.handle}} [<a href='/logout'>logout</a>]
- else
Guest [<a href='/login'>login</a>]
- else
Guest [<a href='/login'>login</a>]
- else
%p UvU
%div{'class': 'grid-item col2'}
%p

View file

@ -38,8 +38,7 @@
- if config.require_approval
Note: This relay requires approval from the admin, so it will show as "Waiting for relay's approvel" in Mastodon until accepted.
- if rules != None
- if config.rules
%div{'class': 'section'}
%h2{'class': 'title'} What are the rules?
{{markdown(config.rules)}}

View file

@ -5,7 +5,11 @@
- block content
%div{'class': 'section'}
%h2{'class': 'title'} Info
{{markdown(config.info)}}
- if config.info
{{markdown(config.info)}}
- else
empty :/
%div{'class': 'section', 'id': 'home_instances'}
%h2{'class': 'title'} Registered Instances

View file

@ -2,7 +2,7 @@
- set page = 'Setup'
- block content
- if get.config('setup') == 'False'
- if config.setup
%div{'class': 'section setup'}
%p{'class': 'sec-header'} Setup
%p<
@ -78,19 +78,17 @@
%option{'value': '{{level}}'}
{{level}}
%div{'class': 'section settings', 'id': 'network'}
%div{'class': 'section settings network', 'id': 'network'}
%p{'class': 'sec-header'} Network
%div{'class': 'grid-container'}
%div{'class': 'grid-item col1'}
%label Listen Address (IP address the relay will listen on)
%input{'type': 'text', 'name': 'address', 'placeholder': 'default: 127.0.0.1'}
%label Listen Address (IP address the relay will listen on)
%input{'type': 'text', 'name': 'address', 'placeholder': '127.0.0.1'}
%label Listen Port (Port the relay will listen on)
%input{'type': 'numeric', 'name': 'port', 'placeholder': 'default: 3621'}
%label Listen Port (Port the relay will listen on)
%input{'type': 'numeric', 'name': 'port', 'placeholder': '3621'}
%label Hostname (Domain the relay is served from)
%input{'type': 'text', 'name': 'host', 'placeholder': 'relay.example.com'}
%label Hostname (Domain the relay is served from)
%input{'type': 'text', 'name': 'host', 'placeholder': 'relay.example.com'}
%div{'class': 'section setup', 'id': 'submit'}
- if code

View file

@ -11,12 +11,12 @@ import urllib3
from sanic import response
from colour import Color
from Crypto.PublicKey import RSA
from jhaml.jhaml import convert
import urllib3
from .config import script_path, stor_path, version, pyv
httpclient = urllib3.PoolManager(num_pools=100, timeout=urllib3.Timeout(connect=5, read=5))
@ -66,7 +66,7 @@ def get_id(data):
return object_id
def get_user(data):
def get_post_user(data):
from .messages import fetch
if data['type'] in ['Follow', 'Undo']:
return
@ -100,48 +100,6 @@ def get_user(data):
return (user, domain)
def build_templates():
timefile = f'{stor_path}/build/times.json'
updated = False
if not isdir(f'{stor_path}/build'):
os.makedirs(f'{stor_path}/build')
if isfile(timefile):
try:
times = json.load(open(timefile))
except:
times = {}
else:
times = {}
for filename in os.listdir(f'{script_path}/frontend/templates'):
modtime = getmtime(f'{script_path}/frontend/templates/{filename}')
base, ext = filename.split('.')
if ext != 'haml':
pass
elif base not in times or times.get(base) != modtime:
updated = True
logging.info(f"Template '{filename}' was changed. Building...")
try:
convert(f'{script_path}/frontend/templates/{filename}', destination=f'{stor_path}/build/{base}.html')
logging.info(f"Template '{filename}' has been built")
except:
traceback.print_exc()
logging.error(f'Failed to build {filename}')
times[base] = modtime
if updated:
with open(timefile, 'w') as filename:
filename.write(json.dumps(times))
def format_date(timestamp):
if timestamp:

View file

@ -7,7 +7,6 @@ from .templates import error
from .signatures import validate
from .views import Login
from .database import get, put
from .admin import bool_check
async def access_log(request, response):
@ -32,9 +31,6 @@ async def authentication(request):
if not get.config('setup') and not request.path.startswith(('/setup', '/style')):
return response.redirect('/setup') if not accept else response.json({'error': 'relay not setup yet'}, status=401)
if not bool_check(get.config('setup')) and not request.path.startswith('/setup'):
return response.redirect('/setup')
apitoken = request.headers.get('token')
token = request.cookies.get('token')

View file

@ -7,7 +7,7 @@ from datetime import datetime
from .log import logging
from .messages import fetch, accept, announce, forward, notification
from .database import get, put
from .functions import get_inbox, cache, get_id, get_user, format_urls
from .functions import get_inbox, cache, get_id, get_post_user, format_urls
def relay_announce(data, actor, urls):
@ -21,7 +21,7 @@ def relay_announce(data, actor, urls):
logging.debug(f'Already relayed {object_id}')
return
username = get_user(data)
username = get_post_user(data)
if username:
user, domain = username
@ -69,7 +69,7 @@ def relay_undo(data, actor, urls):
if action in ['announce', 'create']:
relay_forward(data, actor, urls)
if action == 'follow':
elif action == 'follow':
put.inbox('remove', urls)
else:

View file

@ -9,10 +9,10 @@ from watchdog.events import FileSystemEventHandler
from .log import logging, LOG
from .config import script_path, fwsecret
from .database import get, setup
from .functions import build_templates
from .database import get, setup, randomgen
from .messages import run_retries
from .admin import bool_check
from .templates import build_templates
from . import errors, views, middleware as mw
@ -53,7 +53,7 @@ app.add_route(views.Style.as_view(), '/style-<timestamp>')
app.static('/favicon.ico', f'{script_path}/frontend/favicon.png')
# Return setup page if this is the first run
# Enable setup page if this is the first run
if not bool_check(get.config('setup')):
app.add_route(views.Setup.as_view(), '/setup')

View file

@ -1,4 +1,11 @@
import codecs, traceback
import ujson as json
from os import listdir
from os.path import isfile, isdir, getmtime
from jinja2 import Environment, FileSystemLoader, ChoiceLoader
from hamlpy.hamlpy import Compiler
from sanic import response
from markdown import markdown
@ -33,6 +40,7 @@ env = Environment(
def render(tplfile, request, context, headers=None, status=200):
data = global_variables.copy()
data['request'] = request
data['config'] = get.config('all')
data.update(context)
if type(context) != dict:
@ -50,7 +58,61 @@ def error(request, msg, status):
if 'json' in request.headers.get('accept', '') or (request.path == '/inbox' and 'mozilla' not in request.headers.get('user-agent', '').lower()):
return response.json({'err': msg}, status=status)
data = {'msg': msg, 'code': str(status), 'config': get_config('all')}
data = {'msg': msg, 'code': str(status), 'config': get.config('all')}
return render('error.html', request, data, status=status)
def build_templates():
timefile = f'{stor_path}/build/times.json'
updated = False
if not isdir(f'{stor_path}/build'):
os.makedirs(f'{stor_path}/build')
if isfile(timefile):
try:
times = json.load(open(timefile))
except:
times = {}
else:
times = {}
for filename in listdir(f'{script_path}/frontend/templates'):
modtime = getmtime(f'{script_path}/frontend/templates/{filename}')
base, ext = filename.split('.')
if ext != 'haml':
pass
elif base not in times or times.get(base) != modtime:
updated = True
logging.info(f"Template '{filename}' was changed. Building...")
try:
template = f'{script_path}/frontend/templates/{filename}'
destination = f'{stor_path}/build/{base}.html'
haml_lines = codecs.open(template, 'r', encoding='utf-8').read().splitlines()
if not isfile(template):
return False
compiler = Compiler()
output = compiler.process_lines(haml_lines)
outfile = codecs.open(destination, 'w', encoding='utf-8')
outfile.write(output)
logging.info(f"Template '{filename}' has been built")
except:
traceback.print_exc()
logging.error(f'Failed to build {filename}')
times[base] = modtime
if updated:
with open(timefile, 'w') as filename:
filename.write(json.dumps(times))

View file

@ -11,12 +11,12 @@ from sanic.exceptions import ServerError
from .log import logging
from .config import script_path, version
from .functions import cache, get_user, get_inbox
from .functions import cache, get_inbox
from .processing import process
from .messages import fetch
from .database import get
from .admin import run, sanitize, get_instance_data, get_whitelist_data, get_domainbans, get_userbans
from .database import get, put
from .templates import render, error
from . import admin
host = get.config('host')
@ -218,7 +218,7 @@ class WellknowWebfinger(HTTPMethodView):
# Frontend
class Home(HTTPMethodView):
async def get(self, request):
data = {'instances': get_instance_data()}
data = {'instances': admin.get_instance_data()}
return render('home.html', request, data)
@ -230,15 +230,15 @@ class Faq(HTTPMethodView):
class Admin(HTTPMethodView):
async def get(self, request, *args):
whitelist = get_whitelist_data()
whitelist = admin.get_whitelist_data()
data = {
'instances': get_instance_data(),
'instances': admin.get_instance_data(),
'whitelist': whitelist,
'wldomains': [row['domain'] for row in whitelist],
'requests': get.requests('all'),
'domainban': get_domainbans(),
'userban': get_userbans()
'requests': get.request('all'),
'domainban': admin.get_domainbans(),
'userban': admin.get_userbans()
}
context = {'msg': 'UvU', 'data': data}
@ -248,7 +248,7 @@ class Admin(HTTPMethodView):
action = re.sub(r'[^a-z]+', '', action.lower())
data = request['form']
run(action, data)
admin.run(action, data)
return response.redirect('/admin')
@ -289,10 +289,11 @@ class Login(HTTPMethodView):
if None in [username, password]:
return await reterror(Login, request, 'Missing username or password')
if not check_password(username, password):
if not get.verify_password(username, password):
return await reterror(Login, request, 'Invalid username or password')
tokendata = create_token(username)
tokendata = put.token(username)
print(tokendata)
if not tokendata:
return await reterror(Login, request, 'Failed to create token')
@ -349,8 +350,8 @@ class Register(HTTPMethodView):
else:
data[key] = re.sub(r'[^a-zA-Z0-9@_.\-\!\'d,%{}]+', '', data[key]).strip()
if data['code'] != auth_code:
return await reterror(Register, request, 'Invalid authentication code')
#if data['code'] != get.auth_code:
# return await reterror(Register, request, 'Invalid authentication code')
if get.user(data['username'].lower()):
return await reterror(Register, request, 'User already exists')
@ -358,8 +359,12 @@ class Register(HTTPMethodView):
if data['password'] != data['password2']:
return await reterror(Register, request, 'Passwords don\'t match')
userdata = {'user': data['username'], 'pass': data['password']}
tokendata = admin_user('add', userdata)
userdata = put.user(data['username'], data['password'])
if not userdata:
return await reterror(Register, request, 'Failed to create user')
tokendata = put.token(userdata['id'])
if not tokendata:
return await reterror(Register, request, 'Failed to create user')
@ -374,7 +379,7 @@ class Register(HTTPMethodView):
class Style(HTTPMethodView):
async def get(self, request, **kwargs):
maxage = 60*60*24*7 #ONE WEEK
data = ({'msg': 'uvu'})
data = {'msg': 'uvu'}
headers = {'Content-Type': 'text/css', 'Cache-Control': f'public,max-age={maxage}, immutable'}
return render('color.css', request, data, headers=headers)
@ -382,16 +387,16 @@ class Style(HTTPMethodView):
class Setup(HTTPMethodView):
async def get(self, request, *args):
data = {'code': request['query'].get('code')}
print('heck')
return render('setup.html', request, data)
async def post(self, request, action=''):
from . import main
data = request['form']
#if data.get('code') != get.auth_code:
# return response.redirect('/setup')
run('settings', data)
set_config({'setup': True})
request.app.stop()
put.config({'setup': True})
return await self.get(request)