Assigning roles

Give your app's users, agents and scheduled jobs their roles, from the console, the API or a handler, within the grant ceiling.

A role does nothing until someone holds it. This page covers the three ways a role reaches a principal, and the one rule all three share: you can only hand out what you already hold.

Roles, and whose they are

Roles exist at two levels in an app:

  • Shared roles (user, admin, superuser, agent, and the capability roles such as Schema Builder) are seeded into every app and exist in every organization. They carry the seeded policies, and you can add your own policies to them.
  • An organization's own roles are created by that organization's owner, for their team. They exist only in that organization, and take only that organization's own policies (POLICY_ROLE_SCOPE_MISMATCH otherwise).

A binding is always in an organization: Dana can be admin in her own organization and hold nothing in another she has joined.

From the console

Under App access › Roles you create roles and attach policies to them (Assign Policy). An agent's roles are set on the agent's page, and a scheduled job's on the job's page. A job is created paused and holding no roles, and giving it one (arming it) is a separate decision, flagged high risk, so an agent asked to do it halts for a person to approve.

From your app, over the API

An organization's owner can assign roles to its members. The call has two steps: review returns what would be bound and a reviewHash, and confirm with that hash binds it. The hash ties the confirmation to exactly what was reviewed.

# 1. Review
curl -X POST "$AUTHORIC_URL/$APP_UID/autocrud/users/actions/bind-role" \
  -H "Authorization: Bearer $OWNER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "review", "user_uid": "<user uid>", "role_key": "admin" }'

# 2. Confirm with the hash from the review
curl -X POST "$AUTHORIC_URL/$APP_UID/autocrud/users/actions/bind-role" \
  -H "Authorization: Bearer $OWNER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "mode": "confirm", "user_uid": "<user uid>", "role_key": "admin", "reviewHash": "<reviewHash>" }'
  • Name the person by user_uid (or email), and the role by role_uid or role_key.
  • Inside your app, a binding is always made in the caller's own organization. target_org_uid may name only that one: another organization, or a person who is not in the caller's, answers 404, the same as one that does not exist. From the console, the app's owner binds in any of the app's organizations: the person's home organization, or the one target_org_uid names.
  • Confirming again is harmless: the response says alreadyBound: true.
  • unbind-role takes the same body and removes the binding. Removing access needs no ceiling.
  • GET /{appUid}/autocrud/users/actions/{userUid}/roles lists what a user holds.

Every bind and unbind is recorded in the app's audit trail.

From a handler: sdk.roles

Your app has a roster: someone is promoted to dispatcher, someone leaves. That is application logic, so it can live in handler code:

export default async function (data, ctx) {
  const { sdk } = ctx;

  await sdk.roles.assign({ userUid: data.userUid, roleKey: "dispatcher" });
  const held = await sdk.roles.list({ userUid: data.userUid });
  // later: await sdk.roles.unassign({ userUid: data.userUid, roleKey: "dispatcher" });

  return { roles: held.map((role) => role.key_name) };
}

Three independent checks stand behind every assign:

  1. The handler's capability. Assigning needs sdk:roles_write, which no handler has by default and which is approved when the handler is published. Reading roles is granted by default.
  2. The caller's own policy. The person running the handler must hold users:bind-role themselves. A handler is never a way to gain a power the caller lacks.
  3. The grant ceiling, below.

The organization comes from the run, never from the arguments: orgUid may only restate the caller's own organization (ROLE_ORG_MISMATCH), and the person must already belong to it. Roles are given only to people from handler code; an agent or a scheduled job is never armed this way (PRINCIPAL_NOT_HUMAN).

The grant ceiling

Every door that grants (binding a role to a person, an agent or a job; attaching a policy to a role; creating a policy) first checks that the granter already holds every action being handed out. A role is compared by its contents, so a role that carries one action the granter lacks is refused with 403 GRANT_EXCEEDS_HOLDER, and the response names the missing actions.

This is what makes delegation safe. Whatever you grant an organization's owner is the most that organization can ever spread among its own members.

Errors to branch on

CodeStatusMeaning
GRANT_EXCEEDS_HOLDER403The role carries actions the granter does not hold.
ROLE_NOT_FOUND404No such role in this organization.
ROLE_ORG_MISMATCH403A role is granted inside the caller's own organization.
MEMBER_NOT_FOUND404The person is not a member of that organization.
PRINCIPAL_NOT_HUMAN403Handler code tried to give a role to an agent or a job.
CRON_ROLE_OUTSIDE_ORG403A scheduled job can only be given a role your organization owns.
REVIEW_HASH_MISMATCH409What you confirmed is not what was reviewed. Review again.
ROLE_PROTECTED400A seeded role cannot be deleted.

What's next

On this page