From 94db6e055bf581093e59bcc37f07b3871dfd000d Mon Sep 17 00:00:00 2001 From: parththakkar106 Date: Tue, 21 Jul 2026 00:21:30 +0530 Subject: [PATCH] Add power-user email allowlist to bypass demo turn cap Trusted testers listed in AIDND_POWER_USERS get unmetered turns on the shared demo key: demo_turns_left reports the full cap and count_demo_turn skips them. Registered accounts only, matched case-insensitively. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5 --- backend/.env.example | 4 ++++ backend/app/auth.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/backend/.env.example b/backend/.env.example index 67abd78..bdd3bf2 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -59,6 +59,10 @@ AIDND_DEMO_ENDPOINT_URL= AIDND_DEMO_MODELS= # Successful AI turns per user per day on the demo key. Default: 20 AIDND_DEMO_TURNS_PER_DAY= +# Comma-separated emails of "power users" (trusted testers) who bypass the daily +# demo cap entirely — unmetered turns on the shared demo key. Registered accounts +# only (guests have no email). Matched case-insensitively. +AIDND_POWER_USERS= # The AI endpoint/API key/model are NOT env vars — they are configured at # runtime in the app's Settings page and stored (encrypted) in the database. diff --git a/backend/app/auth.py b/backend/app/auth.py index fde9c4e..e7f1639 100644 --- a/backend/app/auth.py +++ b/backend/app/auth.py @@ -58,6 +58,14 @@ DEMO_MODELS = [ ] or ["google/gemma-4-26b-a4b-it:free"] DEMO_TURNS_PER_DAY = int(os.environ.get("AIDND_DEMO_TURNS_PER_DAY", "20") or 20) +# Trusted testers (by email) who bypass the daily demo cap — unmetered turns on +# the shared demo key. Comma-separated emails; matched case-insensitively. +POWER_USERS = { + e.strip().lower() + for e in os.environ.get("AIDND_POWER_USERS", "").split(",") + if e.strip() +} + DEMO_CAP_MESSAGE = ( f"You've used all {DEMO_TURNS_PER_DAY} free demo turns for today. " "Add your own API key in Settings to keep playing (it resets tomorrow)." @@ -93,13 +101,24 @@ def _today() -> str: return models.utcnow().date().isoformat() +def is_power_user(user: models.User) -> bool: + """Trusted testers (email allowlist) bypass the demo turn cap.""" + return bool(user.email) and user.email.lower() in POWER_USERS + + def demo_turns_left(user: models.User) -> int: + # Power users are never capped; report the full cap so the banner reads + # "N of N" rather than a decrementing count. + if is_power_user(user): + return DEMO_TURNS_PER_DAY used = user.demo_turns_used if user.demo_turns_date == _today() else 0 return max(0, DEMO_TURNS_PER_DAY - used) def count_demo_turn(user: models.User) -> None: """Record one demo turn; the caller's commit persists it.""" + if is_power_user(user): + return # unmetered — power users don't count against the cap today = _today() if user.demo_turns_date != today: user.demo_turns_date = today