Development
This commit is contained in:
parent
8766c6c9a2
commit
ee31a18ac6
43 changed files with 54 additions and 48 deletions
107
docker/routlin-dash/app/action_create_account.py
Normal file
107
docker/routlin-dash/app/action_create_account.py
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
from flask import Blueprint, request, session, redirect, flash
|
||||
import json, os, bcrypt, secrets, smtplib
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from email.message import EmailMessage
|
||||
from auth import require_level
|
||||
from config_utils import PRODUCT_DISPLAY_NAME
|
||||
import sanitize
|
||||
|
||||
bp = Blueprint('action_create_account', __name__)
|
||||
|
||||
DATA_DIR = '/data'
|
||||
ACCOUNTS_FILE = f'{DATA_DIR}/authorized_accounts.json'
|
||||
CODE_TTL_MIN = 15
|
||||
|
||||
|
||||
def _load_accounts():
|
||||
try:
|
||||
with open(ACCOUNTS_FILE) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return {'accounts': []}
|
||||
|
||||
|
||||
def _send_verification_email(to_address, code):
|
||||
host = os.environ.get('SMTP_HOST', '')
|
||||
port = int(os.environ.get('SMTP_PORT', 587))
|
||||
user = os.environ.get('SMTP_USER', '')
|
||||
password = os.environ.get('SMTP_PASSWORD', '')
|
||||
from_addr = os.environ.get('SMTP_FROM', user)
|
||||
|
||||
if not host:
|
||||
raise RuntimeError('SMTP_HOST is not configured.')
|
||||
|
||||
msg = EmailMessage()
|
||||
msg['Subject'] = f'{PRODUCT_DISPLAY_NAME} - Email Verification'
|
||||
msg['From'] = from_addr
|
||||
msg['To'] = to_address
|
||||
msg.set_content(
|
||||
f'Your verification code is: {code}\n\n'
|
||||
f'This code expires in {CODE_TTL_MIN} minutes.\n\n'
|
||||
f'If you did not request this, you can ignore this email.'
|
||||
)
|
||||
|
||||
with smtplib.SMTP(host, port) as smtp:
|
||||
smtp.ehlo()
|
||||
if port != 465:
|
||||
smtp.starttls()
|
||||
if user and password:
|
||||
smtp.login(user, password)
|
||||
smtp.send_message(msg)
|
||||
|
||||
|
||||
@bp.route('/action/create_account', methods=['POST'])
|
||||
@require_level('nothing')
|
||||
def create_account():
|
||||
# Abort if already logged in
|
||||
if session.get('access_level', 'nothing') != 'nothing':
|
||||
return redirect('/view/view_overview')
|
||||
|
||||
email = sanitize.email(request.form.get('email', ''))
|
||||
password = request.form.get('password', '')
|
||||
password_confirm = request.form.get('password_confirm', '')
|
||||
tz = sanitize.timezone(request.form.get('timezone', '').strip())
|
||||
|
||||
if not email or not password or not password_confirm or not tz:
|
||||
flash('All fields are required.', 'error')
|
||||
return redirect('/view/view_create_account')
|
||||
|
||||
if password != password_confirm:
|
||||
flash('Passwords do not match.', 'error')
|
||||
return redirect('/view/view_create_account')
|
||||
|
||||
if len(password) < 8:
|
||||
flash('Password must be at least 8 characters.', 'error')
|
||||
return redirect('/view/view_create_account')
|
||||
|
||||
accounts = _load_accounts().get('accounts', [])
|
||||
account = next((a for a in accounts if a.get('email_address', '').lower() == email), None)
|
||||
|
||||
if account is None:
|
||||
flash('Email address not recognised. Contact your manager.', 'error')
|
||||
return redirect('/view/view_create_account')
|
||||
|
||||
if account.get('hashed_password'):
|
||||
flash('This account is already set up. Please log in instead.', 'error')
|
||||
return redirect('/view/view_create_account')
|
||||
|
||||
salt = bcrypt.gensalt()
|
||||
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
|
||||
code = f'{secrets.randbelow(1000000):06d}'
|
||||
expires = (datetime.now(tz=timezone.utc) + timedelta(minutes=CODE_TTL_MIN)).isoformat()
|
||||
|
||||
try:
|
||||
_send_verification_email(account['email_address'], code)
|
||||
except Exception as exc:
|
||||
flash(f'Could not send verification email: {exc}', 'error')
|
||||
return redirect('/view/view_create_account')
|
||||
|
||||
session['pending_create_account'] = {
|
||||
'email': account['email_address'],
|
||||
'hashed_password': hashed.decode('utf-8'),
|
||||
'timezone': tz,
|
||||
'code': code,
|
||||
'expires': expires,
|
||||
}
|
||||
|
||||
return redirect('/view/view_verify_email')
|
||||
Loading…
Add table
Add a link
Reference in a new issue