Administering access
This page contains task-oriented recipes for managing users, roles, groups, memberships, and pool grants with the qod CLI. They require a valid admin credential on every request.
For a description of the underlying data model, the EffectiveSet, pool-access gates, and privilege-escalation rules, see the "Access control model" page.
Authentication
Every RBAC endpoint admits either of two transports:
- Static key - set
QOD_API_KEYin the manager environment and pass it asX-API-Key. Best for CLI scripts and CI. - Session token -
POST /api/auth/loginwith{"username":"...","password":"..."}. Two ways to use the response:- Cookie (browser default). The login response sets
qod_session=<jwt>as an HttpOnly Secure SameSite=Lax cookie; the browser auto-attaches it on subsequent same-origin calls. JavaScript cannot read or send it manually - the/api/auth/whoamiand/api/auth/logoutendpoints accept the cookie automatically. - Header (CLI). The JSON body still contains a
tokenfield; pass it asX-API-Key: $TOKENfor non-browser callers.
- Cookie (browser default). The login response sets
Tenant scope on every endpoint
Every RBAC handler checks the caller's session scope before mutating. A tenant-A admin session calling a tenant-B endpoint (resource id, query, or body field) gets:
{ "error": "tenant_forbidden",
"message": "session has no admin grant on tenant 't-...'" }
with HTTP 403. Superuser and static-key sessions bypass the gate. Unknown ids return 404 (not 403) so a probe cannot distinguish "exists in another tenant" from "doesn't exist at all". The pattern covers id-only endpoints (/role/delete, /user/delete, /group/delete, /role/permission/revoke, /pool/permission/revoke, all 6 membership ops, the per-user /effective) - those resolve the owning tenant from the resource before applying the check.
The examples below assume qod login has stored a session (see the CLI section); CI scripts can use QOD_API_KEY instead.
1. Create a role and grant a table permission
A role bundles one or more table permissions. First create the role, then attach grants to it.
Create a role
qod role create --tenant acme --name analyst --description "Read-only analyst role"
Response includes the generated id field. Copy it for the next step.
{"id":"r-7a2b...","tenantId":"...","name":"analyst","description":"Read-only analyst role","createdAt":"..."}
Grant a table permission to the role
Request body fields: roleId (required), catalog (default *), schema (default *), table (default *), verb (required; one of RO, RW, DDL, ALL). RO grants read-only, RW grants read + any DML (INSERT/UPDATE/DELETE/MERGE/TRUNCATE), DDL grants CREATE/DROP/ALTER, ALL grants everything.
qod role permission grant --role-id <role-id> --catalog tpch --schema tpch1 --table customer --verb RO
To grant access to every table in a tenant (wildcard), omit --catalog, --schema, and --table (they default to *):
qod role permission grant --role-id <role-id> --verb RO
In the admin UI: open the tenant detail page, navigate to the Roles tab, create a role, then use the permission editor to add grants.
2. Create a group and attach a role
Groups collect multiple users under a shared set of role assignments.
Create a group
qod group create --tenant acme --name data-team --description "Data analysts group"
Attach a role to the group
qod membership group-role add --group-id <group-id> --role-id <role-id>
This command is idempotent: calling it multiple times with the same pair produces no error and no duplicate row.
To remove the assignment:
qod membership group-role remove --group-id <group-id> --role-id <role-id>
In the admin UI: open the tenant detail page, navigate to the Groups tab, create a group, then use the role assignment controls to link roles.
3. Create a user
Tenant-scoped user
A tenant-scoped user can only connect to pools belonging to their tenant. The role field controls the admin UI role (user or admin); it is not an RBAC role in the access-control sense.
qod user create --tenant acme --username alice --password s3cr3t --role user
Superuser (tenant null)
A superuser has tenant: null. Superusers bypass the pool-access gate and the per-statement ACL gate entirely. Only an existing superuser may create another superuser; a tenant-scoped admin attempting this call receives a 403.
qod user create --username ops-admin --password 'str0ng!' --superuser --role admin
Passing --superuser (and omitting --tenant) is equivalent to passing "tenant": null on the REST body.
In the admin UI: open the Users page, click "Create user", fill in the form. The superuser option appears only when the logged-in user is themselves a superuser.