Files
Moon-Household-Budget/settings.py
tonym 74d5e196c7 Add in-app Settings page for secrets; move Gmail creds out of source
- settings.py: JSON-on-volume store with env fallback; secrets never rendered.
- /settings page (⚙️ in nav) to set Gmail address + app password + reminder prefs;
  password fields are write-only (blank keeps existing).
- reminders.py reads Gmail creds from Settings instead of a hardcoded password
  (resolves #2 in code — note the old password is still in git history; rotate it).
- mac_notification no longer crashes off-macOS; FLASK_DEBUG gates debug (#7).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:38:20 -05:00

53 lines
1.5 KiB
Python

"""App settings + secret storage (Gmail credentials, reminder prefs, etc.).
Stored as JSON on the data volume (DATA_DIR/settings.json) via the atomic
storage layer — so it is NEVER committed to source control. Secrets resolve
with an environment-variable fallback, so a value can come from the settings
page OR from the container's .env.
This is what lets us pull the hardcoded Gmail app password out of reminders.py
(issue #2): the password lives in settings.json on the volume instead.
"""
import os
from storage import load_json, save_json, DATA_DIR
SETTINGS_FILE = os.path.join(DATA_DIR, "settings.json")
# Keys holding secrets — their values are never rendered back to the browser.
SECRET_KEYS = {"gmail_app_password"}
DEFAULTS = {
"gmail_address": "",
"gmail_app_password": "",
"reminder_recipient": "", # blank => send to gmail_address
"reminders_enabled": True,
}
def load_settings():
data = load_json(SETTINGS_FILE, {}) or {}
merged = dict(DEFAULTS)
merged.update(data)
return merged
def save_settings(data):
save_json(SETTINGS_FILE, data)
def get_setting(key, env=None, default=""):
"""Resolve a setting: stored value first, then env var, then default."""
val = load_settings().get(key)
if val not in (None, ""):
return val
if env:
env_val = os.environ.get(env)
if env_val not in (None, ""):
return env_val
return default
def is_set(key):
"""True if a (typically secret) value is present, without revealing it."""
return bool(load_settings().get(key))