Skip to content

Replace env-based admin/editor allowlists with app-owned authorization in Convex #199

@anand-testcompare

Description

@anand-testcompare

Replace env-based admin/editor allowlists with app-owned authorization in Convex

Summary

We currently authenticate with WorkOS AuthKit and authorize a small set of privileged behaviors via environment/source allowlists. That works as a stopgap, but it is operationally awkward and not a clean long-term answer for admin vs non-admin behavior.

This issue is to move toward app-owned authorization in Convex data while keeping WorkOS as the authentication layer.

Why

  • Local / preview / production use different WorkOS clients, so subject values differ by environment.
  • Email claims are not reliable enough to be the only authorization primitive in the current token path.
  • We do not have a first-class admin management path today.
  • We already have at least two distinct privilege levels:
    • full admin
    • users who can manage public icon libraries

Current State

  • WorkOS AuthKit is the authentication layer for the web app and Convex backend.
  • Convex derives privileged access from:
    • SKETCHI_ADMIN_EMAILS
    • SKETCHI_ADMIN_SUBJECTS
    • SKETCHI_ICON_LIBRARY_EDITOR_EMAILS
    • SKETCHI_ICON_LIBRARY_EDITOR_SUBJECTS
  • There is also a hardcoded default public icon library editor email (anand@shpit.dev) in source.
  • users.role currently mirrors the env-based admin check during user upsert; it is not the durable source of truth for authorization.
  • Public icon library edit capability is checked in backend permission helpers and icon library queries/mutations.
  • Preview E2E currently covers auth gates and general authenticated continuity, but not admin/public-icon-editor capability.

Relevant current touchpoints:

  • packages/backend/convex/lib/users.ts
  • packages/backend/convex/users.ts
  • packages/backend/convex/iconLibraries.ts
  • .github/workflows/e2e-web.yml

Desired Direction

  • Keep WorkOS responsible for authentication only: “who is this user?”
  • Make Convex data the source of truth for authorization: “what can this user do?”
  • Represent privileged access as explicit app roles/capabilities instead of deployment-specific allowlists.
  • Support an initial bootstrap path for the first admin in a deployment without making env vars the permanent management model.
  • Start with backend management primitives first; add a UI only if/when it becomes worth it.

High-Level Approach

We should introduce an app-owned authorization model in Convex that is keyed off the authenticated WorkOS identity (subject / external ID), not off deployment env vars.

This does not require changing authentication providers. WorkOS can remain the identity provider, while Convex stores and enforces app roles/capabilities.

At a high level, that likely means:

  • keep users as the app-level identity mirror in Convex
  • make roles/capabilities in Convex the durable authz source of truth
  • add a bootstrap path for the first admin in a deployment
  • add guarded backend mutations / internal APIs for later role management
  • optionally add a small admin UI later if role changes become common

Authz Flow

flowchart LR
  A[WorkOS AuthKit] -->|access token| B[Convex auth]
  B -->|subject / externalId| C[Convex users + roles/capabilities]
  C --> D[Authorization checks in queries and mutations]
  E[Admin management API or script] --> C
Loading

In Scope

  • Decide and implement a clean app-owned authorization source of truth in Convex
  • Define the bootstrap story for the first admin in a deployment
  • Define how non-admin elevated permissions should be represented
  • Migrate current admin/public-icon-editor checks away from env/source allowlists
  • Add the minimum management surface needed to maintain roles/capabilities after bootstrap

Out of Scope

  • Adopting a full organization model unless we explicitly decide to do that as part of this issue
  • Fine-grained per-resource ACL/FGA
  • Building a polished admin portal up front if backend management primitives are sufficient

Open Questions

  • Should we model this as coarse roles (user, admin) plus separate capabilities, or capabilities only?
  • Should “manage public icon libraries” remain a distinct capability from full admin?
  • What should the bootstrap path look like in practice:
    • internal mutation + CLI script
    • one-time guarded admin setup flow
    • something else
  • Do we want a minimal admin page in the first pass, or just backend management primitives?

Notes

  • This issue is intentionally about the direction and system shape, not a strict implementation script.
  • WorkOS-native RBAC / roles and permissions remain a viable alternative, but the current app is not organization-aware today.
  • Convex-owned authorization appears to be the best fit for the current product shape.

Acceptance Criteria

  • There is one clear app-owned source of truth for privileged access in Convex
  • The bootstrap story for the first admin in a deployment is defined
  • Full admin and public-icon-editor access are represented intentionally, not as ad hoc allowlists
  • The resulting design is preview/local/prod friendly and does not depend on hardcoded per-env subjects in source
  • The repo has a defined path for managing authorization after bootstrap, even if the first version is backend/API only

Validation Ideas

  • convex: authz helper and role/capability resolution
  • convex: bootstrap path behavior
  • convex: public icon library permission checks
  • stagehand: signed-in admin sees admin-only affordances
  • stagehand: signed-in non-admin cannot access admin-only flows
  • stagehand: signed-in public-icon-editor can edit/create public icon packs without full admin access

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions