-- 20260126_auth_revamp_v2.up.sql DO $$ BEGIN CREATE TYPE user_status AS ENUM ('pending', 'active', 'deactivated'); EXCEPTION WHEN duplicate_object THEN null; END $$; ALTER TABLE public.users ADD COLUMN IF NOT EXISTS status user_status DEFAULT 'active', ADD COLUMN IF NOT EXISTS mfa_enabled BOOLEAN DEFAULT FALSE, ADD COLUMN IF NOT EXISTS last_login TIMESTAMPTZ; CREATE TABLE IF NOT EXISTS public.user_mfa_secrets ( user_id UUID PRIMARY KEY REFERENCES public.users(id) ON DELETE CASCADE, secret TEXT NOT NULL, recovery_codes TEXT[] NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS public.webauthn_credentials ( id BYTEA PRIMARY KEY, -- Credential ID can be long bytes user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, public_key BYTEA NOT NULL, -- COSE encoded public key attestation_type TEXT, aaguid UUID, sign_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_used_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_webauthn_user_id ON public.webauthn_credentials(user_id); CREATE TABLE IF NOT EXISTS public.auth_tokens ( token TEXT PRIMARY KEY, -- Hashed token user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE, type TEXT NOT NULL, -- 'verification', 'password_reset', 'magic_link' expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_auth_tokens_user_id ON public.auth_tokens(user_id);