Files
linumiq_net-web_app/supabase/migrations/0002_mfa_recovery.sql
T

46 lines
1.9 KiB
SQL

-- 0002_mfa_recovery.sql
-- Additive, idempotent migration for mandatory-MFA recovery codes.
-- Safe to run multiple times.
-- 1. Recovery codes -------------------------------------------------------
-- One row per single-use recovery code. We only ever store a SHA-256 hash of
-- the code; the plaintext is shown to the user exactly once at generation.
create table if not exists public.mfa_recovery_codes (
id bigint generated always as identity primary key,
user_id uuid not null,
code_hash text not null,
used_at timestamptz,
created_at timestamptz not null default now()
);
create index if not exists mfa_recovery_codes_user_id_idx
on public.mfa_recovery_codes (user_id);
-- Cascade-delete recovery codes when the owning auth user is removed, so no
-- orphaned rows are left behind (e.g. on account deletion or MFA reset).
do $$
begin
if not exists (
select 1 from pg_constraint
where conname = 'mfa_recovery_codes_user_id_fkey'
and conrelid = 'public.mfa_recovery_codes'::regclass
) then
alter table public.mfa_recovery_codes
add constraint mfa_recovery_codes_user_id_fkey
foreign key (user_id) references auth.users (id) on delete cascade;
end if;
end $$;
-- Fast lookup of an unused code by (user, hash) during redemption.
create unique index if not exists mfa_recovery_codes_user_hash_uidx
on public.mfa_recovery_codes (user_id, code_hash);
-- 2. Lock down with RLS, NO policies ---------------------------------------
-- RLS enabled with NO policies so anon/authenticated roles cannot read or
-- write recovery codes at all. The application generates and redeems codes
-- exclusively through the service-role client, which bypasses RLS.
alter table public.mfa_recovery_codes enable row level security;
comment on table public.mfa_recovery_codes is
'Single-use MFA recovery codes (SHA-256 hashed). RLS enabled with no policies: service-role only.';