You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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
subjectvalues differ by environment.Current State
SKETCHI_ADMIN_EMAILSSKETCHI_ADMIN_SUBJECTSSKETCHI_ICON_LIBRARY_EDITOR_EMAILSSKETCHI_ICON_LIBRARY_EDITOR_SUBJECTSanand@shpit.dev) in source.users.rolecurrently mirrors the env-based admin check during user upsert; it is not the durable source of truth for authorization.Relevant current touchpoints:
packages/backend/convex/lib/users.tspackages/backend/convex/users.tspackages/backend/convex/iconLibraries.ts.github/workflows/e2e-web.ymlDesired Direction
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:
usersas the app-level identity mirror in ConvexAuthz Flow
In Scope
Out of Scope
Open Questions
user,admin) plus separate capabilities, or capabilities only?Notes
Acceptance Criteria
Validation Ideas
convex: authz helper and role/capability resolutionconvex: bootstrap path behaviorconvex: public icon library permission checksstagehand: signed-in admin sees admin-only affordancesstagehand: signed-in non-admin cannot access admin-only flowsstagehand: signed-in public-icon-editor can edit/create public icon packs without full admin access