User Journey: Change someone's access

Table of Contents

This page documents a user journey: A tenant administrator grants and revokes a colleague's roles.

A user journey describes what one person is trying to do, before anything is built. It is a design document that outlives any sprint, so it names the screens, the entity fields each screen shows, and the operations and messages each step needs.

1. Goal

"I need to give a colleague the roles their job needs, and take away the ones it does not." The tenant administrator sets the role list on a colleague's account. The journey is done when the colleague's access reads correctly, and the record of the change names the administrator, the time and the reason.

2. Actor and trigger

The tenant administrator: an account inside one tenant that holds iam::roles:assign and iam::roles:revoke. The trigger is a joiner, a move between desks, or a leaver. It starts when the administrator opens a colleague's account from Accounts, and ends when the role list is right and the change is recorded. This is an administrator acting on someone else's record inside their tenant.

This is the write half of the access topic. The colleague's own read-only view is Know what I may do.

3. Steps

  1. Find the colleague. The administrator opens Accounts and picks the person. The system lists the accounts in the tenant, so tenant isolation decides who is visible.
  2. Read what they hold. The system reads the roles assigned to the account and shows them. This list is the starting point.
  3. Choose a role to grant. The administrator opens the role picker. The system lists the roles defined in the tenant, with their descriptions.
  4. Grant the role. The administrator selects a role and confirms. The system writes an account_role row. The row records assigned_by (the administrator), assigned_at (the time), the change reason and the commentary. This step is what makes the grant auditable.
  5. Revoke a role. The administrator selects a held role, removes it, and gives a reason. The system closes the assignment: it sets valid_to on the open row instead of deleting it, so the grant stays in the history.
  6. Confirm. The system re-reads the role list. The administrator checks the result. The change takes effect on the colleague's next sign-in.
  7. Leave. The administrator closes the screen.

The assignment row carries assigned_by, assigned_at, change_reason_code and change_commentary, so a granted role is auditable in a way a plain join table would not be. A revoke closes the row and a later re-grant opens a new one, so the whole history of a person's access stays readable.

4. Screens and wireframes

One screen: the colleague's access. The identity header, the roles they hold with the assignment audit, the role picker that adds a role, and the change reason and commentary. The administrator does all of this in one place, so the panels belong together.

wireframe_change_someones_access.png

Figure 1: Change someone's access: the colleague, the roles they hold with who assigned each one, the role picker, and the change reason.

5. Entities composed

Entity What it contributes Model
account The colleague whose access changes: username, full name, account type ores.iam.account
role The roles held and the roles on offer: name and description ores.iam.role
account_role The assignment: assigned_by, assigned_at, change_reason_code and change_commentary no entity model — junction table
permission Not shown here. What the roles carry belongs to Know what I may do ores.iam.permission

The account_role row is more than a join. It carries an audit tail, so the grant itself is a record with an author, a time and a reason. There is no module entity and no module table in this model: the component segment of a permission code such as iam::roles:assign is what plays the part a module would play.

This screen shows no credential field. password_hash, password_salt, service_password_hash, totp_secret, cleartext passwords and JWTs must never reach the browser. The account read currently returns credential material anyway, and that is a gap named below.

6. Operations and messages

Step Operation Subject Status
Find the colleague List the accounts in the tenant iam.v1.accounts.list exists
Read what they hold Read the roles on the account iam.v1.ops.get_account_roles exists
Choose a role to grant List the roles defined in the tenant iam.v1.roles.list exists
Grant the role Assign a role to the account iam.v1.ops.assign_role exists
Revoke a role Revoke a role from the account iam.v1.ops.revoke_role exists
Confirm Read the assignment row: who, when, why iam.v1.ops.get_account_roles exists

The role subjects are defined in ores.iam.authorization_messages. Both writes check their own permission before they act: iam.v1.ops.assign_role refuses a caller without iam::roles:assign, and iam.v1.ops.revoke_role refuses one without iam::roles:revoke. That was not true when this journey was written — assign checked nothing, and any signed-in account could grant any role inside its tenant — and it is the first of the two gaps the access group's survey closed.

7. What is missing

The screens run in ores.web: People lists the tenant's accounts with their pictures and roles, and a person's page gives a role for a reason and takes one away. Nobody takes a role away from their own account: the server refuses it and the screen does not offer it (Implement the access journeys in ores.web).

  1. Closed for a grant, open for a revoke. assign_role_request carries change_reason_code and change_commentary, the grant row stores them, and an access reason category offers new joiner, role change, cover for absence and access review. A revoke closes the grant row, which has no place for a reason of its own, so the screen asks for none; recording why a role was taken away needs a closing-reason column or an event of its own.
  2. No guard on an empty role list. The account screen warns when an account has no party, but stays silent when it has no role (see the user guide chapter). The same silence applies when a revoke removes the last role. The candidate is a warning before the last role is revoked.

Two more gaps have closed since this journey was written, both by the access group's first build.

The first is the one this journey called its own: no subject returned the account_role row, so assigned_by, assigned_at, change_reason_code and change_commentary could not be shown even though they were stored. The composed read now carries all four beside each role, for the administrator and for the member alike, so the * Confirm row above answers from the same subject as the rest of the screen.

The second is the credential material this journey recorded on the account read. password_hash, password_salt, service_password_hash and totp_secret are :no_wire: in ores.iam.account now, so no read returns them, and the picker needs nothing else: a username and a full name come over the wire and the secrets do not. That closes it for every reader of the entity rather than for this screen alone.

The read is also no longer open to anybody. It requires iam::roles:read before it looks the account up, and refuses with a denied result rather than an empty list, which is what Authorised reads settles for both access reads.

8. Related journeys

9. Related knowledge

10. See also

Emacs 29.3 (Org mode 9.6.15)