basic greylist functionality
This commit is contained in:
parent
67efd787c7
commit
ada71fb7d7
|
@ -28,18 +28,24 @@ if not isfile(f'{stor_path}/production.env'):
|
|||
logging.error(f'PAWS environment file doesn\'t exist: {stor_path}/production.env')
|
||||
logging.info('Creating a new config file. Be sure to edit it and restart PAWS')
|
||||
|
||||
new_config = '''
|
||||
### Uncomment and adjust any values as necessary
|
||||
new_config = '''###
|
||||
# Uncomment and adjust any values as necessary
|
||||
###
|
||||
|
||||
### Port and host PAWS will listen on
|
||||
#PAWS_HOST=127.0.0.1
|
||||
#PAWS_PORT=3001
|
||||
|
||||
#PAWS_DOMAIN=bappypaws.example.com
|
||||
#PAWS_NORSS=true
|
||||
|
||||
### Require approval for unknown instances. Set to true for all AP servers or a comma-separated list for specific server software
|
||||
### Ex: mastodon,pleroma,miskey,activityrelay,unciarelay,other,unidentified
|
||||
### Require approval for unknown instances. They can be accepted or denied at {{MASTODOMAIN}}/paws/list/requests
|
||||
#PAWS_REQ_APPROVAL=false
|
||||
|
||||
### Override domain in mastodon's config
|
||||
#MASTODOMAIN=mastodon.example.com
|
||||
|
||||
###
|
||||
#MASTOPATH=/home/mastodon/glitch-soc
|
||||
#MASTOHOST=localhost:3000
|
||||
'''
|
||||
|
@ -53,10 +59,7 @@ else:
|
|||
load_envbash(f'{stor_path}/production.env')
|
||||
|
||||
|
||||
req_approval = boolean(env.get('PAWS_REQ_APPROVAL', False), False)
|
||||
|
||||
if not isinstance(req_approval, bool):
|
||||
req_approval = [sw.strip() for sw in req_approval.split(',')]
|
||||
req_approval = boolean(env.get('PAWS_REQ_APPROVAL'), False)
|
||||
|
||||
|
||||
PAWSCONFIG = {
|
||||
|
@ -64,7 +67,7 @@ PAWSCONFIG = {
|
|||
'port': int(env.get('PAWS_PORT', 3001)),
|
||||
'domain': env.get('PAWS_DOMAIN', 'bappypaws.example.com'),
|
||||
'disable_rss': boolean(env.get('PAWS_DISABLE_RSS', True)),
|
||||
'req_approval': req_approval,
|
||||
'require_approval': req_approval,
|
||||
'mastopath': env.get('MASTOPATH', os.getcwd()),
|
||||
'mastohost': env.get('MASTOHOST', 'localhost:3000'),
|
||||
}
|
||||
|
@ -79,7 +82,7 @@ else:
|
|||
load_envbash(f'{masto_path}/.env.production')
|
||||
|
||||
MASTOCONFIG={
|
||||
'domain': env.get('WEB_DOMAIN', env.get('LOCAL_DOMAIN', 'localhost:3000')),
|
||||
'domain': env.get('MASTODOMAIN', env.get('WEB_DOMAIN', env.get('LOCAL_DOMAIN', 'localhost:3000'))),
|
||||
'auth_fetch': boolean(env.get('AUTHORIZED_FETCH')),
|
||||
'dbhost': env.get('DB_HOST', '/var/run/postgresql'),
|
||||
'dbport': int(env.get('DB_PORT', 5432)),
|
||||
|
@ -87,4 +90,3 @@ MASTOCONFIG={
|
|||
'dbuser': env.get('DB_USER', env.get('USER')),
|
||||
'dbpass': env.get('DB_PASS')
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ from Crypto.PublicKey import RSA
|
|||
from mastodon import Mastodon
|
||||
from mastodon.Mastodon import MastodonUnauthorizedError, MastodonBadGatewayError, MastodonNetworkError
|
||||
|
||||
from .config import stor_path, MASTOCONFIG as mdb
|
||||
from .config import stor_path, PAWSCONFIG, MASTOCONFIG as mdb
|
||||
from .functions import fetch, get_nodeinfo
|
||||
|
||||
|
||||
|
@ -36,6 +36,7 @@ def jsondb():
|
|||
instances = db.table('instances')
|
||||
users = db.table('users')
|
||||
whitelist = db.table('whitelist')
|
||||
request = db.table('request')
|
||||
|
||||
return table
|
||||
|
||||
|
@ -223,51 +224,77 @@ def whitelist(action, instance):
|
|||
|
||||
def get_instances(domain=None, state=None):
|
||||
if domain:
|
||||
rows = table.instances.get(query.domain == domain)
|
||||
rows = pawsdb.instances.get(query.domain == domain)
|
||||
|
||||
elif state:
|
||||
rows = table.instances.search(query.state == state)
|
||||
rows = pawsdb.instances.search(query.state == state)
|
||||
|
||||
else:
|
||||
rows = table.instances.all()
|
||||
rows = pawsdb.instances.all()
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
def instances(action, instance, state=None):
|
||||
def instances(action, instance, state='request'):
|
||||
logging.debug(', '.join([action, instance, state]))
|
||||
|
||||
if None in [action, instance, state]:
|
||||
return False
|
||||
|
||||
domain = urlparse(instance).netloc if instance.startswith('http') else instance
|
||||
req_data = table.requests.get(query.domain == domain)
|
||||
instance_data = pawsdb.instances.get(query.domain == domain)
|
||||
state = state.lower()
|
||||
|
||||
if action == 'add':
|
||||
if not state:
|
||||
logging.debug('Instance state not specified')
|
||||
return
|
||||
if instance_data:
|
||||
logging.debug(f'Updating instance state: {domain}, {state}')
|
||||
with trans(pawsdb.instances) as tr:
|
||||
tr.update({'state': state}, doc_ids=[instance_data.doc_id])
|
||||
|
||||
if req_data:
|
||||
logging.debug(f'Domain already in request list: {domain}')
|
||||
return
|
||||
|
||||
ni_software = get_nodeinfo(domain)
|
||||
|
||||
if not ni_software:
|
||||
logging.debug(f'Failed to get nodeinfo data from instance: {domain}')
|
||||
return
|
||||
|
||||
data = {
|
||||
'instance': domain,
|
||||
'domain': domain,
|
||||
'software': ni_software['name'],
|
||||
'state': state,
|
||||
'timestamp': datetime.timestamp(datetime.now())
|
||||
}
|
||||
|
||||
with trans(table.instances) as tr:
|
||||
with trans(pawsdb.instances) as tr:
|
||||
tr.insert(data)
|
||||
|
||||
elif action == 'remove':
|
||||
if not req_data:
|
||||
if not instance_data:
|
||||
logging.debug(f'Domain not in request list: {domain}')
|
||||
|
||||
with trans(table.instances) as tr:
|
||||
tr.remove(doc_ids=[req_data.doc_id])
|
||||
with trans(pawsdb.instances) as tr:
|
||||
tr.remove(doc_ids=[instance_data.doc_id])
|
||||
|
||||
|
||||
def instance_check(domain):
|
||||
timestamp = datetime.timestamp(datetime.now())
|
||||
instance = pawsdb.instances.get(query.domain == domain)
|
||||
state = instance.get('state') if instance else None
|
||||
|
||||
if not domain:
|
||||
return False
|
||||
|
||||
if state == 'accept' or domain == mdb['domain']:
|
||||
return True
|
||||
|
||||
elif state == 'deny':
|
||||
return False
|
||||
|
||||
elif not state:
|
||||
result = instances('add', domain)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def cleanup_users(invalid_check=None):
|
||||
|
|
|
@ -54,7 +54,7 @@ def get_nodeinfo(instance):
|
|||
|
||||
nodeinfo = fetch(f'https://{domain}/nodeinfo/2.0.json')
|
||||
|
||||
if not nodeinfo:
|
||||
if not nodeinfo or (isinstance(nodeinfo, dict) and nodeinfo.get('error')):
|
||||
logging.debug('Wrong nodeinfo url. Finding correct one...')
|
||||
wk_url = f'https://{domain}/.well-known/nodeinfo'
|
||||
well_known = fetch(wk_url)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
import asyncio
|
||||
import json
|
||||
import binascii
|
||||
import base64
|
||||
import traceback
|
||||
import asyncio, json, binascii, base64, traceback, aiohttp
|
||||
|
||||
import aiohttp
|
||||
|
||||
from urllib.parse import urlparse, quote_plus
|
||||
from urllib.parse import urlparse, quote_plus, unquote_plus
|
||||
from random import choice
|
||||
from os.path import isfile
|
||||
from http import client as HTTP
|
||||
|
||||
from IzzyLib import logging
|
||||
from IzzyLib.misc import boolean
|
||||
|
@ -18,17 +14,12 @@ from Crypto.PublicKey import RSA
|
|||
from .signature import validate, pass_hash, sign_headers
|
||||
from .functions import error, user_check, domain_check, parse_sig, parse_ua, dig, distill_query, get_nodeinfo, httpclient
|
||||
from .config import MASTOCONFIG, PAWSCONFIG, VERSION, script_path
|
||||
from .database import pawsdb, query, trans, ban_check, user_ban_check, banned_user_check, wl_check, keys, user_domain_ban_check, admin_check
|
||||
from .database import pawsdb, query, trans, ban_check, user_ban_check, banned_user_check, wl_check, keys, user_domain_ban_check, admin_check, instance_check, instances
|
||||
|
||||
|
||||
paws_host = PAWSCONFIG['domain']
|
||||
masto_host = MASTOCONFIG['domain']
|
||||
|
||||
|
||||
# I'm a little teapot :3
|
||||
class HTTPTeapot(aiohttp.web.HTTPError):
|
||||
status_code = 418
|
||||
|
||||
masto_path = PAWSCONFIG['mastopath']
|
||||
|
||||
blocked_agents = [
|
||||
'gabsocial',
|
||||
|
@ -40,7 +31,9 @@ blocked_agents = [
|
|||
'baraag',
|
||||
'gameliberty',
|
||||
'neckbeard',
|
||||
'soapbox'
|
||||
'soapbox',
|
||||
'qoto',
|
||||
'archive'
|
||||
]
|
||||
|
||||
auth_paths = [
|
||||
|
@ -48,6 +41,66 @@ auth_paths = [
|
|||
'/users'
|
||||
]
|
||||
|
||||
admin_paths = [
|
||||
'/paws/action',
|
||||
'/paws/list'
|
||||
]
|
||||
|
||||
error_msgs = {
|
||||
404: 'Not found',
|
||||
500: 'Server did an oopsie'
|
||||
}
|
||||
|
||||
|
||||
def parse_headers(headers, enc=True):
|
||||
new_headers = {}
|
||||
|
||||
for header in headers:
|
||||
key, value = [item.decode() for item in header]
|
||||
|
||||
if key.lower() in ['content-encoding', 'transfer-encoding'] and not enc:
|
||||
continue
|
||||
|
||||
if key in new_headers:
|
||||
new_headers[key] + '; ' + value
|
||||
|
||||
else:
|
||||
new_headers[key] = value
|
||||
|
||||
return new_headers
|
||||
|
||||
|
||||
async def passthrough2(request, headers):
|
||||
mastohost = PAWSCONFIG['mastohost']
|
||||
raw_data = await request.read()
|
||||
is_json = request.get('jsonreq', False)
|
||||
Headers = parse_headers(request.raw_headers)
|
||||
Headers['Proxy'] = ''
|
||||
|
||||
try:
|
||||
timeout = aiohttp.ClientTimeout(total=5)
|
||||
async with aiohttp.request(request.method, f'http://{mastohost}{request.path}?{request.query_string}', headers=Headers, data=raw_data, timeout=timeout, allow_redirects=False) as resp:
|
||||
data = await resp.read()
|
||||
|
||||
if resp.status not in [200, 202, 301, 302]:
|
||||
if data in [b'Request not signed', 'Request not signed']:
|
||||
err_msg = 'Missing signature'
|
||||
logging.debug(err_msg)
|
||||
|
||||
else:
|
||||
err_msg = f'Recieved error {resp.status} from Mastodon'
|
||||
logging.debug(err_msg)
|
||||
|
||||
return error(request, resp.status, f'Failed to forward request')
|
||||
|
||||
resp_headers = parse_headers(resp.raw_headers, False)
|
||||
|
||||
return aiohttp.web.Response(body=data, headers=resp_headers, status=resp.status)
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return error(request, 504, f'Failed to connect to Mastodon')
|
||||
|
||||
|
||||
async def passthrough(request, headers):
|
||||
mastohost = PAWSCONFIG['mastohost']
|
||||
|
@ -56,10 +109,9 @@ async def passthrough(request, headers):
|
|||
|
||||
try:
|
||||
resp = httpclient.request(request.method, f'http://{mastohost}{request.path}?{request.query_string}', body=req_data, headers=headers, redirect=False)
|
||||
#async with aiohttp.request(request.method, f'http://{mastohost}{request.path}?{request.query_string}', headers=headers, data=data) as resp:
|
||||
data = resp.data
|
||||
|
||||
if resp.status not in [200, 202, 301]:
|
||||
if resp.status not in [200, 202, 301, 302]:
|
||||
if data in [b'Request not signed', 'Request not signed']:
|
||||
err_msg = 'Missing signature'
|
||||
logging.debug(err_msg)
|
||||
|
@ -88,41 +140,46 @@ async def passthrough(request, headers):
|
|||
async def http_filter(app, handler):
|
||||
async def http_filter_handler(request):
|
||||
signature = request.headers.get('signature')
|
||||
ua = request.headers.get('user-agent').lower()
|
||||
sig_domain = parse_sig(signature, short=True)
|
||||
|
||||
ua = request.headers.get('user-agent').lower()
|
||||
ua_domain = parse_ua(ua)
|
||||
domain = ua_domain if not sig_domain else sig_domain
|
||||
|
||||
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
|
||||
user = (user_data['handle'], user_data['instance']) if user_data and user_data.get('instance') else (None, None)
|
||||
|
||||
real_ip = request.headers.get('X-Real-Ip', request.remote)
|
||||
ua_ip = dig(ua_domain)
|
||||
|
||||
nodeinfo = get_nodeinfo(domain)
|
||||
software = nodeinfo['name'] if nodeinfo else None
|
||||
software = nodeinfo.get('name') if nodeinfo else None
|
||||
|
||||
instance = domain if domain != 'unknown' else user[1]
|
||||
allow = instance_check(instance)
|
||||
request['jsonreq'] = True if 'json' in request.headers.get('Accept', '') or request.path.endswith('.json') else False
|
||||
|
||||
# Disable rss feeds
|
||||
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
|
||||
request['admin'] = admin_check(user_data['handle']) if user_data else None
|
||||
|
||||
if request.path in ['/paws/actor', '/paws/inbox', '/.well-known/webfinger'] and request.host != paws_host:
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
# try to find the domain for the request
|
||||
if not 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.debug(f'Blocked garbage: {domain}')
|
||||
return error(request, 418, '418 This teapot kills fascists')
|
||||
return error(request, 418, 'This teapot kills fascists')
|
||||
|
||||
# Disable rss feeds
|
||||
if PAWSCONFIG['disable_rss'] and request.path.endswith('.rss'):
|
||||
return error(request, 403, 'RSS feeds disabled')
|
||||
|
||||
if request.path in ['/paws/actor', '/paws/inbox', '/.well-known/webfinger'] and request.host != paws_host:
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
# give up if a domain can't be found'
|
||||
if not domain:
|
||||
return error(request, 401, 'Can\'t find instance domain')
|
||||
|
||||
# block any suspended instances
|
||||
if ban_check(domain):
|
||||
|
@ -134,7 +191,21 @@ async def http_filter(app, handler):
|
|||
logging.debug(f'Blocked user: {domain}')
|
||||
return error(request, 403, 'Access Denied')
|
||||
|
||||
# prevent unauthed users from accessing the instance lists
|
||||
if any(map(request.path.startswith, admin_paths)) and not request['admin']:
|
||||
return aiohttp.web.HTTPFound('/paws/login')
|
||||
|
||||
if any(map(request.path.startswith, auth_paths)) and request.method == 'GET':
|
||||
if PAWSCONFIG['require_approval'] and not allow:
|
||||
if allow != False:
|
||||
status, message = (401, 'Instance awaiting approval or rejection')
|
||||
instances('add', instance)
|
||||
|
||||
else:
|
||||
status, message = (403, 'Rejected')
|
||||
|
||||
return error(request, status, message)
|
||||
|
||||
# Check signatures if auth fetches are off
|
||||
if not user_check(request.path) and not MASTOCONFIG['auth_fetch']:
|
||||
if signature:
|
||||
|
@ -162,7 +233,7 @@ async def http_filter(app, handler):
|
|||
if user_ban_check(user.lower(), (user_data['handle'].lower(), user_data['instance'])) or user_domain_ban_check(user.lower(), user_data['instance']):
|
||||
return error(request, 403, 'Access Denied')
|
||||
|
||||
if signature and wl_check(domain):
|
||||
if signature and wl_check(domain) and request.method == 'GET':
|
||||
logging.warning(f'{domain} has started signing requests and can be removed from the whitelist')
|
||||
|
||||
if not signature and real_ip == ua_ip and wl_check(domain) and request.method == 'GET':
|
||||
|
@ -185,6 +256,11 @@ async def http_filter(app, handler):
|
|||
HEADERS = request.headers
|
||||
|
||||
if not request.path.startswith('/paws'):
|
||||
masto_file = f'{masto_path}/public{request.path}'
|
||||
|
||||
if isfile(masto_file):
|
||||
return aiohttp.web.FileResponse(masto_file, headers=HEADERS)
|
||||
|
||||
return_data = await passthrough(request, HEADERS)
|
||||
return return_data
|
||||
|
||||
|
@ -202,6 +278,29 @@ async def http_trailing_slash(app, handler):
|
|||
return http_trailing_slash_handler
|
||||
|
||||
|
||||
# Custom error handler
|
||||
async def http_error(app, handler):
|
||||
async def http_error_handler(request):
|
||||
try:
|
||||
response = await handler(request)
|
||||
|
||||
#except aiohttp.web.HTTPException as ex:
|
||||
# message = error_msgs.get(ex.status, 'Server did an oopsie')
|
||||
# response = error(request, ex.status, message)
|
||||
|
||||
except Exception as e:
|
||||
if getattr(e, 'status', None):
|
||||
message = error_msgs.get(e.status, 'Server did an oopsie')
|
||||
response = error(request, e.status, message)
|
||||
|
||||
else:
|
||||
traceback.print_exc()
|
||||
response = error(request, 500, e)
|
||||
|
||||
return response
|
||||
return http_error_handler
|
||||
|
||||
|
||||
async def http_server_header(request, response):
|
||||
response.headers['Server'] = f'PAWS/{VERSION}'
|
||||
|
||||
|
@ -221,10 +320,8 @@ async def http_server_header(request, response):
|
|||
|
||||
|
||||
async def http_access_log(request, response):
|
||||
uagent = request.headers.get('user-agent')
|
||||
client_ip = request.headers.get('X-Real-IP', request.remote)
|
||||
if not request.path.endswith(('js', 'css', 'png', 'jpg', 'gif')):
|
||||
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']
|
||||
logging.info(f'{client_ip} {request.method} {request.path_qs} {response.status} "{uagent}"')
|
||||
|
|
|
@ -103,5 +103,3 @@ def login(user, code):
|
|||
return ('error', msg)
|
||||
|
||||
return (token, fetch_user)
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@ from . import middleware, views, stor_path
|
|||
def webserver():
|
||||
web = aiohttp.web.Application(middlewares=[
|
||||
middleware.http_filter,
|
||||
middleware.http_trailing_slash
|
||||
middleware.http_trailing_slash,
|
||||
middleware.http_error
|
||||
])
|
||||
|
||||
web.on_response_prepare.append(middleware.http_server_header)
|
||||
|
@ -25,8 +26,8 @@ def webserver():
|
|||
aiohttp.web.route('GET', '/paws', views.get_paws),
|
||||
aiohttp.web.route('GET', '/paws/imgay', views.get_gay),
|
||||
aiohttp.web.route('POST', '/paws/action/{action}', views.post_paws),
|
||||
aiohttp.web.route('GET', '/paws/login', views.get_login),
|
||||
aiohttp.web.route('POST', '/paws/login', views.post_login),
|
||||
aiohttp.web.view('/paws/list/{list}', views.lists),
|
||||
aiohttp.web.view('/paws/login', views.login),
|
||||
aiohttp.web.route('GET', '/paws/logout', views.get_logout),
|
||||
aiohttp.web.route('GET', '/paws/auth', views.get_auth),
|
||||
aiohttp.web.route('GET', '/paws/style-{timestamp}.css', views.get_style),
|
||||
|
|
|
@ -22,8 +22,8 @@ summary:hover {
|
|||
}
|
||||
|
||||
#paw_logo_container {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
#content {
|
||||
|
@ -150,16 +150,16 @@ input[type=text]:hover {
|
|||
}
|
||||
|
||||
input[type=text]:focus {
|
||||
width: 90%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
|
||||
/* Admin */
|
||||
#whitelist .col1 {
|
||||
/* lists */
|
||||
.list .col1 {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#whitelist .col2 {
|
||||
.list .col2 {
|
||||
text-align: center;
|
||||
width: 75px;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,11 @@
|
|||
-if request.user
|
||||
-if request.admin
|
||||
.item
|
||||
%a{'href': '/paws/whitelist', 'target': '_self'} Whitelist
|
||||
%a{'href': '/paws/list/whitelist', 'target': '_self'} Whitelist
|
||||
.item
|
||||
%a{'href': '/paws/instances', 'target': '_self'} Instances
|
||||
%a{'href': '/paws/list/signlist', 'target': '_self'} Signlist
|
||||
.item
|
||||
%a{'href': '/paws/list/instances', 'target': '_self'} Instances
|
||||
.item
|
||||
%a{'href': '/paws/logout', 'target': '_self'} Logout
|
||||
-else
|
||||
|
|
106
paws/templates/pages/lists.haml
Normal file
106
paws/templates/pages/lists.haml
Normal file
|
@ -0,0 +1,106 @@
|
|||
-extends 'base.html'
|
||||
-set page = 'Home'
|
||||
|
||||
-block content
|
||||
#panel{'class': 'section'}
|
||||
-if listtype == 'signlist'
|
||||
%center
|
||||
%h2{'class': 'title'} Signlist
|
||||
|
||||
%table{'id': 'signlist', 'class': 'list'}
|
||||
%tr{'class': 'header'}
|
||||
%td
|
||||
%td
|
||||
|
||||
-if len(signlist) > 0
|
||||
-for instance in signlist
|
||||
%tr{'class': 'instance'}
|
||||
%td{'class': 'col1'}
|
||||
%a{'href': 'https://{{instance}}/about', 'target': '_new'}= instance
|
||||
%td{'class': 'col2'}
|
||||
%form{'action': '/paws/action/remove', 'method': 'post'}
|
||||
%input{'name': 'name', 'value': '{{instance}}', 'hidden': None}
|
||||
%input{'type': 'submit', 'value': 'Remove'}
|
||||
|
||||
-else
|
||||
%tr{'class': 'instance'}
|
||||
%td none
|
||||
%td
|
||||
|
||||
%tr{'class': 'instance'}
|
||||
%form{'action': '/paws/action/add', 'method': 'post'}
|
||||
%td{'class': 'col1'}
|
||||
%input{'type': 'text', 'name': 'name', 'placeholder': 'mastodon.social'}
|
||||
%td{'class': 'col2'}
|
||||
%input{'type': 'submit', 'value': 'Add'}
|
||||
|
||||
-if listtype == 'requests'
|
||||
%center
|
||||
%h2{'class': 'title'} Requests
|
||||
|
||||
%table{'id': 'request', 'class': 'list'}
|
||||
%tr{'class': 'header'}
|
||||
%td
|
||||
%td
|
||||
|
||||
-if len(requests) > 0
|
||||
-for instance in requests
|
||||
%tr{'class': 'instance'}
|
||||
%td{'class': 'col1'}
|
||||
%a{'href': 'https://{{instance.domain}}/about', 'target': '_new'}= instance.domain
|
||||
%td{'class': 'col2'}
|
||||
%form{'action': '/paws/action/add', 'method': 'post'}
|
||||
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
|
||||
%input{'type': 'submit', 'name': 'action', 'value': 'Deny'}
|
||||
%input{'type': 'submit', 'name': 'action', 'value': 'Accept'}
|
||||
|
||||
%form{'action': '/paws/action/remove', 'method': 'post'}
|
||||
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
|
||||
%input{'type': 'submit', 'value': 'Remove'}
|
||||
|
||||
-else
|
||||
%tr{'class': 'instance'}
|
||||
%td none
|
||||
%td
|
||||
|
||||
-if listtype == 'instances'
|
||||
%center
|
||||
%h2{'class': 'title'} Instances
|
||||
|
||||
%table{'id': 'accept', 'class': 'list'}
|
||||
%tr{'class': 'header'}
|
||||
%td
|
||||
%td
|
||||
|
||||
-if len(instances) > 0
|
||||
-for instance in instances
|
||||
%tr{'class': 'instance'}
|
||||
%td{'class': 'col1'}
|
||||
%a{'href': 'https://{{instance.domain}}/about', 'target': '_new'}= instance.domain
|
||||
%td{'class': 'col2'}
|
||||
%form{'action': '/paws/action/add', 'method': 'post'}
|
||||
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
|
||||
|
||||
-if instance.state == 'accept'
|
||||
%input{'type': 'submit', 'name': 'action', 'value': 'Deny'}
|
||||
|
||||
-elif instance.state == 'deny'
|
||||
%input{'type': 'submit', 'name': 'action', 'value': 'Accept'}
|
||||
|
||||
%form{'action': '/paws/action/remove', 'method': 'post'}
|
||||
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
|
||||
%input{'type': 'submit', 'value': 'Remove'}
|
||||
|
||||
-else
|
||||
%tr{'class': 'instance'}
|
||||
%td none
|
||||
%td
|
||||
|
||||
%tr{'class': 'instance'}
|
||||
%form{'action': '/paws/action/add', 'method': 'post'}
|
||||
%td{'class': 'col1'}
|
||||
%input{'type': 'text', 'name': 'name', 'placeholder': 'mastodon.social'}
|
||||
%td{'class': 'col2'}
|
||||
%input{'type': 'submit', 'name': 'action', 'value': 'Accept'}
|
||||
%input{'type': 'submit', 'name': 'action', 'value': 'Deny'}
|
||||
|
|
@ -6,34 +6,3 @@
|
|||
%h2{'class': 'title'} PAWS
|
||||
%p
|
||||
PAWS is an extra auth layer for public profiles and posts. The main purpose is to prevent web scrapers from hoarding posts, but it also prevents banned users from viewing toots they shouldn't see
|
||||
|
||||
-if admin
|
||||
%center
|
||||
%h2 Whitelist
|
||||
|
||||
%table{'id': 'whitelist'}
|
||||
%tr{'class': 'header'}
|
||||
%td
|
||||
%td
|
||||
|
||||
-if len(whitelist) > 0
|
||||
-for instance in whitelist
|
||||
%tr{'class': 'instance'}
|
||||
%td{'class': 'col1'}
|
||||
%a{'href': 'https://{{instance}}/about', 'target': '_new'}= instance
|
||||
%td{'class': 'col2'}
|
||||
%form{'action': '/paws/action/remove', 'method': 'post'}
|
||||
%input{'name': 'name', 'value': '{{instance}}', 'hidden': None}
|
||||
%input{'type': 'submit', 'value': 'Remove'}
|
||||
|
||||
-else
|
||||
%tr{'class': 'instance'}
|
||||
%td none
|
||||
%td
|
||||
|
||||
%tr{'class': 'instance'}
|
||||
%form{'action': '/paws/action/add', 'method': 'post'}
|
||||
%td{'class': 'col1'}
|
||||
%input{'type': 'text', 'name': 'name', 'placeholder': 'mastodon.social'}
|
||||
%td{'class': 'col2'}
|
||||
%input{'type': 'submit', 'value': 'Add'}
|
||||
|
|
158
paws/views.py
158
paws/views.py
|
@ -8,9 +8,9 @@ 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 . import oauth
|
||||
from .database import pawsdb, trans, query, where, keys, ban_check, get_user, get_toot, admin_check, whitelist, instances
|
||||
from .functions import error, fed_domain, domain_check, css_ts
|
||||
from .oauth import create_app, login
|
||||
from .config import MASTOCONFIG, PAWSCONFIG
|
||||
|
||||
|
||||
|
@ -18,19 +18,92 @@ paws_host = PAWSCONFIG['domain']
|
|||
masto_host = MASTOCONFIG['domain']
|
||||
|
||||
|
||||
class login(aiohttp.web.View):
|
||||
async def get(self):
|
||||
parms = self.request.rel_url.query
|
||||
redir = parms.get('redir')
|
||||
numid = random.randint(1*1000000, 10*1000000-1)
|
||||
|
||||
if self.request['user']:
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
data = {'redir': redir, 'numid': numid}
|
||||
return aiohttpTemplate('login.html', data, self.request)
|
||||
|
||||
|
||||
async def post(self):
|
||||
data = await self.request.post()
|
||||
domain = data.get('domain')
|
||||
redir = data.get('redir')
|
||||
numid = data.get('numid')
|
||||
|
||||
if 'DROP ' in domain:
|
||||
return aiohttp.web.HTTPFound('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
||||
|
||||
domain = domain_check(domain)
|
||||
|
||||
if not domain:
|
||||
return error(self.request, 400, 'Invalid domain')
|
||||
|
||||
if ban_check(domain):
|
||||
return error(self.request, 403, 'Instance banned')
|
||||
|
||||
appid, appsecret, redir_url = oauth.create_app(domain)
|
||||
|
||||
if appid == 'error':
|
||||
return error(self.request, 500, appsecret)
|
||||
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
tr.insert({
|
||||
'handle': data['numid'],
|
||||
'domain': data['domain'].lower(),
|
||||
'appid': appid,
|
||||
'appsecret': appsecret,
|
||||
'token': None,
|
||||
'timestamp': datetime.timestamp(datetime.now())
|
||||
})
|
||||
|
||||
response = aiohttp.web.HTTPFound(redir_url)
|
||||
response.set_cookie('paws_numid', numid, max_age=60*60, path='/paws')
|
||||
|
||||
if redir not in ['', None]:
|
||||
response.set_cookie('paws_redir', redir, max_age=60*60, path='/paws')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class lists(aiohttp.web.View):
|
||||
async def get(self):
|
||||
request = self.request
|
||||
listtype = request.match_info['list']
|
||||
|
||||
if request['admin']:
|
||||
data = {
|
||||
'listtype': listtype,
|
||||
'signlist': [line['domain'] for line in pawsdb.whitelist.all()],
|
||||
'requests': pawsdb.instances.search(query.state == 'request'),
|
||||
'instances': pawsdb.instances.search(query.state != 'request')
|
||||
}
|
||||
|
||||
else:
|
||||
data = {}
|
||||
|
||||
if listtype not in data.keys():
|
||||
return error(request, 404, 'Invalid list type')
|
||||
|
||||
return aiohttpTemplate('lists.html', data, request)
|
||||
|
||||
async def post(self):
|
||||
pass
|
||||
|
||||
|
||||
async def get_home(request):
|
||||
return aiohttpTemplate('home.html', {}, request)
|
||||
|
||||
|
||||
async def get_paws(request):
|
||||
if request['admin']:
|
||||
whitelist = [line['domain'] for line in pawsdb.whitelist.all()]
|
||||
whitelist.sort()
|
||||
|
||||
else:
|
||||
whitelist = None
|
||||
|
||||
data = {'whitelist': whitelist}
|
||||
data = {}
|
||||
return aiohttpTemplate('panel.html', data, request)
|
||||
|
||||
|
||||
|
@ -38,8 +111,8 @@ async def post_paws(request):
|
|||
data = await request.post()
|
||||
user_data = request['user']
|
||||
domain = data.get('name')
|
||||
action = request.match_info['action']
|
||||
|
||||
action = request.match_info['action'].lower()
|
||||
action = 'add' if action == 'update' else action
|
||||
admin = admin_check(user_data['handle']) if user_data else None
|
||||
|
||||
if not admin:
|
||||
|
@ -54,63 +127,10 @@ async def post_paws(request):
|
|||
if action not in ['add', 'remove']:
|
||||
error(request, 400, 'Invalid action')
|
||||
|
||||
whitelist(action, parsed_domain)
|
||||
result = instances(action, parsed_domain, data.get('action', 'request'))
|
||||
print(result)
|
||||
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
|
||||
async def get_login(request):
|
||||
parms = request.rel_url.query
|
||||
redir = parms.get('redir')
|
||||
numid = random.randint(1*1000000, 10*1000000-1)
|
||||
|
||||
if request['user']:
|
||||
return aiohttp.web.HTTPFound('/paws')
|
||||
|
||||
data = {'redir': redir, 'numid': numid}
|
||||
return aiohttpTemplate('login.html', data, request)
|
||||
|
||||
|
||||
async def post_login(request):
|
||||
data = await request.post()
|
||||
domain = data.get('domain')
|
||||
redir = data.get('redir')
|
||||
numid = data.get('numid')
|
||||
|
||||
if 'DROP ' in domain:
|
||||
return aiohttp.web.HTTPFound('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
|
||||
|
||||
domain = domain_check(domain)
|
||||
|
||||
if not domain:
|
||||
return error(request, 400, 'Invalid domain')
|
||||
|
||||
if ban_check(domain):
|
||||
return error(request, 403, 'Instance banned')
|
||||
|
||||
appid, appsecret, redir_url = create_app(domain)
|
||||
|
||||
if appid == 'error':
|
||||
return error(request, 500, appsecret)
|
||||
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
tr.insert({
|
||||
'handle': data['numid'],
|
||||
'domain': data['domain'].lower(),
|
||||
'appid': appid,
|
||||
'appsecret': appsecret,
|
||||
'token': None,
|
||||
'timestamp': datetime.timestamp(datetime.now())
|
||||
})
|
||||
|
||||
response = aiohttp.web.HTTPFound(redir_url)
|
||||
response.set_cookie('paws_numid', numid, max_age=60*60, path='/paws')
|
||||
|
||||
if redir not in ['', None]:
|
||||
response.set_cookie('paws_redir', redir, max_age=60*60, path='/paws')
|
||||
|
||||
return response
|
||||
return aiohttp.web.HTTPFound('/paws/list/instances')
|
||||
|
||||
|
||||
async def get_auth(request):
|
||||
|
@ -127,7 +147,7 @@ async def get_auth(request):
|
|||
redir = '/paws'
|
||||
|
||||
user = pawsdb.users.get(query.handle == str(numid))
|
||||
token, userinfo = login(user, code)
|
||||
token, userinfo = oauth.login(user, code)
|
||||
|
||||
if token == 'error':
|
||||
return error(request, 500, userinfo)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
exec = python3 -m paws
|
||||
watch_ext = py, env
|
||||
ignore_dirs = build, data
|
||||
ignore_dirs = build
|
||||
ignore_files = reload.py, test.py, heck.py
|
||||
log_level = INFO
|
||||
|
|
Loading…
Reference in a new issue