Development

This commit is contained in:
Matthew Grotke 2026-06-13 09:25:57 -04:00
parent 44261e5b5c
commit 8a8e947fcf
9 changed files with 289 additions and 33 deletions

View file

@ -4,6 +4,7 @@ import bcrypt
import auth
import config_utils
import sanitize
import settings
_PAGE = Path(__file__).parent.name
@ -25,9 +26,62 @@ def accountdetails_save():
return redirect(f'/{_PAGE}')
@bp.route('/action/preferences/email_change_direct', methods=['POST'])
@auth.require_level('viewer')
def email_change_direct():
if session.get('email_address', '').lower() != settings.get_initial_manager_email():
flash('Not authorised.', 'error')
return redirect(f'/{_PAGE}')
new_email = sanitize.email(request.form.get('new_email', '').strip())
if not new_email:
flash('A valid email address is required.', 'error')
return redirect(f'/{_PAGE}')
current_email = session.get('email_address', '').lower()
if new_email == current_email:
flash('That is already your current email address.', 'error')
return redirect(f'/{_PAGE}')
if not settings.is_single_user() and config_utils.get_account_by_email(new_email):
flash('That email address is already in use.', 'error')
return redirect(f'/{_PAGE}')
if not settings.is_single_user():
try:
con = config_utils.open_accounts_db()
con.execute(
'UPDATE accounts SET email=?, requested_email=NULL WHERE account_id=?',
(new_email, session.get('account_id', ''))
)
con.commit()
con.close()
except Exception as exc:
flash(f'Could not update account: {exc}', 'error')
return redirect(f'/{_PAGE}')
try:
import json as _json
cfg_path = Path(settings._APP_CONFIG_PATH)
cfg = _json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
cfg['initial_manager_email'] = new_email
cfg_path.write_text(_json.dumps(cfg, indent=2) + '\n')
settings._app_config_cache = None
except Exception as exc:
flash(f'Could not update app_config.json: {exc}', 'error')
return redirect(f'/{_PAGE}')
session['email_address'] = new_email
flash('Email address updated.', 'success')
return redirect(f'/{_PAGE}')
@bp.route('/action/preferences/email_change_request', methods=['POST'])
@auth.require_level('viewer')
def email_change_request():
if settings.is_single_user():
flash('Not available in single-user mode.', 'error')
return redirect(f'/{_PAGE}')
new_email = sanitize.email(request.form.get('new_email', '').strip())
if not new_email:
flash('A valid email address is required.', 'error')
@ -77,6 +131,26 @@ def changepassword_save():
flash('New password must be at least 8 characters.', 'error')
return redirect(f'/{_PAGE}')
hashed = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
if settings.is_single_user():
stored_hash = settings.get_initial_manager_password_hash()
if not stored_hash or not bcrypt.checkpw(current_password.encode('utf-8'), stored_hash.encode('utf-8')):
flash('Current password is incorrect.', 'error')
return redirect(f'/{_PAGE}')
try:
import json as _json
cfg_path = Path(settings._APP_CONFIG_PATH)
cfg = _json.loads(cfg_path.read_text()) if cfg_path.exists() else {}
cfg['initial_manager_password'] = hashed
cfg_path.write_text(_json.dumps(cfg, indent=2) + '\n')
settings._app_config_cache = None
except Exception as exc:
flash(f'Could not update password: {exc}', 'error')
return redirect(f'/{_PAGE}')
flash('Password changed successfully.', 'success')
return redirect(f'/{_PAGE}')
account = config_utils.get_account_by_id(session.get('account_id', ''))
if account is None:
flash('Account not found. Please log in again.', 'error')
@ -86,8 +160,6 @@ def changepassword_save():
flash('Current password is incorrect.', 'error')
return redirect(f'/{_PAGE}')
hashed = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
try:
con = config_utils.open_accounts_db()
con.execute(

View file

@ -54,7 +54,7 @@
},
{
"type": "form",
"action": "/action/preferences/email_change_request",
"action": "%EMAIL_CHANGE_ACTION%",
"method": "post",
"items": [
{
@ -70,7 +70,7 @@
"items": [
{
"type": "button_primary",
"text": "Submit Request"
"text": "%EMAIL_CHANGE_BTN_TEXT%"
}
]
}

View file

@ -3,6 +3,7 @@ from flask import session
import sanitize
import config_utils
import factory
import settings
def collect_tokens(cfg):
@ -12,8 +13,19 @@ def collect_tokens(cfg):
tokens['PREF_TIMEZONE'] = session.get('timezone', '')
tokens['TIMEZONE_OPTIONS'] = json.dumps(blank + [{'value': tz, 'label': tz} for tz in sanitize.VALID_TIMEZONES])
account = config_utils.get_account_by_id(session.get('account_id', ''))
requested = (account or {}).get('requested_email', '')
is_initial_manager = session.get('email_address', '').lower() == settings.get_initial_manager_email()
if is_initial_manager:
tokens['EMAIL_CHANGE_ACTION'] = '/action/preferences/email_change_direct'
tokens['EMAIL_CHANGE_BTN_TEXT'] = 'Change Email'
else:
tokens['EMAIL_CHANGE_ACTION'] = '/action/preferences/email_change_request'
tokens['EMAIL_CHANGE_BTN_TEXT'] = 'Submit Request'
if not settings.is_single_user():
account = config_utils.get_account_by_id(session.get('account_id', ''))
requested = (account or {}).get('requested_email', '')
else:
requested = ''
if requested:
tokens['PENDING_EMAIL_BAR'] = (
f'<div class="info-bar info-bar-inline info-bar-warning">'