Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Permissions

Role-based access control as a Bijection component — typed, sandboxed, runtime-editable roles and grants keyed by opaque refs

Bijection source
git clone https://github.com/bijectionhq/permissions.git
cd permissions
git checkout 2f4fa8f5f4946a6158715221fcb4b661af28644c

Bijection repository access is required. Request access.

Description

Role-based access control as a Bijection component — typed, sandboxed, runtime-editable roles and grants keyed by opaque refs

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available as a reference

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/permissions. Based on vllnt/convex-permissions. License: MIT. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

Role-based access control as a Convex component — typed, sandboxed, runtime-editable roles and grants keyed by opaque refs

Explore the demo ↗

Upstream overview

From the upstream package documentation.

@vllnt/convex-permissions stores role-based access control rules in isolated Convex tables. Define your own role and action vocabulary, assign roles to subject references, and evaluate access in the backend.

Define roles and grants, assign or revoke roles, and use check or require before a protected operation. Stored roles can change through mutations without a code deployment. Optional scope references partition assignments; unmatched actions are denied.

The host authenticates the caller, derives trusted subject and scope references, and protects role-management functions. Global assignments can apply across scopes, so grant them deliberately. The component is authorization storage and evaluation, not a login provider or an automatic guard on every endpoint.

Maintained by bntvllnt at VLLNT. Find the author on GitHub and X.

Use cases

From the upstream package documentation.

  • SaaS workspaces: distinguish owners, administrators, and members by workspace.
  • Education tools: give instructors and learners different actions.
  • Communities and games: authorize moderation or team-management operations.
  • Internal tools and agents: limit service identities to explicitly granted actions.

How it works

From the upstream package documentation.

Define roles and grants, assign or revoke roles, and use check or require before a protected operation. Stored roles can change through mutations without a code deployment. Optional scope references partition assignments; unmatched actions are denied.

The host authenticates the caller, derives trusted subject and scope references, and protects role-management functions. Global assignments can apply across scopes, so grant them deliberately. The component is authorization storage and evaluation, not a login provider or an automatic guard on every endpoint.

Upstream package FAQ

Does @vllnt/convex-permissions work with any auth provider?

Yes. The @vllnt/convex-permissions component is intentionally agnostic. It identifies subjects, scopes, and actions using opaque strings you supply, with no coupling to Clerk, Auth0, or any other auth library. Your host function authenticates the caller however it chooses, resolves their identity to a subjectRef string, and passes that string to the component.

What happens when a subject has no assigned role or the action is not covered by any grant?

The @vllnt/convex-permissions component is default-deny. If a subject has no matching role assignment, or if none of their assigned roles include a grant covering the requested action, permissions.check() returns false and permissions.require() throws a ConvexError<PermissionDenied>. Unknown subjects are denied without error, so there is no way to accidentally permit an unrecognized caller.

Can roles and grants be changed without redeploying the Convex backend?

Yes. In @vllnt/convex-permissions, role definitions, action grants, and subject assignments are all stored in sandboxed Convex tables. You update them by calling the component's mutation methods (defineRole, removeRole, assign, revoke) from your own host mutations at runtime. No schema change or redeploy is needed.

How do I install and register the @vllnt/convex-permissions component?

Run npm install @vllnt/convex-permissions to add the package. Then register it in your convex/convex.config.ts file by importing the component config and calling app.use(permissions). After that, instantiate a typed client in a convex/permissions.ts file using new Permissions<Role, Action>(components.permissions) with your own Role and Action union types.

Is @vllnt/convex-permissions compatible with self-hosted Convex backends?

Yes. According to the README, @vllnt/convex-permissions runs identically on Convex Cloud and self-hosted convex-backend deployments. The peer dependency is convex@^1.36.1.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Permissions with Bijection.

Read: https://bijection.com/components/permissions/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from vllnt/convex-permissions, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

@vllnt/convex-permissions

Role-based access control as a Convex component — typed, sandboxed, runtime-editable roles and grants keyed by opaque refs.

// Define a role, assign it, enforce it — all from host functions.
await permissions.defineRole(ctx, { name: "editor", grants: ["doc.read", "doc.edit"] });
await permissions.assign(ctx, { subjectRef: userId, role: "editor" });
await permissions.require(ctx, { subjectRef: userId, action: "doc.edit" }); // throws on deny

Features

  • Stored, runtime-editable RBAC — roles, grants, and assignments live in sandboxed tables; change them with a mutation, no redeploy.
  • Typed end to end — Permissions<TRole, TAction> is generic over your role and action unions.
  • Wildcard grants — "doc.*" grants every doc. action; "*" grants everything.
  • Scoped / multi-tenant — assign within an opaque scopeRef; unscoped assignments apply globally.
  • Agnostic by construction — opaque subjectRef / scopeRef, no auth library, no domain model, no vendor.
  • require throws — ConvexError<PermissionDenied> the host maps to a 403.
  • Default-deny — unknown subjects and unmatched actions are denied.
  • Runs anywhere — identical on Convex Cloud and self-hosted convex-backend.

Installation

npm install @vllnt/convex-permissions

Peer dependency: convex@^1.36.1.

Usage

// convex/convex.config.ts
import { defineApp } from "convex/server";
import permissions from "@vllnt/convex-permissions/convex.config";

const app = defineApp();
app.use(permissions);
export default app;
// convex/permissions.ts — instantiate the typed client, then define / assign / check.
import { components } from "./_generated/api";
import { Permissions } from "@vllnt/convex-permissions";

type Role = "admin" | "editor" | "viewer";
type Action = "doc.read" | "doc.edit" | "doc.delete";

export const permissions = new Permissions<Role, Action>(components.permissions);

// from any host mutation/query (gate management behind your own admin auth):
await permissions.defineRole(ctx, { name: "editor", grants: ["doc.read", "doc.edit"] });
await permissions.assign(ctx, { subjectRef: userId, role: "editor", scopeRef: orgId });
const allowed = await permissions.check(ctx, { subjectRef: userId, action: "doc.edit" });
await permissions.require(ctx, { subjectRef: userId, action: "doc.edit" }); // throws on deny

API Reference

MethodKindResult
defineRole(ctx, { name, grants, description? })mutationUpsert a role (runtime-editable)
removeRole(ctx, name)mutationDelete a role by name
assign(ctx, { subjectRef, role, scopeRef? })mutationGrant a role to a subject
revoke(ctx, { subjectRef, role, scopeRef? })mutationRemove a role from a subject
check(ctx, { subjectRef, action, scopeRef? })queryBoolean permission check (default-deny)
require(ctx, { subjectRef, action, scopeRef? })queryEnforce access — throws on deny
rolesFor(ctx, { subjectRef, scopeRef? })queryList role names for a subject
permissionsFor(ctx, { subjectRef, scopeRef? })queryList distinct grants for a subject
listRoles(ctx)queryAll role definitions

Full reference: docs/API.md.

Security

  • Host owns auth — it authenticates the caller, resolves identity to an opaque subjectRef, and gates the management methods behind its own admin authorization.
  • Opaque refs only — subjectRef, scopeRef, and action keys are arbitrary strings; tables are sandboxed (reached only via the client).
  • Default-deny — no matching grant ⇒ denied; boundary validation rejects refs not matching ^[A-Za-z0-9_.:-]{1,128}$.

See docs/API.md.

Testing

pnpm test           # single run
pnpm test:coverage  # enforced 100% on covered files

Tests run against the real component runtime via convex-test (@edge-runtime/vm), not mocks.

Contributing

See CONTRIBUTING.md.

Author

Built by bntvllnt · bntvllnt.com · X @bntvllnt

Part of the @vllnt Convex component fleet — vllnt.com

If this is useful, sponsor the work.

License

MIT — see LICENSE.

Keywords