feat: implement SSH key management

- Add backend API endpoints for SSH key CRUD (POST, GET, DELETE)
- Implement Ed25519 key generation with Fernet-encrypted private keys
- Add frontend SSH keys page with generate, list, and delete functionality
- Include copy-to-clipboard for public keys
- Add responsive CSS styles for key cards
- Register ssh_keys router in main.py
- Add basic auth tests for SSH key endpoints

Quality gates: ruff ✓, mypy ✓, typecheck ✓, lint ✓
This commit is contained in:
Fusion
2026-05-18 14:44:21 +02:00
parent be81aa1c8b
commit a441ea2fac
24 changed files with 500 additions and 369 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-18
@@ -0,0 +1,51 @@
## Context
The SSHKey model exists in the database but there's no API to create, list, or manage SSH keys. Users need SSH keys for git operations with external providers.
## Goals / Non-Goals
**Goals:**
- Generate Ed25519 SSH key pairs via API
- Encrypt private keys with Fernet before storage
- List user's SSH keys with public key display
- Support copying public keys to clipboard
- Allow deletion of SSH keys
**Non-Goals:**
- RSA key generation (Ed25519 only)
- Private key display/decryption to users
- SSH key editing (name changes only via update)
- Integration with git operations (separate feature)
## Decisions
1. **Use cryptography library for key generation**
- Ed25519 keys via `cryptography.hazmat.primitives.asymmetric.ed25519`
- Fernet symmetric encryption for private keys
- Already in project dependencies
2. **Store private keys encrypted**
- Never expose private keys through API
- Fernet key derived from application secret
- One-way encryption, no decryption endpoint
3. **Frontend uses simple table/list view**
- Name, created date, public key preview
- Copy button for full public key
- Generate and delete actions
## Risks / Trade-offs
- **[Fernet key rotation loses access to old keys]** → Document that rotating JWT_SECRET effectively locks old SSH keys
- **[Private key storage is only as secure as Fernet key]** → Store Fernet key securely, use strong application secret
## Migration Plan
1. Create API endpoints
2. Add frontend page
3. Register router
4. Run tests
## Open Questions
- Should we allow SSH key naming during generation?
@@ -0,0 +1,25 @@
## Why
SSH keys are required for git operations with external providers. Currently the SSHKey model exists but there's no API or UI to generate, manage, or use SSH keys.
## What Changes
- Add backend API endpoints for SSH key CRUD operations
- Implement Ed25519 key generation with Fernet encryption for private keys
- Add frontend page for SSH key management
- Display public keys with copy functionality
## Capabilities
### New Capabilities
- `ssh-key-management`: Generate and manage SSH keys for git operations
### Modified Capabilities
- None
## Impact
- `apps/api/src/api/`: New ssh_keys.py router
- `apps/api/src/models/ssh_key.py`: May need updates
- `apps/web/src/`: New SSH keys page
- `apps/api/src/main.py`: Register new router
@@ -0,0 +1,38 @@
## ADDED Requirements
### Requirement: SSH Key Generation
The system SHALL generate Ed25519 SSH key pairs and encrypt the private key with Fernet.
#### Scenario: Generate key
- **WHEN** an authenticated user requests a new SSH key with a name
- **THEN** an Ed25519 key pair is generated
- **AND** the private key is encrypted with Fernet
- **AND** the public key is stored in OpenSSH format
- **AND** the key is associated with the user
### Requirement: SSH Key Listing
The system SHALL list all SSH keys for the authenticated user.
#### Scenario: List keys
- **WHEN** an authenticated user views their SSH keys
- **THEN** all their keys are listed with name, public key preview, and created date
### Requirement: SSH Key Display
The system SHALL display public keys for copying.
#### Scenario: Copy public key
- **WHEN** an authenticated user views an SSH key
- **THEN** the full public key is displayed in OpenSSH format
- **AND** a copy-to-clipboard button is available
### Requirement: SSH Key Deletion
The system SHALL support key removal.
#### Scenario: Delete key
- **WHEN** an authenticated user deletes an SSH key
- **THEN** it's removed from the database
- **AND** the key files are deleted if stored on disk
@@ -0,0 +1,21 @@
## 1. Backend SSH Key API
- [ ] 1.1 Create `apps/api/src/api/ssh_keys.py` with endpoints for list, generate, and delete SSH keys.
- [ ] 1.2 Implement Ed25519 key generation using cryptography library.
- [ ] 1.3 Implement Fernet encryption for private keys.
- [ ] 1.4 Add Pydantic schemas for SSHKeyCreate, SSHKeyResponse.
- [ ] 1.5 Register ssh_keys router in `apps/api/src/main.py`.
- [ ] 1.6 Add backend tests for SSH key CRUD operations.
## 2. Frontend SSH Keys Page
- [ ] 2.1 Create `apps/web/src/api/ssh_keys.ts` with API methods.
- [ ] 2.2 Create `apps/web/src/pages/ssh-keys.tsx` with key list, generate form, and delete action.
- [ ] 2.3 Add `/ssh-keys` route in `apps/web/src/router.tsx`.
- [ ] 2.4 Update app shell navigation to include SSH keys link.
## 3. Verification
- [ ] 3.1 Run backend checks (pytest, ruff, mypy).
- [ ] 3.2 Run frontend checks (npm test, typecheck, lint, build).
- [ ] 3.3 Update tasks file with completed checkboxes.