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

     1  package handler
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/google/uuid"
    12  
    13  	pb "github.com/tickoalcantara12/micro/v3/proto/auth"
    14  	"github.com/tickoalcantara12/micro/v3/service/auth"
    15  	"github.com/tickoalcantara12/micro/v3/service/errors"
    16  	"github.com/tickoalcantara12/micro/v3/service/logger"
    17  	"github.com/tickoalcantara12/micro/v3/service/store"
    18  	authns "github.com/tickoalcantara12/micro/v3/util/auth/namespace"
    19  	"github.com/tickoalcantara12/micro/v3/util/auth/token"
    20  	"github.com/tickoalcantara12/micro/v3/util/auth/token/basic"
    21  	"github.com/tickoalcantara12/micro/v3/util/namespace"
    22  	"golang.org/x/crypto/bcrypt"
    23  )
    24  
    25  const (
    26  	storePrefixAccounts      = "account"
    27  	storePrefixRefreshTokens = "refresh"
    28  
    29  	// used to enable login with username rather than ID (username can change e.g. email, id is stable)
    30  	storePrefixAccountsByName = "accountByName"
    31  )
    32  
    33  var defaultAccount = auth.Account{
    34  	ID:       "admin",
    35  	Type:     "user",
    36  	Scopes:   []string{"admin"},
    37  	Secret:   "micro",
    38  	Metadata: map[string]string{},
    39  }
    40  
    41  // Auth processes RPC calls
    42  type Auth struct {
    43  	Options       auth.Options
    44  	TokenProvider token.Provider
    45  
    46  	namespaces map[string]bool
    47  	sync.Mutex
    48  	// Prevent the generation of default accounts
    49  	DisableAdmin bool
    50  }
    51  
    52  // Init the auth
    53  func (a *Auth) Init(opts ...auth.Option) {
    54  	for _, o := range opts {
    55  		o(&a.Options)
    56  	}
    57  
    58  	// setup a token provider
    59  	if a.TokenProvider == nil {
    60  		a.TokenProvider = basic.NewTokenProvider(token.WithStore(store.DefaultStore))
    61  	}
    62  }
    63  
    64  func (a *Auth) setupDefaultAccount(ns string) error {
    65  	if ns != namespace.DefaultNamespace {
    66  		return nil
    67  	}
    68  	if a.DisableAdmin {
    69  		return nil
    70  	}
    71  	a.Lock()
    72  	defer a.Unlock()
    73  
    74  	// setup the namespace cache if not yet done
    75  	if a.namespaces == nil {
    76  		a.namespaces = make(map[string]bool)
    77  	}
    78  
    79  	// check to see if the default account has already been verified
    80  	if _, ok := a.namespaces[ns]; ok {
    81  		return nil
    82  	}
    83  
    84  	// check to see if we need to create the default account
    85  	prefix := strings.Join([]string{storePrefixAccounts, ns, ""}, joinKey)
    86  	recs, err := store.Read(prefix, store.ReadPrefix())
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	hasUser := false
    92  	for _, rec := range recs {
    93  		acc := &auth.Account{}
    94  		err := json.Unmarshal(rec.Value, acc)
    95  		if err != nil {
    96  			return err
    97  		}
    98  		if acc.Type == "user" {
    99  			hasUser = true
   100  			break
   101  		}
   102  	}
   103  
   104  	// create the account if none exist in the namespace
   105  	if !hasUser {
   106  		acc := defaultAccount
   107  		acc.Issuer = ns
   108  		acc.Metadata["created"] = fmt.Sprintf("%d", time.Now().Unix())
   109  		if err := a.createAccount(&acc); err != nil {
   110  			return err
   111  		}
   112  	}
   113  
   114  	// set the namespace in the cache
   115  	a.namespaces[ns] = true
   116  	return nil
   117  }
   118  
   119  // Generate an account
   120  func (a *Auth) Generate(ctx context.Context, req *pb.GenerateRequest, rsp *pb.GenerateResponse) error {
   121  	// validate the request
   122  	if len(req.Id) == 0 {
   123  		return errors.BadRequest("auth.Auth.Generate", "ID required")
   124  	}
   125  
   126  	// set the defaults
   127  	if len(req.Type) == 0 {
   128  		req.Type = "user"
   129  	}
   130  	if len(req.Secret) == 0 {
   131  		req.Secret = uuid.New().String()
   132  	}
   133  	if req.Options == nil {
   134  		req.Options = &pb.Options{}
   135  	}
   136  	if len(req.Options.Namespace) == 0 {
   137  		req.Options.Namespace = namespace.FromContext(ctx)
   138  	}
   139  
   140  	// authorize the request
   141  	if err := authns.AuthorizeAdmin(ctx, req.Options.Namespace, "auth.Auth.Generate"); err != nil {
   142  		return err
   143  	}
   144  
   145  	// check the user does not already exists
   146  	key := strings.Join([]string{storePrefixAccounts, req.Options.Namespace, req.Id}, joinKey)
   147  	if _, err := store.Read(key); err != store.ErrNotFound {
   148  		return errors.BadRequest("auth", "Account with this ID already exists")
   149  	}
   150  
   151  	// construct the account
   152  	acc := &auth.Account{
   153  		ID:       req.Id,
   154  		Type:     req.Type,
   155  		Scopes:   req.Scopes,
   156  		Metadata: req.Metadata,
   157  		Issuer:   req.Options.Namespace,
   158  		Secret:   req.Secret,
   159  		Name:     req.Name,
   160  	}
   161  
   162  	// create the account
   163  	if err := a.createAccount(acc); err != nil {
   164  		return err
   165  	}
   166  
   167  	// return the account
   168  	rsp.Account = serializeAccount(acc)
   169  	rsp.Account.Secret = req.Secret // return unhashed secret
   170  	return nil
   171  }
   172  func (a *Auth) createAccount(acc *auth.Account) error {
   173  	// check the user does not already exists
   174  	key := strings.Join([]string{storePrefixAccounts, acc.Issuer, acc.ID}, joinKey)
   175  	if _, err := store.Read(key); err != store.ErrNotFound {
   176  		return errors.BadRequest("auth.Auth.Generate", "Account with this ID already exists")
   177  	}
   178  	if acc.Metadata == nil {
   179  		acc.Metadata = map[string]string{
   180  			"created": fmt.Sprintf("%d", time.Now().Unix()),
   181  		}
   182  	}
   183  
   184  	// set created time if not defined
   185  	if _, ok := acc.Metadata["created"]; !ok {
   186  		acc.Metadata["created"] = fmt.Sprintf("%d", time.Now().Unix())
   187  	}
   188  
   189  	if acc.Name == "" {
   190  		acc.Name = acc.ID
   191  	}
   192  
   193  	usernameKey := strings.Join([]string{storePrefixAccountsByName, acc.Issuer, acc.Name}, joinKey)
   194  	if _, err := store.Read(usernameKey); err != store.ErrNotFound {
   195  		return errors.BadRequest("auth.Auth.Generate", "Account with this Name already exists")
   196  	}
   197  
   198  	// hash the secret
   199  	secret, err := hashSecret(acc.Secret)
   200  	if err != nil {
   201  		return errors.InternalServerError("auth.Auth.Generate", "Unable to hash password: %v", err)
   202  	}
   203  	acc.Secret = secret
   204  
   205  	// marshal to json
   206  	bytes, err := json.Marshal(acc)
   207  	if err != nil {
   208  		return errors.InternalServerError("auth.Auth.Generate", "Unable to marshal json: %v", err)
   209  	}
   210  
   211  	// write to the store
   212  	if err := store.Write(&store.Record{Key: key, Value: bytes}); err != nil {
   213  		return errors.InternalServerError("auth.Auth.Generate", "Unable to write account to store: %v", err)
   214  	}
   215  
   216  	if err := store.Write(&store.Record{Key: usernameKey, Value: bytes}); err != nil {
   217  		return errors.InternalServerError("auth.Auth.Generate", "Unable to write account to store: %v", err)
   218  	}
   219  
   220  	// set a refresh token
   221  	if err := a.setRefreshToken(acc.Issuer, acc.ID, uuid.New().String()); err != nil {
   222  		return errors.InternalServerError("auth.Auth.Generate", "Unable to set a refresh token: %v", err)
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // Inspect a token and retrieve the account
   229  func (a *Auth) Inspect(ctx context.Context, req *pb.InspectRequest, rsp *pb.InspectResponse) error {
   230  	acc, err := a.TokenProvider.Inspect(req.Token)
   231  	if err == token.ErrInvalidToken || err == token.ErrNotFound {
   232  		return errors.BadRequest("auth.Auth.Inspect", err.Error())
   233  	} else if err != nil {
   234  		return errors.InternalServerError("auth.Auth.Inspect", "Unable to inspect token: %v", err)
   235  	}
   236  
   237  	rsp.Account = serializeAccount(acc)
   238  	return nil
   239  }
   240  
   241  // Token generation using an account ID and secret
   242  func (a *Auth) Token(ctx context.Context, req *pb.TokenRequest, rsp *pb.TokenResponse) error {
   243  	// set defaults
   244  	if req.Options == nil {
   245  		req.Options = &pb.Options{}
   246  	}
   247  	if len(req.Options.Namespace) == 0 {
   248  		req.Options.Namespace = namespace.DefaultNamespace
   249  	}
   250  
   251  	// setup the defaults incase none exist
   252  	err := a.setupDefaultAccount(req.Options.Namespace)
   253  	if err != nil {
   254  		// failing gracefully here
   255  		logger.Errorf("Error setting up default accounts: %v", err)
   256  	}
   257  
   258  	// validate the request
   259  	if (len(req.Id) == 0 || len(req.Secret) == 0) && len(req.RefreshToken) == 0 {
   260  		return errors.BadRequest("auth.Auth.Token", "Credentials or a refresh token required")
   261  	}
   262  
   263  	// check to see if the secret is a JWT. this is a workaround to allow accounts issued
   264  	// by the runtime to be refreshed whilst keeping the private key in the server.
   265  	if a.TokenProvider.String() == "jwt" {
   266  		jwt := req.Secret
   267  		if len(req.RefreshToken) > 0 {
   268  			jwt = req.RefreshToken
   269  		}
   270  
   271  		if acc, err := a.TokenProvider.Inspect(jwt); err == nil {
   272  			expiry := time.Duration(int64(time.Second) * req.TokenExpiry)
   273  			tok, _ := a.TokenProvider.Generate(acc, token.WithExpiry(expiry))
   274  			rsp.Token = serializeToken(tok, tok.Token)
   275  			return nil
   276  		}
   277  	}
   278  
   279  	// Declare the account id and refresh token
   280  	accountID := req.Id
   281  	refreshToken := req.RefreshToken
   282  
   283  	// If the refresh token is set, check this
   284  	if len(req.RefreshToken) > 0 {
   285  		accID, err := a.accountIDForRefreshToken(req.Options.Namespace, req.RefreshToken)
   286  		if err == store.ErrNotFound {
   287  			return errors.BadRequest("auth.Auth.Token", auth.ErrInvalidToken.Error())
   288  		} else if err != nil {
   289  			return errors.InternalServerError("auth.Auth.Token", "Unable to lookup token: %v", err)
   290  		}
   291  		accountID = accID
   292  	}
   293  
   294  	acc, err := a.getAccountForID(accountID, req.Options.Namespace, "auth.Auth.Token")
   295  	if err != nil {
   296  		return err
   297  	}
   298  
   299  	// If the refresh token was not used, validate the secrets match and then set the refresh token
   300  	// so it can be returned to the user
   301  	if len(req.RefreshToken) == 0 {
   302  		if !secretsMatch(acc.Secret, req.Secret) {
   303  			return errors.BadRequest("auth.Auth.Token", "Secret not correct")
   304  		}
   305  
   306  		refreshToken, err = a.refreshTokenForAccount(req.Options.Namespace, acc.ID)
   307  		if err != nil {
   308  			return errors.InternalServerError("auth.Auth.Token", "Unable to get refresh token: %v", err)
   309  		}
   310  	}
   311  
   312  	// Generate a new access token
   313  	duration := time.Duration(req.TokenExpiry) * time.Second
   314  	tok, err := a.TokenProvider.Generate(acc, token.WithExpiry(duration))
   315  	if err != nil {
   316  		return errors.InternalServerError("auth.Auth.Token", "Unable to generate token: %v", err)
   317  	}
   318  
   319  	rsp.Token = serializeToken(tok, refreshToken)
   320  	return nil
   321  }
   322  
   323  func (a *Auth) getAccountForID(id, namespace, errCode string) (*auth.Account, error) {
   324  	// Lookup the account in the store
   325  	key := strings.Join([]string{storePrefixAccounts, namespace, id}, joinKey)
   326  	recs, err := store.Read(key)
   327  	if err != nil && err != store.ErrNotFound {
   328  		return nil, errors.InternalServerError(errCode, "Unable to read from store: %v", err)
   329  	}
   330  	if err == store.ErrNotFound {
   331  		// maybe id is the username and not the actual ID
   332  		key = strings.Join([]string{storePrefixAccountsByName, namespace, id}, joinKey)
   333  		recs, err = store.Read(key)
   334  		if err == store.ErrNotFound {
   335  			return nil, errors.BadRequest(errCode, "Account not found with this ID")
   336  		}
   337  		if err != nil {
   338  			return nil, errors.InternalServerError(errCode, "Unable to read from store: %v", err)
   339  		}
   340  	}
   341  
   342  	// Unmarshal the record
   343  	var acc *auth.Account
   344  	if err := json.Unmarshal(recs[0].Value, &acc); err != nil {
   345  		return nil, errors.InternalServerError(errCode, "Unable to unmarshal account: %v", err)
   346  	}
   347  	return acc, nil
   348  }
   349  
   350  // set the refresh token for an account
   351  func (a *Auth) setRefreshToken(ns, id, token string) error {
   352  	key := strings.Join([]string{storePrefixRefreshTokens, ns, id, token}, joinKey)
   353  	return store.Write(&store.Record{Key: key})
   354  }
   355  
   356  // get the refresh token for an account
   357  func (a *Auth) refreshTokenForAccount(ns, id string) (string, error) {
   358  	prefix := strings.Join([]string{storePrefixRefreshTokens, ns, id, ""}, joinKey)
   359  	recs, err := store.Read(prefix, store.ReadPrefix())
   360  	if err != nil {
   361  		return "", err
   362  	} else if len(recs) == 0 {
   363  		return "", store.ErrNotFound
   364  	}
   365  
   366  	comps := strings.Split(recs[0].Key, "/")
   367  	if len(comps) != 4 {
   368  		return "", store.ErrNotFound
   369  	}
   370  	return comps[3], nil
   371  }
   372  
   373  // get the account ID for the given refresh token
   374  func (a *Auth) accountIDForRefreshToken(ns, token string) (string, error) {
   375  	prefix := strings.Join([]string{storePrefixRefreshTokens, ns}, joinKey)
   376  	keys, err := store.List(store.ListPrefix(prefix))
   377  	if err != nil {
   378  		return "", err
   379  	}
   380  
   381  	for _, k := range keys {
   382  		if strings.HasSuffix(k, "/"+token) {
   383  			comps := strings.Split(k, "/")
   384  			if len(comps) != 4 {
   385  				return "", store.ErrNotFound
   386  			}
   387  			return comps[2], nil
   388  		}
   389  	}
   390  
   391  	return "", store.ErrNotFound
   392  }
   393  
   394  func serializeToken(t *token.Token, refresh string) *pb.Token {
   395  	return &pb.Token{
   396  		Created:      t.Created.Unix(),
   397  		Expiry:       t.Expiry.Unix(),
   398  		AccessToken:  t.Token,
   399  		RefreshToken: refresh,
   400  	}
   401  }
   402  
   403  func hashSecret(s string) (string, error) {
   404  	saltedBytes := []byte(s)
   405  	hashedBytes, err := bcrypt.GenerateFromPassword(saltedBytes, bcrypt.DefaultCost)
   406  	if err != nil {
   407  		return "", err
   408  	}
   409  
   410  	hash := string(hashedBytes[:])
   411  	return hash, nil
   412  }
   413  
   414  func secretsMatch(hash string, s string) bool {
   415  	incoming := []byte(s)
   416  	existing := []byte(hash)
   417  	return bcrypt.CompareHashAndPassword(existing, incoming) == nil
   418  }