User Journey: See who has access

Table of Contents

This page documents a user journey: A tenant administrator reads the account roster and each account's state.

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 want to see who can get in, and what state each of them is in." The administrator reads one screen and gets one row per person. The journey is done when every row answers three questions: can this account sign in, what can it reach, and does it owe a password change.

2. Actor and trigger

The tenant administrator: an account of type user that holds the TenantAdmin role, signed in inside their own tenant. The trigger is a question about access: a joiner, a leaver, a lock-out, or a periodic review. It starts when the administrator opens Directory, and ends when the roster reads correctly.

This journey writes nothing. It is the read half of the directory topic; the write half is Bring someone in.

3. Steps

  1. Open the roster. The administrator opens Directory. The system shows one screen: a filter panel, one row per account, and a state panel for the selected row.
  2. Read the accounts. The system lists every account in the tenant. Each row shows the username, the full name, the account type and the job title.
  3. Attach the sign-in state. The system reads the login record for each account and adds the locked flag, the online flag, the last sign-in time and the password-change flag to the row.
  4. Attach the parties. The system reads the account's party links and turns each party id into a name. The row lists the parties the account may act for.
  5. Attach the roles. The system reads the roles assigned to the account. The row lists the role names.
  6. Filter. The administrator narrows the list, for example to locked accounts, or to accounts that must change their password. The filter runs on the rows already loaded.
  7. Leave. The administrator goes back. Nothing was written, so the journey carries no state.

4. Screens and wireframes

One screen. The filter, the roster table and the selected account's state are panels of that screen, because the administrator reads them as one answer. The join is the screen's real work: account holds the identity, login_info holds the state, account_party holds the scope and role holds the access.

wireframe_see_who_has_access.png

Figure 1: The roster: a filter, one row per account, and the selected account's state.

5. Entities composed

Entity What it contributes Model
account Username, full name, account type, job title ores.iam.account
login_info Locked, online, last sign-in, failed logins, password change due ores.iam.login_info
account_party The parties the account may act for ores.iam.account_party
party The name behind each party link ores.refdata.party
role The role names assigned to the account ores.iam.role

The screen shows no field from password_hash, password_salt or totp_secret. Those columns are secret.

6. Operations and messages

Step Operation Subject Status
Read the roster Read the rows and their state in one call none missing
Read the accounts List the tenant's accounts iam.v1.accounts.list exists
Attach the sign-in state List the tenant's login records iam.v1.login_info.list exists
Attach the parties List one account's party links iam.v1.account_parties.list_by_account_id exists
Attach the parties Resolve the party ids to names refdata.v1.parties.get_many exists
Attach the roles List one account's roles iam.v1.roles.by-account exists
Read a row in full Read one account by key iam.v1.accounts.get exists

The server has no joined read. iam.v1.accounts.list returns accounts only, and its request carries offset, limit and order and nothing else. The screen builds the row itself: one accounts.list call, one login_info.list call, then one roles.by-account call and one account_parties.list_by_account_id call for every account on the page. A page of fifty accounts costs more than a hundred calls. The join is the journey's real work, and the screen owns it today.

iam.v1.accounts.get returns one account by key. It is not a substitute for the roster, because the roster needs the login state and the assignments as well.

7. What is missing

  1. No joined read. Every row is assembled from four resources, and two of them are read once per account. The candidate is one read, iam.v1.accounts.list_with_state, that returns the account with its login state, its party links and its roles. Until it exists the screen composes the calls above and pages the per-account calls.
  2. No filter and no stated order on the roster request. iam.v1.accounts.list takes no filter, so the screen cannot ask for locked accounts, or for accounts that must change their password. The handler also refuses a stated order, so the page arrives in key order and the screen cannot sort by username, state or sign-in date. The screen loads pages, sorts them and filters them in the client. The candidate is a filter and a sort on the list request, or on the joined read.
  3. Roles are read one account at a time. iam.v1.roles.by-account takes one account_id. There is no bulk read for a page of accounts. The candidate is iam.v1.roles.by-accounts.
  4. Party names need a second call. The link carries a party_id only, so the screen resolves the names with refdata.v1.parties.get_many. The joined read should carry the name, or the link should.
  5. The read is not permission-gated. iam::accounts:read and iam::login_info:read exist in the permission list, but no handler checks them. Any signed-in account can list every account and every sign-in record in the tenant. The candidate is to enforce both on the read handlers.
  6. The account read carries secrets. The generated Account type carries password_hash, password_salt and totp_secret, so accounts.list and accounts.get deliver them to the browser. The candidate is to drop them from the wire type, because a screen cannot be trusted to ignore a secret it is handed.

8. Related journeys

9. Related knowledge

10. See also

Emacs 29.3 (Org mode 9.6.15)