W007: Revoke API token
Soft-deletes a PAT by setting revoked_at. Subsequent auth attempts with that plaintext fail at the middleware with 401.
Steps
- Read userId from ctx.
- Soft-delete the row. Call
Platform.revokeApiToken(tokenId, userId)—UPDATE … SET revoked_at = NOW() WHERE id = $1 AND user_id = $2 AND revoked_at IS NULL. Affected-row count tells us whether the token existed and was revocable. - 404 on no-match. If
revokeApiTokenreturns false (token doesn't exist, belongs to a different user, or already revoked), throwNotFoundError. We deliberately don't distinguish "not found" from "not yours" to avoid leaking token existence.
Returns
Void. The L4 boundary responds with { deletedId: id } echoing the path param.
Business rules
- Cross-user revoke returns 404, not 403. Treating these the same prevents an attacker from probing token IDs to discover which exist.
- Already-revoked is also 404. A revoked token is functionally "not found" for revocation purposes.
- Cascade safety. The row stays in the table after revocation so the prefix + name can show up in audit/history later (out of scope for v1; design future-friendly).
Errors
ForbiddenError— no userId on ctx.NotFoundError— token not found, not owned by caller, or already revoked.