# UAT — Trezor Safekeeping (Task #11)

**Feature:** Hardware-protected admin release/refund actions via Trezor signing  
**Branch:** `integrate-main-into-development`  
**Backend version:** 2.6.59  
**Tester role required:** Admin  
**Browser requirement:** Chromium-based only (Chrome, Edge, Brave) — WebUSB is not supported in Firefox or Safari  

---

## Overview

Task #11 adds a hardware security layer over two critical admin actions:

| Action | Without Trezor | With Trezor |
|---|---|---|
| Release payment to seller | Admin clicks → tx hash → confirm | Admin clicks → Trezor signs → tx hash → confirm |
| Refund payment to buyer | Admin clicks → tx hash → confirm | Admin clicks → Trezor signs → tx hash → confirm |

The Trezor requirement is controlled by the backend env var `TREZOR_SAFEKEEPING_REQUIRED`. When `false`, the entire Trezor layer is transparent and the UI behaves exactly as before.

An emergency **break-glass** mechanism lets an admin temporarily bypass Trezor for 1 hour with a mandatory Telegram alarm.

---

## Flows Implemented

### Flow A — Trezor Registration (one-time setup)

**URL:** `/dashboard/admin/trezor`  
**When:** First time, or when replacing the registered device.

Steps:
1. Admin opens the Trezor Safekeeping page.
2. (Optional) Admin enters a device label in the text field.
3. Admin clicks **Connect Trezor**.
4. Browser shows a USB device picker — admin selects the Trezor.
5. Trezor device shows "Export public key?" — admin confirms on device.
6. Backend issues a registration message tied to the xpub + address.
7. UI moves to **Sign** step — admin clicks **Sign on Trezor**.
8. Trezor device shows the message to sign — admin confirms on device.
9. UI shows "Signature obtained — ready to register."
10. Admin clicks **Register Trezor** → backend stores xpub fingerprint + registration address.
11. Page refreshes and shows the registered account status (address, fingerprint, path, address count).

If a Trezor is already registered, the page shows a **Re-register** section instead of the initial registration section. The flow is identical; it overwrites the old record.

---

### Flow B — Release Payment (with Trezor)

**URL:** `/dashboard/admin/payments-awaiting-confirmation`  
**Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, break-glass inactive, Trezor registered.

Steps:
1. Admin opens the Payments Awaiting Confirmation list.
2. Clicks the green **Release** button on a payment row.
3. Dialog opens at step 1 — **Build Instruction**.
   - Shows payment ID and amount.
   - Admin clicks **Build Instruction**.
4. Backend builds the release instruction (validates payment state).
5. Dialog advances to step 2 — **Sign with Trezor**.
   - Shows provider and network info from the instruction.
   - Admin connects Trezor (if not already connected) and clicks **Sign on Trezor**.
6. Trezor device shows the operation message — admin confirms on device.
7. Dialog advances to step 3 — **Enter tx hash**.
   - Shows "Trezor approved — signer: 0x…" confirmation.
   - Admin pastes the on-chain transaction hash of the actual transfer.
   - Admin clicks **Next**.
8. Dialog advances to step 4 — **Confirm**.
   - Admin clicks **Confirm Release** (green).
9. Backend verifies the Trezor signature against the registered xpub, records the tx hash, and marks the payment released.
10. Dialog closes, list refreshes.

---

### Flow C — Refund Payment (with Trezor)

**URL:** `/dashboard/admin/payments-awaiting-confirmation`  
**Precondition:** Same as Flow B.

Identical to Flow B except:
- Admin clicks the orange **Refund** button.
- Step 1 shows an optional **Refund reason** text field.
- Final button is **Confirm Refund** (orange).
- `reason` is passed through to the confirm API if provided.

---

### Flow D — Release / Refund without Trezor (break-glass active or safekeeping disabled)

**Precondition:** Either `TREZOR_SAFEKEEPING_REQUIRED=false` OR break-glass is active.

The dialog runs a shorter 3-step flow:

| Step | Label | Action |
|---|---|---|
| 1 | Build instruction | Same as above |
| 2 | Enter tx hash | No Trezor prompt; shows "Break-glass mode active" notice |
| 3 | Confirm | Submits without `trezor` field in body |

The backend accepts the request because `isTrezorSafekeepingRequired()` returns `false` in both cases.

---

### Flow E — Break-glass Activation

**URL:** `/dashboard/admin/trezor`  
**When:** Emergency — Trezor is unavailable, lost, or broken.

Steps:
1. Admin opens the Trezor Safekeeping page.
2. The Break-glass section shows "Trezor safekeeping is active (normal mode)".
3. Admin clicks **Activate Break-glass**.
4. Confirmation dialog appears with warning text.
5. Admin clicks **Activate**.
6. Backend activates break-glass for 1 hour and fires a Telegram alarm to the monitoring bot (`TG_NOTIFY_BOT_TOKEN`).
7. Page shows red alert: "Break-glass is ACTIVE — Activated by: [email] — Expires: [time] — Remaining: ~60 min".
8. The payments-awaiting-confirmation list now passes `skipTrezor={true}` to the dialog.

---

### Flow F — Break-glass Deactivation

**URL:** `/dashboard/admin/trezor`

Steps:
1. Admin opens the Trezor Safekeeping page while break-glass is active.
2. The section shows the red active alert with remaining time.
3. Admin clicks **Deactivate Break-glass**.
4. Confirmation dialog appears.
5. Admin clicks **Deactivate**.
6. Backend clears break-glass immediately and fires a Telegram "restored" notification.
7. Page shows green "Trezor safekeeping is active (normal mode)".

---

### Flow G — Break-glass auto-expiry

No UI action required.

- After 1 hour, `isBreakGlassActive()` on the backend returns `false` automatically.
- On the next page load or dialog open, `getBreakGlassStatus()` returns `active: false`.
- `skipTrezor` reverts to `false` on the payments list.

---

## API Endpoints

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/api/trezor/account` | Get registered Trezor status |
| `GET` | `/api/trezor/registration-message` | Get message to sign for registration |
| `POST` | `/api/trezor/register` | Submit registration (xpub + proof signature) |
| `POST` | `/api/trezor/operation-message` | Get message to sign for a release/refund operation |
| `POST` | `/api/payment/:id/release` | Build release instruction |
| `POST` | `/api/payment/:id/release/confirm` | Confirm release with tx hash (+ optional trezor sig) |
| `POST` | `/api/payment/:id/refund` | Build refund instruction |
| `POST` | `/api/payment/:id/refund/confirm` | Confirm refund with tx hash (+ optional trezor sig) |
| `GET` | `/api/admin/settings/break-glass` | Get break-glass status |
| `POST` | `/api/admin/settings/break-glass` | Activate break-glass |
| `DELETE` | `/api/admin/settings/break-glass` | Deactivate break-glass |

---

## Acceptance Tests

### AT-01 — Page loads and shows unregistered state

**Precondition:** No Trezor registered in DB.  
**Steps:** Navigate to `/dashboard/admin/trezor` as admin.  
**Expected:**
- [ ] "No Trezor registered — admin actions are unprotected" warning is shown.
- [ ] "Register Trezor" card with device label field and **Connect Trezor** button is visible.
- [ ] Break-glass card shows "Trezor safekeeping is active (normal mode)" in green.

---

### AT-02 — Registration flow completes (hardware path)

**Precondition:** Trezor Model One or T connected via USB. Chromium browser.  
**Steps:** Follow Flow A above.  
**Expected:**
- [ ] USB device picker appears after clicking Connect Trezor.
- [ ] Trezor device shows public key export prompt.
- [ ] After approval, page moves to Sign step and shows the registration message.
- [ ] Trezor device shows the message to sign.
- [ ] After signing, Submit step shows "Signature obtained".
- [ ] After Submit, page shows account status card with green "Trezor registered" alert.
- [ ] Chips show correct registration address (matches what was shown on device), fingerprint, path `m/44'/60'/0'`, and address count `0`.

---

### AT-03 — Registration rejected on device

**Precondition:** Trezor connected.  
**Steps:** Click Connect Trezor → reject the export on device.  
**Expected:**
- [ ] Toast shows "Trezor getPublicKey failed: …" or similar error.
- [ ] Registration flow resets to idle (Connect button visible again).

---

### AT-04 — Release without Trezor (safekeeping disabled)

**Precondition:** Backend `TREZOR_SAFEKEEPING_REQUIRED=false`.  
**Steps:**
1. Go to `/dashboard/admin/payments-awaiting-confirmation`.
2. Click Release on any payment with status awaiting confirmation.

**Expected:**
- [ ] Dialog opens with 3-step stepper (Build instruction / Enter tx hash / Confirm).
- [ ] Build Instruction succeeds without Trezor prompt.
- [ ] "Break-glass mode active — Trezor signature not required." notice shown in step 1.
- [ ] Admin enters a dummy tx hash and clicks Next → Confirm.
- [ ] `POST /api/payment/:id/release/confirm` called with `txHash` but without `trezor` field.
- [ ] Success toast, dialog closes, list refreshes.

---

### AT-05 — Release with Trezor (full hardware path)

**Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, Trezor registered, break-glass inactive, Trezor device connected.  
**Steps:** Follow Flow B above.  
**Expected:**
- [ ] Dialog shows 4-step stepper.
- [ ] Sign with Trezor step shows provider/network info from instruction.
- [ ] Trezor device prompts to sign the operation message.
- [ ] After signing, tx hash step shows "Trezor approved — signer: 0x…".
- [ ] Confirm call includes `trezor: { message, signature }` in body.
- [ ] Backend returns 200. Toast: "Release confirmed".

---

### AT-06 — Release blocked when Trezor required but not signed

**Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, Trezor registered, break-glass inactive.  
**Steps:** POST directly to `/api/payment/:id/release/confirm` with only `txHash`, no `trezor` field.  
**Expected:**
- [ ] Backend returns 403 or 400 with message indicating Trezor signature is required.

---

### AT-07 — Refund with reason

**Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=false` (for UI simplicity).  
**Steps:** Follow Flow C. Enter "Item not received" in the reason field.  
**Expected:**
- [ ] Reason field visible in Build Instruction step.
- [ ] `POST /api/payment/:id/refund/confirm` includes `reason: "Item not received"`.
- [ ] Toast: "Refund confirmed".

---

### AT-08 — Break-glass activation fires Telegram alarm

**Precondition:** Backend `TG_NOTIFY_BOT_TOKEN` and `TG_NOTIFY_CHAT_ID` set.  
**Steps:** Follow Flow E.  
**Expected:**
- [ ] Confirmation dialog warns about 1-hour bypass and Telegram alarm.
- [ ] After clicking Activate, page shows red break-glass active alert.
- [ ] Telegram monitoring chat receives alarm message from the notify bot (NOT the mini app bot).
- [ ] `expiresAt` shown is approximately 1 hour from now.
- [ ] `remainingMs` shown is approximately 60 min.

---

### AT-09 — Break-glass makes Trezor step disappear in release dialog

**Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, break-glass active.  
**Steps:**
1. Confirm break-glass is active on Trezor page.
2. Go to payments-awaiting-confirmation.
3. Click Release on any payment.

**Expected:**
- [ ] Dialog shows 3-step stepper (Build instruction / Enter tx hash / Confirm).
- [ ] No "Sign with Trezor" step visible.
- [ ] Step 1 shows "Break-glass mode active — Trezor signature not required."
- [ ] Release completes without Trezor device.

---

### AT-10 — Break-glass deactivation restores Trezor step

**Precondition:** Break-glass currently active.  
**Steps:**
1. Go to Trezor page and click Deactivate Break-glass → confirm.
2. Go to payments-awaiting-confirmation and click Release.

**Expected:**
- [ ] Trezor page shows green "Trezor safekeeping is active (normal mode)".
- [ ] Telegram monitoring chat receives "break-glass restored" notification.
- [ ] Release dialog shows 4-step stepper again.

---

### AT-11 — Break-glass expires automatically

**Precondition:** Break-glass activated.  
**Steps:** Wait 1 hour (or manually set `BREAK_GLASS_DURATION_MS` to a short value in `breakGlassRoutes.ts` for testing).  
**Expected:**
- [ ] After expiry, `GET /api/admin/settings/break-glass` returns `active: false`.
- [ ] Page refresh shows normal mode.
- [ ] Release dialog shows 4-step stepper.

---

### AT-12 — Non-admin cannot access Trezor endpoints

**Precondition:** Logged in as buyer or seller.  
**Steps:** Call `GET /api/trezor/account` and `GET /api/admin/settings/break-glass` with non-admin JWT.  
**Expected:**
- [ ] Both return 403.

---

### AT-13 — Re-registration overwrites old device

**Precondition:** Trezor already registered.  
**Steps:**
1. Note the current registration address on the Trezor page.
2. Click **Register New Device**, complete registration flow with same or different Trezor.

**Expected:**
- [ ] Page shows updated registration address after re-registration.
- [ ] If different device: new fingerprint, new address.
- [ ] Previous Trezor's signatures are no longer accepted on subsequent releases.

---

### AT-14 — Cancel at any step resets dialog

**Precondition:** Any.  
**Steps:** Open release dialog, complete Build Instruction step, click Cancel.  
**Expected:**
- [ ] Dialog closes with no action taken.
- [ ] Reopening the dialog starts at step 1 with empty state.

---

## Test Environment Setup

### Minimal (no hardware — UI flow only)

```bash
# backend/.env
TREZOR_SAFEKEEPING_REQUIRED=false
```

Covers: AT-01, AT-04, AT-07, AT-08 to AT-10, AT-12, AT-14

### With safekeeping enforced (no hardware — break-glass path)

```bash
# backend/.env
TREZOR_SAFEKEEPING_REQUIRED=true
```

1. Go to `/dashboard/admin/trezor`
2. Activate break-glass
3. Now release/refund works without Trezor

Covers: AT-09, AT-10

### Full hardware path

Requirements:
- Trezor Model One or Model T
- USB cable
- Chromium browser (Chrome / Edge / Brave)
- `TREZOR_SAFEKEEPING_REQUIRED=true`
- Frontend served on `https://` or `localhost` (WebUSB blocks non-secure production origins)

Covers: AT-02, AT-03, AT-05, AT-06, AT-13

---

## Known Limitations / Out of Scope

| Item | Status |
|---|---|
| Firefox / Safari WebUSB support | Not supported — Trezor registration and signing only works in Chromium |
| Multiple Trezor accounts | Only one active registration at a time; re-register to replace |
| Break-glass persistence across backend restarts | Intentionally not persisted — restart clears break-glass as a security property |
| Payout / sweep actions via Trezor | Backend `assertTrezorSignatureForOperation()` is wired; no UI yet |
| Audit log of Trezor operations | Not implemented in this task |
