Files
proxui/backend/internal/sshkey/handler.go
T

205 lines
5.1 KiB
Go

package sshkey
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"proxui/backend/internal/auth"
"proxui/backend/internal/membership"
"proxui/backend/internal/rbac"
)
type Repository interface {
List(ctx context.Context, profileID string, tenantID string) ([]SSHKey, bool, error)
Get(ctx context.Context, profileID string, keyID string) (SSHKey, bool, error)
Create(ctx context.Context, profileID string, tenantID string, name string, publicKey string) (SSHKey, bool, error)
Delete(ctx context.Context, profileID string, keyID string) (bool, bool, error)
}
type Handler struct {
repository Repository
}
func NewHandler(repository Repository) Handler {
return Handler{repository: repository}
}
func (h Handler) ListTenantKeys(w http.ResponseWriter, r *http.Request) {
principal, ok := auth.PrincipalFromRequest(r)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
tenantID := r.PathValue("tenantID")
if tenantID == "" {
writeError(w, http.StatusBadRequest, "tenant_id_required")
return
}
keys, found, err := h.repository.List(r.Context(), principal.Subject, tenantID)
if err != nil {
writeError(w, http.StatusInternalServerError, "ssh_keys_list_failed")
return
}
if !found {
writeError(w, http.StatusNotFound, "tenant_not_found")
return
}
writeJSON(w, http.StatusOK, map[string][]SSHKey{"data": keys})
}
func (h Handler) GetKey(w http.ResponseWriter, r *http.Request) {
principal, ok := auth.PrincipalFromRequest(r)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
keyID := r.PathValue("keyID")
if keyID == "" {
writeError(w, http.StatusBadRequest, "key_id_required")
return
}
key, found, err := h.repository.Get(r.Context(), principal.Subject, keyID)
if err != nil {
writeError(w, http.StatusInternalServerError, "ssh_key_get_failed")
return
}
if !found {
writeError(w, http.StatusNotFound, "ssh_key_not_found")
return
}
membershipID, _ := membership.FromRequest(r)
if membershipID.TenantID != key.TenantID {
writeError(w, http.StatusForbidden, "forbidden")
return
}
if !rbac.Can(rbac.Role(membershipID.Role), rbac.ActionSSHKeyRead) {
writeError(w, http.StatusForbidden, "forbidden")
return
}
writeJSON(w, http.StatusOK, key)
}
func (h Handler) CreateKey(w http.ResponseWriter, r *http.Request) {
principal, ok := auth.PrincipalFromRequest(r)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
tenantID := r.PathValue("tenantID")
if tenantID == "" {
writeError(w, http.StatusBadRequest, "tenant_id_required")
return
}
membershipID, _ := membership.FromRequest(r)
if membershipID.TenantID != tenantID {
writeError(w, http.StatusForbidden, "forbidden")
return
}
if !rbac.Can(rbac.Role(membershipID.Role), rbac.ActionSSHKeyManage) {
writeError(w, http.StatusForbidden, "forbidden")
return
}
var req createRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid_body")
return
}
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
writeError(w, http.StatusBadRequest, "name_required")
return
}
req.PublicKey = strings.TrimSpace(req.PublicKey)
if req.PublicKey == "" {
writeError(w, http.StatusBadRequest, "public_key_required")
return
}
key, found, err := h.repository.Create(r.Context(), principal.Subject, tenantID, req.Name, req.PublicKey)
if err != nil {
writeError(w, http.StatusInternalServerError, "ssh_key_create_failed")
return
}
if !found {
writeError(w, http.StatusNotFound, "tenant_not_found")
return
}
writeJSON(w, http.StatusCreated, key)
}
func (h Handler) DeleteKey(w http.ResponseWriter, r *http.Request) {
principal, ok := auth.PrincipalFromRequest(r)
if !ok {
writeError(w, http.StatusUnauthorized, "unauthorized")
return
}
keyID := r.PathValue("keyID")
if keyID == "" {
writeError(w, http.StatusBadRequest, "key_id_required")
return
}
deleted, member, err := h.repository.Delete(r.Context(), principal.Subject, keyID)
if err != nil {
writeError(w, http.StatusInternalServerError, "ssh_key_delete_failed")
return
}
if !member {
writeError(w, http.StatusNotFound, "ssh_key_not_found")
return
}
if !deleted {
writeError(w, http.StatusNotFound, "ssh_key_not_found")
return
}
membershipID, _ := membership.FromRequest(r)
if !rbac.Can(rbac.Role(membershipID.Role), rbac.ActionSSHKeyManage) {
writeError(w, http.StatusForbidden, "forbidden")
return
}
w.WriteHeader(http.StatusNoContent)
}
type SSHKey struct {
ID string `json:"id"`
TenantID string `json:"tenant_id"`
Name string `json:"name"`
PublicKey string `json:"public_key"`
CreatedAt time.Time `json:"created_at"`
}
type createRequest struct {
Name string `json:"name"`
PublicKey string `json:"public_key"`
}
func writeJSON(w http.ResponseWriter, status int, body any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(body)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}