fourth commit
This commit is contained in:
parent
04156e39b3
commit
6acc767923
168
buckler.py
168
buckler.py
|
@ -2,6 +2,7 @@
|
||||||
"""
|
"""
|
||||||
A security shield for protecting a number of small web applications.
|
A security shield for protecting a number of small web applications.
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import secrets
|
import secrets
|
||||||
import functools
|
import functools
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
@ -14,7 +15,9 @@ from passlib.hash import argon2
|
||||||
import asyncpg
|
import asyncpg
|
||||||
import uvloop
|
import uvloop
|
||||||
|
|
||||||
|
import mail
|
||||||
import config
|
import config
|
||||||
|
import validation
|
||||||
|
|
||||||
uvloop.install()
|
uvloop.install()
|
||||||
routes = web.RouteTableDef()
|
routes = web.RouteTableDef()
|
||||||
|
@ -28,18 +31,19 @@ def auth_required(func):
|
||||||
login_url = request.app.router['login'].url_for()
|
login_url = request.app.router['login'].url_for()
|
||||||
sid = request.cookies.get('session')
|
sid = request.cookies.get('session')
|
||||||
try:
|
try:
|
||||||
userid = int(request.cookies.get('userid'))
|
user_id = int(request.cookies.get('userid', '0'))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
userid = None
|
user_id = None
|
||||||
if not sid or not userid:
|
if not sid or not user_id:
|
||||||
raise web.HTTPFound(location=login_url)
|
raise web.HTTPFound(location=login_url)
|
||||||
async with request.app['pool'].acquire() as conn:
|
async with request.app['pool'].acquire() as conn:
|
||||||
sessions = await conn.fetch(
|
sessions = await conn.fetch(
|
||||||
"SELECT * FROM user_session "
|
"SELECT * FROM user_session "
|
||||||
"WHERE user_id = $1 AND expiry > NOW() ",
|
"WHERE user_id = $1 AND expires > NOW()",
|
||||||
userid)
|
user_id)
|
||||||
session = [s for s in sessions if s.get('id') == sid]
|
session = [s for s in sessions if s.get('id') == sid]
|
||||||
if session:
|
if session:
|
||||||
|
resp = await func(request, *args, **kwargs)
|
||||||
session = session[0]
|
session = session[0]
|
||||||
tz = session['last_used'].tzinfo
|
tz = session['last_used'].tzinfo
|
||||||
delta = datetime.now(tz) - session['last_used']
|
delta = datetime.now(tz) - session['last_used']
|
||||||
|
@ -47,14 +51,12 @@ def auth_required(func):
|
||||||
async with request.app['pool'].acquire() as conn:
|
async with request.app['pool'].acquire() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"UPDATE user_session SET last_used = NOW(), "
|
"UPDATE user_session SET last_used = NOW(), "
|
||||||
"expiry = NOW() + INTERVAL '30 DAYS'"
|
"expires = NOW() + INTERVAL '30 DAYS' "
|
||||||
"WHERE user_id = $1",
|
"WHERE user_id = $1",
|
||||||
userid)
|
user_id)
|
||||||
resp = await func(request, *args, **kwargs)
|
|
||||||
if delta.seconds > 600:
|
|
||||||
resp.set_cookie(
|
resp.set_cookie(
|
||||||
'userid',
|
'userid',
|
||||||
userid,
|
user_id,
|
||||||
max_age=30*24*60*60,
|
max_age=30*24*60*60,
|
||||||
secure=True,
|
secure=True,
|
||||||
httponly=True)
|
httponly=True)
|
||||||
|
@ -81,12 +83,13 @@ async def index(request):
|
||||||
@routes.post(config.url_prefix + '/login', name='login')
|
@routes.post(config.url_prefix + '/login', name='login')
|
||||||
async def login(request):
|
async def login(request):
|
||||||
"""Handle login."""
|
"""Handle login."""
|
||||||
|
login_failed = False
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
return render_template("login.html", request, locals())
|
return render_template("login.html", request, locals())
|
||||||
|
|
||||||
data = await request.post()
|
form = await request.post()
|
||||||
username = data.get('username')
|
username = form.get('username')
|
||||||
password = data.get('password')
|
password = form.get('password')
|
||||||
|
|
||||||
async with request.app['pool'].acquire() as conn:
|
async with request.app['pool'].acquire() as conn:
|
||||||
user_info = await conn.fetchrow(
|
user_info = await conn.fetchrow(
|
||||||
|
@ -94,6 +97,7 @@ async def login(request):
|
||||||
username)
|
username)
|
||||||
|
|
||||||
if not user_info:
|
if not user_info:
|
||||||
|
login_failed = True
|
||||||
return render_template("login.html", request, locals())
|
return render_template("login.html", request, locals())
|
||||||
|
|
||||||
if argon2.verify(password, user_info['password_hash']):
|
if argon2.verify(password, user_info['password_hash']):
|
||||||
|
@ -121,6 +125,7 @@ async def login(request):
|
||||||
httponly=True)
|
httponly=True)
|
||||||
raise resp
|
raise resp
|
||||||
else:
|
else:
|
||||||
|
login_failed = True
|
||||||
return render_template("login.html", request, locals())
|
return render_template("login.html", request, locals())
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,27 +133,130 @@ async def login(request):
|
||||||
@routes.post(config.url_prefix + '/register', name='register')
|
@routes.post(config.url_prefix + '/register', name='register')
|
||||||
async def register(request):
|
async def register(request):
|
||||||
"""Register new accounts."""
|
"""Register new accounts."""
|
||||||
if request.method == 'POST':
|
confirm_token = request.query.get('confirm')
|
||||||
data = await request.post()
|
if confirm_token:
|
||||||
username = data.get('username')
|
async with request.app['pool'].acquire() as conn:
|
||||||
email = data.get('email')
|
confirm = await conn.fetchrow(
|
||||||
password = data.get('password')
|
"SELECT * FROM email_confirmation WHERE token = $1",
|
||||||
password_verify = data.get('password_verify')
|
confirm_token)
|
||||||
|
if confirm:
|
||||||
errors = {}
|
|
||||||
if password != password_verify:
|
|
||||||
errors[''] = ''
|
|
||||||
|
|
||||||
pw_hash = argon2.hash(password)
|
|
||||||
async with request.app['pool'].acquire() as conn:
|
async with request.app['pool'].acquire() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"INSERT INTO user_info (username, email, password_hash) "
|
"UPDATE user_info SET active = TRUE WHERE id = $1",
|
||||||
"VALUES ($1, $2, $3)",
|
confirm['user_id'])
|
||||||
username, email, pw_hash)
|
await conn.execute(
|
||||||
login_url = request.app.router['login'].url_for()
|
"DELETE FROM email_confirmation WHERE user_id = $1",
|
||||||
raise web.HTTPFound(location=login_url)
|
confirm['user_id'])
|
||||||
|
message = "Success!" # TODO: more informative message
|
||||||
|
else:
|
||||||
|
message = "Invalid confirmation token."
|
||||||
|
return render_template("register_result.html", request, locals())
|
||||||
|
|
||||||
|
invite_token = request.query.get('invite')
|
||||||
|
if invite_token:
|
||||||
|
async with request.app['pool'].acquire() as conn:
|
||||||
|
invite = await conn.fetchrow(
|
||||||
|
"SELECT * FROM invite WHERE token = $1",
|
||||||
|
invite_token)
|
||||||
|
if invite:
|
||||||
|
if request.method == 'GET':
|
||||||
|
errors = {}
|
||||||
return render_template("register.html", request, locals())
|
return render_template("register.html", request, locals())
|
||||||
|
|
||||||
|
form = await request.post()
|
||||||
|
errors = await validation.validate_register(request, form)
|
||||||
|
if any(errors.values()):
|
||||||
|
return render_template("register.html", request, locals())
|
||||||
|
|
||||||
|
username = form.get('username')
|
||||||
|
email = form.get('email')
|
||||||
|
password = form.get('password')
|
||||||
|
pw_hash = argon2.hash(password)
|
||||||
|
async with request.app['pool'].acquire() as conn:
|
||||||
|
user = await conn.fetchrow(
|
||||||
|
"INSERT INTO user_info (username, email, password_hash) "
|
||||||
|
"VALUES ($1, $2, $3) RETURNING id",
|
||||||
|
username, email, pw_hash)
|
||||||
|
await conn.execute(
|
||||||
|
"DELETE FROM invite WHERE token = $1",
|
||||||
|
invite_token)
|
||||||
|
await mail.send_confirmation(request, user['id'], email)
|
||||||
|
message = "An email has been sent." # TODO: more better
|
||||||
|
else:
|
||||||
|
message = "Invalid invitation token."
|
||||||
|
return render_template("register_result.html", request, locals())
|
||||||
|
|
||||||
|
message = "Secret club only."
|
||||||
|
return render_template("register_result.html", request, locals())
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get(config.url_prefix + '/get_session', name='get_session')
|
||||||
|
async def get_session(request):
|
||||||
|
"""Returns a user's application session."""
|
||||||
|
# TODO: only allow LAN IPs
|
||||||
|
app_id = request.query.get('app_id')
|
||||||
|
app_key = request.query.get('app_key')
|
||||||
|
user_id = request.query.get('userid')
|
||||||
|
user_sid = request.query.get('session')
|
||||||
|
|
||||||
|
try:
|
||||||
|
app_id = int(app_id)
|
||||||
|
user_id = int(user_id)
|
||||||
|
assert all((app_id, app_key, user_id, user_sid))
|
||||||
|
except (ValueError, AssertionError):
|
||||||
|
return web.json_response({'error': "Invalid credentials."})
|
||||||
|
|
||||||
|
conn = await request.app['pool'].acquire()
|
||||||
|
app = await conn.fetchrow("SELECT * FROM app_info WHERE id = $1", app_id)
|
||||||
|
if app:
|
||||||
|
if argon2.verify(app_key, app['key_hash']):
|
||||||
|
sessions = await conn.fetch(
|
||||||
|
"SELECT * FROM user_session "
|
||||||
|
"WHERE user_id = $1 AND expires > NOW()",
|
||||||
|
user_id)
|
||||||
|
session = [s for s in sessions if s.get('id') == user_sid]
|
||||||
|
if session:
|
||||||
|
session = session[0]
|
||||||
|
data = await conn.fetchrow(
|
||||||
|
"SELECT user_info.username, app_user.session_data "
|
||||||
|
"FROM user_info, app_user "
|
||||||
|
"WHERE user_info.id = $1 AND app_user.app_id = $2",
|
||||||
|
session['user_id'], app['id'])
|
||||||
|
|
||||||
|
tz = session['last_used'].tzinfo
|
||||||
|
delta = datetime.now(tz) - session['last_used']
|
||||||
|
if delta.seconds > 600:
|
||||||
|
await conn.execute(
|
||||||
|
"UPDATE user_session SET last_used = NOW(), "
|
||||||
|
"expires = NOW() + INTERVAL '30 DAYS' "
|
||||||
|
"WHERE user_id = $1",
|
||||||
|
user_id)
|
||||||
|
await conn.close()
|
||||||
|
|
||||||
|
data_meta = dict(data)
|
||||||
|
data_meta.update(
|
||||||
|
{'last_used': session['last_used'].isoformat()})
|
||||||
|
data = {
|
||||||
|
'meta': data_meta,
|
||||||
|
'session_data': json.loads(data_meta.pop('session_data'))}
|
||||||
|
|
||||||
|
print(data)
|
||||||
|
return web.json_response(data)
|
||||||
|
else:
|
||||||
|
error = {'error': "User ID or Session ID invalid."}
|
||||||
|
else:
|
||||||
|
error = {'error': "App ID or Key invalid."}
|
||||||
|
else:
|
||||||
|
error = {'error': "App ID or Key invalid."}
|
||||||
|
await conn.close()
|
||||||
|
return web.json_response(error)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post(config.url_prefix + '/set_session', name='set_session')
|
||||||
|
async def set_session(request):
|
||||||
|
"""Allows an application to set a user app session."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def init_app():
|
async def init_app():
|
||||||
"""Initializes the application."""
|
"""Initializes the application."""
|
||||||
|
|
34
buckler.sql
34
buckler.sql
|
@ -2,7 +2,10 @@ CREATE TABLE IF NOT EXISTS user_info (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
username TEXT NOT NULL,
|
username TEXT NOT NULL,
|
||||||
email TEXT NOT NULL,
|
email TEXT NOT NULL,
|
||||||
password_hash TEXT
|
password_hash TEXT,
|
||||||
|
date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
admin BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS user_session (
|
CREATE TABLE IF NOT EXISTS user_session (
|
||||||
|
@ -10,6 +13,33 @@ CREATE TABLE IF NOT EXISTS user_session (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
ip_address INET NOT NULL,
|
ip_address INET NOT NULL,
|
||||||
date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
expiry TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '30 DAYS',
|
expires TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '30 DAYS',
|
||||||
last_used TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
last_used TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invite (
|
||||||
|
email TEXT PRIMARY KEY,
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
expires TIMESTAMP WITH TIME ZONE DEFAULT NOW() + INTERVAL '24 HOURS'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS email_confirmation (
|
||||||
|
user_id INTEGER PRIMARY KEY references user_info(id) ON DELETE CASCADE,
|
||||||
|
token TEXT NOT NULL,
|
||||||
|
date_created TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS app_info (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
key_hash TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS app_user (
|
||||||
|
user_id INTEGER references user_info(id) ON DELETE CASCADE,
|
||||||
|
app_id INTEGER references app_info(id) ON DELETE CASCADE,
|
||||||
|
session_data JSON,
|
||||||
|
PRIMARY KEY (user_id, app_id)
|
||||||
|
);
|
||||||
|
|
12
config.py.template
Normal file → Executable file
12
config.py.template
Normal file → Executable file
|
@ -1,16 +1,26 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Configuration settings for Buckler.
|
Configuration settings for Buckler.
|
||||||
|
`server_domain` is the server's domain.
|
||||||
`url_prefix` is the root path you wish app to reside at
|
`url_prefix` is the root path you wish app to reside at
|
||||||
eg. https://example.com/buckler
|
eg. https://example.com/buckler
|
||||||
`db` specifies parameters for connecting to the PostgreSQL database.
|
`db` specifies parameters for connecting to the PostgreSQL database.
|
||||||
|
`email` specifies parameters for connecting to the SMTP server.
|
||||||
"""
|
"""
|
||||||
|
server_domain = 'https://steelbea.me'
|
||||||
url_prefix = '/buckler'
|
url_prefix = '/buckler'
|
||||||
|
|
||||||
db = {
|
db = {
|
||||||
'database': 'buckler',
|
'database': 'buckler',
|
||||||
'user': 'buckler',
|
'user': 'buckler',
|
||||||
'password': 'password',
|
'password': """password""",
|
||||||
'host': 'localhost',
|
'host': 'localhost',
|
||||||
'port': 5432,
|
'port': 5432,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
email = {
|
||||||
|
'host': 'mail.steelbea.me',
|
||||||
|
'port': 465,
|
||||||
|
'user': 'buckler@steelbea.me',
|
||||||
|
'password': """password"""
|
||||||
|
}
|
||||||
|
|
56
mail.py
Normal file
56
mail.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Tools for sending emails.
|
||||||
|
"""
|
||||||
|
import email.mime.text
|
||||||
|
import smtplib
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
import config
|
||||||
|
|
||||||
|
def send_mail(to_addr, subject, body):
|
||||||
|
"""
|
||||||
|
Sends an email.
|
||||||
|
"""
|
||||||
|
msg = email.mime.text.MIMEText(body, 'plain', 'utf8')
|
||||||
|
msg['From'] = config.email['user']
|
||||||
|
msg['To'] = to_addr
|
||||||
|
msg['Subject'] = subject
|
||||||
|
|
||||||
|
with smtplib.SMTP_SSL(config.email['host'],config.email['port']) as server:
|
||||||
|
server.login(config.email['user'], config.email['password'])
|
||||||
|
server.send_message(msg)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_invite(request, to_addr):
|
||||||
|
"""
|
||||||
|
Sends an invitation.
|
||||||
|
"""
|
||||||
|
token = secrets.token_urlsafe(32)
|
||||||
|
async with request.app['pool'].acquire() as conn:
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO invite (email, token) "
|
||||||
|
"VALUES ($1, $2)",
|
||||||
|
to_addr, token)
|
||||||
|
d = {'invite': token}
|
||||||
|
invite_url = request.app.router['register'].url_for().with_query(d)
|
||||||
|
invite_url = config.server_domain + config.url_prefix + invite_url
|
||||||
|
body = "Buckle up.\n" + invite_url
|
||||||
|
send_mail(to_addr, "Buckler Invite", body)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_confirmation(request, user_id, to_addr):
|
||||||
|
"""
|
||||||
|
Sends an email confirmation.
|
||||||
|
"""
|
||||||
|
token = secrets.token_urlsafe(32)
|
||||||
|
async with request.app['pool'].acquire() as conn:
|
||||||
|
await conn.execute(
|
||||||
|
"INSERT INTO email_confirmation (user_id, token) "
|
||||||
|
"VALUES ($1, $2)",
|
||||||
|
user_id, token)
|
||||||
|
d = {'confirm': token}
|
||||||
|
confirm_url = request.app.router['register'].url_for().with_query(d)
|
||||||
|
confirm_url = config.server_domain + str(confirm_url)
|
||||||
|
body = "Buckle up.\n" + confirm_url
|
||||||
|
send_mail(to_addr, "Buckler Invite", body)
|
|
@ -10,6 +10,9 @@
|
||||||
<input id="username" name="username" type="text"><br>
|
<input id="username" name="username" type="text"><br>
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input id="password" name="password" type="password"><br>
|
<input id="password" name="password" type="password"><br>
|
||||||
|
{% if login_failed %}
|
||||||
|
<ul><li>Username and/or password incorrect</li></ul>
|
||||||
|
{% endif %}
|
||||||
<input type="submit" value="Login">
|
<input type="submit" value="Login">
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -5,15 +5,36 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Buckler Register</h1>
|
<h1>Buckler Register</h1>
|
||||||
<form action="{{ request.app.router['register'].url_for() }}" method="post" enctype="application/x-www-form-urlencoded">
|
<form method="POST" enctype="application/x-www-form-urlencoded">
|
||||||
<label for="username">Username</label>
|
<label for="username">Username</label>
|
||||||
<input id="username" name="username" type="text" minlength="3" maxlength="20"><br>
|
<input id="username" name="username" type="text" minlength="3" maxlength="20"><br>
|
||||||
|
{% if errors['username'] %}
|
||||||
|
<ul>
|
||||||
|
{% for error in errors['username'] %}
|
||||||
|
<li class="error">{{ error }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
<label for="email">Email</label>
|
<label for="email">Email</label>
|
||||||
<input id="email" name="email" type="email"><br>
|
<input id="email" name="email" type="email"><br>
|
||||||
|
{% if errors['email'] %}
|
||||||
|
<ul>
|
||||||
|
{% for error in errors['email'] %}
|
||||||
|
<li class="error">{{ error }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
<label for="password">Password</label>
|
<label for="password">Password</label>
|
||||||
<input id="password" name="password" type="password" minlength="8" maxlength="10240"><br>
|
<input id="password" name="password" type="password" minlength="8" maxlength="10240"><br>
|
||||||
<label for="password_verify">Verify Password</label>
|
<label for="password_verify">Verify Password</label>
|
||||||
<input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"><br>
|
<input id="password_verify" name="password_verify" type="password" minlength="8" maxlength="10240"><br>
|
||||||
|
{% if errors['password'] %}
|
||||||
|
<ul>
|
||||||
|
{% for error in errors['password'] %}
|
||||||
|
<li class="error">{{ error }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
<input type="submit" value="Register">
|
<input type="submit" value="Register">
|
||||||
</form>
|
</form>
|
||||||
</body>
|
</body>
|
||||||
|
|
9
templates/register_result.html
Normal file
9
templates/register_result.html
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Buckler - Register</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ message }}
|
||||||
|
</body>
|
||||||
|
</html>
|
33
validation.py
Normal file
33
validation.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Functions for validating forms.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def validate_register(request, form):
|
||||||
|
"""Validate data from the registration form."""
|
||||||
|
username = form.get('username')
|
||||||
|
email = form.get('email')
|
||||||
|
password = form.get('password')
|
||||||
|
password_verify = form.get('password_verify')
|
||||||
|
|
||||||
|
async with request.app['pool'].acquire() as conn:
|
||||||
|
users = await conn.fetch(
|
||||||
|
"SELECT username, email FROM user_info "
|
||||||
|
"WHERE username = $1 OR email = $2",
|
||||||
|
username, email)
|
||||||
|
|
||||||
|
errors = {'password': [], 'username': [], 'email': []}
|
||||||
|
if password != password_verify:
|
||||||
|
errors['password'].append("Passwords do not match.")
|
||||||
|
if len(password) < 8 or len(password) > 10240:
|
||||||
|
errors['password'].append(
|
||||||
|
"Password must be between 8 and 10240 characters long.")
|
||||||
|
if len(username) < 3 or len(username) > 20:
|
||||||
|
errors['username'].append(
|
||||||
|
"Username must be between 3 and 20 characters long.")
|
||||||
|
if username in [user['username'] for user in users]:
|
||||||
|
errors['username'].append("Username already in use.")
|
||||||
|
if email in [user['email'] for user in users]:
|
||||||
|
errors['email'].append("Email already in use.")
|
||||||
|
|
||||||
|
return errors
|
Loading…
Reference in New Issue
Block a user