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:
@@ -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.
|
||||
Reference in New Issue
Block a user