Development

This commit is contained in:
Matthew Grotke 2026-06-10 14:23:47 -04:00
parent f5722f3c7b
commit d60bf15ce4
15 changed files with 367 additions and 285 deletions

View file

@ -1,7 +1,7 @@
from pathlib import Path
from flask import Blueprint, request, session, redirect, flash
import json, os, bcrypt, secrets, smtplib
from datetime import datetime, timezone, timedelta
import os, bcrypt, secrets, smtplib
import time
from email.message import EmailMessage
import auth
import config_utils
@ -11,15 +11,7 @@ _PAGE = Path(__file__).parent.name
bp = Blueprint(_PAGE, __name__)
CODE_TTL_MIN = 15
def _load_accounts():
try:
with open(config_utils.ACCOUNTS_FILE) as f:
return json.load(f)
except Exception:
return {'accounts': []}
CODE_TTL_SECS = 15 * 60
def _send_verification_email(to_address, code):
@ -38,7 +30,7 @@ def _send_verification_email(to_address, code):
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'This code expires in 15 minutes.\n\n'
f'If you did not request this, you can ignore this email.'
)
@ -51,10 +43,19 @@ def _send_verification_email(to_address, code):
smtp.send_message(msg)
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()
@bp.route('/action/accountcreate/form_create', methods=['POST'])
@auth.require_level('nothing')
def form_create():
# Abort if already logged in
if session.get('access_level', 'nothing') != 'nothing':
return redirect('/overview')
@ -75,8 +76,7 @@ def form_create():
flash('Password must be at least 8 characters.', 'error')
return redirect(f'/{_PAGE}')
accounts = _load_accounts().get('accounts', [])
account = next((a for a in accounts if a.get('email_address', '').lower() == email), None)
account = config_utils.get_account_by_email(email)
if account is None:
flash('Email address not recognised. Contact your manager.', 'error')
@ -86,10 +86,11 @@ def form_create():
flash('This account is already set up. Please log in instead.', 'error')
return redirect(f'/{_PAGE}')
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()
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
code = f'{secrets.randbelow(1000000):06d}'
expires_ts = int(time.time()) + CODE_TTL_SECS
tz_offset = _tz_to_offset_seconds(tz)
try:
_send_verification_email(account['email_address'], code)
@ -97,12 +98,20 @@ def form_create():
flash(f'Could not send verification email: {exc}', 'error')
return redirect(f'/{_PAGE}')
session['pending_create_account'] = {
'email': account['email_address'],
'hashed_password': hashed.decode('utf-8'),
'timezone': tz,
'code': code,
'expires': expires,
}
try:
con = config_utils.open_accounts_db()
con.execute(
'''INSERT OR REPLACE INTO pending_verifications
(email, hashed_password, tz_offset_seconds, code, expires_ts)
VALUES (?,?,?,?,?)''',
(account['email_address'].lower(), hashed, tz_offset, code, expires_ts)
)
con.commit()
con.close()
except Exception as exc:
flash(f'Could not store verification: {exc}', 'error')
return redirect(f'/{_PAGE}')
session['pending_verify_email'] = account['email_address']
return redirect('/accountverifyemail')