From c1f6060fa98e9ceb905d14857a2e7336381ee3a7 Mon Sep 17 00:00:00 2001 From: miquelmatoses Date: Tue, 14 Jul 2026 12:36:57 +0200 Subject: [PATCH] =?UTF-8?q?feat(db):=20migration=20032=20=E2=80=94=20auth?= =?UTF-8?q?=5Fusers.email=5Fverified=20for=20beta-grant=20gating?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds email_verified to auth_users so the "first 500 free Full Moon" grant in ensure_profile can require a verified email, closing the disposable-email slot-farming vector on the unverified password-signup path. Backfill without revocation: column added with default TRUE (fills existing rows), then default flipped to FALSE so only new accounts must verify. Magic -link and Google set it TRUE by construction; password accounts start FALSE. Idempotent per ADR 0011; applied via apply-migrations.yml, not on merge. Co-Authored-By: Claude Opus 4.8 --- .../032_auth_users_email_verified.sql | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 db/migrations/032_auth_users_email_verified.sql diff --git a/db/migrations/032_auth_users_email_verified.sql b/db/migrations/032_auth_users_email_verified.sql new file mode 100644 index 000000000..3095e474c --- /dev/null +++ b/db/migrations/032_auth_users_email_verified.sql @@ -0,0 +1,25 @@ +-- Migration 032: auth_users.email_verified — prove email ownership before a +-- beta/premium slot can be claimed. +-- +-- The "first 500 free Full Moon" grant in ensure_profile() (api/main.py) fires +-- for any authenticated account. Password signup (api/auth.py) creates an +-- account with no email verification, so one person can farm slots with +-- disposable emails. This column lets the grant require a verified email. +-- Magic-link and Google OAuth prove ownership by construction and set this TRUE +-- at account creation; password accounts start FALSE and flip TRUE on verify. +-- +-- Backfill without revocation: every account that predates this migration was +-- created under the prior rules and keeps its standing (including any +-- is_beta = TRUE grant). We do this by adding the column with default TRUE +-- (Postgres fills all existing rows), then flipping the default to FALSE so +-- only NEW rows must earn verification. +-- +-- Idempotent: `add column if not exists` is a no-op on re-run (it never touches +-- existing data once the column exists), and `set default false` is a no-op. +-- A re-run therefore never clobbers a legitimately-unverified newer account. + +alter table public.auth_users + add column if not exists email_verified boolean not null default true; + +alter table public.auth_users + alter column email_verified set default false;