Leave Localhost logoLeave LocalhostDocs
Recipes

Protect a Sensitive Action

Wrap a dangerous backend action with step-up verification so it requires a fresh proof of identity, for any account type.

Gate a dangerous operation behind step-up verification. See Sensitive Action Protection for the concepts; this is the wiring.

1. Register the action

In packages/backend/convex/security/, add the action id to validators.ts and an entry to SENSITIVE_ACTIONS in sensitiveActions.ts with a label, risk level, and org scope:

"report.purge": {
  label: "Purge all reports",
  level: 4,          // critical: single-use grant, no fresh-session bypass
  orgScoped: true,
},

Risk levels: 1 (fresh session ok) → 4 (critical, single-use). See the risk-level table.

2. Enforce it in the backend

Call the matching helper inside the protected function, passing the authenticated app userId (and organizationId for org-scoped actions):

await requireSensitiveActionInMutation(ctx, {
  action: "report.purge",
  userId: actor.appUserId,
  organizationId: actor.organizationId,
});

Use requireSensitiveActionInAction from a Convex action. If the user has not verified, this throws SENSITIVE_VERIFICATION_REQUIRED.

3. Wrap the call in the UI

const { runSensitiveAction, dialog } = useSensitiveAction();

await runSensitiveAction({ run: () => purgeReports() });
return dialog; // renders the password / email-code ceremony when needed

For active-workspace actions the client may omit organizationId; the challenge mutations resolve the active org server-side so the grant scope matches enforcement.

4. Verify

Confirm the dialog appears for OAuth and magic-link accounts (which have no password and fall back to email codes), and that a level-4 action requires a fresh verification each time.

On this page