linuxrouter/docker/routlin-dash/app/pages/accountcreate/action.py

121 lines
4.1 KiB
Python
Raw Normal View History

2026-05-27 22:04:04 -04:00
from pathlib import Path
2026-05-17 03:26:01 -04:00
from flask import Blueprint, request, session, redirect, flash
2026-06-10 14:23:47 -04:00
import os, bcrypt, secrets, smtplib
import time
2026-05-17 03:26:01 -04:00
from email.message import EmailMessage
2026-06-07 00:21:08 -04:00
import auth
import config_utils
2026-05-17 03:26:01 -04:00
import sanitize
2026-05-27 22:04:04 -04:00
_PAGE = Path(__file__).parent.name
bp = Blueprint(_PAGE, __name__)
2026-05-17 03:26:01 -04:00
2026-06-10 14:23:47 -04:00
CODE_TTL_SECS = 15 * 60
2026-05-17 03:26:01 -04:00
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()
2026-06-07 00:21:08 -04:00
msg['Subject'] = f'{config_utils.WEB_APP_DISPLAY_NAME} - Email Verification'
2026-05-17 03:26:01 -04:00
msg['From'] = from_addr
msg['To'] = to_address
msg.set_content(
f'Your verification code is: {code}\n\n'
2026-06-10 14:23:47 -04:00
f'This code expires in 15 minutes.\n\n'
2026-05-17 03:26:01 -04:00
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)
2026-06-10 14:23:47 -04:00
def _tz_to_offset_seconds(tz_str):
try:
from zoneinfo import ZoneInfo
from datetime import datetime
return int(datetime.now(ZoneInfo(tz_str)).utcoffset().total_seconds())
except Exception:
import settings as _s
return _s.get_host_utc_offset()
2026-05-27 22:04:04 -04:00
@bp.route('/action/accountcreate/form_create', methods=['POST'])
2026-06-07 00:21:08 -04:00
@auth.require_level('nothing')
2026-05-27 22:04:04 -04:00
def form_create():
2026-05-17 03:26:01 -04:00
if session.get('access_level', 'nothing') != 'nothing':
2026-05-27 22:04:04 -04:00
return redirect('/overview')
2026-05-17 03:26:01 -04:00
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')
2026-05-27 22:04:04 -04:00
return redirect(f'/{_PAGE}')
2026-05-17 03:26:01 -04:00
if password != password_confirm:
flash('Passwords do not match.', 'error')
2026-05-27 22:04:04 -04:00
return redirect(f'/{_PAGE}')
2026-05-17 03:26:01 -04:00
if len(password) < 8:
flash('Password must be at least 8 characters.', 'error')
2026-05-27 22:04:04 -04:00
return redirect(f'/{_PAGE}')
2026-05-17 03:26:01 -04:00
2026-06-10 14:23:47 -04:00
account = config_utils.get_account_by_email(email)
2026-05-17 03:26:01 -04:00
if account is None:
flash('Email address not recognised. Contact your manager.', 'error')
2026-05-27 22:04:04 -04:00
return redirect(f'/{_PAGE}')
2026-05-17 03:26:01 -04:00
if account.get('hashed_password'):
flash('This account is already set up. Please log in instead.', 'error')
2026-05-27 22:04:04 -04:00
return redirect(f'/{_PAGE}')
2026-05-17 03:26:01 -04:00
2026-06-10 14:23:47 -04:00
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
2026-06-10 22:57:55 -04:00
code = f'{secrets.randbelow(1000000):06d}'
tz_offset = _tz_to_offset_seconds(tz)
2026-05-17 03:26:01 -04:00
try:
_send_verification_email(account['email_address'], code)
except Exception as exc:
flash(f'Could not send verification email: {exc}', 'error')
2026-05-27 22:04:04 -04:00
return redirect(f'/{_PAGE}')
2026-05-17 03:26:01 -04:00
2026-06-10 14:23:47 -04:00
try:
con = config_utils.open_accounts_db()
con.execute(
2026-06-10 22:57:55 -04:00
'''INSERT INTO clients
(cookie_unique_token, email, hashed_password, tz_offset_seconds, verification_code, code_sent_ts)
VALUES (?,?,?,?,?,?)
ON CONFLICT(cookie_unique_token) DO UPDATE SET
email=excluded.email,
hashed_password=excluded.hashed_password,
tz_offset_seconds=excluded.tz_offset_seconds,
verification_code=excluded.verification_code,
code_sent_ts=excluded.code_sent_ts''',
(session.sid, account['email_address'].lower(), hashed, tz_offset, code, int(time.time()))
2026-06-10 14:23:47 -04:00
)
con.commit()
con.close()
except Exception as exc:
flash(f'Could not store verification: {exc}', 'error')
return redirect(f'/{_PAGE}')
2026-05-27 22:04:04 -04:00
return redirect('/accountverifyemail')