Release 0.3.0: private-beta gate + anonymous read mode
Adds an email allowlist (toggleable per deployment) that restricts OAuth sign-in to listed emails while keeping read paths public. Anonymous visitors now see the full app shell in read-only mode instead of the §14.1 landing wall. Empty allowlist = gate off, so deployments that don't enable it behave exactly as 0.2.3. Also fixes single-finger scroll on /philosophy and other .chrome-pane views on iOS Safari (.app: 100vh → 100dvh). Renames deploy/nginx/rfc.wiggleverse.org.conf → ohm.wiggleverse.org.conf to match the deployed-domain rename (rfc.wiggleverse.org deprovisioned 2026-05-27). See CHANGELOG.md for full details + upgrade steps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,43 @@ async def fetch_user_profile(config: Config, access_token: str) -> dict[str, Any
|
||||
return resp.json()
|
||||
|
||||
|
||||
def allowlist_is_active() -> bool:
|
||||
"""The private-beta gate is on iff the `allowed_emails` table has any
|
||||
rows. Empty list means "open" — any successful OAuth provisions a
|
||||
user; first row added flips the deployment into private-beta mode.
|
||||
See `migrations/011_allowlist.sql` for the reasoning.
|
||||
"""
|
||||
row = db.conn().execute("SELECT 1 FROM allowed_emails LIMIT 1").fetchone()
|
||||
return row is not None
|
||||
|
||||
|
||||
def is_allowed_sign_in(profile: dict[str, Any]) -> bool:
|
||||
"""Decide whether a freshly-completed OAuth profile may sign in.
|
||||
|
||||
Three accept paths:
|
||||
1. The allowlist is empty (gate off).
|
||||
2. The Gitea profile's email is in `allowed_emails` (case-insensitive).
|
||||
3. A `users` row already exists for this `gitea_id` — grandfather
|
||||
per `migrations/011_allowlist.sql`.
|
||||
"""
|
||||
gitea_id = profile.get("id")
|
||||
if gitea_id is not None:
|
||||
existing = db.conn().execute(
|
||||
"SELECT 1 FROM users WHERE gitea_id = ? LIMIT 1", (gitea_id,)
|
||||
).fetchone()
|
||||
if existing is not None:
|
||||
return True
|
||||
if not allowlist_is_active():
|
||||
return True
|
||||
email = (profile.get("email") or "").strip()
|
||||
if not email:
|
||||
return False
|
||||
row = db.conn().execute(
|
||||
"SELECT 1 FROM allowed_emails WHERE email = ? LIMIT 1", (email,)
|
||||
).fetchone()
|
||||
return row is not None
|
||||
|
||||
|
||||
def provision_user(config: Config, profile: dict[str, Any]) -> SessionUser:
|
||||
"""Insert or update the users row for this Gitea profile.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user