diff --git a/internal/admin/apiservices.go b/internal/admin/apiservices.go index 347a6ab..381a08b 100644 --- a/internal/admin/apiservices.go +++ b/internal/admin/apiservices.go @@ -1,10 +1,17 @@ package admin import ( + "encoding/json" + "log" + "net/http" "time" "gitea.local/admin/hspguard/internal/repository" + "gitea.local/admin/hspguard/internal/util" + "gitea.local/admin/hspguard/internal/web" + "github.com/go-chi/chi/v5" "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" ) type ApiServiceDTO struct { @@ -34,3 +41,235 @@ func NewApiServiceDTO(service repository.ApiService) ApiServiceDTO { IsActive: service.IsActive, } } + +func (h *AdminHandler) GetApiServices(w http.ResponseWriter, r *http.Request) { + services, err := h.repo.ListApiServices(r.Context()) + if err != nil { + log.Println("ERR: Failed to list api services from db:", err) + web.Error(w, "failed to get api services", http.StatusInternalServerError) + return + } + + apiServices := make([]ApiServiceDTO, 0) + + for _, service := range services { + apiServices = append(apiServices, NewApiServiceDTO(service)) + } + + type Response struct { + Items []ApiServiceDTO `json:"items"` + Count int `json:"count"` + } + + encoder := json.NewEncoder(w) + + if err := encoder.Encode(Response{ + Items: apiServices, + Count: len(apiServices), + }); err != nil { + web.Error(w, "failed to encode response", http.StatusInternalServerError) + } +} + +type AddServiceRequest struct { + Name string `json:"name"` + Description string `json:"description"` + RedirectUris []string `json:"redirect_uris"` + Scopes []string `json:"scopes"` + GrantTypes []string `json:"grant_types"` + IsActive bool `json:"is_active"` +} + +type ApiServiceCredentials struct { + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + +func (h *AdminHandler) AddApiService(w http.ResponseWriter, r *http.Request) { + var req AddServiceRequest + + decoder := json.NewDecoder(r.Body) + + if err := decoder.Decode(&req); err != nil { + web.Error(w, "failed to parse request body", http.StatusBadRequest) + return + } + + if req.Name == "" { + web.Error(w, "name is required for an api service", http.StatusBadRequest) + return + } + + clientId, err := util.GenerateClientID() + if err != nil { + web.Error(w, "failed to generate client id", http.StatusInternalServerError) + return + } + + clientSecret, err := util.GenerateClientSecret() + if err != nil { + web.Error(w, "failed to generate client secret", http.StatusInternalServerError) + return + } + + hashSecret, err := util.HashPassword(clientSecret) + if err != nil { + web.Error(w, "failed to create client secret", http.StatusInternalServerError) + return + } + + params := repository.CreateApiServiceParams{ + ClientID: clientId, + ClientSecret: hashSecret, + Name: req.Name, + RedirectUris: req.RedirectUris, + Scopes: req.Scopes, + GrantTypes: req.GrantTypes, + IsActive: req.IsActive, + } + + if req.Description != "" { + params.Description = pgtype.Text{ + String: req.Description, + Valid: true, + } + } + + service, err := h.repo.CreateApiService(r.Context(), params) + if err != nil { + web.Error(w, "failed to create new api service", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + service.ClientSecret = clientSecret + + type Response struct { + Service ApiServiceDTO `json:"service"` + Credentials ApiServiceCredentials `json:"credentials"` + } + + encoder := json.NewEncoder(w) + if err := encoder.Encode(Response{ + Service: NewApiServiceDTO(service), + Credentials: ApiServiceCredentials{ + ClientId: service.ClientID, + ClientSecret: service.ClientSecret, + }, + }); err != nil { + web.Error(w, "failed to encode response", http.StatusInternalServerError) + } +} + +func (h *AdminHandler) GetApiService(w http.ResponseWriter, r *http.Request) { + serviceId := chi.URLParam(r, "id") + parsed, err := uuid.Parse(serviceId) + if err != nil { + web.Error(w, "service id provided is not a valid uuid", http.StatusBadRequest) + return + } + + service, err := h.repo.GetApiServiceId(r.Context(), parsed) + if err != nil { + web.Error(w, "service with provided id not found", http.StatusNotFound) + return + } + + encoder := json.NewEncoder(w) + + if err := encoder.Encode(NewApiServiceDTO(service)); err != nil { + web.Error(w, "failed to encode response", http.StatusInternalServerError) + } +} + +func (h *AdminHandler) RegenerateApiServiceSecret(w http.ResponseWriter, r *http.Request) { + serviceId := chi.URLParam(r, "id") + parsed, err := uuid.Parse(serviceId) + if err != nil { + web.Error(w, "provided service id is not valid", http.StatusBadRequest) + return + } + + service, err := h.repo.GetApiServiceId(r.Context(), parsed) + if err != nil { + web.Error(w, "service with provided id not found", http.StatusNotFound) + return + } + + clientSecret, err := util.GenerateClientSecret() + if err != nil { + web.Error(w, "failed to generate client secret", http.StatusInternalServerError) + return + } + + if err := h.repo.UpdateClientSecret(r.Context(), repository.UpdateClientSecretParams{ + ClientID: service.ClientID, + ClientSecret: clientSecret, + }); err != nil { + web.Error(w, "failed to update client secret for service", http.StatusInternalServerError) + return + } + + encoder := json.NewEncoder(w) + + if err := encoder.Encode(ApiServiceCredentials{ + ClientId: service.ClientID, + ClientSecret: clientSecret, + }); err != nil { + web.Error(w, "failed to send credentials", http.StatusInternalServerError) + } +} + +type UpdateApiServiceRequest struct { + Name string `json:"name"` + Description string `json:"description"` + RedirectUris []string `json:"redirect_uris"` + Scopes []string `json:"scopes"` + GrantTypes []string `json:"grant_types"` +} + +func (h *AdminHandler) UpdateApiService(w http.ResponseWriter, r *http.Request) { + serviceId := chi.URLParam(r, "id") + parsed, err := uuid.Parse(serviceId) + if err != nil { + web.Error(w, "provided service id is not valid", http.StatusBadRequest) + return + } + + var req UpdateApiServiceRequest + + decoder := json.NewDecoder(r.Body) + if err := decoder.Decode(&req); err != nil { + web.Error(w, "missing required fields to update service", http.StatusBadRequest) + return + } + + if req.Name == "" { + web.Error(w, "service name is required", http.StatusBadRequest) + return + } + + if req.Description == "" { + web.Error(w, "service name is required", http.StatusBadRequest) + return + } + + service, err := h.repo.GetApiServiceId(r.Context(), parsed) + if err != nil { + web.Error(w, "service with provided id not found", http.StatusNotFound) + return + } + + if err := h.repo.UpdateApiService(r.Context(), repository.UpdateApiServiceParams{ + ClientID: service.ClientID, + Name: , + Description: pgtype.Text{}, + RedirectUris: []string{}, + Scopes: []string{}, + GrantTypes: []string{}, + }); err != nil { + web.Error(w, "failed to update api service", http.StatusInternalServerError) + return + } +} diff --git a/internal/admin/routes.go b/internal/admin/routes.go index 8c6cb6b..dcf435f 100644 --- a/internal/admin/routes.go +++ b/internal/admin/routes.go @@ -1,18 +1,10 @@ package admin import ( - "encoding/json" - "log" - "net/http" - "gitea.local/admin/hspguard/internal/config" imiddleware "gitea.local/admin/hspguard/internal/middleware" "gitea.local/admin/hspguard/internal/repository" - "gitea.local/admin/hspguard/internal/util" - "gitea.local/admin/hspguard/internal/web" "github.com/go-chi/chi/v5" - "github.com/google/uuid" - "github.com/jackc/pgx/v5/pgtype" ) type AdminHandler struct { @@ -36,146 +28,7 @@ func (h *AdminHandler) RegisterRoutes(router chi.Router) { r.Get("/api-services", h.GetApiServices) r.Get("/api-services/{id}", h.GetApiService) r.Post("/api-services", h.AddApiService) + r.Patch("/api-services/{id}", h.RegenerateApiServiceSecret) + r.Put("/api-services/{id}", h.RegenerateApiServiceSecret) }) } - -func (h *AdminHandler) GetApiServices(w http.ResponseWriter, r *http.Request) { - services, err := h.repo.ListApiServices(r.Context()) - if err != nil { - log.Println("ERR: Failed to list api services from db:", err) - web.Error(w, "failed to get api services", http.StatusInternalServerError) - return - } - - apiServices := make([]ApiServiceDTO, 0) - - for _, service := range services { - apiServices = append(apiServices, NewApiServiceDTO(service)) - } - - type Response struct { - Items []ApiServiceDTO `json:"items"` - Count int `json:"count"` - } - - encoder := json.NewEncoder(w) - - if err := encoder.Encode(Response{ - Items: apiServices, - Count: len(apiServices), - }); err != nil { - web.Error(w, "failed to encode response", http.StatusInternalServerError) - } -} - -type AddServiceRequest struct { - Name string `json:"name"` - Description string `json:"description"` - RedirectUris []string `json:"redirect_uris"` - Scopes []string `json:"scopes"` - GrantTypes []string `json:"grant_types"` - IsActive bool `json:"is_active"` -} - -func (h *AdminHandler) AddApiService(w http.ResponseWriter, r *http.Request) { - var req AddServiceRequest - - decoder := json.NewDecoder(r.Body) - - if err := decoder.Decode(&req); err != nil { - web.Error(w, "failed to parse request body", http.StatusBadRequest) - return - } - - if req.Name == "" { - web.Error(w, "name is required for an api service", http.StatusBadRequest) - return - } - - clientId, err := util.GenerateClientID() - if err != nil { - web.Error(w, "failed to generate client id", http.StatusInternalServerError) - return - } - - clientSecret, err := util.GenerateClientSecret() - if err != nil { - web.Error(w, "failed to generate client secret", http.StatusInternalServerError) - return - } - - hashSecret, err := util.HashPassword(clientSecret) - if err != nil { - web.Error(w, "failed to create client secret", http.StatusInternalServerError) - return - } - - params := repository.CreateApiServiceParams{ - ClientID: clientId, - ClientSecret: hashSecret, - Name: req.Name, - RedirectUris: req.RedirectUris, - Scopes: req.Scopes, - GrantTypes: req.GrantTypes, - IsActive: req.IsActive, - } - - if req.Description != "" { - params.Description = pgtype.Text{ - String: req.Description, - Valid: true, - } - } - - service, err := h.repo.CreateApiService(r.Context(), params) - if err != nil { - web.Error(w, "failed to create new api service", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - - service.ClientSecret = clientSecret - - type ApiServiceCredentials struct { - ClientId string `json:"client_id"` - ClientSecret string `json:"client_secret"` - } - - type Response struct { - Service ApiServiceDTO `json:"service"` - Credentials ApiServiceCredentials `json:"credentials"` - } - - encoder := json.NewEncoder(w) - if err := encoder.Encode(Response{ - Service: NewApiServiceDTO(service), - Credentials: ApiServiceCredentials{ - ClientId: service.ClientID, - ClientSecret: service.ClientSecret, - }, - }); err != nil { - web.Error(w, "failed to encode response", http.StatusInternalServerError) - } -} - -func (h *AdminHandler) GetApiService(w http.ResponseWriter, r *http.Request) { - serviceId := chi.URLParam(r, "id") - parsed, err := uuid.Parse(serviceId) - if err != nil { - web.Error(w, "service id provided is not a valid uuid", http.StatusBadRequest) - return - } - - service, err := h.repo.GetApiServiceId(r.Context(), parsed) - if err != nil { - web.Error(w, "service with provided id not found", http.StatusNotFound) - return - } - - encoder := json.NewEncoder(w) - - if err := encoder.Encode(NewApiServiceDTO(service)); err != nil { - web.Error(w, "failed to encode response", http.StatusInternalServerError) - } -}