AUTH4D-217 Flows at refresh, sign-out and account deletion
This ticket adds three values to the existing Flows trigger enum. It adds no HTTP route. Flow
definitions, bindings, execution traces and validation still use the AUTH4D-159 management API and
the shared schemas in packages/contracts.
| Trigger | Invocation point | Allowed steps | Failure behavior |
|---|---|---|---|
pre_refresh |
After tenant, client, user and refresh-family checks, before rotating the refresh token. | deny, condition |
A deny, malformed hook, step failure or timeout refuses the refresh as invalid_grant; the old token was not consumed. onError must be fail_closed. |
post_sign_out |
After an access-token revocation or hosted sign-out has revoked the session. | call_backend |
The hook result cannot restore the session or change the successful sign-out response. A step failure follows its configured onError and is present in the flow trace. |
pre_account_deletion |
Before POST /users/{userId}/deletion disables the user or queues a job. |
deny, condition, require_verified_email, allow_list, block_list, call_backend |
A deny or fail-closed error refuses the request; it does not disable the user or queue a deletion job. This trigger is project-scoped and uses application_id: null. |
Every trigger uses the existing maximum of 16 steps and a 2,000 ms flow budget. A definition may
choose a lower budget. Refresh conditions may reference only trigger, method, country,
provider, application.id and user.id; they do not read D1 or call a remote service. Refresh
flows therefore have no network step and any timeout fails closed before token rotation. Account
deletion cannot use token or stored-role/metadata patch steps. A sign-out flow’s backend answer may
not modify account metadata.
The allowlist and refresh rules are checked when a draft is published, when a draft is test-run
(400 flow_invalid) and again when a published flow runs. A published flow that breaks them fails
closed for its trigger. Older publish-only checks, such as the AUTH4D-208 claim-name rule, are not
re-applied at run time.
Changed responses on existing routes (management paths are under /management/tenants/{tenantId};
OAuth paths take tenant_id as before):
| Route | Change |
|---|---|
PUT and DELETE /flow-bindings/pre_account_deletion/clients/{clientId} |
400 project_scope_required. Account-deletion flows bind only to the project. |
POST /flows/{flowId}/test |
event.applicationId is null for pre_account_deletion and required for other triggers; otherwise 404. |
POST /users/{userId}/deletion |
403 flow_denied when the project’s account-deletion flow denies the request. |
POST /oauth/token (grant_type=refresh_token) |
400 invalid_grant when the refresh flow denies, fails or times out. The presented token stays usable. |
POST /oauth/revoke and hosted sign-out |
Unchanged responses; the sign-out flow runs after the session is revoked. |
The migration 0035_flow_lifecycle_triggers.sql extends the allowed trigger values in flow
definitions and bindings, and permits project-wide execution traces without an application ID. It
preserves existing flow definitions, bindings, traces, custom-code records and step secrets.
Verification
tests/integration/auth4d-217.flow-triggers.test.ts covers trigger parsing, per-trigger step
allowlists, project-only account-deletion traces, the refresh timeout and failure policy, and the
pre-rotation callback’s refusal behavior. infra/cloudflare/d1/migrations/0035_flow_lifecycle_triggers.sql
is also applied by that integration test after the earlier Flows migration.
Source: docs/api-spec/auth4d-217.md