---
module: msp.users
title: Users
tab: Internal
order: 20
audience: internal
---

Ops/support/admin view of the **Users** screen (frontend `hiveops-msp/frontend/src/components/Users/UsersView.svelte`, app `msp`, `msp.bcos.cloud`). This is a self-service user-admin panel: an MSP admin lists, edits, enables/disables, and password-resets users across the institutions their MSP manages. The user records live in **hiveops-auth** (`users` table, DB `hiveiq_auth`); the institution dropdown comes from **hiveops-mgmt**. See [platform.service-ownership].

## Who can use it

- Every endpoint the screen calls is class-gated on `MspUserController` with `@PreAuthorize("hasAnyRole('MSP_ADMIN', 'BCOS_ADMIN')")`. A plain `CUSTOMER`/`USER` token gets 403.
- **`MSP_ADMIN`** → scoped to their own institutions only (see scoping below).
- **`BCOS_ADMIN`** → superuser; sees and edits every non-`SYSTEM` user, all institutions.
- The panel is admin-only by role, but there is **no per-action gate** beyond the class annotation — any `MSP_ADMIN` in scope can edit/disable/reset any user they own.

## What the screen actually calls

- Load list → `GET /auth/api/msp/users?size=200` (hiveops-auth, via NGINX `/api/auth/`). The frontend always pulls `size=200` and does **all** search/role/status/institution filtering and institution-grouping **client-side**.
- Institution dropdown → `GET /mgmt/api/v1/msp/institutions` (hiveops-mgmt `MspController`).
- Edit → `PUT /auth/api/msp/users/{id}` body `{name, role, fleetTaskApprover}` (email is never sent/changed).
- Enable → `POST /auth/api/msp/users/{id}/enable` (also clears `failed_login_attempts` and `locked_until` — doubles as an unlock).
- Disable → `POST /auth/api/msp/users/{id}/disable`.
- Reset PW → `POST /auth/api/msp/users/{id}/reset-password` (sends a reset email; does **not** set a password).
- Auto-refresh re-runs the list `GET` every 60s (toggle persisted in `localStorage` key `msp_users_autoRefresh`).

## How MSP scoping is resolved (important for "why can't I see user X")

For a non-`BCOS_ADMIN` caller, hiveops-auth derives the MSP's institution keys **fresh from mgmt at request time**, not from the JWT:

1. Take the email domain from the caller's JWT.
2. Call hiveops-mgmt internal API `GET /api/internal/customers/domain/{domain}/institution-keys` (header `X-Internal-Secret`).
3. A user is "owned" (visible/editable) if their `customer_license_key` is in those keys **OR** their email domain maps to an in-scope key via the `customer_domains` table (DB `hiveiq_auth`).
- If mgmt is unreachable or returns no keys → the caller sees an **empty list** (silent). This is the #1 cause of "all my users disappeared."

## First things to check when it misbehaves

1. **Empty user list for an MSP admin who should have users** → the mgmt internal lookup failed or returned no keys. Check: is `hiveops-mgmt` up? Does `internal.service-secret` in auth match mgmt's expected `X-Internal-Secret`? Does the caller's email domain resolve to a Customer with institution access in `hiveiq_mgmt`?
2. **A user is missing but others in the same institution show** → their institution linkage is unresolved: `customer_license_key` is null AND their email domain has no `customer_domains` row for an in-scope key. Fix the `customer_domains` mapping or set the user's `customer_license_key`.
3. **Institution dropdown empty but users load** → mgmt `GET /api/v1/msp/institutions` is failing while the auth list still returns (different service). Check mgmt health; the dropdown is sourced from `customer_institution_access` in `hiveiq_mgmt`.
4. **Only 200 users show for a huge MSP** → the frontend hard-codes `size=200` with no pagination UI; users past 200 (post-`name` sort) never render. Filter server-side by institution or raise `size`.
5. **403 opening the panel** → token lacks `MSP_ADMIN`/`BCOS_ADMIN`. A customer promoted to MSP admin must re-login to get a fresh role claim.
6. **Reset email never arrives** → reset is fire-and-forget: `POST .../reset-password` returns 200 even if the email send throws (logged as WARN in auth, not surfaced). Check hiveops-auth logs + `mail.bcos.cloud` relay; token is written to `password_reset_tokens`.

## Common failure modes + fixes

| Symptom | Likely cause | Fix |
|---|---|---|
| MSP admin sees zero users | mgmt institution-keys lookup failed/empty | Check mgmt up + `X-Internal-Secret` parity; verify domain→institution mapping in `hiveiq_mgmt` |
| One user missing from list | No `customer_license_key` and no `customer_domains` row for an in-scope key | Add `customer_domains` mapping or set the user's license key |
| "You cannot disable your own account" (400) | Self-disable guard in `disableUser` | Expected; have another admin (or BCOS_ADMIN) do it |
| Disabled/locked user can't log in after "Enable" | — | Enable already clears `failed_login_attempts` + `locked_until`; if still locked, check DB directly |
| Reset email not received | Send failed silently after 200 | Check auth logs + SMTP relay; token row exists in `password_reset_tokens` |
| Role change didn't persist | Unknown enum name in `role` | `role` must be a valid `User.Role` name (`CUSTOMER`/`MSP_ADMIN`); invalid values are silently ignored |

## Notes

- **Enable = unlock.** `POST .../enable` sets `enabled=true`, `failed_login_attempts=0`, `locked_until=null`. Use it to recover a lockout, not just a disabled account.
- **Self-disable is blocked** (returns 400) — an admin can't lock themselves out via this screen.
- **Email is immutable here** — the edit panel shows it read-only; there is no change-email path in this controller.
- `SYSTEM`-role users are always filtered out of the list, even for BCOS_ADMIN.
- No Kafka, no background jobs — every action is a synchronous REST call. If the screen is stale, it's a failed/mis-scoped `GET`, not an event lag.
