# Temporary administrator MFA policy

Migration `202610010001_admin_login_security.sql` creates a private policy row and keeps the default **MFA required**. Applying or reapplying the migration does not activate a pause or extend an existing pause.

The single enforcement point remains `public.marketplace_role()`, used by existing admin RLS policies and protected RPCs. It returns the existing member's actual owner/publisher/editor role only when:

- The signed Supabase JWT has `aal=aal2`; or
- The signed JWT has `aal=aal1`, its `amr` array contains `{"method":"password"}`, and the private `paused_until` timestamp is still in the future.

Ordinary customers do not gain staff membership. OTP, recovery, OAuth and user-editable metadata do not satisfy the password condition. The shape of the signed AMR claim is documented in [Supabase's JWT claims reference](https://supabase.com/docs/guides/auth/jwt-fields). Existing passwords, MFA factors, customer authentication and role distinctions remain unchanged.

## RPC contract

All three functions return `{mfa_required:boolean, paused_until:string|null, updated_at:string}`:

- `marketplace_admin_login_policy()` is readable only by an existing authenticated staff member with an AAL1 or AAL2 JWT, so the password-login path can decide whether to request TOTP. It is not a grant of admin access; the server must still validate `marketplace_role()` before issuing an admin session.
- `marketplace_admin_login_security_get()` requires the current effective owner role.
- `marketplace_admin_login_security_save(p_enabled boolean, p_days integer default 7, p_expected_updated_at timestamptz default null)` requires the current effective owner role. `p_enabled=true` clears the pause; `false` pauses for 1–7 days. An absent or stale timestamp raises `admin_login_policy_conflict`. Invalid settings raise `invalid_admin_login_policy`. Every actual change writes its actor and before/after policy to the existing admin audit log.

Re-enabling MFA immediately prevents existing AAL1 sessions from acquiring an admin role on subsequent requests. The saving call returns its committed policy directly, so an AAL1 owner can receive the successful enable result even though further owner-only reads now require MFA. Once a pause expires, strict MFA applies automatically without a scheduled job. Retrying an already-enabled no-op does not create an extra audit entry.

## Explicit activation and rollback

The separate `admin-login-security-pause-7-days.sql` is an operator action, **not** an automatic migration. Run it only after approval of the concrete temporary security change. It pauses the policy for seven days from execution and records a null actor with `source=authorized_sql_operator` and `reason=user_requested_local_testing_temporary_pause`. It does not pretend that an authenticated staff member performed the database-console action.

Restore MFA early from the admin settings or run `admin-login-security-restore.sql` as an authorized SQL operator. Rollback clears the pause and appends an audit record; it preserves existing factors and history. Reapplying the activation file would extend the deadline, so do not use it as a migration or automatic retry.

The current project has no linked Supabase CLI metadata or `supabase`/`psql` executable on PATH. Prior deployment records describe the authenticated Supabase SQL editor. These files have been prepared and locally tested; they do not claim any live activation.

## Verification

`node --test tests/admin-login-security-db.test.mjs` applies the real original marketplace RLS migration followed by this policy migration. It checks strict default behavior, owner/member/MFA boundaries, existing editor restrictions, 1–7-day bounds, optimistic concurrency, malformed/non-password AMR rejection, automatic expiry, immediate restoration, operator activation/rollback, migration replay and preservation of passwords/factors/memberships/audit history. The test-only JWT stub represents already-validated Supabase claims; the application must never accept caller-created JWT claims or use a service-role bridge for the temporary login.
