---
module: profile.profile
title: Profile
tab: Internal
order: 20
audience: internal
---

Ops/support view of the **Profile** tab (`ProfileInfo.svelte`) in the HiveIQ profile SPA (`profile.bcos.cloud`, container port 5184). It is a thin self-service editor over one user row in **hiveops-auth**. There is no admin-only action here — every user edits **only their own** record, identified by the email in their JWT.

## Who can do what
- **Any authenticated user** can read and edit their own name, job title, phone, and avatar. There is **no** `@PreAuthorize` / role gate on these endpoints — identity comes from `@AuthenticationPrincipal` (`userDetails.getUsername()` = the caller's email).
- There is **no admin endpoint to edit another user's** name/phone/avatar from this screen. Editing someone else's profile is not possible via this tab.
- Email, System role, and Institution are **read-only** here. Role and license are managed in `hiveops-mgmt`; email is the login identity.

## What the tab actually calls
- `GET /auth/api/users/me` — loads the current values (fired by the SPA shell, cached in the `userInfo` store).
- `PUT /auth/api/users/me` — saves `{ name, phone, jobTitle, avatarData }`. Only non-null fields are applied (partial update).
- Backend: **hiveops-auth** (service port 8082, prod host port 8001), DB **`hiveiq_auth`**, table **`users`**.

## First things to check when it misbehaves
1. **"Save failed" toast** → the `PUT /auth/api/users/me` returned non-2xx. Check auth-service logs (Loki, service `hiveiq-auth`) and confirm the caller's JWT is valid/unexpired. A 401 means the token expired (access token = 24h).
2. **Institution shows blank / "—"** → this field is **not** stored on the user row. It is resolved live from **hiveops-mgmt** by the email domain at response-build time (`MgmtServiceClient.getCompanyName`). If mgmt is down or the domain isn't mapped, it degrades to `null` and shows blank. Check `hiveiq-mgmt` health and the `internal.service-secret` (`X-Internal-Secret`) config on both services.
3. **Avatar won't upload** → client rejects files over **2 MB** before any request is sent (pure browser check, no network call). Larger images never reach the server. Have the user shrink the image.
4. **Avatar looks cropped** → expected. The browser cover-crops to a **200×200** square and re-encodes as JPEG (quality 0.85) before saving as a base64 data URL.
5. **Edits don't stick after reload** → the SPA writes the `PUT` response back into the `userInfo` store; if the store looks stale, it's a frontend cache issue — hard-reload. The persisted source of truth is the `users` row.
6. **CORS error in console** → `profile.bcos.cloud` must be present in `CORS_ALLOWED_ORIGINS` on hiveops-auth (and mirrored on mgmt). It is currently listed; a missing origin after an `.env` regen is the usual cause.

## Common failure modes + fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Save 401 | Expired/absent JWT | Re-login; token TTL is 24h |
| Institution blank | mgmt down / domain not mapped / wrong internal secret | Check mgmt health + `X-Internal-Secret` match |
| Avatar rejected | File > 2 MB (client check) | Resize below 2 MB |
| Save 500 | auth DB / `users` row missing | Check `hiveiq_auth` connectivity; user must exist |
| Whole SPA fails to load `me` | CORS origin missing on auth | Re-add `profile.bcos.cloud` to `CORS_ALLOWED_ORIGINS`, restart auth |

## Direct DB / API checks
```sql
-- inspect a user's editable profile fields (hiveiq_auth, on 10.10.10.188 via ProxyJump)
SELECT email, name, job_title, phone,
       (avatar_data IS NOT NULL) AS has_avatar, length(avatar_data) AS avatar_bytes
FROM users WHERE email = 'user@example.com';
```
```bash
# reproduce what the tab does, with a real JWT
curl -s https://api.bcos.cloud/auth/api/users/me -H "Authorization: Bearer $JWT"
```

> Note: this tab does **not** change passwords or MFA. Password change (`POST /auth/api/change-password`) and MFA live in the sibling **Security** tab, not here.
