storj/satellite/admin/apikeys.go

341 lines
7.7 KiB
Go
Raw Normal View History

// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
package admin
import (
"database/sql"
"encoding/json"
"errors"
"io"
"net/http"
"time"
"github.com/gorilla/mux"
"storj.io/common/macaroon"
"storj.io/common/uuid"
"storj.io/storj/satellite/console"
)
func (server *Server) addAPIKey(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
projectUUIDString, ok := vars["project"]
if !ok {
sendJSONError(w, "project-uuid missing",
"", http.StatusBadRequest)
return
}
projectUUID, err := uuid.FromString(projectUUIDString)
if err != nil {
sendJSONError(w, "invalid project-uuid",
err.Error(), http.StatusBadRequest)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
sendJSONError(w, "failed to read body",
err.Error(), http.StatusInternalServerError)
return
}
var input struct {
Name string `json:"name"`
}
err = json.Unmarshal(body, &input)
if err != nil {
sendJSONError(w, "failed to unmarshal request",
err.Error(), http.StatusBadRequest)
return
}
if input.Name == "" {
sendJSONError(w, "Name is not set",
"", http.StatusBadRequest)
return
}
_, err = server.db.Console().APIKeys().GetByNameAndProjectID(ctx, input.Name, projectUUID)
if err == nil {
sendJSONError(w, "api-key with given name already exists",
"", http.StatusConflict)
return
}
secret, err := macaroon.NewSecret()
if err != nil {
sendJSONError(w, "could not create macaroon secret",
err.Error(), http.StatusInternalServerError)
return
}
key, err := macaroon.NewAPIKey(secret)
if err != nil {
sendJSONError(w, "could not create api-key",
err.Error(), http.StatusInternalServerError)
return
}
apikey := console.APIKeyInfo{
Name: input.Name,
ProjectID: projectUUID,
Secret: secret,
}
_, err = server.db.Console().APIKeys().Create(ctx, key.Head(), apikey)
if err != nil {
sendJSONError(w, "unable to add api-key to database",
err.Error(), http.StatusInternalServerError)
return
}
var output struct {
APIKey string `json:"apikey"`
}
output.APIKey = key.Serialize()
data, err := json.Marshal(output)
if err != nil {
sendJSONError(w, "json encoding failed",
err.Error(), http.StatusInternalServerError)
return
}
sendJSONData(w, http.StatusOK, data)
}
func (server *Server) getAPIKey(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
apikeyString, ok := vars["apikey"]
if !ok {
sendJSONError(w, "apikey missing",
"", http.StatusBadRequest)
return
}
apikey, err := macaroon.ParseAPIKey(apikeyString)
if err != nil {
sendJSONError(w, "invalid apikey format",
err.Error(), http.StatusBadRequest)
return
}
apiKeyInfo, err := server.db.Console().APIKeys().GetByHead(ctx, apikey.Head())
if errors.Is(err, sql.ErrNoRows) {
sendJSONError(w, "API key does not exist",
"", http.StatusNotFound)
return
}
if err != nil {
sendJSONError(w, "could not get apikey id",
err.Error(), http.StatusInternalServerError)
return
}
project, err := server.db.Console().Projects().Get(ctx, apiKeyInfo.ProjectID)
if err != nil {
sendJSONError(w, "unable to fetch project details",
err.Error(), http.StatusInternalServerError)
return
}
user, err := server.db.Console().Users().Get(ctx, project.OwnerID)
if err != nil {
sendJSONError(w, "unable to fetch user details",
err.Error(), http.StatusInternalServerError)
return
}
type apiKeyData struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"createdAt"`
}
type projectData struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
}
type ownerData struct {
ID uuid.UUID `json:"id"`
FullName string `json:"fullName"`
Email string `json:"email"`
PaidTier bool `json:"paidTier"`
}
data, err := json.Marshal(struct {
APIKey apiKeyData `json:"api_key"`
Project projectData `json:"project"`
Owner ownerData `json:"owner"`
}{
APIKey: apiKeyData{
ID: apiKeyInfo.ID,
Name: apiKeyInfo.Name,
CreatedAt: apiKeyInfo.CreatedAt.UTC(),
},
Project: projectData{
ID: project.ID,
Name: project.Name,
},
Owner: ownerData{
ID: user.ID,
FullName: user.FullName,
Email: user.Email,
PaidTier: user.PaidTier,
},
})
if err != nil {
sendJSONError(w, "json encoding failed",
err.Error(), http.StatusInternalServerError)
return
}
sendJSONData(w, http.StatusOK, data)
}
func (server *Server) deleteAPIKey(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
apikeyString, ok := vars["apikey"]
if !ok {
sendJSONError(w, "apikey missing",
"", http.StatusBadRequest)
return
}
apikey, err := macaroon.ParseAPIKey(apikeyString)
if err != nil {
sendJSONError(w, "invalid apikey format",
err.Error(), http.StatusBadRequest)
return
}
info, err := server.db.Console().APIKeys().GetByHead(ctx, apikey.Head())
if errors.Is(err, sql.ErrNoRows) {
sendJSONError(w, "API key does not exist",
"", http.StatusNotFound)
return
}
if err != nil {
sendJSONError(w, "could not get apikey id",
err.Error(), http.StatusInternalServerError)
return
}
err = server.db.Console().APIKeys().Delete(ctx, info.ID)
if err != nil {
sendJSONError(w, "unable to delete apikey",
err.Error(), http.StatusInternalServerError)
return
}
}
func (server *Server) deleteAPIKeyByName(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
projectUUIDString, ok := vars["project"]
if !ok {
sendJSONError(w, "project-uuid missing",
"", http.StatusBadRequest)
return
}
projectUUID, err := uuid.FromString(projectUUIDString)
if err != nil {
sendJSONError(w, "invalid project-uuid",
err.Error(), http.StatusBadRequest)
return
}
apikeyName, ok := vars["name"]
if !ok {
sendJSONError(w, "apikey name missing",
"", http.StatusBadRequest)
return
}
info, err := server.db.Console().APIKeys().GetByNameAndProjectID(ctx, apikeyName, projectUUID)
if errors.Is(err, sql.ErrNoRows) {
sendJSONError(w, "API key with specified name does not exist",
"", http.StatusNotFound)
return
}
if err != nil {
sendJSONError(w, "could not get apikey id",
err.Error(), http.StatusInternalServerError)
return
}
err = server.db.Console().APIKeys().Delete(ctx, info.ID)
if err != nil {
sendJSONError(w, "unable to delete apikey",
err.Error(), http.StatusInternalServerError)
return
}
}
func (server *Server) listAPIKeys(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
projectUUIDString, ok := vars["project"]
if !ok {
sendJSONError(w, "project-uuid missing",
"", http.StatusBadRequest)
return
}
projectUUID, err := uuid.FromString(projectUUIDString)
if err != nil {
sendJSONError(w, "invalid project-uuid",
err.Error(), http.StatusBadRequest)
return
}
const apiKeysPerPage = 50 // This is the current maximum API Keys per page.
var apiKeys []console.APIKeyInfo
for i := uint(1); true; i++ {
page, err := server.db.Console().APIKeys().GetPagedByProjectID(
ctx, projectUUID, console.APIKeyCursor{
Limit: apiKeysPerPage,
Page: i,
Order: console.KeyName,
OrderDirection: console.Ascending,
},
)
if err != nil {
sendJSONError(w, "failed retrieving a cursor page of API Keys list",
err.Error(), http.StatusInternalServerError,
)
return
}
apiKeys = append(apiKeys, page.APIKeys...)
if len(page.APIKeys) < apiKeysPerPage {
break
}
}
var data []byte
if len(apiKeys) == 0 {
data = []byte("[]")
} else {
data, err = json.Marshal(apiKeys)
if err != nil {
sendJSONError(w, "json encoding failed",
err.Error(), http.StatusInternalServerError)
return
}
}
sendJSONData(w, http.StatusOK, data)
}