mirror of https://github.com/artifacthub/hub.git
164 lines
4.6 KiB
Go
164 lines
4.6 KiB
Go
package apikey
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"crypto/sha512"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/artifacthub/hub/internal/hub"
|
|
"github.com/artifacthub/hub/internal/util"
|
|
"github.com/jackc/pgx/v4"
|
|
"github.com/satori/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
// Database queries
|
|
addAPIKeyDBQ = `select add_api_key($1::jsonb)`
|
|
deleteAPIKeyDBQ = `select delete_api_key($1::uuid, $2::uuid)`
|
|
getAPIKeyDBQ = `select get_api_key($1::uuid, $2::uuid)`
|
|
getAPIKeyUserIDDBQ = `select user_id, secret from api_key where api_key_id = $1`
|
|
getUserAPIKeysDBQ = `select get_user_api_keys($1::uuid)`
|
|
updateAPIKeyDBQ = `select update_api_key($1::jsonb)`
|
|
)
|
|
|
|
// Manager provides an API to manage api keys.
|
|
type Manager struct {
|
|
db hub.DB
|
|
}
|
|
|
|
// NewManager creates a new Manager instance.
|
|
func NewManager(db hub.DB) *Manager {
|
|
return &Manager{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// Add adds the provided api key to the database.
|
|
func (m *Manager) Add(ctx context.Context, ak *hub.APIKey) (*hub.APIKey, error) {
|
|
ak.UserID = ctx.Value(hub.UserIDKey).(string)
|
|
|
|
// Validate input
|
|
if ak.Name == "" {
|
|
return nil, fmt.Errorf("%w: %s", hub.ErrInvalidInput, "name not provided")
|
|
}
|
|
|
|
// Generate API key secret
|
|
randomBytes := make([]byte, 32)
|
|
if _, err := rand.Read(randomBytes); err != nil {
|
|
return nil, err
|
|
}
|
|
apiKeySecret := base64.StdEncoding.EncodeToString(randomBytes)
|
|
apiKeySecretHashed := fmt.Sprintf("%x", sha512.Sum512([]byte(apiKeySecret)))
|
|
|
|
// Add api key to the database
|
|
var apiKeyID string
|
|
ak.Secret = apiKeySecretHashed
|
|
akJSON, _ := json.Marshal(ak)
|
|
if err := m.db.QueryRow(ctx, addAPIKeyDBQ, akJSON).Scan(&apiKeyID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &hub.APIKey{
|
|
APIKeyID: apiKeyID,
|
|
Secret: apiKeySecret,
|
|
}, nil
|
|
}
|
|
|
|
// Check checks if the api key provided is valid.
|
|
func (m *Manager) Check(ctx context.Context, apiKeyID, apiKeySecret string) (*hub.CheckAPIKeyOutput, error) {
|
|
// Validate input
|
|
if apiKeyID == "" || apiKeySecret == "" {
|
|
return nil, fmt.Errorf("%w: %s", hub.ErrInvalidInput, "api key id or secret not provided")
|
|
}
|
|
|
|
// Get key's user id and secret from database
|
|
var userID, apiKeySecretHashed string
|
|
err := m.db.QueryRow(ctx, getAPIKeyUserIDDBQ, apiKeyID).Scan(&userID, &apiKeySecretHashed)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return &hub.CheckAPIKeyOutput{Valid: false}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Check if the secret provided is valid
|
|
switch {
|
|
case strings.HasPrefix(apiKeySecretHashed, "$2a$"):
|
|
// Bcrypt hash, will be deprecated soon
|
|
err = bcrypt.CompareHashAndPassword([]byte(apiKeySecretHashed), []byte(apiKeySecret))
|
|
if err != nil {
|
|
return &hub.CheckAPIKeyOutput{Valid: false}, nil
|
|
}
|
|
default:
|
|
// SHA512 hash
|
|
if fmt.Sprintf("%x", sha512.Sum512([]byte(apiKeySecret))) != apiKeySecretHashed {
|
|
return &hub.CheckAPIKeyOutput{Valid: false}, nil
|
|
}
|
|
}
|
|
|
|
return &hub.CheckAPIKeyOutput{
|
|
Valid: true,
|
|
UserID: userID,
|
|
}, nil
|
|
}
|
|
|
|
// Delete deletes the provided api key from the database.
|
|
func (m *Manager) Delete(ctx context.Context, apiKeyID string) error {
|
|
userID := ctx.Value(hub.UserIDKey).(string)
|
|
|
|
// Validate input
|
|
if _, err := uuid.FromString(apiKeyID); err != nil {
|
|
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid api key id")
|
|
}
|
|
|
|
// Delete api key from database
|
|
_, err := m.db.Exec(ctx, deleteAPIKeyDBQ, userID, apiKeyID)
|
|
return err
|
|
}
|
|
|
|
// GetJSON returns the requested api key as a json object.
|
|
func (m *Manager) GetJSON(ctx context.Context, apiKeyID string) ([]byte, error) {
|
|
userID := ctx.Value(hub.UserIDKey).(string)
|
|
|
|
// Validate input
|
|
if _, err := uuid.FromString(apiKeyID); err != nil {
|
|
return nil, fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid api key id")
|
|
}
|
|
|
|
// Get api key from database
|
|
return util.DBQueryJSON(ctx, m.db, getAPIKeyDBQ, userID, apiKeyID)
|
|
}
|
|
|
|
// GetOwnedByUserJSON returns the api keys belonging to the requesting user as
|
|
// a json array.
|
|
func (m *Manager) GetOwnedByUserJSON(ctx context.Context) ([]byte, error) {
|
|
userID := ctx.Value(hub.UserIDKey).(string)
|
|
|
|
// Get api keys from database
|
|
return util.DBQueryJSON(ctx, m.db, getUserAPIKeysDBQ, userID)
|
|
}
|
|
|
|
// Update updates the provided api key in the database.
|
|
func (m *Manager) Update(ctx context.Context, ak *hub.APIKey) error {
|
|
ak.UserID = ctx.Value(hub.UserIDKey).(string)
|
|
|
|
// Validate input
|
|
if _, err := uuid.FromString(ak.APIKeyID); err != nil {
|
|
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "invalid api key id")
|
|
}
|
|
if ak.Name == "" {
|
|
return fmt.Errorf("%w: %s", hub.ErrInvalidInput, "name not provided")
|
|
}
|
|
|
|
// Update api key in database
|
|
akJSON, _ := json.Marshal(ak)
|
|
_, err := m.db.Exec(ctx, updateAPIKeyDBQ, akJSON)
|
|
return err
|
|
}
|