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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions