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
- 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.
- Read what they hold. The system reads the roles assigned to the account and shows them. This list is the starting point.
- Choose a role to grant. The administrator opens the role picker. The system lists the roles defined in the tenant, with their descriptions.
- Grant the role. The administrator selects a role and confirms. The system
writes an
account_rolerow. The row recordsassigned_by(the administrator),assigned_at(the time), the change reason and the commentary. This step is what makes the grant auditable. - Revoke a role. The administrator selects a held role, removes it, and
gives a reason. The system closes the assignment: it sets
valid_toon the open row instead of deleting it, so the grant stays in the history. - Confirm. The system re-reads the role list. The administrator checks the result. The change takes effect on the colleague's next sign-in.
- 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.
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).
- Closed for a grant, open for a revoke.
assign_role_requestcarrieschange_reason_codeandchange_commentary, the grant row stores them, and anaccessreason 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. - 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
- Know what I may do — the colleague's own read-only view of the roles you grant here
- Shape the role catalogue — define or fix a role before the picker offers it
- See who has access — the roster you pick the colleague from and return to afterwards
- Register a service account — create the non-human account when the role belongs to a service or an algorithm
- Ask for more access — the same grant, requested by the colleague rather than started by the administrator
9. Related knowledge
- Identity and Access Management
- Role-Based Access Control
- Accounts and Roles — the user guide chapter
- How do I add a PlantUML diagram?
- Access journeys — the group hub