Permissions
Role-based access control as a Bijection component — typed, sandboxed, runtime-editable roles and grants keyed by opaque refs
git clone https://github.com/bijectionhq/permissions.git
cd permissions
git checkout 2f4fa8f5f4946a6158715221fcb4b661af28644cBijection 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.
Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.
Getting started
- Get the source at the revision above and read its package documentation.
- Check the required Bijection SDK version, dependencies, and external service credentials.
- Follow the package setup instructions and the guide to using components.
- 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.
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 everydoc.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. requirethrows —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
| Method | Kind | Result |
|---|---|---|
defineRole(ctx, { name, grants, description? }) | mutation | Upsert a role (runtime-editable) |
removeRole(ctx, name) | mutation | Delete a role by name |
assign(ctx, { subjectRef, role, scopeRef? }) | mutation | Grant a role to a subject |
revoke(ctx, { subjectRef, role, scopeRef? }) | mutation | Remove a role from a subject |
check(ctx, { subjectRef, action, scopeRef? }) | query | Boolean permission check (default-deny) |
require(ctx, { subjectRef, action, scopeRef? }) | query | Enforce access — throws on deny |
rolesFor(ctx, { subjectRef, scopeRef? }) | query | List role names for a subject |
permissionsFor(ctx, { subjectRef, scopeRef? }) | query | List distinct grants for a subject |
listRoles(ctx) | query | All 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.