Aug 27 - Update free-plan gate, MFA is no longer bypassable via the app

This commit is contained in:
2026-08-27 12:14:09 -04:00
parent 2d68bad966
commit d291dfc513
8 changed files with 251 additions and 20 deletions
+44 -1
View File
@@ -37,14 +37,41 @@ from control.models import Plan, Tenant, ProvisioningJob
from control.seed import seed_plans
from control.tenant_migrate import bootstrap_tenant, chain_head
# What a COMPLETE tenant database holds: the baseline tables plus everything
# the guarded tail (phase33 onward) adds. This list used to stop at the
# baseline, which is why it passed while bootstrap was producing databases that
# could not serve a login — the expectation encoded the bug.
EXPECTED_TABLES = {
# baseline (0003_add_user_active)
'api_device_tokens', 'api_refresh_tokens', 'areas', 'audit_logs', 'broadcasts',
'checklist_items', 'customer_assignments', 'device_registrations', 'facilities',
'facility_score_alerts', 'inspection_results', 'inspection_templates',
'inspections', 'inspector_assignments', 'issue_comments', 'issue_followers',
'issues', 'notification_matrix', 'notification_preferences', 'notifications',
'projects', 'scheduled_reports', 'support_ticket_replies', 'support_tickets',
'users',
'tenant_settings', 'users',
# guarded tail
'inspection_schedules', # phase34
'issue_work_orders', # phase36
'project_notification_recipients', # phase37
'support_chat_sessions', # phase40
'support_chat_messages', # phase40
'support_knowledge', # phase40
'user_notification_matrix', # phase54
'template_contracts', # phase55
}
# Columns added after the baseline, on tables the baseline already creates.
# A missing one here is what broke login: SQLAlchemy SELECTs every mapped
# column, so the first User.query fails with "Unknown column".
EXPECTED_COLUMNS = {
'users': ['mfa_enabled', 'mfa_secret', 'mfa_recovery_codes', 'ui_theme'],
'facilities': ['qr_token'],
'areas': ['qr_token'],
'issues': ['handler_type', 'facility_handler_name'],
'inspections': ['inspection_schedule_id', 'follow_up_requested_by',
'follow_up_requested_at', 'follow_up_assigned_to'],
'support_knowledge': ['sort_order'],
}
@@ -148,6 +175,22 @@ def verify(args):
if need not in ucols:
print(f" !! users.{need} missing"); ok = False
print(f" users columns: {len(ucols)} (incl id/username/email/role)")
# Columns added AFTER the baseline. These are the ones that decide
# whether the tenant can serve a request at all: SQLAlchemy SELECTs
# every mapped column, so one missing here means the first query
# against that table raises "Unknown column" — for users, that is the
# login. Checked explicitly because a table-only check passed happily
# while every post-baseline column was absent.
for table, needed in sorted(EXPECTED_COLUMNS.items()):
if table not in built:
continue # already reported as a missing table above
have = {c['name'] for c in insp.get_columns(table)}
gone = [c for c in needed if c not in have]
if gone:
print(f" !! {table} missing post-baseline columns: {gone}"); ok = False
if ok:
print(" post-baseline columns: all present")
finally:
engine.dispose()
return ok