Files
fressh/docs/archive/key-management-plan.md
EthanShoeDev c0d70ad99c docs
2025-09-12 22:43:53 -04:00

142 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Key Management UX & Tech Plan
Goal: Make SSH private key management clear and consistent: selecting a key,
generating/importing keys, renaming, deleting, and setting a default — all with
a unified, styled UI and predictable data semantics.
## Current State (Summary)
- Security type uses a `Picker` in the main form; defaults to `password`.
- Switching to key auth shows a `Picker` for keys (awkward empty state) and a
“Manage Keys” button.
- Keys can be generated, renamed, deleted, set as default inside a modal.
- Storage uses a chunked manifest in `expo-secure-store` (works for >2KB values)
with metadata (`priority`, `createdAtMs`, `label?`, `isDefault?`).
- All key mutations go through one `upsertPrivateKey` that invalidates the React
Query `keys` query.
## UX Objectives
- Remove the inline key `Picker`; select a key within the Key Manager modal.
- Replace the security type `Picker` with a simple toggle/switch.
- Handle the “no keys yet” case gracefully by auto-opening the modal when user
chooses key auth.
- Keep visual styling consistent with text inputs (button heights, paddings,
colors).
- Prepare the modal for importing private keys (paste text or select file) in a
future phase.
## Phase 1 — Selection UX + Styling
1. Replace security type `Picker` with toggle
- Use a `SwitchField` (styled like inputs) labeled "Use Private Key".
- Value mapping: off → password; on → key.
- When toggled ON and there is no selected key and no keys exist, auto-open
Key Manager.
2. Remove inline key `Picker`
- Replace with a read-only field styled like inputs: label: "Private Key",
value: current key label or "None".
- The field is a `Pressable` that opens the Key Manager for selection.
- Disabled state (no keys): show "None" with hint "Open Key Manager to add a
key".
3. Key Manager: add selection mode
- Add a radio-style control on each row to select the key for this session.
- Modal accepts an optional `selectedKeyId` and `onSelect(id)` callback.
- When user taps a row (or radio), call `onSelect(id)` and close the modal.
- Continue to support generate/rename/delete/set-default; selection should
update live.
4. Styling parity
- Ensure the read-only "Private Key" field height/padding matches
`TextField`.
- Use consistent typography/colors for labels, values, and hints.
5. Empty states
- If no keys: show a friendly empty state in modal with primary action
"Generate New Key" and secondary "Import Key" (Import lands in Phase 2).
Deliverables (Phase 1)
- Update `apps/mobile/src/app/index.tsx` form:
- Toggle for auth type.
- Read-only key field that opens modal.
- Update `KeyManagerModal`:
- Support selection behavior with visual radio and `onSelect` callback.
- Keep existing generate/rename/delete/set-default.
## Phase 2 — Import Keys
1. Import entry points in modal
- Add "Import" button/menu in the modal header or as secondary action in
empty state.
- Options:
- Paste PEM text (multiline input)
- Pick a file (use `expo-document-picker`)
2. Validation + Storage
- Validate PEM or OpenSSH formats; detect supported types
(rsa/ecdsa/ed25519/ed448/dsa).
- Optional passphrase field (store encrypted if supported by library;
otherwise prompt each use — confirm feasibility with SSH lib).
- On success, call the single `upsertPrivateKey({ keyId, value, metadata })`
and close import flow.
3. UX details
- Show parse/validation errors inline.
- Set initial `label` from filename (file import) or "Imported Key" (paste);
allow editing label on success or selection.
Deliverables (Phase 2)
- Modal import screen(s) with paste/file flows.
- PEM validation utility and error handling.
## Phase 3 — Data Model + Semantics
1. Default key semantics
- Guarantee exactly one default key at most by flipping `isDefault` on upsert
when `isDefault === true`.
- Consider: separate lightweight persistent "defaultId" in manifest root to
avoid iterating all keys on default change.
2. Robustness
- Add safety around manifest chunk growth: if
`manifestChunkSize + newEntrySize > sizeLimit`, create a new chunk.
- Ensure `createdAtMs` is preserved across upserts (done) and add
`modifiedAtMs` if useful.
3. Logging + Dev ergonomics
- Gate verbose logs behind a debug flag to avoid log spam in production
builds.
## Phase 4 — Edge Cases & Polish
- Deleting the currently-selected key: clear selection and show hint to
pick/create a new key.
- Auto-select the default key when switching to key auth and no key is selected.
- Handle failures from `SecureStore` (permissions/storage full) with
user-friendly messages.
- Handle very large keys with chunking (already supported), surface an error if
size exceeds safe thresholds.
- Accessibility: ensure labels/roles for controls in modal and the selection
field.
## Phase 5 — Testing & QA
- Unit test PEM parsing/validation (Phase 2).
- E2E flows:
- First run → toggle to key → auto-open modal → generate key → selected →
connect.
- Import key by paste/file; rename; set default; delete.
- Delete selected key; confirm the form state updates.
## Implementation Notes
- Keep a single write path for keys: `upsertPrivateKey` (invalidates the `keys`
query).
- Prefer modal-based selection with a simple “field-as-button” in the form.
- For future passphrase support, confirm the SSH librarys API shape and storage
expectations.