func (v1 *V1) checkRequestedScopes(ctx context.Context, requestedScopes []string) bool {
	allowedScopes, err := v1.listAPIs()
	if err != nil {
		return false
	}
	allowedScopes = append(allowedScopes, "*")
	for _, requested := range requestedScopes {
		found := false
		for _, allowed := range allowedScopes {
			if allowed == requested {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}
checkRequestedScopes returns true if account has sufficient privileges for them to generate the requestedScopes.
package handler
import (
	"bytes"
	"context"
	"crypto/sha256"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"
	"github.com/go-redis/redis/v8"
	"github.com/golang-jwt/jwt"
	alertpb "github.com/m3o/services/alert/proto/alert"
	balance "github.com/m3o/services/balance/proto"
	m3oauth "github.com/m3o/services/pkg/auth"
	"github.com/m3o/services/pkg/events/proto/requests"
	publicapi "github.com/m3o/services/publicapi/proto"
	v1 "github.com/m3o/services/v1/proto"
	authpb "github.com/micro/micro/v3/proto/auth"
	"github.com/micro/micro/v3/service"
	"github.com/micro/micro/v3/service/api"
	"github.com/micro/micro/v3/service/auth"
	"github.com/micro/micro/v3/service/client"
	"github.com/micro/micro/v3/service/config"
	"github.com/micro/micro/v3/service/context/metadata"
	"github.com/micro/micro/v3/service/errors"
	"github.com/micro/micro/v3/service/events"
	"github.com/micro/micro/v3/service/logger"
	log "github.com/micro/micro/v3/service/logger"
	"github.com/micro/micro/v3/service/registry"
	"github.com/micro/micro/v3/service/server"
	"github.com/micro/micro/v3/service/store"
)
const (
	keyCachePrefix = "v1-service/keycache"
)
type V1 struct {
	papi         publicapi.PublicapiService
	accsvc       authpb.AccountsService
	keyRecCache  *expiringLRUCache
	pricingCache pricingCache
	balanceCache balanceCache
	alerts       alertpb.AlertService
}
type pricingCache struct {
	sync.RWMutex
	prices map[string]int64
	papi   publicapi.PublicapiService
}
func (p *pricingCache) getPrice(api, endpoint string) int64 {
	p.RLock()
	defer p.RUnlock()
	return p.prices[strings.ToLower(fmt.Sprintf("%s.%s", api, endpoint))]
}
func (p *pricingCache) init() error {
	load := func() error {
		rsp, err := p.papi.List(context.Background(), &publicapi.ListRequest{}, client.WithAuthToken())
		if err != nil {
			logger.Errorf("Failed to load publicapi pricing %s", err)
			return err
		}
		newMap := map[string]int64{}
		for _, api := range rsp.Apis {
			for k, v := range api.Pricing {
				newMap[strings.ToLower(k)] = v
			}
		}
		p.Lock()
		p.prices = newMap
		p.Unlock()
		return nil
	}
	if err := load(); err != nil {
		return err
	}
	go func() {
		for {
			time.Sleep(10 * time.Minute)
			load()
		}
	}()
	return nil
}
type balanceCache struct {
	balsvc balance.BalanceService
}
func (b *balanceCache) getBalance(ctx context.Context, userID string) (int64, error) {
	rsp, err := b.balsvc.Current(ctx, &balance.CurrentRequest{
		CustomerId: userID,
	}, client.WithAuthToken())
	if err != nil {
		return 0, err
	}
	return rsp.CurrentBalance, nil
}
type expiringLRUCache struct {
	redisClient *redis.Client
	ttl         time.Duration
}
func (c *expiringLRUCache) Add(ctx context.Context, key string, value interface{}) error {
	val, _ := json.Marshal(value)
	return c.redisClient.Set(ctx, fmt.Sprintf("%s:%s", keyCachePrefix, key), val, c.ttl).Err()
}
func (c *expiringLRUCache) Remove(ctx context.Context, key string) error {
	return c.redisClient.Del(ctx, fmt.Sprintf("%s:%s", keyCachePrefix, key)).Err()
}
func (c *expiringLRUCache) GetAPIKeyRecord(ctx context.Context, key string) (*apiKeyRecord, error) {
	b, err := c.redisClient.Get(ctx, fmt.Sprintf("%s:%s", keyCachePrefix, key)).Bytes()
	if err != nil {
		return nil, err
	}
	var keyRec apiKeyRecord
	if err := json.Unmarshal(b, &keyRec); err != nil {
		return nil, err
	}
	return &keyRec, nil
}
const (
	storePrefixHashedKey = "hashed"
	storePrefixUserID    = "user"
	storePrefixKeyID     = "key"
	lruCacheTTL = 5 * time.Minute
)
var (
	errUnauthorized = errors.Unauthorized("v1", "Unauthorized")
	errInternal     = errors.InternalServerError("v1", "Error processing request")
)
func NewHandler(srv *service.Service) *V1 {
	redisConfig := struct {
		Address  string
		User     string
		Password string
	}{}
	val, err := config.Get("micro.v1.redis")
	if err != nil {
		log.Fatalf("No redis config found %s", err)
	}
	if err := val.Scan(&redisConfig); err != nil {
		log.Fatalf("Error parsing redis config %s", err)
	}
	if len(redisConfig.Password) == 0 || len(redisConfig.User) == 0 || len(redisConfig.Password) == 0 {
		log.Fatalf("Missing redis config %s", err)
	}
	rc := redis.NewClient(&redis.Options{
		Addr:     redisConfig.Address,
		Username: redisConfig.User,
		Password: redisConfig.Password,
		TLSConfig: &tls.Config{
			InsecureSkipVerify: false,
		},
	})
	papi := publicapi.NewPublicapiService("publicapi", srv.Client())
	keyRecCache := expiringLRUCache{redisClient: rc, ttl: lruCacheTTL}
	priceCache := pricingCache{
		prices: map[string]int64{},
		papi:   papi,
	}
	if err := priceCache.init(); err != nil {
		logger.Fatalf("Failed to init price cache %s", err)
	}
	v1 := &V1{
		papi:         papi,
		accsvc:       authpb.NewAccountsService("auth", srv.Client()),
		keyRecCache:  &keyRecCache,
		pricingCache: priceCache,
		balanceCache: balanceCache{
			balsvc: balance.NewBalanceService("balance", srv.Client()),
		},
		alerts: alertpb.NewAlertService("alert", srv.Client()),
	}
	go v1.consumeEvents()
	return v1
}
func (v1 *V1) writeAPIRecord(ctx context.Context, rec *apiKeyRecord) error {
	b, err := json.Marshal(rec)
	if err != nil {
		return err
	}
	if err := store.Write(&store.Record{
		Key:   fmt.Sprintf("%s:%s", storePrefixHashedKey, rec.ApiKey),
		Value: b,
	}); err != nil {
		return err
	}
	if err := store.Write(&store.Record{
		Key:   fmt.Sprintf("%s:%s:%s:%s", storePrefixUserID, rec.Namespace, rec.UserID, rec.ApiKey),
		Value: b,
	}); err != nil {
		return err
	}
	if err := store.Write(&store.Record{
		Key:   fmt.Sprintf("%s:%s:%s:%s", storePrefixKeyID, rec.Namespace, rec.UserID, rec.ID),
		Value: b,
	}); err != nil {
		return err
	}
	v1.keyRecCache.Add(ctx, rec.ApiKey, rec)
	return nil
}
func (v1 *V1) deleteAPIRecord(ctx context.Context, rec *apiKeyRecord) error {
	if err := store.Delete(fmt.Sprintf("%s:%s", storePrefixHashedKey, rec.ApiKey)); err != nil {
		return err
	}
	if err := store.Delete(fmt.Sprintf("%s:%s:%s:%s", storePrefixUserID, rec.Namespace, rec.UserID, rec.ApiKey)); err != nil {
		return err
	}
	if err := store.Delete(fmt.Sprintf("%s:%s:%s:%s", storePrefixKeyID, rec.Namespace, rec.UserID, rec.ID)); err != nil {
		return err
	}
	v1.keyRecCache.Remove(ctx, rec.ApiKey)
	return nil
}
func readAPIRecordByKeyID(ns, user, keyID string) (*apiKeyRecord, error) {
	recs, err := store.Read(fmt.Sprintf("%s:%s:%s:%s", storePrefixKeyID, ns, user, keyID))
	if err != nil {
		return nil, err
	}
	rec := recs[0]
	keyRec := &apiKeyRecord{}
	if err := json.Unmarshal(rec.Value, keyRec); err != nil {
		return nil, err
	}
	return keyRec, nil
}
func hashSecret(s string) (string, error) {
	h := sha256.New()
	h.Write([]byte(s))
	h.Sum(nil)
	return fmt.Sprintf("%x", h.Sum(nil)), nil
}