Skip to content

[WAVE-39] LOW: No organization suspension state — API keys remain valid after plan cancellation #335

Description

@walidboulanouar

File: apps/api/src/middleware/auth.ts lines 67–87; packages/db/src/schema/auth.ts organizations table

Vulnerability: The organizations table has no status, suspended_at, or cancelled_at column. The apiKeyAuth middleware joins api_keys → organizations and checks only key expiry and revocation — it has no gate for org-level suspension:

.where(
  and(
    eq(apiKeys.keyHash, keyHash),
    or(isNull(apiKeys.expiresAt), gt(apiKeys.expiresAt, new Date())),
    isNull(apiKeys.revokedAt),
  ),
)

If an org's subscription is cancelled, the only available remediation is:

  1. Delete the entire org (destroys all audit data).
  2. Revoke every API key one by one.
  3. Remove productsEnabled (only gates requireProduct routes — non-product routes remain accessible).

There is no single-operation "suspend org" that blocks all API access while preserving the audit trail.

Impact: A churned or payment-lapsed org retains full API access until every individual API key is manually revoked. An automated billing webhook that sets suspended_at cannot stop access with the current schema.

Fix:

  1. Add suspended_at timestamp to organizations.
  2. In apiKeyAuth, add isNull(organizations.suspendedAt) to the WHERE clause.
  3. Expose a PATCH /v1/admin/organizations/:id field (suspended: true) in the admin route.

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity findings

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions