Olympus Docs
CookbookTools

Show users their admin-trail

"What admins did to my account" transparency

For trust, let users see when admins / support agents interacted with their account.

Pattern

In settings:

<section>
  <h3>Account activity by support</h3>
  {events.length === 0 ? (
    <p>No activity</p>
  ) : (
    <table>
      {events.map(e => (
        <tr>
          <td>{e.created_at}</td>
          <td>{e.action}</td>
          <td>Support agent: {e.actor_name}</td>
          <td>{e.reason}</td>
        </tr>
      ))}
    </table>
  )}
</section>

User sees what was done.

Query

SELECT 
  a.created_at,
  a.event_type AS action,
  i.traits->>'first_name' AS actor_name,
  a.metadata->>'reason' AS reason
FROM security_audit a
JOIN identities i ON a.actor_id = i.id
WHERE a.target_id = $user_id
  AND a.actor_type = 'admin'
  AND a.actor_id != $user_id  -- exclude self-actions
ORDER BY a.created_at DESC
LIMIT 50;

What was done to them, by whom.

Visible actions

Show:

  • Password reset (by admin).
  • MFA disabled (by admin).
  • Email changed (by admin).
  • Sessions revoked.
  • Account locked.
  • Account unlocked.
  • Role changed.

Don't show:

  • Reads (administrative reading of their profile is normal; not noteworthy).
  • Audit log views by admin (they don't change the user's account).
const visibleActions = [
  "password_reset_by_admin",
  "mfa_disabled_by_admin",
  "email_changed_by_admin",
  "sessions_revoked_by_admin",
  "account_locked",
  "account_unlocked",
  "role_changed",
];

Reasons

When admin performs action, require a reason:

// Athena admin tool
<form>
  <textarea name="reason" required placeholder="Reason for this action (logged)" />
  <Button onClick={revokeSessions}>Revoke all sessions</Button>
</form>
INSERT INTO security_audit (event_type, actor_id, target_id, metadata)
VALUES (
  'sessions_revoked_by_admin',
  $admin_id,
  $user_id,
  '{"reason": "$reason"}'
);

Reason shown to user:

"Reason: Suspected account compromise per ticket #1234"

If admin tried to skip reason: forced, UI blocks empty.

Anonymize admin names?

Two views:

Option A: real names

User sees "John Smith reset your password."

Pros: full transparency. Cons: admin's name exposed; could be abused.

Option B: anonymized

User sees "Support agent A reset your password."

Pros: protects admin from harassment. Cons: less specific.

Most B2C: option B. B2B with named CSMs: option A.

When was it done

Time-stamps important:

<time dateTime={e.created_at}>{formatDateAgo(e.created_at)}</time>
"2 hours ago"
"May 10, 2026 at 3:30 PM"

User context.

Self-actions

Hide admin actions where user did it (admin = self):

WHERE actor_id != target_id

Otherwise you'd show "You changed your password", confusing.

Notification on admin action

Beyond passive view, push notifications:

// When admin acts:
await sendEmail(user.email, "Account activity", `
  Hi,
  
  A member of our support team [name? or "support"] performed this action 
  on your account:
  
    Action: ${action}
    Reason: ${reason}
    Date: ${date}
  
  If you didn't expect this contact, please reply to this email.
`);

User informed in real-time.

Trust signaling

Beyond audit, communicate practices:

"Our policy: admins never view your account without a logged reason.
You can see all admin activity in Settings → Account activity."

Privacy-respecting framing.

Restrictions

Some actions might not be user-visible (security investigations in progress):

// Filter by visibility flag
WHERE a.metadata->>'visibility' != 'hidden'

Use sparingly, fixed-period hiding (30 days). Don't hide indefinitely.

Compare to industry

Various models:

  • Google: Recent activity page shows admin actions on your account (rare).
  • Apple: less transparent.
  • Stripe: account events log includes Stripe's own actions.

Olympus default: transparent. Customer can override.

Disputed actions

User says "I didn't request password reset; admin shouldn't have done it."

Have a dispute flow:

  1. User clicks "Dispute this action."
  2. Support reviews ticket reference.
  3. If admin error: train, fix.
  4. If user mistaken: explain.

Audit the audit view

When user views admin trail:

INSERT INTO security_audit (event_type, actor_id, target_id, metadata)
VALUES ('admin_trail_viewed', $user_id, $user_id, '{}');

Trace who viewed what. Helps detect curious users (potential reconnaissance).

On this page