a lot
This commit is contained in:
parent
bd7ef6c31c
commit
34833acae5
|
@ -1 +1,10 @@
|
|||
'''heck'''
|
||||
from os.path import dirname, abspath
|
||||
from IzzyLib import logging, template
|
||||
|
||||
|
||||
logging.setConfig({'level': 'debug'})
|
||||
logging.debug(f'Config: {logging.getConfig()}')
|
||||
|
||||
templates = abspath(dirname(__file__))+'/templates'
|
||||
template.addSearchPath(templates)
|
||||
template.setup()
|
||||
|
|
111
paws/cache.py
111
paws/cache.py
|
@ -1,111 +0,0 @@
|
|||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from collections import OrderedDict
|
||||
|
||||
|
||||
def parse_ttl(ttl):
|
||||
m = re.match(r'^(\d+)([smhdw]?)$', ttl)
|
||||
|
||||
if not m:
|
||||
logging.warning(f'Invalid TTL: {ttl}. Setting to default: 1h')
|
||||
amount = 1
|
||||
unit = 'h'
|
||||
|
||||
else:
|
||||
amount = m.group(1)
|
||||
unit = m.group(2)
|
||||
|
||||
units = {
|
||||
's': 1,
|
||||
'm': 60,
|
||||
'h': 60 * 60,
|
||||
'd': 24 * 60 * 60,
|
||||
'w': 7 * 24 * 60 * 60,
|
||||
}
|
||||
|
||||
if unit:
|
||||
multiplier = units[unit]
|
||||
|
||||
else:
|
||||
multiplier = 1
|
||||
|
||||
return multiplier * int(amount)
|
||||
|
||||
|
||||
class TTLCache:
|
||||
def __init__(self, ttl='1h', maxsize=1024):
|
||||
self.items = OrderedDict()
|
||||
self.ttl = parse_ttl(ttl)
|
||||
self.maxsize = maxsize
|
||||
|
||||
|
||||
def invalidate(self, key):
|
||||
if key in self.items:
|
||||
del self.items[key]
|
||||
|
||||
|
||||
def store(self, key, value):
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
item = self.items.get(key)
|
||||
|
||||
while len(self.items) >= self.maxsize and self.maxsize != 0:
|
||||
self.items.popitem(last=False)
|
||||
|
||||
if item == None:
|
||||
data = {'data': value}
|
||||
self.items[key] = data
|
||||
|
||||
elif self.items[key]['timestamp'] + self.ttl < timestamp:
|
||||
del self.items[key]
|
||||
|
||||
self.items[key]['timestamp'] = timestamp + self.ttl
|
||||
self.items.move_to_end(key)
|
||||
|
||||
|
||||
def fetch(self, key):
|
||||
item = self.items.get(key)
|
||||
|
||||
if item != None:
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
|
||||
if timestamp >= self.items[key]['timestamp']:
|
||||
del self.items[key]
|
||||
|
||||
else:
|
||||
self.items[key]['timestamp'] = timestamp + self.ttl
|
||||
self.items.move_to_end(key)
|
||||
return self.items[key]['data']
|
||||
|
||||
|
||||
class LRUCache:
|
||||
def __init__(self, maxsize=1024):
|
||||
self.items = OrderedDict()
|
||||
self.maxsize = maxsize
|
||||
|
||||
|
||||
def invalidate(self, key):
|
||||
if key in self.items:
|
||||
del self.items[key]
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def store(self, key, value):
|
||||
while len(self.items) >= self.maxsize and self.maxsize != 0:
|
||||
self.items.popitem(last=False)
|
||||
|
||||
if (key in self.items) == False:
|
||||
self.items[key] = value
|
||||
|
||||
self.items.move_to_end(key)
|
||||
|
||||
|
||||
def fetch(self, key):
|
||||
if key in self.items:
|
||||
return self.items[key]
|
||||
|
||||
return
|
||||
|
|
@ -1,47 +1,27 @@
|
|||
import sys
|
||||
import os
|
||||
|
||||
import logging as logger
|
||||
|
||||
from os import environ as env
|
||||
from os.path import isdir, isfile, abspath, dirname, basename
|
||||
from envbash import load_envbash
|
||||
|
||||
from .functions import bool_check
|
||||
from envbash import load_envbash
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.misc import boolean
|
||||
|
||||
VERSION = '0.2.2'
|
||||
|
||||
full_path = abspath(sys.executable) if getattr(sys, 'frozen', False) else abspath(__file__)
|
||||
script_path = getattr(sys, '_MEIPASS', dirname(abspath(__file__)))
|
||||
script_name = basename(full_path)
|
||||
stor_path = abspath(f'{script_path}/../data')
|
||||
stor_path = abspath(f'{os.getcwd()}/data')
|
||||
|
||||
|
||||
if not isdir(stor_path):
|
||||
os.makedirs(stor_path, exist_ok=True)
|
||||
|
||||
|
||||
if not bool_check(env.get('LOGDATE', 'yes').lower()):
|
||||
log_date = ''
|
||||
|
||||
else:
|
||||
log_date = '[%(asctime)s] '
|
||||
|
||||
|
||||
logging = logger.getLogger()
|
||||
logging.setLevel(logger.DEBUG)
|
||||
log_format = f'{log_date}%(levelname)s: %(message)s'
|
||||
|
||||
logger.addLevelName(5, 'VERBOSE')
|
||||
logger.addLevelName(30, 'WARN')
|
||||
logger.addLevelName(50, 'CRIT')
|
||||
|
||||
console = logger.StreamHandler()
|
||||
console.name = 'Console Log'
|
||||
console.level = logger.INFO
|
||||
console.formatter = logger.Formatter(log_format, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
logging.addHandler(console)
|
||||
if not boolean(env.get('LOGDATE', 'yes').lower()):
|
||||
logging.setConfig({'date': False})
|
||||
|
||||
|
||||
if not isfile(f'{stor_path}/production.env'):
|
||||
|
@ -54,6 +34,7 @@ if not isfile(f'{stor_path}/production.env'):
|
|||
#PAWS_PORT=3001
|
||||
|
||||
#PAWS_DOMAIN=bappypaws.example.com
|
||||
#PAWS_NORSS=true
|
||||
|
||||
#MASTOPATH=/home/mastodon/glitch-soc
|
||||
#MASTOHOST=localhost:3000
|
||||
|
@ -71,6 +52,7 @@ PAWSCONFIG = {
|
|||
'host': env.get('PAWS_HOST', '127.0.0.1'),
|
||||
'port': int(env.get('PAWS_PORT', 3001)),
|
||||
'domain': env.get('PAWS_DOMAIN', 'bappypaws.example.com'),
|
||||
'disable_rss': boolean(env.get('PAWS_DISABLE_RSS', True)),
|
||||
'mastopath': env.get('MASTOPATH', os.getcwd()),
|
||||
'mastohost': env.get('MASTOHOST', 'localhost:3000')
|
||||
}
|
||||
|
@ -86,7 +68,7 @@ else:
|
|||
|
||||
MASTOCONFIG={
|
||||
'domain': env.get('WEB_DOMAIN', env.get('LOCAL_DOMAIN', 'localhost:3000')),
|
||||
'auth_fetch': bool_check(env.get('AUTHORIZED_FETCH')),
|
||||
'auth_fetch': boolean(env.get('AUTHORIZED_FETCH')),
|
||||
'dbhost': env.get('DB_HOST', '/var/run/postgresql'),
|
||||
'dbport': int(env.get('DB_PORT', 5432)),
|
||||
'dbname': env.get('DB_NAME', 'mastodon_production'),
|
||||
|
|
|
@ -4,6 +4,8 @@ from datetime import datetime
|
|||
from urllib.parse import urlparse
|
||||
from json.decoder import JSONDecodeError
|
||||
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.cache import LRUCache
|
||||
from DBUtils.PooledPg import PooledPg as DB
|
||||
from tinydb import TinyDB, Query, where
|
||||
from tinydb_smartcache import SmartCacheTable
|
||||
|
@ -12,9 +14,8 @@ from tldextract import extract
|
|||
from Crypto.PublicKey import RSA
|
||||
from mastodon import Mastodon
|
||||
|
||||
from .config import stor_path, logging, MASTOCONFIG as mdb
|
||||
from .config import stor_path, MASTOCONFIG as mdb
|
||||
from .functions import bool_check
|
||||
from .cache import LRUCache
|
||||
|
||||
|
||||
def jsondb():
|
||||
|
@ -217,20 +218,37 @@ def whitelist(action, instance):
|
|||
return 'InvalidAction'
|
||||
|
||||
|
||||
def cleanup_users():
|
||||
timestamp = datetime.now()
|
||||
def cleanup_users(invalid_check=None):
|
||||
timestamp = datetime.timestamp(datetime.now())
|
||||
invalid_offset = 60 * 15
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
for user in pawsdb.users.all():
|
||||
if not user.get('timestamp'):
|
||||
tr.update({'timestamp': timestamp})
|
||||
logging.debug(f'adding timestamp for user: {user["handle"]}@{user["domain"]}')
|
||||
tr.update({'timestamp': timestamp}, doc_ids=[user.doc_id])
|
||||
continue
|
||||
|
||||
if invalid_check:
|
||||
if user['timestamp'] < timestamp - invalid_offset and not user['token']:
|
||||
logging.debug(f'old and incomplete access token for {user["handle"]}@{user["domain"]}')
|
||||
tr.remove(doc_ids=[user.doc_id])
|
||||
continue
|
||||
|
||||
else:
|
||||
client = Mastodon(api_base_url=user['domain'], access_token=user['token'])
|
||||
print(client.me())
|
||||
if not user['token']:
|
||||
logging.debug(f'no access token for {user["handle"]}@{user["domain"]}')
|
||||
continue
|
||||
|
||||
tr.remove((query.offset == None) & (Query.timestamp < timestamp - invalid_offset))
|
||||
client = Mastodon(api_base_url=user['domain'], access_token=user['token'])
|
||||
|
||||
try:
|
||||
client.me()
|
||||
logging.debug(f'valid token for {user["handle"]}@{user["domain"]}')
|
||||
|
||||
except Mastodon.MastodonUnauthorizedError:
|
||||
logging.debug(f'invalid token for {user["handle"]}@{user["domain"]}')
|
||||
tr.remove(doc_ids=[user.doc_id])
|
||||
|
||||
|
||||
pawsdb = jsondb()
|
||||
|
|
|
@ -1,28 +1,11 @@
|
|||
import re
|
||||
import json
|
||||
import logging
|
||||
import socket
|
||||
import os
|
||||
|
||||
import aiohttp
|
||||
import validators
|
||||
import json, socket, os, re
|
||||
import aiohttp, validators
|
||||
|
||||
from http import client as http
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from colour import Color
|
||||
from aiohttp_jinja2 import render_template as render
|
||||
|
||||
|
||||
error_codes = {
|
||||
400: 'BadRequest',
|
||||
404: 'NotFound',
|
||||
401: 'Unauthorized',
|
||||
403: 'Forbidden',
|
||||
404: 'NotFound',
|
||||
500: 'InternalServerError',
|
||||
504: 'GatewayTimeout'
|
||||
}
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.template import aiohttpTemplate
|
||||
|
||||
|
||||
def bool_check(value):
|
||||
|
@ -36,25 +19,23 @@ def bool_check(value):
|
|||
return value
|
||||
|
||||
|
||||
def error(code, error):
|
||||
def error(request, code, msg, isjson=False):
|
||||
if isjson or request.get('jsonreq'):
|
||||
return json_error(code, msg)
|
||||
|
||||
else:
|
||||
return http_error(request, code, msg)
|
||||
|
||||
|
||||
def json_error(code, error):
|
||||
error_body = json.dumps({'error': error})
|
||||
cont_type = 'application/json'
|
||||
|
||||
if code == 418:
|
||||
return HTTPTeapot(body=error_body, content_type=cont_type)
|
||||
|
||||
elif code not in error_codes.keys():
|
||||
logging.error(f'Hey! You specified a wrong error code: {code} {error}')
|
||||
|
||||
error_body = json.dumps({'error': 'DevError'})
|
||||
|
||||
return aiohttp.web.HTTPInternalServerError(body=error_body, content_type=cont_type)
|
||||
|
||||
return eval('aiohttp.web.HTTP'+error_codes[code]+'(body=error_body, content_type=cont_type)')
|
||||
return aiohttp.web.Response(body=error_body, content_type=cont_type, status=code)
|
||||
|
||||
|
||||
def http_error(request, code, msg):
|
||||
return render('pages/error.html', request, {'msg': msg, 'code': str(code)}, status=code)
|
||||
data = {'msg': msg, 'code': str(code)}
|
||||
return aiohttpTemplate('pages/error.html', data, request, status=code)
|
||||
|
||||
|
||||
def fed_domain(user, domain):
|
||||
|
@ -156,55 +137,3 @@ def css_ts():
|
|||
layout = css_check('layout')
|
||||
|
||||
return color + layout
|
||||
|
||||
|
||||
class color:
|
||||
def __init__(self):
|
||||
self.check = lambda color: Color(f'#{str(color)}' if re.search(r'^(?:[0-9a-fA-F]{3}){1,2}$', color) else color)
|
||||
|
||||
def multi(self, multiplier):
|
||||
if multiplier >= 1:
|
||||
return 1
|
||||
|
||||
elif multiplier <= 0:
|
||||
return 0
|
||||
|
||||
return multiplier
|
||||
|
||||
def lighten(self, color, multiplier):
|
||||
col = self.check(color)
|
||||
col.luminance += ((1 - col.luminance) * self.multi(multiplier))
|
||||
|
||||
return col.hex_l
|
||||
|
||||
def darken(self, color, multiplier):
|
||||
col = self.check(color)
|
||||
col.luminance -= (col.luminance * self.multi(multiplier))
|
||||
|
||||
return col.hex_l
|
||||
|
||||
|
||||
def saturate(self, color, multiplier):
|
||||
col = self.check(color)
|
||||
col.saturation += ((1 - col.saturation) * self.multi(multiplier))
|
||||
|
||||
return col.hex_l
|
||||
|
||||
|
||||
def desaturate(self, color, multiplier):
|
||||
col = self.check(color)
|
||||
col.saturation -= (col.saturation * self.multi(multiplier))
|
||||
|
||||
return col.hex_l
|
||||
|
||||
|
||||
def rgba(self, color, transparency):
|
||||
col = self.check(color)
|
||||
|
||||
red = col.red*255
|
||||
green = col.green*255
|
||||
blue = col.blue*255
|
||||
trans = self.multi(transparency)
|
||||
|
||||
return f'rgba({red:0.2f}, {green:0.2f}, {blue:0.2f}, {trans:0.2f})'
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import binascii
|
||||
import base64
|
||||
import traceback
|
||||
|
@ -10,7 +9,8 @@ import aiohttp
|
|||
from urllib.parse import urlparse, quote_plus
|
||||
from random import choice
|
||||
|
||||
from aiohttp_jinja2 import render_template as render
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.misc import boolean
|
||||
from aiohttp.http_exceptions import *
|
||||
from aiohttp.client_exceptions import *
|
||||
from Crypto.PublicKey import RSA
|
||||
|
@ -55,10 +55,7 @@ async def passthrough(request, headers):
|
|||
is_json = True if request.get('reqtype') == 'json' else False
|
||||
|
||||
try:
|
||||
# I don't think I need this, but I'll leave it here just in case
|
||||
#async with aiohttp.request(reqtype, f'https://{MASTOCONFIG["domain"]}/{path}{query}', headers=headers, data=post) as resp:
|
||||
|
||||
async with aiohttp.request(request.method, f'http://{mastohost}{request.path}?{distill_query(request.query)}', headers=headers, data=data) as resp:
|
||||
async with aiohttp.request(request.method, f'http://{mastohost}{request.path}?{request.query_string}', headers=headers, data=data) as resp:
|
||||
data = await resp.read()
|
||||
|
||||
if resp.status not in [200, 202]:
|
||||
|
@ -89,71 +86,68 @@ async def http_filter(app, handler):
|
|||
token = request.cookies.get('paws_token')
|
||||
user_data = None if not token else pawsdb.users.get(query.token == token)
|
||||
user = (user_data['handle'], user_data['instance']) if user_data and user_data.get('instance') else None
|
||||
real_ip = request.headers.get('X-Real-Ip')
|
||||
real_ip = request.headers.get('X-Real-Ip', request.remote)
|
||||
ua_ip = dig(ua_domain)
|
||||
|
||||
request['jsonreq'] = True if 'json' in request.headers.get('Accept', '') or request.path.endswith('.json') else False
|
||||
|
||||
# Disable rss feeds
|
||||
if request.path.endswith('.rss'):
|
||||
return error(403, 'RSS feeds disabled')
|
||||
if PAWSCONFIG['disable_rss'] and request.path.endswith('.rss'):
|
||||
return error(request, 403, 'RSS feeds disabled')
|
||||
|
||||
# add logged in user data to the request for the frontend
|
||||
request['user'] = user_data
|
||||
|
||||
if request.path in ['/paws/actor', '/paws/inbox', '/.well-known/webfinger'] and request.host != paws_host:
|
||||
return aiohttp.web.HTTPFound(f'http://{masto_host}/paws')
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
# try to find the domain for the request
|
||||
if not domain:
|
||||
raise error(401, 'Can\'t find instance domain')
|
||||
return error(request, 401, 'Can\'t find instance domain')
|
||||
|
||||
# block nazis and general garbage
|
||||
for agent in blocked_agents:
|
||||
if agent in ua:
|
||||
logging.info(f'Blocked garbage: {domain}')
|
||||
raise HTTPTeapot(body='418 This teapot kills fascists', content_type='text/plain')
|
||||
return error(request, 418, '418 This teapot kills fascists')
|
||||
|
||||
# block any suspended instances
|
||||
if ban_check(domain):
|
||||
logging.info(f'Blocked instance: {domain}')
|
||||
raise error(403, 'Forbidden')
|
||||
return error(request, 403, 'Forbidden')
|
||||
|
||||
# block any suspended users
|
||||
if banned_user_check(user):
|
||||
logging.info(f'Blocked user: {domain}')
|
||||
return http_error(request, 403, 'Access Denied')
|
||||
return error(request, 403, 'Access Denied')
|
||||
|
||||
if any(map(request.path.startswith, auth_paths)) and request.method == 'GET':
|
||||
if 'json' in request.headers.get('Accept', '') or request.path.endswith('.json'):
|
||||
request['reqtype'] = 'json'
|
||||
# Check signatures if auth fetches are off
|
||||
if not user_check(request.path) and not MASTOCONFIG['auth_fetch']:
|
||||
if signature:
|
||||
actor = parse_sig(signature)
|
||||
|
||||
# Check signatures if auth fetches are off
|
||||
if not user_check(request.path) and not MASTOCONFIG['auth_fetch']:
|
||||
if signature:
|
||||
actor = parse_sig(signature)
|
||||
if not (await validate(actor, request)):
|
||||
logging.info(f'Signature validation failed for: {actor}')
|
||||
return error(request, 401, 'signature check failed, signature did not match key')
|
||||
|
||||
if not (await validate(actor, request)):
|
||||
logging.info(f'Signature validation failed for: {actor}')
|
||||
raise error(401, 'signature check failed, signature did not match key')
|
||||
elif real_ip == ua_ip and wl_check(domain):
|
||||
logging.info(f'Letting {domain} through')
|
||||
|
||||
elif real_ip == ua_ip and wl_check(domain):
|
||||
logging.info(f'Letting {domain} through')
|
||||
|
||||
else:
|
||||
msg = 'missing signature'
|
||||
logging.warning(msg)
|
||||
raise error(401, msg)
|
||||
|
||||
else:
|
||||
request['reqtype'] = 'html'
|
||||
else:
|
||||
msg = 'missing signature'
|
||||
logging.warning(msg)
|
||||
return error(request, 401, msg)
|
||||
|
||||
if not request['jsonreq']:
|
||||
if not token or not user_data:
|
||||
return aiohttp.web.HTTPFound(f'https://{masto_host}/paws/login?redir={quote_plus(request.path)}')
|
||||
return aiohttp.web.HTTPFound(f'/paws/login?redir={quote_plus(request.path)}')
|
||||
|
||||
split_path = request.path.split('/')
|
||||
user = split_path[1].replace('@', '') if request.path.startswith('/@') else split_path[2]
|
||||
|
||||
if user_ban_check(user.lower(), (user_data['handle'].lower(), user_data['instance'])) or user_domain_ban_check(user.lower(), user_data['instance']):
|
||||
return http_error(request, 403, 'Access Denied')
|
||||
return error(request, 403, 'Access Denied')
|
||||
|
||||
if signature and wl_check(domain):
|
||||
logging.warning(f'{domain} has started signing requests and can be removed from the whitelist')
|
||||
|
@ -213,4 +207,11 @@ async def http_server_header(request, response):
|
|||
response.headers['msg'] = value
|
||||
|
||||
|
||||
async def http_access_log(request, response):
|
||||
uagent = request.headers.get('user-agent')
|
||||
client_ip = request.headers.get('X-Real-IP', request.remote)
|
||||
|
||||
logging.info(f'{client_ip} {request.method} {request.path_qs} {response.status} "{uagent}"')
|
||||
|
||||
|
||||
__all__ = ['http_signatures_middleware', 'http_auth_middleware', 'http_filter_middleware', 'http_trailing_slash', 'http_server_header']
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import os
|
||||
import json
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
import traceback
|
||||
|
||||
import http.client as http
|
||||
|
||||
from IzzyLib import logging
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from mastodon import Mastodon
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import os, sys, asyncio, aiohttp, aiohttp_jinja2, jinja2
|
||||
|
||||
from IzzyLib import logging, template, color
|
||||
from ipaddress import ip_address as address
|
||||
from urllib.parse import urlparse
|
||||
from mastodon import Mastodon
|
||||
|
||||
from .config import PAWSCONFIG, MASTOCONFIG, VERSION, script_path, logging
|
||||
from .functions import color, css_ts
|
||||
from .config import PAWSCONFIG, MASTOCONFIG, VERSION, script_path
|
||||
from .functions import css_ts
|
||||
from .database import cleanup_users
|
||||
from . import middleware
|
||||
from . import middleware, views
|
||||
|
||||
|
||||
def webserver():
|
||||
from . import views
|
||||
|
||||
web = aiohttp.web.Application(middlewares=[
|
||||
middleware.http_filter,
|
||||
middleware.http_trailing_slash
|
||||
])
|
||||
|
||||
web.on_response_prepare.append(middleware.http_server_header)
|
||||
web.on_response_prepare.append(middleware.http_access_log)
|
||||
|
||||
web.add_routes([
|
||||
aiohttp.web.route('GET', '/paws', views.get_paws),
|
||||
|
@ -38,30 +38,13 @@ def webserver():
|
|||
#aiohttp.web.route('GET', '/paws/imgay', views.get_gay)
|
||||
])
|
||||
|
||||
async def global_vars(request):
|
||||
return {
|
||||
'VERSION': VERSION,
|
||||
'len': len,
|
||||
'css_ts': css_ts,
|
||||
'request': request,
|
||||
'domain': MASTOCONFIG['domain'],
|
||||
'urlparse': urlparse,
|
||||
'lighten': color().lighten,
|
||||
'darken': color().darken,
|
||||
'saturate': color().saturate,
|
||||
'desaturate': color().desaturate,
|
||||
'rgba': color().rgba
|
||||
}
|
||||
|
||||
|
||||
aiohttp_jinja2.setup(
|
||||
web,
|
||||
loader=jinja2.FileSystemLoader(f'{script_path}/templates'),
|
||||
autoescape=jinja2.select_autoescape(['html', 'css']),
|
||||
context_processors=[global_vars],
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True
|
||||
)
|
||||
template.addEnv({
|
||||
'css_ts': css_ts,
|
||||
'domain': MASTOCONFIG['domain'],
|
||||
'VERSION': VERSION,
|
||||
'len': len,
|
||||
'urlparse': urlparse
|
||||
})
|
||||
|
||||
return web
|
||||
|
||||
|
@ -108,19 +91,24 @@ async def start_webserver():
|
|||
|
||||
|
||||
async def cleanup_tokens():
|
||||
while True
|
||||
logging.info('Cleaning up tokens')
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(60*60)
|
||||
logging.debug('Cleaning up tokens')
|
||||
cleanup_users()
|
||||
|
||||
await asyncio.sleep(60*60)
|
||||
|
||||
async def cleanup_invalid_users():
|
||||
while True:
|
||||
logging.debug('Cleaning up invalid users')
|
||||
cleanup_users(True)
|
||||
await asyncio.sleep(60*15)
|
||||
|
||||
def main():
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(start_webserver())
|
||||
asyncio.ensure_future(cleanup_tokens())
|
||||
asyncio.ensure_future(cleanup_invalid_users())
|
||||
loop.run_forever()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
@ -3,8 +3,9 @@ import aiohttp.web
|
|||
import binascii
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.cache import LRUCache, TTLCache
|
||||
from aiohttp.http_exceptions import *
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Hash import SHA, SHA256, SHA512
|
||||
|
@ -15,7 +16,6 @@ from .database import keys
|
|||
|
||||
|
||||
class cache:
|
||||
from .cache import LRUCache, TTLCache
|
||||
messages = LRUCache()
|
||||
actors = TTLCache()
|
||||
keys = LRUCache()
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<tr class="instance">
|
||||
<td class="col1"><a href="https://{{instance}}/about" target="_new">{{instance}}</a></td>
|
||||
<td class="col2">
|
||||
<form action="https://{{domain}}/paws/action/remove" method="post">
|
||||
<form action="/paws/action/remove" method="post">
|
||||
<input name="name" value="{{instance}}" hidden>
|
||||
<input type="submit" value="X">
|
||||
</form>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<tr><td>none</td><td></td></tr>
|
||||
{% endif %}
|
||||
<tr class="instance">
|
||||
<form action="https://{{domain}}/paws/action/add" method="post">
|
||||
<form action="/paws/action/add" method="post">
|
||||
<td class="col1"><input type="text" name="name" placeholder="bofa.lol"></td>
|
||||
<td class="col2">
|
||||
<input type="submit" value="Add">
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import aiohttp
|
||||
import random
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
from aiohttp_jinja2 import render_template as render
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.cache import TTLCache
|
||||
from IzzyLib.template import aiohttpTemplate
|
||||
from urllib.parse import quote_plus, unquote_plus, urlparse
|
||||
from datetime import datetime
|
||||
|
||||
from .database import pawsdb, trans, query, where, keys, ban_check, get_user, get_toot, admin_check, whitelist
|
||||
from .functions import error, http_error, fed_domain, domain_check, css_ts
|
||||
from .functions import error, fed_domain, domain_check, css_ts
|
||||
from .oauth import create_app, login
|
||||
from .config import MASTOCONFIG, PAWSCONFIG
|
||||
from .cache import TTLCache
|
||||
|
||||
|
||||
paws_host = PAWSCONFIG['domain']
|
||||
|
@ -19,9 +19,7 @@ masto_host = MASTOCONFIG['domain']
|
|||
|
||||
|
||||
async def get_home(request):
|
||||
'heck2'
|
||||
|
||||
return render('pages/home.html', request, {})
|
||||
return aiohttpTemplate('pages/home.html', {}, request)
|
||||
|
||||
|
||||
async def get_paws(request):
|
||||
|
@ -37,7 +35,8 @@ async def get_paws(request):
|
|||
else:
|
||||
whitelist = None
|
||||
|
||||
return render('pages/panel.html', request, {'admin': admin, 'whitelist': whitelist})
|
||||
data = {'admin': admin, 'whitelist': whitelist}
|
||||
return aiohttpTemplate('pages/panel.html', data, request)
|
||||
|
||||
|
||||
async def post_paws(request):
|
||||
|
@ -50,20 +49,20 @@ async def post_paws(request):
|
|||
admin = admin_check(user_data['handle']) if user_data else None
|
||||
|
||||
if not admin:
|
||||
return http_error(request, 403, 'Not an admin')
|
||||
return error(request, 403, 'Not an admin')
|
||||
|
||||
if None in [action, domain]:
|
||||
return http_error(request, 400, 'Missing action or doamin')
|
||||
return error(request, 400, 'Missing action or doamin')
|
||||
|
||||
domain = urlparse(domain.replace(' ', ''))
|
||||
parsed_domain = domain.netloc if domain.netloc != '' else domain.path
|
||||
|
||||
if action not in ['add', 'remove']:
|
||||
http_error(request, 400, 'Invalid action')
|
||||
error(request, 400, 'Invalid action')
|
||||
|
||||
whitelist(action, parsed_domain)
|
||||
|
||||
return aiohttp.web.HTTPFound(f'https://{masto_host}/paws')
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
|
||||
async def get_login(request):
|
||||
|
@ -75,9 +74,10 @@ async def get_login(request):
|
|||
logging.warning(token)
|
||||
|
||||
if token and pawsdb.users.get(query.token == token):
|
||||
return aiohttp.web.HTTPFound(f'https://{masto_host}/paws')
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
return render('pages/login.html', request, {'redir': redir, 'numid': numid})
|
||||
data = {'redir': redir, 'numid': numid}
|
||||
return aiohttpTemplate('pages/login.html', data, request)
|
||||
|
||||
|
||||
async def post_login(request):
|
||||
|
@ -92,15 +92,15 @@ async def post_login(request):
|
|||
domain = domain_check(domain)
|
||||
|
||||
if not domain:
|
||||
return http_error(request, 200, 'Invalid domain')
|
||||
return error(request, 400, 'Invalid domain')
|
||||
|
||||
if ban_check(domain):
|
||||
return http_error(request, 403, 'Instance banned')
|
||||
return error(request, 403, 'Instance banned')
|
||||
|
||||
appid, appsecret, redir_url = create_app(domain)
|
||||
|
||||
if appid == 'error':
|
||||
return http_error(request, 500, appsecret)
|
||||
return error(request, 500, appsecret)
|
||||
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
|
@ -109,7 +109,8 @@ async def post_login(request):
|
|||
'domain': data['domain'],
|
||||
'appid': appid,
|
||||
'appsecret': appsecret,
|
||||
'token': None
|
||||
'token': None,
|
||||
'timestamp': datetime.timestamp(datetime.now())
|
||||
})
|
||||
|
||||
response = aiohttp.web.HTTPFound(redir_url)
|
||||
|
@ -129,7 +130,7 @@ async def get_auth(request):
|
|||
code = parms.get('code')
|
||||
|
||||
if None in [numid, code]:
|
||||
return http_error(request, 500, 'Missing temporary userid or auth code')
|
||||
return error(request, 500, 'Missing temporary userid or auth code')
|
||||
|
||||
logging.warning(redir)
|
||||
|
||||
|
@ -140,12 +141,21 @@ async def get_auth(request):
|
|||
token, userinfo = login(user, code)
|
||||
|
||||
if token == 'error':
|
||||
return http_error(request, 500, userinfo)
|
||||
return error(request, 500, userinfo)
|
||||
|
||||
instance = fed_domain(userinfo['username'], user['domain'])
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
tr.update({'handle': userinfo['username'], 'instance': instance, 'token': token, 'appid': None, 'appsecret': None}, where('handle') == numid)
|
||||
tr.update(
|
||||
{
|
||||
'handle': userinfo['username'],
|
||||
'instance': instance,
|
||||
'token': token,
|
||||
'appid': None,
|
||||
'appsecret': None
|
||||
},
|
||||
where('handle') == numid
|
||||
)
|
||||
|
||||
response = aiohttp.web.HTTPFound(redir)
|
||||
response.set_cookie('paws_token', token, max_age=60*60*24*14)
|
||||
|
@ -169,9 +179,7 @@ async def get_logout(request):
|
|||
async def get_style(request):
|
||||
maxage = 60*60*24*7
|
||||
|
||||
response = render('color.css', request, {})
|
||||
response.headers['Content-type'] = 'text/css'
|
||||
#response.headers['Last-Modified'] = datetime.utcfromtimestamp(css_ts()).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
response = aiohttpTemplate('color.css', {}, request, content_type='text/css')
|
||||
response.headers['Cache-Control'] = 'public,max-age={maxage},immutable'
|
||||
|
||||
return response
|
||||
|
@ -210,7 +218,7 @@ async def get_actor(request):
|
|||
rsakey = keys('default')
|
||||
|
||||
if not rsakey:
|
||||
return http_error(request, 500, 'Missing actor keys')
|
||||
return error(request, 500, 'Missing actor keys')
|
||||
|
||||
PUBKEY = rsakey['pubkey']
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
exec = ./server.py
|
||||
exec = python3 -m paws
|
||||
watch_ext = py, env
|
||||
ignore_dirs = build, data
|
||||
ignore_files = reload.py, test.py, heck.py
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# todo: module version numbers
|
||||
git+https://git.barkshark.xyz/izaliamae/izzylib.git@0.1
|
||||
dbutils
|
||||
pygresql
|
||||
tinydb
|
||||
|
@ -10,7 +10,4 @@ tldextract
|
|||
envbash
|
||||
ipaddress
|
||||
mastodon.py
|
||||
aiohttp_jinja2
|
||||
colour
|
||||
validators>=0.14.1
|
||||
|
||||
|
|
Loading…
Reference in a new issue