205 lines
5.1 KiB
Go
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})
|
|
} |