{"id":51317926,"url":"https://github.com/vllnt/convex-permissions","last_synced_at":"2026-07-01T09:31:28.887Z","repository":{"id":364821491,"uuid":"1267333656","full_name":"vllnt/convex-permissions","owner":"vllnt","description":"Typed authorization for Convex — define roles and policies, enforce access with type-safe guards (require/check/can).","archived":false,"fork":false,"pushed_at":"2026-06-14T15:47:09.000Z","size":102,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T17:23:37.191Z","etag":null,"topics":["abac","authorization","authz","convex","convex-component","permissions","rbac","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@vllnt/convex-permissions","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vllnt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null},"funding":{"github":"bntvllnt"}},"created_at":"2026-06-12T12:52:11.000Z","updated_at":"2026-06-14T15:55:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/vllnt/convex-permissions","commit_stats":null,"previous_names":["vllnt/convex-permissions"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/vllnt/convex-permissions","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-permissions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-permissions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-permissions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-permissions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vllnt","download_url":"https://codeload.github.com/vllnt/convex-permissions/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vllnt%2Fconvex-permissions/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35001648,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["abac","authorization","authz","convex","convex-component","permissions","rbac","typescript"],"created_at":"2026-07-01T09:31:24.144Z","updated_at":"2026-07-01T09:31:28.879Z","avatar_url":"https://github.com/vllnt.png","language":"TypeScript","funding_links":["https://github.com/sponsors/bntvllnt"],"categories":[],"sub_categories":[],"readme":"\u003c!-- Badges --\u003e\n[![Convex Component](https://img.shields.io/badge/convex-component-EE342F.svg)](https://www.convex.dev/components)\n[![npm version](https://img.shields.io/npm/v/@vllnt/convex-permissions.svg)](https://www.npmjs.com/package/@vllnt/convex-permissions)\n[![CI](https://github.com/vllnt/convex-permissions/actions/workflows/ci.yml/badge.svg)](https://github.com/vllnt/convex-permissions/actions/workflows/ci.yml)\n[![License](https://img.shields.io/npm/l/@vllnt/convex-permissions.svg)](./LICENSE)\n\n# @vllnt/convex-permissions\n\nRole-based access control as a Convex component — typed, sandboxed,\nruntime-editable roles and grants keyed by opaque refs.\n\n```ts\n// Define a role, assign it, enforce it — all from host functions.\nawait permissions.defineRole(ctx, { name: \"editor\", grants: [\"doc.read\", \"doc.edit\"] });\nawait permissions.assign(ctx, { subjectRef: userId, role: \"editor\" });\nawait permissions.require(ctx, { subjectRef: userId, action: \"doc.edit\" }); // throws on deny\n```\n\n## Features\n\n- **Stored, runtime-editable RBAC** — roles, grants, and assignments live in sandboxed tables; change them with a mutation, no redeploy.\n- **Typed end to end** — `Permissions\u003cTRole, TAction\u003e` is generic over your role and action unions.\n- **Wildcard grants** — `\"doc.*\"` grants every `doc.` action; `\"*\"` grants everything.\n- **Scoped / multi-tenant** — assign within an opaque `scopeRef`; unscoped assignments apply globally.\n- **Agnostic by construction** — opaque `subjectRef` / `scopeRef`, no auth library, no domain model, no vendor.\n- **`require` throws** — `ConvexError\u003cPermissionDenied\u003e` the host maps to a 403.\n- **Default-deny** — unknown subjects and unmatched actions are denied.\n- **Runs anywhere** — identical on Convex Cloud and self-hosted `convex-backend`.\n\n## Installation\n\n```bash\nnpm install @vllnt/convex-permissions\n```\n\nPeer dependency: `convex@^1.36.1`.\n\n## Usage\n\n```ts\n// convex/convex.config.ts\nimport { defineApp } from \"convex/server\";\nimport permissions from \"@vllnt/convex-permissions/convex.config\";\n\nconst app = defineApp();\napp.use(permissions);\nexport default app;\n```\n\n```ts\n// convex/permissions.ts — instantiate the typed client, then define / assign / check.\nimport { components } from \"./_generated/api\";\nimport { Permissions } from \"@vllnt/convex-permissions\";\n\ntype Role = \"admin\" | \"editor\" | \"viewer\";\ntype Action = \"doc.read\" | \"doc.edit\" | \"doc.delete\";\n\nexport const permissions = new Permissions\u003cRole, Action\u003e(components.permissions);\n\n// from any host mutation/query (gate management behind your own admin auth):\nawait permissions.defineRole(ctx, { name: \"editor\", grants: [\"doc.read\", \"doc.edit\"] });\nawait permissions.assign(ctx, { subjectRef: userId, role: \"editor\", scopeRef: orgId });\nconst allowed = await permissions.check(ctx, { subjectRef: userId, action: \"doc.edit\" });\nawait permissions.require(ctx, { subjectRef: userId, action: \"doc.edit\" }); // throws on deny\n```\n\n## API Reference\n\n| Method | Kind | Result |\n|--------|------|--------|\n| `defineRole(ctx, { name, grants, description? })` | mutation | Upsert a role (runtime-editable) |\n| `removeRole(ctx, name)` | mutation | Delete a role by name |\n| `assign(ctx, { subjectRef, role, scopeRef? })` | mutation | Grant a role to a subject |\n| `revoke(ctx, { subjectRef, role, scopeRef? })` | mutation | Remove a role from a subject |\n| `check(ctx, { subjectRef, action, scopeRef? })` | query | Boolean permission check (default-deny) |\n| `require(ctx, { subjectRef, action, scopeRef? })` | query | Enforce access — throws on deny |\n| `rolesFor(ctx, { subjectRef, scopeRef? })` | query | List role names for a subject |\n| `permissionsFor(ctx, { subjectRef, scopeRef? })` | query | List distinct grants for a subject |\n| `listRoles(ctx)` | query | All role definitions |\n\nFull reference: [docs/API.md](docs/API.md).\n\n## Security\n\n- **Host owns auth** — it authenticates the caller, resolves identity to an opaque `subjectRef`, and gates the management methods behind its own admin authorization.\n- **Opaque refs only** — `subjectRef`, `scopeRef`, and action keys are arbitrary strings; tables are sandboxed (reached only via the client).\n- **Default-deny** — no matching grant ⇒ denied; boundary validation rejects refs not matching `^[A-Za-z0-9_.:-]{1,128}$`.\n\nSee [docs/API.md](docs/API.md).\n\n## Testing\n\n```bash\npnpm test           # single run\npnpm test:coverage  # enforced 100% on covered files\n```\n\nTests run against the real component runtime via `convex-test` (`@edge-runtime/vm`), not mocks.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## Author\n\nBuilt by [bntvllnt](https://github.com/bntvllnt) · [bntvllnt.com](https://bntvllnt.com) · [X @bntvllnt](https://x.com/bntvllnt)\n\nPart of the [@vllnt](https://github.com/vllnt) Convex component fleet — [vllnt.com](https://vllnt.com)\n\nIf this is useful, [sponsor the work](https://github.com/sponsors/bntvllnt).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvllnt%2Fconvex-permissions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvllnt%2Fconvex-permissions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvllnt%2Fconvex-permissions/lists"}