User Journey: Protect my account
Table of Contents
This page documents a user journey: A member changes their password and sees where their account is signed in.
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 keep my account safe, and know where it is signed in." The member changes their own password, then reads the state of the account: the last sign-in, the failed attempts, whether the account is locked, and whether a password change is required. The journey is done when the new password works and the sign-in panel reads as the member expects.
2. Actor and trigger
The member: an account of type user, signed in, acting on their own record.
The trigger is a password prompt, a security reminder, or an unfamiliar entry
in the sign-in panel. It starts when the member opens Security from the
account menu, and ends when they leave it.
This is the self-service half of the credentials topic. An administrator who must restore somebody else's access runs Rescue access instead, and the record of who signed in is Audit sign-ins.
3. Steps
- Open Security. The member chooses Security from the account menu. The system shows one screen: the password panel, the sign-in state, and the places the account is signed in.
- Change the password. The member enters the current password, the new password twice, and reads the strength hint. The system checks the new password against the policy, rejects a password equal to the current one, stores the new hash, and clears the password-reset flag.
- Read the sign-in state. The member reads the last sign-in, the failed attempt count, the lock state and the reset flag. The panel is read-only, because the service writes that state as a side effect of signing in.
- See where the account is signed in. The member reads the active sign-ins: client, address, country and start time. The system shows one row for each session that has no end time.
- Sign out. The member ends the current session with Sign out. There is no control to end one other session, so a member who sees an unfamiliar sign-in can only change the password and sign out.
- Leave. The member goes back. The account menu shows the account as it is.
4. Screens and wireframes
One screen. The password form, the sign-in state and the active sign-ins are panels of that screen rather than separate screens, because the member thinks of them as one thing.
Figure 1: Protect my account: the password form, the sign-in state, and the places the account is signed in.
5. Prototype
Accepted by the product owner on 2026-09-30: variant B, sessions first. The
prototype is merged into main and kept there as the record; the branch names
the accepted state.
- Accepted commit
c4d2435c23, the tip ofprototype/protect-my-account, merged intomain- Accepted variant
b- Source
doc/prototypes/credentials/protect-my-account/index.html- Open
/OreStudio/doc/prototypes/credentials/protect-my-account/once the site is built, or the file directly- Design record
- Task: Prototype Protect my account
6. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
account |
Username and account type; the row the password change writes | ores.iam.account |
login_info |
Last sign-in, failed attempts, locked, online, reset required, last addresses | ores.iam.login_info |
session |
Client, address, country, start time and end time for each sign-in | ores.iam.session |
Three account columns must never reach this screen: password_hash,
password_salt and totp_secret. The password panel writes a hash; it never
reads one back.
7. Operations and messages
| Step | Operation | Subject | Status |
|---|---|---|---|
| Open Security | Read my account | iam.v1.accounts.get |
exists |
| Read the sign-in state | Read my login record | iam.v1.login_info.get |
exists |
| Change the password | Set a new password for myself | iam.v1.ops.change_password |
exists |
| See where I am signed in | List my active sessions | iam.v1.ops.get_active_sessions |
partial |
| Sign out | End my own session | iam.v1.ops.logout |
exists |
| End one other sign-in | End a chosen session | none | missing |
| Turn on two-factor authentication | Enrol a TOTP secret | none | missing |
| See statistics over time | Read the session statistics | none | missing |
iam.v1.ops.change_password takes the account from the caller's token, so
the member needs no administrator permission. The subject is declared in
ores.iam.account_messages. The
request carries current_password, and the service verifies it against the
stored hash before it writes anything
(account_operations_service::change_password), so the screen may say the
server checks the field.
iam.v1.ops.get_active_sessions is partial: the subject exists in
ores.iam.session_messages, and
the handler replies with success and returns no sessions. ores.http exposes
it as POST /api/v1/iam/sessions/active, which forwards to the same subject
and so returns the same empty answer. The browser reaches it on no path: the
BFF is the browser's only HTTP surface, and it serves no session route.
8. What is missing
- No way to end one session.
iam.v1.ops.logoutends only the caller's own session. A member who sees an unfamiliar sign-in cannot end it. The candidate isiam.v1.sessions.end, restricted to the caller's own sessions. - No TOTP enrolment.
account.totp_secretexists as a column, andiam.v1.accounts.putaccepts a secret when the account is created, but nothing enrols, replaces or clears a secret for an existing account. The candidate isiam.v1.accounts.enrol-totp, plus a read that states whether two-factor authentication is on. The secret itself never reaches the screen. iam.v1.ops.get_active_sessionsreturns nothing. The subject exists and is a stub, and theores.httproute forwards to it, so no path reads the session table for the member's own sign-ins. Until one does, the screen states that the active sign-ins are not available.- No session statistics.
ores_iam_session_stats_daily_vwand its hourly and aggregate siblings exist as TimescaleDB continuous aggregates, with no subject. The candidate isiam.v1.sessions.statistics.
9. Related journeys
- Sign in — where this password is used, and where a forced change happens
- Rescue access — the administrator's route back in when the account is locked and the member cannot sign in
- Audit sign-ins — the tenant-wide view of the sessions this screen shows for the member alone