Roles, tenants and an audit log
Roles, per-tenant isolation and the audit trail an enterprise buyer asks for — the authorization half of SSO, never the sign-in half
Decides what each signed-in person is allowed to do, keeps one company’s data apart from another’s, and writes down who changed what. It begins after sign-in — you bring the sign-in itself.
A feature of the Playground
This is a feature of the Vibe Coder Playground. It runs inside the Playground, with its audit record and its caveats beside it.
The mistake it removes
Two failures, both silent. A role model with no identity behind it (looks complete, denies nothing), and tenant isolation enforced in application code rather than at the data layer — where one forgotten WHERE clause serves another customer's rows and nothing errors.
What you still have to do
The feature cannot own your secrets, your host or your legal obligations. This is the part it deliberately does not claim.
- A real identity provider you wire yourself — OIDC or SAML sign-in, a session, and a membership record mapping each signed-in user to a tenant and a role — because can()/assertCan() read the role the CALLER hands them and will grant whatever you pass. This is the exact failure the source codebase discloses about itself: a complete ranked role table that could not deny anything, because nothing bound a request to a non-owner role. Until identity is bound, every request resolves to one actor and the role check is documentation.
- DISCOVERY, SAID PLAINLY: searching this registry for "sso", "single sign on" or "saml" no longer returns this block, and that is deliberate — it implements none of them. There is no OIDC client, no SAML assertion parsing, no session and no membership table, so if you came here for sign-in you would still have to build or buy all of it. What this block gives you is the half that runs AFTER sign-in: deciding what an already-identified actor may do. The terms it refuses to answer are listed machine-readably in notImplemented, and the companion control is named in ddCoverage for DD category 1.
- IT IS A SCHEDULED GAP, NOT AN ABANDONED ONE: real OIDC/SAML sign-in, sessions and membership records are planned for the v1.0 release of this registry (roadmap ph105tsk12), sized at 5-7 weeks. Until they ship, bring your own identity provider — do not wait for this block to grow one under you.
- This gives you the AUTHORIZATION half. Real per-user identity (SSO/OIDC, sessions, membership records) is the other half, and without it every request still resolves to one actor — exactly the gap the source codebase discloses about itself. Wire identity before claiming RBAC to a buyer.
- withTenant() scopes queries in application code. That is the right first step and it is NOT equivalent to row-level security: one query written without it still reads across tenants. Enable RLS at the database when you take real customer data.
- withTenant() PARSES the query you hand it rather than patching its text: it parenthesizes your WHERE clause and inserts the tenant filter before any ORDER BY, LIMIT, GROUP BY or RETURNING. It scopes ONE SELECT, UPDATE or DELETE over ONE simple table — aliased or schema-qualified is fine, and so is a sub-select inside your select list or your WHERE. It REFUSES, BY NAME, every statement whose tenant predicate it cannot place with certainty: a CTE ("WITH …"), a JOIN or a comma-separated FROM list, UPDATE…FROM, DELETE…USING, a sub-select or table function in the FROM position, an INSERT (put the tenant id in the row you write), a UNION/INTERSECT/EXCEPT (scope each branch first), more than one statement, and any SQL it cannot fully read. WHAT THAT MEANS FOR YOU: a multi-table query is yours to scope by hand, with the tenant condition written where you can see which table it binds to. The refusal is deliberate and it is the safe direction — a query that will not run is visible in seconds, where a filter bound to the wrong table serves, overwrites or deletes another customer's rows silently.
What lands in your project
node
- node/enterprise-kit.js
can()/assertCan() over a ranked role table, withTenant() to scope every query, and auditLog() for the trail SOC 2 asks about. Roles come from the CALLER's identity — the block refuses to guess.
What you supply
- roles — ranked least→most: none, member, admin, owner
- actions — an explicit table — an UNKNOWN action is owner-only, never allowed
- tenantColumn — the column every scoped query filters on (default tenant_id)
Get it
Download the Playground See pricing
A feature of the Vibe Coder Playground.
Questions
- Is Roles, tenants and an audit log audited?
- No. No full audit has been run against this block yet, which is not the same as a pass. What is verified about it is listed on this page, and what is not is listed beside it.
- What do I still have to do myself?
- A real identity provider you wire yourself — OIDC or SAML sign-in, a session, and a membership record mapping each signed-in user to a tenant and a role — because can()/assertCan() read the role the CALLER hands them and will grant whatever you pass. This is the exact failure the source codebase discloses about itself: a complete ranked role table that could not deny anything, because nothing bound a request to a non-owner role. Until identity is bound, every request resolves to one actor and the role check is documentation.
- How do I get this?
- It is a feature of the Vibe Coder Playground. Download the Playground, and see the pricing page for what each plan includes.
All Playground features · Free code blocks · Learn to build from zero · The coding guide