package handlers import ( "crypto/md5" "encoding/json" "fmt" "net/http" "strconv" "strings" "github.com/go-chi/chi/v5" "golang.org/x/crypto/ssh" "xorm.io/xorm" "github.com/forgeo/forgebucket/internal/api/middleware" "github.com/forgeo/forgebucket/internal/models" ) type SSHKeyHandler struct { db *xorm.Engine } func NewSSHKeyHandler(db *xorm.Engine) *SSHKeyHandler { return &SSHKeyHandler{db: db} } func (h *SSHKeyHandler) List(w http.ResponseWriter, r *http.Request) { userID, _ := middleware.UserIDFromContext(r.Context()) var keys []models.SSHKey if err := h.db.Where("user_id = ?", userID).Find(&keys); err != nil { jsonError(w, "could not list SSH keys", http.StatusInternalServerError) return } if keys == nil { keys = []models.SSHKey{} } jsonOK(w, keys) } func (h *SSHKeyHandler) Add(w http.ResponseWriter, r *http.Request) { userID, _ := middleware.UserIDFromContext(r.Context()) var body struct { Title string `json:"title"` PublicKey string `json:"publicKey"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { jsonError(w, "invalid request body", http.StatusBadRequest) return } if body.Title == "" || body.PublicKey == "" { jsonError(w, "title and publicKey are required", http.StatusBadRequest) return } // Parse and validate the public key pubKeyBytes := []byte(strings.TrimSpace(body.PublicKey)) pub, _, _, _, err := ssh.ParseAuthorizedKey(pubKeyBytes) if err != nil { jsonError(w, "invalid SSH public key format", http.StatusBadRequest) return } // Compute MD5 fingerprint (standard display format) fingerprint := fingerprintMD5(pub) key := &models.SSHKey{ UserID: userID, Title: body.Title, Fingerprint: fingerprint, PublicKey: strings.TrimSpace(body.PublicKey), } if _, err := h.db.Insert(key); err != nil { jsonError(w, "key already exists or could not be saved", http.StatusConflict) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(key) } func (h *SSHKeyHandler) Delete(w http.ResponseWriter, r *http.Request) { userID, _ := middleware.UserIDFromContext(r.Context()) keyID, err := strconv.ParseInt(chi.URLParam(r, "keyID"), 10, 64) if err != nil { jsonError(w, "invalid key ID", http.StatusBadRequest) return } n, err := h.db.Where("id = ? AND user_id = ?", keyID, userID).Delete(&models.SSHKey{}) if err != nil || n == 0 { jsonError(w, "key not found", http.StatusNotFound) return } w.WriteHeader(http.StatusNoContent) } func fingerprintMD5(pub ssh.PublicKey) string { hash := md5.Sum(pub.Marshal()) parts := make([]string, len(hash)) for i, b := range hash { parts[i] = fmt.Sprintf("%02x", b) } return strings.Join(parts, ":") }