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 neededFor 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.