User Journey: Audit sign-ins

Table of Contents

This page documents a user journey: A tenant administrator reviews active sessions, activity and failed attempts.

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 is signed in, what they are doing, and who is failing to get in." The administrator reviews the active sessions, the session activity and the failed attempts for the tenant. The journey is done when the administrator can account for every open session, or has raised the ones that cannot be explained.

2. Actor and trigger

The tenant administrator: an account inside the tenant that may read sessions and login records. The trigger is a routine review, an incident, or a support call. It starts when the administrator opens Audit: sign-ins, and ends when they leave it.

The system administrator is the second actor, over the system tenant, where the platform's services sign in. The tenant-wide log answers "what happened in this tenant"; the question "what did this account do" is answered from the account itself: opening an account on the roster shows its sign-in state and its sessions, newest first (See who has access, See an account's sign-ins).

This is the record of what actually happened. The member's own half is Protect my account, and the repair is Rescue access.

3. Steps

  1. Open Audit. The administrator opens Audit: sign-ins. The screen is an event log, not a versioned entity, so it carries no versions, no diff and no revert. Its only action is Refresh.
  2. Filter. The administrator narrows the view by account, period and event.
  3. Review the active sessions. The administrator reads one row for each session with no end time: account, client, address, country and start time.
  4. Read the session activity. The administrator reads the duration and the byte counters for a session, as a time series of samples.
  5. Review the failed attempts. The administrator reads the failed attempt count, the last attempt address and the lock state for each account. A locked account explains a support call; a burst of failures explains an incident.
  6. Refresh. The screen re-reads the same subjects. There is nothing to edit here, and no history to browse.
  7. Leave. The administrator goes back, and raises the sessions that cannot be explained.

4. Screens and wireframes

One screen. The filters, the active sessions, the session activity and the failed attempts are panels of that screen rather than separate screens, because the administrator reads them together.

wireframe_audit_sign_ins.png

Figure 1: Audit sign-ins: filters, active sessions, session activity and failed attempts, with Refresh as the only action.

5. Prototype

Accepted by the product owner on 2026-09-30: variant B, tabs over one filter bar. The prototype is merged into main and kept there as the record; the branch names the accepted state.

Accepted commit
6460cd2653, the tip of prototype/audit-sign-ins, merged into main
Accepted variant
b
Source
doc/prototypes/credentials/audit-sign-ins/index.html
Open
/OreStudio/doc/prototypes/credentials/audit-sign-ins/ once the site is built, or the file directly
Design record
Task: Prototype Audit sign-ins

6. Entities composed

Entity What it contributes Model
session Account, client, address, country, start time, end time and byte counters ores.iam.session
session_samples Per-session time series of bytes sent and received ores.iam.session_samples_messages
login_info Failed attempts, last attempt address, locked and online ores.iam.login_info

session_samples is a message shape, not an entity: no table backs it, and the samples are declared in the operation model. The session row holds the running byte totals; the samples hold how those totals moved.

7. Operations and messages

Step Operation Subject Status
Open Audit List every session iam.v1.sessions.list partial
Open Audit List the active sessions iam.v1.ops.get_active_sessions exists
Read the session activity Read the samples for one session iam.v1.ops.get_session_samples partial
Review the failed attempts List the login records iam.v1.login_info.list exists
Read the auth event log Read the authentication events iam.v1.auth_events.list exists
See the statistics Read the session statistics iam.v1.ops.get_session_statistics exists
End another account's session Write the end time of a session iam.v1.ops.end_session exists
End another account's session Delete the session row iam.v1.sessions.delete partial

The only action on the screen is Refresh, and it re-reads the subjects in the table above. There is no write step, so the screen carries no versions, no diff and no revert.

iam.v1.sessions.list is partial: it pages every session with offset, limit and order, and takes no account filter, so the screen cannot ask for one account's sessions. It is declared by ores.iam.session.

iam.v1.ops.get_active_sessions was partial and is not any more: the handler reads the sessions whose end time is empty, so the subject answers with the tenant's open sessions and the screen reads them. The three reads this journey needs are served to the browser by the BFF, which is its only HTTP surface. Corrected 2026-10-01 by the screen that reads them; the paragraph that claimed otherwise named a tree that no longer exists.

iam.v1.ops.get_session_samples is still partial, and for a reason no route can fix: nothing writes a row of ores_iam_session_samples_tbl, so the subject would answer with none however it were wired. The candidate is a producer, not a route.

iam.v1.sessions.delete is partial: it removes the audit row, it is keyed by the composite storage key, and it needs iam::sessions:delete, which is not a permission of its own. Only a holder of the iam::* wildcard can call it, so it is not the administrator action this screen needs.

8. What is missing

The journey is built. Each of the four gaps below records what closed it and what remains.

  1. The auth events had no subject. ores_iam_auth_events_tbl holds one row per login, failure, logout and token refresh with hourly and daily aggregates over the same table, and nothing read it. Closed by iam.v1.auth_events.list, which scopes on the caller's tenant because the table carries no row-level security, and filters by account, period and event type. The aggregates still have no subject of their own; the screen does not need them.
  2. The session statistics had no subject, and in this edition no views either. The three ores_iam_session_stats_*_vw continuous aggregates are created only under the Timescale timescale licence, and a development database reports apache, so they do not exist. Closed by iam.v1.ops.get_session_statistics, which aggregates ores_iam_sessions_tbl itself and so answers in either edition.
  3. No operation to end another account's session. Closed by iam.v1.ops.end_session, behind its own iam::sessions:end, scoped to the caller's tenant. The logout path already ended the caller's own session, though it zeroed the session's byte counters on the way out; it now carries them through. A lock still does not end a session, and iam.v1.ops.lock_account still leaves open sessions open.
  4. The readings had no path the browser could use. Closed. The BFF serves the sessions, the login records, the auth events, the session statistics and the address-to-country lookup, and the screen reads all of them. One panel is still empty: iam.v1.ops.get_session_samples answers with nothing because nothing writes a row of ores_iam_session_samples_tbl, so the session-activity panel states that the data is not available. That candidate is a producer, not a route.

9. Related journeys

10. Related knowledge

11. See also

Emacs 29.3 (Org mode 9.6.15)