This commit is contained in:
Izalia Mae 2021-08-22 18:03:37 -04:00
parent ab33e0db9c
commit 749b1f3619
12 changed files with 204 additions and 45 deletions

View file

@ -18,11 +18,12 @@ setupvenv:
$(PYTHON_SYS) -m venv $(VENV)
$(PYTHON) -m pip install -U pip
$(PYTHON) -m pip install wheel, setuptools
# $(PYTHON) -m pip install -r requirements.txt
$(PYTHON) -m pip install -r requirements.txt
setupizzylib:
$(PYTHON) -m pip uninstall -y izzylib-base izzylib-templates izzylib-sql izzylib-requests-client izzylib-dbus
$(PYTHON) -m pip install "git+https://git.barkshark.xyz/izaliamae/izzylib.git@rework#egg=izzylib-base&subdirectory=base"
$(PYTHON) -m pip install "git+https://git.barkshark.xyz/izaliamae/izzylib.git@rework#egg=izzylib-password-hasher&subdirectory=hasher"
$(PYTHON) -m pip install "git+https://git.barkshark.xyz/izaliamae/izzylib.git@rework#egg=izzylib-http-server&subdirectory=http_server"
$(PYTHON) -m pip install "git+https://git.barkshark.xyz/izaliamae/izzylib.git@rework#egg=izzylib-templates&subdirectory=template"
$(PYTHON) -m pip install "git+https://git.barkshark.xyz/izaliamae/izzylib.git@rework#egg=izzylib-sql&subdirectory=sql"

View file

@ -1,10 +1,9 @@
import getpass, sys
from envbash import load_envbash
from izzylib import DotDict, Path, boolean, logging
from os import getcwd, environ as env
from izzylib import DotDict, Path, logging
scriptpath = Path(__file__).resolve.parent
@ -36,7 +35,7 @@ SYNC_DB_HOST=/var/run/postgresql
SYNC_DB_PORT=5432
SYNC_DB_NAME=pywebsync
SYNC_DB_USER={getpass.getuser()}
SYNC_DB_PASS=changeme
#SYNC_DB_PASS=changeme
SYNC_DB_SSL=False
""")
@ -51,5 +50,21 @@ config = DotDict(
alt_hosts = env.get('SYNC_ALT_HOSTS', '').split(',')
)
if env.get('SYNC_DB_TYPE', 'sqlite').lower() == 'sqlite':
dbconfig = DotDict(
name = path.data.join('database.db')
)
else:
dbconfig = DotDict({
'host': env.get('SYNC_DB_HOST', '/var/run/postgresql'),
'port': int(env.get('SYNC_DB_PORT', 5432)),
'name': env.get('SYNC_DB_NAME', 'pywebsync'),
'user': env.get('SYNC_DB_USER', getpass.getuser()),
'pass': env.get('SYNC_DB_PASS'),
'ssl': boolean(env.get('SYNC_DB_SSL'))
})
config.host = env.get('SYNC_HOST', f'{config.listen}:{config.port}')
logging.set_config('level', env.get('SYNC_LOG_LEVEL', 'info'))

View file

View file

@ -1,4 +0,0 @@
-extends 'base.haml'
-set page = 'Account'
-block content
#title -> =page

View file

@ -1,4 +0,0 @@
-extends 'base.haml'
-set page = 'Admin'
-block content
#title -> =page

View file

@ -2,14 +2,14 @@
-set page = 'Login'
-block head
%link rel='stylesheet' type='text/css' href='{{cfg.proto}}://{{cfg.web_host}}/style/logreg_form.css'
%link rel='stylesheet' type='text/css' href='/style/logreg_form.css'
-block content
#title -> =page
%form(action='{{cfg.proto}}://{{cfg.web_host}}/login', method='post')
%form(action='/login', method='post')
%center
%input(type='text', name='username', placeholder='username')
%input(type='text', name='handle', placeholder='username', value='{{handle if handle else ""}}')
%br
%input(type='password', name='password', placeholder='password'})
%br
%input(type='submit', value='submit', class='button')
%input(type='submit', value='Login', class='button')

View file

@ -2,17 +2,18 @@
-set page = "Register"
-block head
%link rel='stylesheet' type='text/css' href='{{cfg.proto}}://{{cfg.web_host}}/style/logreg_form.css'
%link rel='stylesheet' type='text/css' href='/style/logreg_form.css'
-block content
#title -> =page
%form(action="https://{{cfg.web_host}}/register", method="post", autocomplete="new-password")
%form(action="/register", method="post", autocomplete="new-password")
%center
%input(type="text", name="username", placeholder="username")
%br
%input(type="text", name="password", placeholder="password")
%br
%input(type="text", name="password2", placeholder="password again")
%br
%input(type="text", name="display", placeholder="display name", value="{{form.display or ''}}")
%input(type="text", name="handle", placeholder="username", value="{{form.handle or ''}}")
%input(type="password", name="password", placeholder="password", value="{{form.password or ''}}")
%input(type="password", name="password2", placeholder="password again", value="{{form.password2 or ''}}")
%img(src="data:image/webp;base64,{{captcha_base64}}")
%input(type="text", name="captcha_text", placeholder="image text (case insensitive)")
%input(type="hidden", name="captcha_base64", value="{{captcha_base64}}")
%input(type="submit", value="submit", class="button")

View file

@ -1,7 +1,8 @@
input {
padding: 3px;
padding: 5px;
text-align: center;
border-radius: 5px;
display: block;
}
input[type='submit'] {

View file

@ -0,0 +1,37 @@
from izzylib.http_server import MiddlewareBase, Response
from .database import db
auth_paths = [
'/login',
'/register'
]
reg_paths = [
'/account',
'/admin',
'/logout'
]
class LoginDetect(MiddlewareBase):
async def handler(self, request):
response = Response(self.app, request)
code = request.cookies.get('token')
token = db.get('token', code)
if token and token.user:
request.ctx.token = token
request.ctx.user = token.user
request.user_level = token.user.level
else:
request.ctx.token = None
request.ctx.user = None
if any(map(request.path.startswith, reg_paths)) and not token:
return response.redir('/login')
if (any(map(request.path.startswith, auth_paths))) and token and token.user:
return response.redir('/account')

View file

@ -1,8 +1,28 @@
import sys
from izzylib import logging
from izzylib.http_server import Application, View
from os import environ as env
from . import views, __software__, __version__
from . import middleware, views, __software__, __version__
from .config import config, path
from .database import OperationalError, db, version
try:
dbversion = db.get('config', 'version')
if not dbversion:
logging.error('Database has not been setup. Please run `make setup` first.')
sys.exit()
if dbversion < version:
logging.error('Database needs to be migrated. Please run `make migrate`.')
sys.exit()
except OperationalError as e:
logging.error('Database has not been setup. Please run `make setup` first.')
sys.exit()
app = Application(
@ -10,9 +30,12 @@ app = Application(
version = __version__,
git_repo = 'https://git.barkshark.xyz/izaliamae/pyweb-sync',
tpl_search = [path.frontend],
access_log = True,
**config
)
app.add_middleware(middleware.LoginDetect)
## Frontend
for cls in dir(views):

View file

@ -1,9 +1,19 @@
from izzylib import DotDict, logging
from izzylib.cache import TtlCache
from izzylib.http_server import UserLevel, View
from ..config import config
from ..database import db
from ..functions import Captcha
cache = DotDict(
captcha = TtlCache(ttl='30m')
)
class Home(View):
paths = ['/']
menu = ('Home', paths[0])
async def get(self, request, response):
return response.template('page/home.haml')
@ -11,42 +21,119 @@ class Home(View):
class Register(View):
paths = ['/register']
menu = ('Register', paths[0])
async def get(self, request, response):
return response.template('page/register.haml')
async def get(self, request, response, data={}, msg=None, error=None):
capt = Captcha()
if logging.get_config('level') == logging.Levels.DEBUG:
capt.linecolor = (0,0,0,0)
captcha_base64 = capt.get_captcha().decode()
cache.captcha.store(captcha_base64, capt)
data = {
'captcha_base64': captcha_base64,
'form': data
}
if msg:
data['message'] = msg
if error:
data['error'] = error
return response.template('page/register.haml', data)
async def post(self, request, response):
pass
capt_img = request.data.form.pop('captcha_base64', None)
capt_text = request.data.form.pop('captcha_text', '')
capt = cache.captcha.pop(capt_img, None)
handle = request.data.form.get('handle')
password = request.data.form.get('password')
password2 = request.data.form.get('password2')
display = request.data.form.get('display', handle)
if not handle:
return await self.get(request, response, data=request.data.form, error='Missing handle')
if not password or not password2:
return await self.get(request, response, data=request.data.form, error='Missing one or both passwords')
if password != password2:
return await self.get(request, response, data=request.data.form, error='Passwords do not match')
if not capt_img:
return await self.get(request, response, data=request.data.form, error='Missing captcha')
if not capt:
return await self.get(request, response, data=request.data.form, error='Invalid captcha')
if capt_text.lower() != capt.text.lower():
return await self.get(request, response, data=request.data.form, error='Captcha text does not match')
user = db.put('user', handle, password, display)
token = db.put('token', user.id)
token.set_cookie(response)
return response.redir('/account/home')
class Login(View):
paths = ['/login']
menu = ('Login', paths[0])
async def get(self, request, response):
return response.template('page/login.haml')
async def get(self, request, response, msg=None, error=None, handle=None):
data = {'handle': handle}
if msg:
data['message'] = msg
if error:
data['error'] = error
return response.template('page/login.haml', context=data)
async def post(self, request, response):
pass
username = request.data.form.get('handle')
password = request.data.form.get('password')
if not username:
return await self.get(request, response, error='Missing username')
if not password:
return await self.get(request, response, error='Missing password', handle=username)
try:
if db.get('password', username, password):
return response.redir('/account/home')
except AttributeError:
pass
return await self.get(request, response, error='Invalid username or password', handle=username)
class Logout(View):
paths = ['/logout']
menu = ('Logout', paths[0], UserLevel.USER)
async def get(self, request, response):
return response.template('page/logout.haml')
print(request.ctx.token)
response = await Login.get(self, request, response, msg='Logged out')
print(request.ctx.token)
request.ctx.token.del_cookie(response)
return response
class Account(View):
paths = ['/account']
menu = ('Account', paths[0], UserLevel.USER)
paths = ['/account', '/account/<page>']
async def get(self, request, response):
return response.template('page/account.haml')
async def get(self, request, response, page=None):
if not page:
return response.redir('/account/home')
return response.template(f'page/account/{page}.haml')
async def post(self, request, response):
@ -54,11 +141,13 @@ class Account(View):
class Admin(View):
paths = ['/admin']
menu = ('Admin', paths[0], UserLevel.ADMIN)
paths = ['/admin', '/admin/<page>']
async def get(self, request, response):
return response.template('page/admin.haml')
if not page:
return response.redir('/admin/home')
return response.template(f'page/admin/{page}.haml')
async def post(self, request, response):

View file

@ -1,5 +1,5 @@
{
"bin": "python3",
"bin": "~/.local/share/venv/pyweb-sync/bin/python3",
"args": ["-m", "pyweb_sync"],
"env": {"LOG_LEVEL": "DEBUG"},
"path": "/home/zoey/data/src/mine/pyweb-sync",