start account page and add auth code management

This commit is contained in:
Izalia Mae 2020-02-22 07:07:35 -05:00
parent a896c11642
commit 84ded6a5af
12 changed files with 199 additions and 70 deletions

View file

@ -227,6 +227,18 @@ def remret(data):
put.del_retries(rowid)
def auth_code(data):
action = data.get('action')
if action in ['delete', 'regen']:
get.code(action)
act_msg = 'Updated' if action == 'regen' else 'Removed'
return f'{act_msg} authentication code'
return f'Invalid auth code action: {action}'
def run(action, data):
cmd = {
'settings': settings,
@ -238,11 +250,12 @@ def run(action, data):
'deny': deny,
'eject': eject,
'retry': retry,
'remret': remret
'remret': remret,
'authcode': auth_code
}
if action in cmd:
cmd[action](data)
return cmd[action](data)
else:
logging.error(f'Invalid admin post action: {action}')

View file

@ -11,7 +11,7 @@ Hash = HashContext()
Hash.setsalt()
KEY = LRUCache()
auth_code = None
@connection
def rsa_key(actor, db=None):
@ -176,15 +176,19 @@ def token(data, db=None):
if type(data) == str:
query_string = {'token': data}
if type(data) == int:
query_string = {'id': data}
elif type(data) == dict:
if 'token' in data.keys():
query_string = {'token': data['token']}
elif 'userid' in data.keys():
query_string = {'userid': data['userid']}
return query('tokens', query_string, one=False)
return query('tokens', query_string, one=False, sort='timestamp')
else:
logging.error(f'Invalid data for get.token: {data}')
return
return query('tokens', query_string)
@ -197,9 +201,18 @@ def verify_password(username, password, db=None):
return Hash.verify(password, user_data['password'])
def code(action=None):
global auth_code
if action in ['regen', 'delete']:
auth_code = randomgen() if action == 'regen' else None
return auth_code
# generate an auth code if there are no admin users
if len(user('all')) < 1:
auth_code = randomgen()
code('regen')
host = config('host')
if config('setup'):

View file

@ -244,3 +244,28 @@ def del_token(token, db=None):
db.delete('tokens', id=row['id'])
@newtrans
def acct_name(handle, username=None, db=None):
data = {'handle', handle}
if username:
data['username'] = username
user = get.user(handle)
if not user:
logging.warning(f'Invalid user: {handle}')
return
db.update('users', data, id=user['id'])
@newtrans
def password(handle, password, db=None):
user = get.user(handle)
if not user:
logging.warning(f'Invalid user: {handle}')
db.update('users', {'password': Hash.hash(password)}, id=user['id'])

View file

@ -12,7 +12,7 @@
--bg-color-lighter: {{lighten(background, 0.075)}};
--bg-dark: {{desaturate(darken(primary, 0.90), 0.5)}};
--primary: {{primary}};
--valid: {{desaturate(darken('green', 0.2), 0.25)}};
--valid: {{desaturate(darken('green', 0.5), 0.5)}};
--shadow-color: {{rgba('black', 0.5)}};
--shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
@ -164,4 +164,10 @@ tr:not(.header):hover td a {
background-color: {{desaturate(darken(error, 0.85), 0.50)}};
}
/* account page */
.tokens .active td {
background-color: var(--valid);
}
{% include 'layout.css' %}

View file

@ -251,6 +251,10 @@ tr:last-child .col2 {
width: 50%;
}
#code .col2 {
text-align: right;
}
/* Admin menu */
#setmenu {
@ -339,13 +343,26 @@ tr:last-child .col2 {
}
.account input:not([type="submit"]) {
width: calc(100% - 20px);
width: calc(50% - 20px);
}
.account input[type="submit"] {
min-width: 200px;
}
.account .tokens .col1 {
text-align: left;
min-width: 100px;
}
.account .tokens .col2 {
width: 50px;
}
.account .tokens .col2 input[type="submit"] {
min-width: 50px;
}
/* mobile/small screen */
@media (max-width : 1000px) {

View file

@ -2,27 +2,49 @@
- set title = 'Login'
- block content
%div{'class': 'section account token'}
%h2{'class': 'title'} Tokens
%table{'class': 'tokens'}
%tr{'class': 'header'}
%td{'class': 'col1'} Token ID
%td Timestamp
%td{'class': 'col2'} Action
-for token in tokens
-if token.token == cookie.token
-set current = 'active'
-else
-set current = ''
%tr{'class': 'token_row {{current}}'}
%td{'class': 'col1'}
-if current == 'active'
{{token.token}} ({{current}})
-else
{{token.token}}
%td
{{token.timestamp}}
%td{'class': 'col2'}
%form{'action': 'https://{{config.host}}/account/token', 'method': 'post'}
%input{'type': 'hidden', 'name': 'token', 'value': '{{token.token}}'}
%input{'type': 'submit', 'value': 'Delete'}
%div{'class': 'section account profile'}
%h2{'class': 'title'} Profile
%form{'action': 'https://{{config.host}}/admin/profile', 'method': 'post'}
%input{'type': 'text', 'name': 'displayname', 'placeholder': 'displayname'}
%form{'action': 'https://{{config.host}}/account/profile', 'method': 'post'}
%label< Display Name:
%input{'type': 'text', 'name': 'displayname', 'placeholder': 'displayname', 'value': '{{user.handle}}'}
%br
%input{'type': 'text', 'name': 'username', 'placeholder': 'username'}
%label< Username:
%input{'type': 'text', 'name': 'username', 'placeholder': 'username', 'value': '{{user.username}}'}
%br
%input{'type': 'submit', 'value': 'Submit'}
%div{'class': 'section account token'}
%h2{'class': 'title'} Tokens
%form{'action': 'https://{{config.host}}/admin/profile', 'method': 'post'}
%input{'type': 'text', 'name': 'displayname', 'placeholder': 'displayname'}
%br
%input{'type': 'text', 'name': 'username', 'placeholder': 'username'}
%br
%input{'type': 'submit', 'value': 'submit'}
%div{'class': 'section account password'}
%h2{'class': 'title'} Password
%form{'action': 'https://{{config.host}}/admin/profile/password', 'method': 'post'}
%form{'action': 'https://{{config.host}}/account/password', 'method': 'post'}
%input{'type': 'password', 'name': 'oldpassword', 'placeholder': 'old password'}
%br
%input{'type': 'password', 'name': 'password1', 'placeholder': 'new password'}
@ -30,3 +52,10 @@
%input{'type': 'password', 'name': 'password2', 'placeholder': 'new password again'}
%br
%input{'type': 'submit', 'value': 'Submit'}
%div{'class': 'section account delete'}
%h2{'class': 'title'} Delete Account
%form{'action': 'https://{{config.host}}/account/delete', 'method': 'post'}
%input{'type': 'password', 'name': 'password1', 'placeholder': 'new password'}
%br
%input{'type': 'submit', 'value': 'Delete'}

View file

@ -68,21 +68,25 @@
-if config.whitelist or config.require_approval:
-if instance.domain not in data.wldomains:
%form{'action': 'https://{{config.host}}/admin/add', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'WL Add'}
-else
%form{'action': 'https://{{config.host}}/admin/remove', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'WL Remove'}
%td{'class': 'action'}
%form{'action': 'https://{{config.host}}/admin/eject', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Remove'}
%td{'class': 'col2 action'}
%form{'action': 'https://{{config.host}}/admin/ban', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Ban'}
@ -94,6 +98,7 @@
%input{'class': 'domain', 'name': 'reason', 'placeholder': 'Ban reason'}
%td{'class': 'col2 mainban'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Ban'}
-if page == 'requests'
@ -112,10 +117,12 @@
%td{'class': 'col2'}
%form{'action': 'https://{{config.host}}/admin/accept', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{domain.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Accept'}
%form{'action': 'https://{{config.host}}/admin/deny', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{domain.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Deny'}
@ -136,6 +143,7 @@
%td{'class': 'col2'}
%form{'action': 'https://{{config.host}}/admin/remove', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{instance.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Remove'}
@ -145,6 +153,7 @@
%input{'class': 'domain', 'name': 'name', 'placeholder': 'domain'}
%td{'class': 'col2'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Add'}
-if page == 'domainbans'
@ -167,6 +176,7 @@
%td{'class': 'col2'}
%form{'action': 'https://{{config.host}}/admin/unban', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{domain.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Unban'}
@ -195,6 +205,7 @@
%td{'class': 'col2'}
%form{'action': 'https://{{config.host}}/admin/unban', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{user.user}}@{{user.domain}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Unban'}
@ -228,11 +239,13 @@
%td{'class': 'action'}
%form{'action': 'https://{{config.host}}/admin/retry', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{retry.id}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Retry'}
%td{'class': 'col2 action'}
%form{'action': 'https://{{config.host}}/admin/remret', 'method': 'post'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'name': 'name', 'value': '{{retry.id}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Remove'}
@ -360,4 +373,27 @@
%input{'type': 'text', 'name': 'host', 'placeholder': 'relay.example.com', 'value': '{{config.host}}'}
%div{'class': 'section settings', 'id': 'submit'}
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}
%input{'type': 'submit', 'value': 'Save Settings', 'class': 'submit'}
%div{'class': 'section settings', 'id': 'code'}
%p{'class': 'sec-header'}< Authentication Code
%div{'class': 'grid-container'}
%div{'class': 'grid-item col1'}
-if data.auth_code
%a{'href': 'https://{{config.host}}/register?code=={data.auth_code}', 'target': '_new'}<
{{data.auth_code}}
-else
No Code
%div{'class': 'grid-item col2'}
%form{'action': 'https://{{config.host}}/admin/auth_code', 'method': 'post'}<
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}<
%input{'type': 'hidden', 'name': 'action', 'value': 'delete'}<
%input{'type': 'submit', 'value': 'Delete'}<
%form{'action': 'https://{{config.host}}/admin/auth_code', 'method': 'post'}<
%input{'name': 'page', 'value': '{{page}}', 'hidden': None}<
%input{'type': 'hidden', 'name': 'action', 'value': 'regen'}<
%input{'type': 'submit', 'value': 'Regen'}<

View file

@ -36,6 +36,8 @@
-if user
.item
%a{'href': 'https://{{config.host}}/admin', 'target': '_self'} Admin
.item
%a{'href': 'https://{{config.host}}/account', 'target': '_self'} Account
.item
%a{'href': 'https://{{config.host}}/logout', 'target': '_self'} Logout
-else
@ -48,7 +50,7 @@
{{config.name}}
-if msg
%div{'id': 'message', 'class': 'section'}
%div{'id': 'message', 'class': 'section error'}
{{msg}}
-block content

View file

@ -6,12 +6,7 @@
%div{'class': 'section setup'}
%p{'class': 'sec-header'} Setup
%p<
The relay has been successfully setup stopped. Please start the relay again and setup an admin account via the register url that shows up in the console log
- else
-if msg:
%div{'class': 'section setup error'}
{{msg}}
The relay has been successfully setup. Please start the relay again and setup an admin account via the register url that shows up in the console log
%div{'class': 'section setup'}
%p{'class': 'sec-header'} Setup

View file

@ -52,7 +52,7 @@ async def authentication(request):
if not get.user('all') and not accept and request.path.startswith(('/admin', '/login')):
return response.redirect('/register')
if request.path.startswith(('/api', '/admin')) and (not token or not get.token(token)):
if request.path.startswith(('/api', '/admin', '/account')) and (not token or not get.token(token)):
if accept:
return error(request, 'Missing or invalid token', 401) if accept else await Login().get(request)

View file

@ -40,8 +40,8 @@ app.add_route(views.Nodeinfo.as_view(), '/nodeinfo/2.0.json')
# Register web frontend routes
app.add_route(views.Home.as_view(), '/')
app.add_route(views.Faq.as_view(), '/faq')
app.add_route(views.Account.as_view(), '/admin/account')
app.add_route(views.Account.as_view(), '/admin/account/<action>')
app.add_route(views.Account.as_view(), '/account')
app.add_route(views.Account.as_view(), '/account/<action>')
app.add_route(views.Admin.as_view(), '/admin')
app.add_route(views.Admin.as_view(), '/admin/<action>')
app.add_route(views.Login.as_view(), '/login')
@ -94,12 +94,11 @@ def main():
setup()
dblisten = get.config('address')
dbport = get.config('port')
dbwork = 1
dbport = int(get.config('port'))
build_templates()
observer = start_template_watcher()
app.run(host=dblisten, port=dbport, workers=dbwork, debug=False, access_log=False)
app.run(host=dblisten, port=dbport, workers=1, debug=False, access_log=False)
logging.info('Stopping template watcher')
observer.stop()

View file

@ -11,7 +11,7 @@ from sanic.exceptions import ServerError
from .log import logging
from .config import script_path, version
from .functions import cache, get_inbox
from .functions import cache, get_inbox, format_date
from .processing import process
from .messages import fetch
from .database import get, put
@ -24,8 +24,7 @@ keys = get.rsa_key('default')
async def reterror(view, request, error):
request['msg'] = error
return await view.get(view, request)
return await view.get(view, request, msg=error)
# ActivityPub-related Endpoints
@ -230,9 +229,8 @@ class Faq(HTTPMethodView):
class Admin(HTTPMethodView):
async def get(self, request, *args, action=None, **kwargs):
page = request['query'].get('page', 'instances')
msg = kwargs.get('msg')
async def get(self, request, *args, action=None, msg=None, **kwargs):
page = kwargs.get('page', request['query'].get('page', 'instances'))
if action:
return error(request, f'Not found: {request.path}', 404)
@ -246,7 +244,8 @@ class Admin(HTTPMethodView):
'wldomains': [row['domain'] for row in whitelist],
'requests': get.request('all'),
'domainban': admin.get_domainbans(),
'userban': admin.get_userbans()
'userban': admin.get_userbans(),
'auth_code': get.auth_code
}
context = {'msg': msg, 'data': data}
@ -255,21 +254,33 @@ class Admin(HTTPMethodView):
async def post(self, request, action=''):
action = re.sub(r'[^a-z]+', '', action.lower())
data = request['form']
page = data.get('page')
page = request['form'].get('page', 'instances')
msg = admin.run(action, data)
return await self.get(request, msg=msg)
return await self.get(request, msg=msg, page=page)
class Account(HTTPMethodView):
async def get(self, request):
context = {}
token = request.cookies.get('token')
token_data = get.token(token)
if not token_data:
return await Login().get(request, msg='Invalid token')
user = get.user(token_data['userid'])
tokens = get.token({'userid': token_data['userid']})
context = {
'tokens': [{'id': token['id'], 'token': token['token'], 'timestamp': format_date(token['timestamp'])} for token in tokens],
'user': user
}
return render('account.html', request, context)
async def post(self, requrest, action=''):
action = re.sub(r'[^a-z]+', '', action.lower())
pass
return await self.get(request)
class Cache(HTTPMethodView):
@ -287,13 +298,7 @@ class Cache(HTTPMethodView):
class Login(HTTPMethodView):
async def get(self, request):
try:
msg = request['msg']
except KeyError:
msg = None
async def get(self, request, msg=None):
data = {
'msg': msg,
'code': request['query'].get('code')
@ -326,33 +331,20 @@ class Login(HTTPMethodView):
class Logout(HTTPMethodView):
async def get(self, request):
token = request.cookies.get('token')
resp = response.redirect('/')
if not token:
return response.redirect('/')
put.del_token(token)
resp = response.redirect('/')
del resp.cookies['token']
if token:
put.del_token(token)
del resp.cookies['token']
return resp
class Register(HTTPMethodView):
async def get(self, request):
try:
msg = request['msg']
except KeyError:
msg = None
try:
code = request['codee']
except KeyError:
code = request['query'].get('code')
async def get(self, request, msg=None):
data = {
'msg': msg,
'code': code
'code': request['query'].get('code')
}
return render('register.html', request, data)
@ -391,6 +383,8 @@ class Register(HTTPMethodView):
resp.cookies['token'] = tokendata['token']
resp.cookies['token']['domain'] = host
get.code('delete')
return resp