basic oauth support
This commit is contained in:
parent
257fd8900e
commit
46489746c5
|
@ -46,7 +46,7 @@ rewrite {
|
|||
rewrite {
|
||||
if_op or
|
||||
if {path} starts_with /@
|
||||
if {path} starts_with /authorize
|
||||
if {path} starts_with /paws
|
||||
to {path} /auth/{path}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ from envbash import load_envbash
|
|||
|
||||
from .functions import bool_check
|
||||
|
||||
VERSION = '0.1'
|
||||
VERSION = '0.2-beta'
|
||||
|
||||
full_path = abspath(sys.executable) if getattr(sys, 'frozen', False) else abspath(__file__)
|
||||
script_path = getattr(sys, '_MEIPASS', dirname(abspath(__file__)))
|
||||
|
|
|
@ -2,7 +2,7 @@ import sys
|
|||
|
||||
from DBUtils.PooledPg import PooledPg as DB
|
||||
from datetime import datetime
|
||||
from tinydb import TinyDB, Query
|
||||
from tinydb import TinyDB, Query, where
|
||||
from tinydb_smartcache import SmartCacheTable
|
||||
from tinyrecord import transaction as trans
|
||||
from tldextract import extract
|
||||
|
@ -23,14 +23,12 @@ def jsondb():
|
|||
|
||||
db.table_class = SmartCacheTable
|
||||
|
||||
tables = {
|
||||
'bans': db.table('bans'),
|
||||
'follows': db.table('follows'),
|
||||
'users': db.table('users'),
|
||||
'domains': db.table('domains')
|
||||
}
|
||||
class table:
|
||||
bans = db.table('bans')
|
||||
follows = db.table('follows')
|
||||
users = db.table('users')
|
||||
|
||||
return tables
|
||||
return table
|
||||
|
||||
|
||||
def pgdb():
|
||||
|
|
|
@ -3,6 +3,9 @@ import re
|
|||
import json
|
||||
import logging
|
||||
|
||||
from colour import Color
|
||||
from aiohttp_jinja2 import render_template as render
|
||||
|
||||
|
||||
error_codes = {
|
||||
400: 'BadRequest',
|
||||
|
@ -26,7 +29,7 @@ def bool_check(value):
|
|||
return value
|
||||
|
||||
|
||||
def json_error(code, error):
|
||||
def error(code, error):
|
||||
error_body = json.dumps({'error': error})
|
||||
cont_type = 'application/json'
|
||||
|
||||
|
@ -56,3 +59,54 @@ def user_check(path):
|
|||
|
||||
return False
|
||||
|
||||
|
||||
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})'
|
||||
|
||||
|
|
|
@ -6,14 +6,14 @@ import binascii
|
|||
import base64
|
||||
import traceback
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlparse, quote_plus
|
||||
from aiohttp.http_exceptions import *
|
||||
from aiohttp.client_exceptions import *
|
||||
|
||||
from .signature import validate, pass_hash
|
||||
from .functions import json_error, user_check
|
||||
from .config import MASTOCONFIG, PAWSCONFIG, script_path
|
||||
from . import database as db
|
||||
from .functions import error, user_check
|
||||
from .config import MASTOCONFIG, PAWSCONFIG, VERSION, script_path
|
||||
from .database import pawsdb, query, trans, ban_check
|
||||
|
||||
|
||||
# I'm a little teapot :3
|
||||
|
@ -99,40 +99,41 @@ async def passthrough(path, headers, post=None, query=None):
|
|||
|
||||
if resp.status not in [200, 202]:
|
||||
print(data)
|
||||
logging.warning(f'Recieved error {resp.status} from Mastodon')
|
||||
json_error(resp.status, f'Failed to forward request. Recieved error {resp.status} from Mastodon')
|
||||
logging.debug(f'Recieved error {resp.status} from Mastodon')
|
||||
error(resp.status, f'Failed to forward request. Recieved error {resp.status} from Mastodon')
|
||||
|
||||
raise aiohttp.web.HTTPOk(body=data, content_type=resp.content_type)
|
||||
|
||||
except ClientConnectorError:
|
||||
traceback.print_exc()
|
||||
return json_error(504, f'Failed to connect to Mastodon')
|
||||
return error(504, f'Failed to connect to Mastodon')
|
||||
|
||||
|
||||
async def http_redirect(app, handler):
|
||||
async def redirect_handler(request):
|
||||
querydata = request.query
|
||||
if not request.path.startswith('/paws'):
|
||||
querydata = request.query
|
||||
|
||||
rawquery = '?'
|
||||
rawquery = '?'
|
||||
|
||||
if len(querydata) > 0:
|
||||
for var in querydata:
|
||||
if rawquery == '?':
|
||||
rawquery += f'{var}={querydata[var]}'
|
||||
if len(querydata) > 0:
|
||||
for var in querydata:
|
||||
if rawquery == '?':
|
||||
rawquery += f'{var}={querydata[var]}'
|
||||
|
||||
else:
|
||||
rawquery += f'&{var}={querydata[var]}'
|
||||
else:
|
||||
rawquery += f'&{var}={querydata[var]}'
|
||||
|
||||
query = rawquery if rawquery != '' else None
|
||||
query = rawquery if rawquery != '' else None
|
||||
|
||||
try:
|
||||
data = await request.json()
|
||||
try:
|
||||
data = await request.json()
|
||||
|
||||
except Exception as e:
|
||||
#logging.warning(f'failed to grab data: {e}')
|
||||
data = None
|
||||
except Exception as e:
|
||||
#logging.warning(f'failed to grab data: {e}')
|
||||
data = None
|
||||
|
||||
await passthrough(request.path, request.headers, post=data, query=query)
|
||||
await passthrough(request.path, request.headers, post=data, query=query)
|
||||
|
||||
return (await handler(request))
|
||||
return redirect_handler
|
||||
|
@ -152,40 +153,19 @@ async def http_signatures(app, handler):
|
|||
|
||||
if not signature:
|
||||
logging.warning('missing signature')
|
||||
raise json_error(401, 'Missing signature')
|
||||
raise error(401, 'Missing signature')
|
||||
|
||||
if not (await validate(actor, request)):
|
||||
logging.info(f'Signature validation failed for: {actor}')
|
||||
raise json_error(401, 'signature check failed, signature did not match key')
|
||||
raise error(401, 'signature check failed, signature did not match key')
|
||||
|
||||
else:
|
||||
elif not request.path.startswith('/paws/'):
|
||||
request['reqtype'] = 'html'
|
||||
|
||||
auth_username = PAWSCONFIG['user']
|
||||
auth_password = PAWSCONFIG['pass']
|
||||
auth_realm = 'Nope'
|
||||
token = request.cookies.get('paws_token')
|
||||
|
||||
auth_header = request.headers.get(aiohttp.hdrs.AUTHORIZATION)
|
||||
|
||||
if auth_header == None or not auth_header.startswith('Basic '):
|
||||
return await raise_auth_error(request, auth_realm)
|
||||
|
||||
try:
|
||||
secret = auth_header[6:].encode('utf-8')
|
||||
auth_decoded = base64.decodebytes(secret).decode('utf-8')
|
||||
|
||||
except (UnicodeDecodeError, UnicodeEncodeError, binascii.Error):
|
||||
await raise_auth_error(request)
|
||||
|
||||
credentials = auth_decoded.split(':')
|
||||
|
||||
if len(credentials) != 2:
|
||||
await raise_auth_error(request, auth_realm)
|
||||
|
||||
username, password = credentials
|
||||
|
||||
if username != auth_username or password != auth_password:
|
||||
await raise_auth_error(request, auth_realm)
|
||||
if not token or not pawsdb.users.get(query.token == token):
|
||||
return aiohttp.web.HTTPFound(f'/paws/login?redir={quote_plus(request.path)}')
|
||||
|
||||
return (await handler(request))
|
||||
return http_signatures_handler
|
||||
|
@ -203,15 +183,15 @@ async def http_filter(app, handler):
|
|||
domain = ua_domain if not sig_domain else sig_domain
|
||||
|
||||
if not domain:
|
||||
raise json_error(401, 'Can\'t find instance domain')
|
||||
raise error(401, 'Can\'t find instance domain')
|
||||
|
||||
if [agent 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')
|
||||
|
||||
if db.ban_check(domain):
|
||||
if ban_check(domain):
|
||||
logging.info(f'Blocked instance: {domain}')
|
||||
raise json_error(403, 'Forbidden')
|
||||
raise error(403, 'Forbidden')
|
||||
|
||||
return (await handler(request))
|
||||
return http_filter_handler
|
||||
|
@ -227,4 +207,9 @@ async def http_trailing_slash(app, handler):
|
|||
return http_trailing_slash_handler
|
||||
|
||||
|
||||
__all__ = ['http_signatures_middleware', 'http_auth_middleware', 'http_filter_middleware', 'http_trailing_slash']
|
||||
async def http_server_header(request, response):
|
||||
response.headers['SErver'] = f'PAWS/{VERSION}'
|
||||
response.headers['trans_rights'] = 'are human rights'
|
||||
|
||||
|
||||
__all__ = ['http_signatures_middleware', 'http_auth_middleware', 'http_filter_middleware', 'http_trailing_slash', 'http_server_header']
|
||||
|
|
|
@ -2,11 +2,14 @@ import os
|
|||
import sys
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import aiohttp_jinja2
|
||||
import jinja2
|
||||
|
||||
from ipaddress import ip_address as address
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .config import PAWSCONFIG, VERSION, script_path, logging
|
||||
from .config import PAWSCONFIG, MASTOCONFIG, VERSION, script_path, logging
|
||||
from .functions import color
|
||||
from . import middleware
|
||||
|
||||
|
||||
|
@ -19,10 +22,40 @@ def webserver():
|
|||
middleware.http_redirect
|
||||
])
|
||||
|
||||
web.on_response_prepare.append(middleware.http_server_header)
|
||||
|
||||
web.add_routes([
|
||||
aiohttp.web.route('GET', '/authorize', views.authorize),
|
||||
aiohttp.web.route('GET', '/paws/login', views.get_login),
|
||||
aiohttp.web.route('POST', '/paws/login', views.post_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.css', views.get_style)
|
||||
])
|
||||
|
||||
async def global_vars(request):
|
||||
return {
|
||||
'VERSION': VERSION,
|
||||
'len': len,
|
||||
'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
|
||||
)
|
||||
|
||||
return web
|
||||
|
||||
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Nope.mov</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #111;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.center {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -150px;
|
||||
margin-top: -150px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="https://static.barkshark.xyz/main/img/YouDidntSayTheMagicWord.gif" class="center" />
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,92 @@
|
|||
import aiohttp
|
||||
import random
|
||||
|
||||
from aiohttp_jinja2 import render_template as render
|
||||
from urllib.parse import quote_plus, unquote_plus
|
||||
|
||||
from .database import pawsdb, trans, query, where
|
||||
from .functions import error
|
||||
from .oauth import create_app, login
|
||||
|
||||
|
||||
async def get_login(request):
|
||||
parms = request.rel_url.query
|
||||
redir = parms.get('redir')
|
||||
numid = random.randint(1*1000000, 10*1000000-1)
|
||||
|
||||
return render('pages/login.html', request, {'redir': redir, 'numid': numid})
|
||||
|
||||
|
||||
async def post_login(request):
|
||||
data = await request.post()
|
||||
domain = data.get('domain')
|
||||
redir = data.get('redir')
|
||||
numid = data.get('numid')
|
||||
|
||||
if domain in ['', None]:
|
||||
return render('pages/login.html', request, {'msg': 'Missing domain'})
|
||||
|
||||
appid, appsecret, redir_url = create_app(domain)
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
tr.insert({
|
||||
'handle': data['numid'],
|
||||
'domain': data['domain'],
|
||||
'icon': None,
|
||||
'appid': appid,
|
||||
'appsecret': appsecret,
|
||||
'token': None
|
||||
})
|
||||
|
||||
response = aiohttp.web.HTTPFound(redir_url)
|
||||
response.set_cookie('paws_numid', numid, max_age=60*60*24*14, path='/paws')
|
||||
|
||||
if redir not in ['', None]:
|
||||
response.set_cookie('paws_redir', redir, max_age=60*60*24*14, path='/paws')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def get_auth(request):
|
||||
parms = request.rel_url.query
|
||||
cookies = request.cookies
|
||||
redir = cookies.get('paws_redir')
|
||||
numid = cookies.get('paws_numid')
|
||||
code = parms.get('code')
|
||||
|
||||
if None in [numid, code]:
|
||||
response = render('pages/error.html', request, {'msg': 'Missing temporary userid or auth code', 'code': 500}, status=500)
|
||||
|
||||
user = pawsdb.users.get(query.handle == str(numid))
|
||||
token, userinfo = login(user, code)
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
tr.update({'handle': userinfo['username'], 'icon': userinfo['avatar_static'], 'token': token}, where('handle') == numid)
|
||||
|
||||
print(user)
|
||||
|
||||
response = aiohttp.web.HTTPFound(redir)
|
||||
response.set_cookie('paws_token', token, max_age=60*60*24*14)
|
||||
response.del_cookie('paws_redir', path='/paws')
|
||||
response.del_cookie('paws_numid', path='/paws')
|
||||
return response
|
||||
|
||||
|
||||
async def get_logout(request):
|
||||
token = request.cookies.get('paws_token')
|
||||
|
||||
with trans(pawsdb.users) as tr:
|
||||
tr.remove(where('token') == token)
|
||||
|
||||
response = render('pages/login.html', request, {'msg': 'Logged out'})
|
||||
response.del_cookie('token')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def get_style(request):
|
||||
response = render('color.css', request, {})
|
||||
response.headers['Content-Type'] = 'text/css'
|
||||
|
||||
return response
|
||||
|
||||
async def authorize(request):
|
||||
data = {['heck']}
|
||||
return aiohttp.web.json_response(data)
|
||||
|
|
|
@ -9,4 +9,6 @@ pycryptodome
|
|||
tldextract
|
||||
envbash
|
||||
ipaddress
|
||||
|
||||
mastodon.py
|
||||
aiohttp_jinja2
|
||||
colour
|
||||
|
|
Loading…
Reference in a new issue