commit e6b95c16bfe3edcc6fa07843203ca38a82afdfef Author: iou1name Date: Wed Nov 11 13:27:18 2020 -0500 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..610faed --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +*.swp +*.swo +config.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9813d47 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2020, iou1name + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc35e97 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# Stickup +A frontend for managing a user's email password. + +## Requirements +Python 3.8+ +Python packages: `gunicorn aiohttp aiohttp_jinja2 uvloop asyncpg passlib argon2_cffi` + +## Install +1. Get on the floor +2. Walk the dinosaur + +## Usage +`gunicorn stickup:init_app --bind localhost:5050 --worker-class aiohttp.GunicornWebWorker` diff --git a/buckler_aiohttp.py b/buckler_aiohttp.py new file mode 100644 index 0000000..af5b14b --- /dev/null +++ b/buckler_aiohttp.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +""" +Session interface middlewares to integrate the aiohttp app with Buckler. +""" +import json +from datetime import datetime + +import aiohttp +from aiohttp import web + +import config + +@web.middleware +async def buckler_session(request, handler): + """ + Verifies the user with the configured Buckler app and retrieves any + session data they may have. Redirects them to the login page otherwise. + """ + user_id = request.cookies.get('userid', '') + user_sid = request.cookies.get('session', '') + + url = config.buckler['url'] + '/get_session' + params = { + 'app_id': config.buckler['app_id'], + 'app_key': config.buckler['app_key'], + 'userid': user_id, + 'session': user_sid + } + async with aiohttp.ClientSession() as session: + async with session.get(url, params=params) as resp: + data = await resp.json() + if data.get('error'): + resp = web.HTTPFound(config.buckler['login_url']) + resp.set_cookie( + 'redirect', + request.url, + domain=config.server_domain, + secure=True, + httponly=True) + #samesite='strict') + raise resp + request['session'] = data['session_data'] + request['meta'] = data['meta'] + + resp = await handler(request) + + if request['session'] != data['session_data']: # session data modified + url = config.buckler['url'] + '/set_session' + data = json.dumps(request['session']) + session.post(url, params=params, data=data) # TODO: error handle? + + last_used = datetime.fromisoformat(request['meta']['last_used']) + now = datetime.now(last_used.tzinfo) + delta = now - last_used + if delta.seconds > 600: + resp.set_cookie( + 'userid', + user_id, + domain=config.server_domain, + max_age=30*24*60*60, + secure=True, + httponly=True) + #samesite='strict') + resp.set_cookie( + 'session', + user_sid, + domain=config.server_domain, + max_age=30*24*60*60, + secure=True, + httponly=True) + #samesite='strict') + + return resp diff --git a/config.py.template b/config.py.template new file mode 100644 index 0000000..1a20f3b --- /dev/null +++ b/config.py.template @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +""" +Configuration settings for the Scorch musc streaming app. +`url_prefix` is the root path you wish app to reside at +eg. https://example.com/scorch. +`buckler` specifies settings pertaining to the Buckler server. +""" +url_prefix = '/scorch' +music_dir = "/home/iou1name/music/Music" +db = { + 'database': 'scorch', + 'user': 'scorch', + 'password': """password""", + 'host': 'localhost', + 'port': 5432, +} + +buckler = { + 'url': "http://127.0.0.1:5400/buckler", + 'app_id': 1, + 'app_key': """password""", + 'login_url': "/buckler/login", +} diff --git a/stickup.py b/stickup.py new file mode 100755 index 0000000..204e5ab --- /dev/null +++ b/stickup.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" +An email management frontend. +""" +import asyncio + +from aiohttp import web +import jinja2 +import aiohttp_jinja2 +from aiohttp_jinja2 import render_template +import uvloop + +import config +import buckler_aiohttp + +uvloop.install() +routes = web.RouteTableDef() + +@routes.get('/', name='index') +async def index(request): + """The index page.""" + return render_template('index.html', request, locals()) + + +async def init_app(): + """Initializes the application.""" + app = web.Application(middlewares=[buckler_aiohttp.buckler_session]) + #app = web.Application() + aiohttp_jinja2.setup( + app, + trim_blocks=True, + lstrip_blocks=True, + undefined=jinja2.StrictUndefined, + loader=jinja2.FileSystemLoader('templates'), + ) + app.router.add_routes(routes) + app_wrap = web.Application() + app_wrap.add_subapp(config.url_prefix, app) + return app_wrap + +if __name__ == "__main__": + app = asyncio.run(init_app()) + aiohttp.web.run_app(app, host='0.0.0.0', port=5050) diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..dbf9b54 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,11 @@ + + + + Stickup + + + + +

This is a stickup!

+ +