github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/auth/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"strings"
     5  	"sync"
     6  	"time"
     7  
     8  	pb "github.com/tickoalcantara12/micro/v3/proto/auth"
     9  	"github.com/tickoalcantara12/micro/v3/service/auth"
    10  	"github.com/tickoalcantara12/micro/v3/service/client"
    11  	"github.com/tickoalcantara12/micro/v3/service/context"
    12  	"github.com/tickoalcantara12/micro/v3/service/errors"
    13  	"github.com/tickoalcantara12/micro/v3/service/logger"
    14  	"github.com/tickoalcantara12/micro/v3/util/auth/rules"
    15  	"github.com/tickoalcantara12/micro/v3/util/auth/token"
    16  	"github.com/tickoalcantara12/micro/v3/util/auth/token/jwt"
    17  )
    18  
    19  const (
    20  	ruleCacheTTL = 2 * time.Minute
    21  )
    22  
    23  type rulesCache struct {
    24  	sync.RWMutex
    25  	ruleCache map[string]*cacheEntry
    26  	ttl       time.Duration
    27  }
    28  
    29  func (r *rulesCache) get(key string) []*auth.Rule {
    30  	r.RLock()
    31  	entry := r.ruleCache[key]
    32  	r.RUnlock()
    33  	if entry != nil && time.Since(entry.t) < r.ttl {
    34  		return entry.v
    35  	}
    36  	return nil
    37  }
    38  
    39  func (r *rulesCache) put(key string, v []*auth.Rule) {
    40  	r.Lock()
    41  	r.ruleCache[key] = &cacheEntry{t: time.Now(), v: v}
    42  	r.Unlock()
    43  }
    44  
    45  type cacheEntry struct {
    46  	t time.Time
    47  	v []*auth.Rule
    48  }
    49  
    50  // srv is the service implementation of the Auth interface
    51  type srv struct {
    52  	options   auth.Options
    53  	auth      pb.AuthService
    54  	rules     pb.RulesService
    55  	token     token.Provider
    56  	ruleCache rulesCache
    57  }
    58  
    59  func (s *srv) String() string {
    60  	return "service"
    61  }
    62  
    63  func (s *srv) Init(opts ...auth.Option) {
    64  	for _, o := range opts {
    65  		o(&s.options)
    66  	}
    67  	s.auth = pb.NewAuthService("auth", client.DefaultClient)
    68  	s.rules = pb.NewRulesService("auth", client.DefaultClient)
    69  	s.setupJWT()
    70  	s.ruleCache = rulesCache{
    71  		ruleCache: map[string]*cacheEntry{},
    72  		ttl:       ruleCacheTTL,
    73  	}
    74  }
    75  
    76  func (s *srv) Options() auth.Options {
    77  	return s.options
    78  }
    79  
    80  // Generate a new account
    81  func (s *srv) Generate(id string, opts ...auth.GenerateOption) (*auth.Account, error) {
    82  	options := auth.NewGenerateOptions(opts...)
    83  	if len(options.Issuer) == 0 {
    84  		options.Issuer = s.options.Issuer
    85  	}
    86  
    87  	// we have the JWT private key and generate ourselves an account
    88  	if len(s.options.PrivateKey) > 0 {
    89  		acc := &auth.Account{
    90  			ID:       id,
    91  			Type:     options.Type,
    92  			Scopes:   options.Scopes,
    93  			Metadata: options.Metadata,
    94  			Issuer:   options.Issuer,
    95  		}
    96  
    97  		tok, err := s.token.Generate(acc, token.WithExpiry(time.Hour*24*365))
    98  		if err != nil {
    99  			return nil, err
   100  		}
   101  
   102  		// when using JWTs, the account secret is the JWT's token. This
   103  		// can be used as an argument in the Token method.
   104  		acc.Secret = tok.Token
   105  		return acc, nil
   106  	}
   107  
   108  	rsp, err := s.auth.Generate(context.DefaultContext, &pb.GenerateRequest{
   109  		Id:       id,
   110  		Type:     options.Type,
   111  		Secret:   options.Secret,
   112  		Scopes:   options.Scopes,
   113  		Metadata: options.Metadata,
   114  		Provider: options.Provider,
   115  		Options: &pb.Options{
   116  			Namespace: options.Issuer,
   117  		},
   118  		Name: options.Name,
   119  	}, s.callOpts()...)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	return serializeAccount(rsp.Account), nil
   125  }
   126  
   127  // Grant access to a resource
   128  func (s *srv) Grant(rule *auth.Rule) error {
   129  	access := pb.Access_UNKNOWN
   130  	if rule.Access == auth.AccessGranted {
   131  		access = pb.Access_GRANTED
   132  	} else if rule.Access == auth.AccessDenied {
   133  		access = pb.Access_DENIED
   134  	}
   135  
   136  	_, err := s.rules.Create(context.DefaultContext, &pb.CreateRequest{
   137  		Rule: &pb.Rule{
   138  			Id:       rule.ID,
   139  			Scope:    rule.Scope,
   140  			Priority: rule.Priority,
   141  			Access:   access,
   142  			Resource: &pb.Resource{
   143  				Type:     rule.Resource.Type,
   144  				Name:     rule.Resource.Name,
   145  				Endpoint: rule.Resource.Endpoint,
   146  			},
   147  		},
   148  		Options: &pb.Options{
   149  			Namespace: s.Options().Issuer,
   150  		},
   151  	}, s.callOpts()...)
   152  	go s.refreshRulesCache(s.Options().Issuer)
   153  	return err
   154  }
   155  
   156  // Revoke access to a resource
   157  func (s *srv) Revoke(rule *auth.Rule) error {
   158  	_, err := s.rules.Delete(context.DefaultContext, &pb.DeleteRequest{
   159  		Id: rule.ID, Options: &pb.Options{
   160  			Namespace: s.Options().Issuer,
   161  		},
   162  	}, s.callOpts()...)
   163  	go s.refreshRulesCache(s.Options().Issuer)
   164  	return err
   165  }
   166  
   167  func (s *srv) refreshRulesCache(ns string) error {
   168  	rsp, err := s.rules.List(context.DefaultContext, &pb.ListRequest{
   169  		Options: &pb.Options{Namespace: ns},
   170  	}, s.callOpts()...)
   171  	if err != nil {
   172  		logger.Errorf("Error refreshing rules cache %s", err)
   173  		return err
   174  	}
   175  
   176  	rules := make([]*auth.Rule, len(rsp.Rules))
   177  	for i, r := range rsp.Rules {
   178  		rules[i] = serializeRule(r)
   179  	}
   180  	s.ruleCache.put(ns, rules)
   181  	return nil
   182  }
   183  
   184  func (s *srv) Rules(opts ...auth.RulesOption) ([]*auth.Rule, error) {
   185  	var options auth.RulesOptions
   186  	for _, o := range opts {
   187  		o(&options)
   188  	}
   189  	if options.Context == nil {
   190  		options.Context = context.DefaultContext
   191  	}
   192  	if len(options.Namespace) == 0 {
   193  		options.Namespace = s.options.Issuer
   194  	}
   195  
   196  	if ret := s.ruleCache.get(options.Namespace); ret != nil {
   197  		return ret, nil
   198  	}
   199  	if err := s.refreshRulesCache(options.Namespace); err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	return s.ruleCache.get(options.Namespace), nil
   204  }
   205  
   206  // Verify an account has access to a resource
   207  func (s *srv) Verify(acc *auth.Account, res *auth.Resource, opts ...auth.VerifyOption) error {
   208  	var options auth.VerifyOptions
   209  	for _, o := range opts {
   210  		o(&options)
   211  	}
   212  
   213  	rs, err := s.Rules(
   214  		auth.RulesContext(options.Context),
   215  		auth.RulesNamespace(options.Namespace),
   216  	)
   217  	if err != nil {
   218  		return err
   219  	}
   220  	return rules.VerifyAccess(rs, acc, res, opts...)
   221  }
   222  
   223  // Inspect a token
   224  func (s *srv) Inspect(token string) (*auth.Account, error) {
   225  	// validate the request
   226  	if len(token) == 0 {
   227  		return nil, auth.ErrInvalidToken
   228  	}
   229  
   230  	// optimisation - is the key the right format for jwt auth?
   231  	if s.token.String() == "jwt" && strings.Count(token, ".") != 2 {
   232  		return nil, auth.ErrInvalidToken
   233  	}
   234  
   235  	// try to decode JWT locally and fall back to srv if an error occurs
   236  	if len(strings.Split(token, ".")) == 3 && len(s.options.PublicKey) > 0 {
   237  		return s.token.Inspect(token)
   238  	}
   239  
   240  	// the token is not a JWT or we do not have the keys to decode it,
   241  	// fall back to the auth service
   242  	rsp, err := s.auth.Inspect(context.DefaultContext, &pb.InspectRequest{
   243  		Token: token, Options: &pb.Options{Namespace: s.Options().Issuer},
   244  	}, s.callOpts()...)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	return serializeAccount(rsp.Account), nil
   249  }
   250  
   251  // Token generation using an account ID and secret
   252  func (s *srv) Token(opts ...auth.TokenOption) (*auth.AccountToken, error) {
   253  	options := auth.NewTokenOptions(opts...)
   254  	if len(options.Issuer) == 0 {
   255  		options.Issuer = s.options.Issuer
   256  	}
   257  
   258  	tok := options.RefreshToken
   259  	if len(options.Secret) > 0 {
   260  		tok = options.Secret
   261  	}
   262  
   263  	// we have the JWT private key and refresh accounts locally
   264  	if len(s.options.PrivateKey) > 0 && len(strings.Split(tok, ".")) == 3 {
   265  		acc, err := s.token.Inspect(tok)
   266  		if err != nil {
   267  			return nil, err
   268  		}
   269  
   270  		token, err := s.token.Generate(acc, token.WithExpiry(options.Expiry))
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  
   275  		return &auth.AccountToken{
   276  			Expiry:       token.Expiry,
   277  			AccessToken:  token.Token,
   278  			RefreshToken: tok,
   279  		}, nil
   280  	}
   281  
   282  	rsp, err := s.auth.Token(context.DefaultContext, &pb.TokenRequest{
   283  		Id:           options.ID,
   284  		Secret:       options.Secret,
   285  		RefreshToken: options.RefreshToken,
   286  		TokenExpiry:  int64(options.Expiry.Seconds()),
   287  		Options: &pb.Options{
   288  			Namespace: options.Issuer,
   289  		},
   290  	}, s.callOpts()...)
   291  	if err != nil && errors.FromError(err).Detail == auth.ErrInvalidToken.Error() {
   292  		return nil, auth.ErrInvalidToken
   293  	} else if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	return serializeToken(rsp.Token), nil
   298  }
   299  
   300  func serializeToken(t *pb.Token) *auth.AccountToken {
   301  	return &auth.AccountToken{
   302  		AccessToken:  t.AccessToken,
   303  		RefreshToken: t.RefreshToken,
   304  		Created:      time.Unix(t.Created, 0),
   305  		Expiry:       time.Unix(t.Expiry, 0),
   306  	}
   307  }
   308  
   309  func serializeAccount(a *pb.Account) *auth.Account {
   310  	return &auth.Account{
   311  		ID:       a.Id,
   312  		Secret:   a.Secret,
   313  		Issuer:   a.Issuer,
   314  		Metadata: a.Metadata,
   315  		Scopes:   a.Scopes,
   316  		Name:     a.Name,
   317  		Type:     a.Type,
   318  	}
   319  }
   320  
   321  func serializeRule(r *pb.Rule) *auth.Rule {
   322  	var access auth.Access
   323  	if r.Access == pb.Access_GRANTED {
   324  		access = auth.AccessGranted
   325  	} else {
   326  		access = auth.AccessDenied
   327  	}
   328  
   329  	return &auth.Rule{
   330  		ID:       r.Id,
   331  		Scope:    r.Scope,
   332  		Access:   access,
   333  		Priority: r.Priority,
   334  		Resource: &auth.Resource{
   335  			Type:     r.Resource.Type,
   336  			Name:     r.Resource.Name,
   337  			Endpoint: r.Resource.Endpoint,
   338  		},
   339  	}
   340  }
   341  
   342  func (s *srv) callOpts() []client.CallOption {
   343  	return []client.CallOption{
   344  		client.WithAddress(s.options.Addrs...),
   345  		client.WithAuthToken(),
   346  	}
   347  }
   348  
   349  // NewAuth returns a new instance of the Auth service
   350  func NewAuth(opts ...auth.Option) auth.Auth {
   351  	service := &srv{
   352  		auth:    pb.NewAuthService("auth", client.DefaultClient),
   353  		rules:   pb.NewRulesService("auth", client.DefaultClient),
   354  		options: auth.NewOptions(opts...),
   355  	}
   356  
   357  	service.setupJWT()
   358  
   359  	return service
   360  }
   361  
   362  func (s *srv) setupJWT() {
   363  	tokenOpts := []token.Option{}
   364  
   365  	// if we have a JWT public key passed as an option,
   366  	// we can decode tokens with the type "JWT" locally
   367  	// and not have to make an RPC call
   368  	if key := s.options.PublicKey; len(key) > 0 {
   369  		tokenOpts = append(tokenOpts, token.WithPublicKey(key))
   370  	}
   371  
   372  	// if we have a JWT private key passed as an option,
   373  	// we can generate accounts locally and not have to make
   374  	// an RPC call, this is used for micro clients such as
   375  	// api, web, proxy.
   376  	if key := s.options.PrivateKey; len(key) > 0 {
   377  		tokenOpts = append(tokenOpts, token.WithPrivateKey(key))
   378  	}
   379  
   380  	s.token = jwt.NewTokenProvider(tokenOpts...)
   381  }