Skip to content

User Management ​

Users sign in through Pocket ID; the Manager stores a mirror of each user and decides what they may do through four separate role systems.

The four role systems ​

LevelWhere it livesValuesWhat it controls
System roleUser.rolenormal, audit, administratorInstall-wide operator powers
Project roleUser2Project.rolereadonly, tester, developer, maintainer, ownerWho can open and manage a project in the Manager
Tenant roleUser2Tenant.role → a project-defined TenantRoleproject-configurable names (seeded: member, admin, owner)Tenant self-service permissions
Application roleUser2Application.rolereadonly, normalAccess inside your application's own UI, not Manager access

System role ​

administrator is the platform operator. It passes the "operator override" in project-scoped gates, and it alone can read the whole audit log export, run access reviews, push proxy configuration, list every user, and grant or revoke system roles. audit is stored but nothing reads it; it grants nothing beyond normal.

Administrators manage system roles on the System roles page (/system-roles; the sidebar entry is shown only to administrators). The server refuses to demote the last administrator. Every change writes an audit row.

graphql
mutation {
  setUserSystemRole(userId: "user-uuid", role: administrator) {
    id
    externalEmail
    role
  }
}

systemRoleDirectory { users { id externalName externalEmail role } administratorCount } lists everyone with their system role. Both are gated on administrator.

Project roles ​

Any project role lets a user open the project and read its tenants, applications, endpoints and settings. Managing (members, environments, endpoints, triggers, machine users, pilets, settings and application enablement) needs maintainer or owner, and so does renaming or deleting the project itself and any of its applications. The server does not treat tester and developer differently from readonly; they only differ in rank.

The one thing a readonly member can still write is a new tenant, application or language pack; those three creates are gated on bare membership. See Access Model.

Rank, lowest to highest: readonly < normal < tester < developer < maintainer < owner (one ladder shared with application roles, which use readonly and normal). You can assign only a role at or below your own (cannot assign role higher than your own); a system administrator overrides the rank check, which is the recovery path for a project that has locked itself out.

removeUserFromProject refuses to remove you from a project. Separately, and for every caller including an administrator, a removal or demotion that would leave a project with no owner is refused; the guard runs on the raw deleteUser2Project mutation too.

Tenant roles ​

Tenant roles are permission sets, not a fixed list. Each project defines its own roles over the vocabulary tenant.members.read, tenant.members.manage, tenant.invites.manage, tenant.roles.manage, tenant.apps.read, tenant.apps.configure, tenant.lifecycle.manage; new projects are seeded with member, admin and owner. When you assign a tenant role, its permissions must be a subset of your own permissions in that tenant. A membership whose role cannot be resolved grants nothing (shown as No role in the UI). See Access Model.

Application roles ​

readonly and normal are forwarded to your application for its own authorization decisions. The banner on the members card says it: Application roles control external UI access only. Manager access is controlled by tenant/project roles. Assigning them needs project maintainer or owner.

Adding users ​

To a project ​

  1. Open the project page (/<project>) and scroll to Project members.
  2. Click Add User.
  3. Search for user: the search runs against Pocket ID, so the user must exist there.
  4. Pick a Role (only roles at or below your own are offered).
  5. Click Add.
graphql
mutation {
  addUserToProject(projectID: "project-uuid", userID: "user-uuid", role: developer) {
    id
    role
    user {
      externalUsername
      externalEmail
    }
  }
}

To a tenant ​

  1. Open the tenant page (/<project>/<tenant>) and scroll to Tenant members.
  2. Click Add User, search, pick a Role (one of the project's tenant role names), click Add.

You need the tenant.members.manage permission in that tenant. A project maintainer or owner who is not a member of the tenant sees the panel read-only. The tenant's creator is made an owner member automatically.

graphql
mutation {
  addUserToTenant(tenantID: "tenant-uuid", userID: "user-uuid", roleName: "admin") {
    id
    roleName
  }
}

To an application ​

  1. Open the application page (/<project>/applications/<app>) and scroll to Application members.
  2. Click Add User, search, pick readonly or normal, click Add.
graphql
mutation {
  addUserToApplication(applicationID: "app-uuid", userID: "user-uuid", role: readonly) {
    id
    role
  }
}

Changing a role ​

In any of the three members lists, change the Role dropdown on the user's row. The change is saved immediately and confirmed with a notification.

graphql
mutation {
  updateUserProjectRole(id: "user2project-uuid", role: maintainer) { id role }
}
graphql
mutation {
  updateUserTenantRole(id: "user2tenant-uuid", roleName: "admin") { id roleName }
}
graphql
mutation {
  updateUserApplicationRole(id: "user2application-uuid", role: normal) { id role }
}

The id is the membership row, not the user.

Removing a user ​

Click the trash icon (Remove user) on the row and confirm.

graphql
mutation {
  removeUserFromProject(id: "user2project-uuid")
}

removeUserFromTenant(id) and removeUserFromApplication(id) work the same way.

Memberships are independent

Removing a project membership deletes only that row. Tenant and application memberships the user holds in the same project stay in place and must be removed separately.

Looking up users ​

Search Pocket ID for users who have not yet appeared in the Manager. The query needs an authority that lets you add a member somewhere: maintainer or owner on any project, tenant.members.manage in any tenant, or the system administrator role. Each match is upserted into the Manager's users table, so the search is also a write.

graphql
query {
  userProviderSearch(name: "john") {
    id
    username
    email
    displayName
    disabled
  }
}

The authenticated user:

graphql
query {
  me {
    id
    externalName
    externalUsername
    externalEmail
    role
  }
}

Audit trail ​

Membership changes on tenants, projects and applications (add, re-role, remove), invites, role definitions and system-role changes write AuditLog rows: entities User2Tenant, User2Project and User2Application respectively, carrying actor, target and the before/after role. The generated updateUser2* / deleteUser2* mutations write the same rows as the named ones, so a grant cannot be changed off the audited path.

Filter fields: entityType, entityID, action, description, ipAddress; sort fields: createdAt, action, entityType. entityType matches exactly (case-insensitively), so User2Project returns grants and Project returns project lifecycle rows; asking for one will not return the other.

target is not always the user

On User2Project and User2Application rows written by these mutations, metadata.target is the granted user and metadata.resource is the project or application. On User2Tenant rows it is the grant row's own id. See Audit Logs.

graphql
query {
  auditLogs(
    projectID: "project-uuid"
    filters: [{ field: "entityType", value: "User2Tenant" }]
    order: { field: "createdAt", direction: DESC }
    pagination: { limit: 50, offset: 0 }
  ) {
    id
    action
    description
    user {
      externalUsername
    }
    createdAt
  }
}

See Audit Logs.

Pocket ID configuration ​

The Manager reads Pocket ID's API to search users:

yaml
pocket_id:
  host: http://pocketid:1411
  api_key: your-api-key

pocket_id.api_key is required in production and can also be set with the PFY_POCKET_ID_API_KEY environment variable. Login itself (OIDC) is handled by the Proxy; see Proxy Configuration and Configuration.

Troubleshooting ​

The user is not found when searching ​

They do not exist in Pocket ID, or the Manager cannot reach Pocket ID (pocket_id.host / pocket_id.api_key).

cannot assign role higher than your own ​

Project and application roles are ranked; you can only hand out your own rank or lower. Tenant roles must be a permission subset of yours.

The tenant member controls are read-only for me ​

You hold a project role but no membership in the tenant. tenant.members.manage comes only from a tenant membership; ask a tenant member with that permission to add you.

A user cannot open a project ​

They have no project role. Any role (including readonly) is enough to open it.

API reference ​

See also: Access Model, Managing Tenants, Managing Projects.