# Prototype: per-user Woo credentials Branch: `proto-per-user-woo-creds` (cut from `overnight-whole-app`). ## Goal 2–3 owners can share one SlabUploader install and one Woo store URL, each using their own WordPress user + application password. Inference URL/key stay global. No full auth, ACL, or publish mutex in this prototype. ## Credential scheme | Layer | What | | --- | --- | | Browser | `localStorage` keys `slabuploader.woo.device.username` / `…appPassword` | | Gate | Full-screen `WooSetupGate` on App mount when this browser lacks device creds | | HTTP | Headers `X-Woo-Username` + `X-Woo-App-Password` on Woo-touching calls (**required**) | | Server | `client_for_request(conn, headers)` → override only; **no Settings fallback** on those routes | | Settings | Shop-default `woo_wp_username` / encrypted `woo_app_password` — used by **health probe** only; **never** updated by the device-creds / gate path | Woo-touching calls that **require** headers (401 `woo_device_creds_required` if missing): - `POST /api/v1/slabs/{id}/publish` - `POST /api/v1/admin/taxonomy/sync` - `POST /api/v1/settings/test-woo` Inference / Call 2 / Settings PUT for inference keys are unchanged (shared Settings). Optional test escape hatch: `SLAB_WOO_ALLOW_SETTINGS_FALLBACK=1` (prefer sending headers in tests). ## Setup gate 1. On App mount, if this browser has no device username + app password → blocking overlay. 2. Fields: device WP user + app password (required); Woo base URL display-only from Settings; inference URL/key show configured / not set (masked) and may be entered → PUT Settings for inference only. 3. Primary CTA: **Save device creds & Test Woo**. Gate dismisses only after Test Woo succeeds. 4. Returning browser with device creds → skip gate. 5. Publish (or Test Woo) Woo auth failure / missing headers → reopen gate. ## Flow (request → WooClient) 1. Frontend reads device creds from localStorage (`wooDeviceCreds.ts`). 2. `withWooHeaders()` merges `X-Woo-*` onto the fetch. 3. Backend `client_for_request` builds a **new** `WooClient` for that request only (Basic auth from override plaintext). Missing headers → `WooDeviceCredsRequired`. 4. Publish / taxonomy / media use that client instance — no process-global “current user”. ## UAT with two browsers 1. Deploy this branch to the **proto** stack only (`slab-proto-woo-creds` on `:10892` / `:10893`). Do **not** touch overnight `:10890`; never `:10880`. 2. In WordPress, create two application passwords for two WP users on the same store. 3. Browser A (or profile A): setup gate → enter user A + app password → **Save device creds & Test Woo** → gate dismisses. 4. Browser B: same for user B (separate localStorage). 5. Confirm Test Woo succeeds for both, and that device passwords were not written into shop-default Settings. 6. Publish different slabs from each browser (do not work the same slab concurrently). 7. Optional: clear device creds in Settings → reload → gate reappears. ## Risks left (prototype) - **LAN can forge headers** — there is no app login; any client on the network can send arbitrary `X-Woo-*` values. Treat as trusted LAN only. - Device passwords sit in **localStorage** (XSS-readable). Acceptable for this prototype only. - No slab ownership ACL / taxonomy mutex / publish CAS — operators must not edit the same slab at once. - Taxonomy cache remains shared; a sync from either user refreshes the same rows.