feat: auth-gate demo chat + budget circuit breaker
Dev — Build & local smoke test / build-smoke (pull_request) Successful in 2m10s

Gate the demo chat behind Supabase auth: the widget now fetches Supabase
config on open, shows a compact sign-in pane (Google OAuth or email/password)
when the user is unauthenticated, and passes the access_token to /api/demo.
The server verifies the token via supabase_auth_user() before any processing
and uses the verified user ID as the rate-limit key.

Add a budget kill switch: a demo_config table in Supabase holds a
demo_enabled flag that /api/demo polls every 60s (cached, fails open).
A Cloud Function (demo-budget-guard) is triggered by a GCP Pub/Sub budget
alert and sets demo_enabled = 'false' when spend crosses 90% of the $150
daily budget. Budget and topic are provisioned; function is live in
us-central1.
This commit is contained in:
2026-05-10 23:44:54 -05:00
parent bdb6ddc581
commit fe418bf3f7
8 changed files with 383 additions and 34 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+85
View File
@@ -1794,6 +1794,91 @@ el_val_t page_css(void) {
" }\n"
" #neuron-demo-send:hover { background: #0078D4; }\n"
" #neuron-demo-send:disabled { opacity: 0.5; cursor: not-allowed; }\n"
" #neuron-demo-auth {\n"
" flex-direction: column;\n"
" align-items: center;\n"
" gap: 0.75rem;\n"
" padding: 1.5rem 1.25rem;\n"
" flex: 1;\n"
" }\n"
" .demo-auth-heading {\n"
" font-family: var(--body);\n"
" font-size: 0.85rem;\n"
" font-weight: 600;\n"
" color: var(--t1);\n"
" text-align: center;\n"
" margin: 0 0 0.25rem;\n"
" }\n"
" .demo-auth-google-btn {\n"
" display: flex;\n"
" align-items: center;\n"
" gap: 0.6rem;\n"
" width: 100%;\n"
" padding: 0.65rem 1rem;\n"
" font-family: var(--body);\n"
" font-size: 0.82rem;\n"
" font-weight: 500;\n"
" color: var(--t1);\n"
" background: #fff;\n"
" border: 1px solid var(--border);\n"
" border-radius: 6px;\n"
" cursor: pointer;\n"
" justify-content: center;\n"
" transition: border-color 180ms, box-shadow 180ms;\n"
" }\n"
" .demo-auth-google-btn:hover {\n"
" border-color: rgba(0,82,160,0.45);\n"
" box-shadow: 0 0 0 3px rgba(0,82,160,0.08);\n"
" }\n"
" .demo-auth-email-toggle {\n"
" background: none;\n"
" border: none;\n"
" cursor: pointer;\n"
" font-family: var(--body);\n"
" font-size: 0.78rem;\n"
" color: var(--t3);\n"
" text-decoration: underline;\n"
" padding: 0;\n"
" }\n"
" .demo-auth-email-form {\n"
" display: none;\n"
" flex-direction: column;\n"
" gap: 0.5rem;\n"
" width: 100%;\n"
" }\n"
" .demo-auth-email-form input {\n"
" width: 100%;\n"
" padding: 0.6rem 0.75rem;\n"
" font-family: var(--body);\n"
" font-size: 0.82rem;\n"
" color: var(--t1);\n"
" background: var(--bg);\n"
" border: 1px solid var(--border);\n"
" border-radius: 6px;\n"
" outline: none;\n"
" }\n"
" .demo-auth-email-form input:focus { border-color: var(--navy); }\n"
" .demo-auth-submit-btn {\n"
" width: 100%;\n"
" padding: 0.6rem 1rem;\n"
" font-family: var(--body);\n"
" font-size: 0.82rem;\n"
" font-weight: 600;\n"
" color: #fff;\n"
" background: var(--navy);\n"
" border: none;\n"
" border-radius: 6px;\n"
" cursor: pointer;\n"
" transition: background 180ms;\n"
" }\n"
" .demo-auth-submit-btn:hover { background: #0078D4; }\n"
" .demo-auth-submit-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n"
" .demo-auth-msg {\n"
" font-family: var(--body);\n"
" font-size: 0.75rem;\n"
" margin: 0;\n"
" text-align: center;\n"
" }\n"
" @media (max-width: 600px) {\n"
" #neuron-demo-text { font-size: 1rem; padding: 1rem; }\n"
" #neuron-demo-send { padding: 1rem 1.25rem; min-width: 64px; }\n"