github.com/Bytom/bytom@v1.1.2-0.20210127130405-ae40204c0b09/net/http/authn/authn.go (about)

     1  package authn
     2  
     3  import (
     4  	"context"
     5  	"net"
     6  	"net/http"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/bytom/bytom/accesstoken"
    12  	"github.com/bytom/bytom/errors"
    13  )
    14  
    15  const tokenExpiry = time.Minute * 5
    16  
    17  var loopbackOn = true
    18  
    19  var (
    20  	//ErrInvalidToken is returned when authenticate is called with invalid token.
    21  	ErrInvalidToken = errors.New("invalid token")
    22  	//ErrNoToken is returned when authenticate is called with no token.
    23  	ErrNoToken = errors.New("no token")
    24  )
    25  
    26  //API describe the token authenticate.
    27  type API struct {
    28  	disable  bool
    29  	tokens   *accesstoken.CredentialStore
    30  	tokenMu  sync.Mutex // protects the following
    31  	tokenMap map[string]tokenResult
    32  }
    33  
    34  type tokenResult struct {
    35  	lastLookup time.Time
    36  }
    37  
    38  //NewAPI create a token authenticate object.
    39  func NewAPI(tokens *accesstoken.CredentialStore, disable bool) *API {
    40  	return &API{
    41  		disable:  disable,
    42  		tokens:   tokens,
    43  		tokenMap: make(map[string]tokenResult),
    44  	}
    45  }
    46  
    47  // Authenticate returns the request, with added tokens and/or localhost
    48  // flags in the context, as appropriate.
    49  func (a *API) Authenticate(req *http.Request) (*http.Request, error) {
    50  	ctx := req.Context()
    51  
    52  	token, err := a.tokenAuthn(req)
    53  	if err == nil && token != "" {
    54  		// if this request was successfully authenticated with a token, pass the token along
    55  		ctx = newContextWithToken(ctx, token)
    56  	}
    57  
    58  	local := a.localhostAuthn(req)
    59  	if local {
    60  		ctx = newContextWithLocalhost(ctx)
    61  	}
    62  
    63  	if !local && strings.HasPrefix(req.URL.Path, "/backup-wallet") {
    64  		return req.WithContext(ctx), errors.New("only local can get access backup-wallets")
    65  	}
    66  
    67  	if !local && strings.HasPrefix(req.URL.Path, "/restore-wallet") {
    68  		return req.WithContext(ctx), errors.New("only local can get access restore-wallet")
    69  	}
    70  
    71  	if !local && strings.HasPrefix(req.URL.Path, "/list-access-tokens") {
    72  		return req.WithContext(ctx), errors.New("only local can get access token list")
    73  	}
    74  
    75  	// Temporary workaround. Dashboard is always ok.
    76  	// See loopbackOn comment above.
    77  	if strings.HasPrefix(req.URL.Path, "/dashboard/") || req.URL.Path == "/dashboard" {
    78  		return req.WithContext(ctx), nil
    79  	}
    80  	// Adding this workaround for Equity Playground.
    81  	if strings.HasPrefix(req.URL.Path, "/equity/") || req.URL.Path == "/equity" {
    82  		return req.WithContext(ctx), nil
    83  	}
    84  	if loopbackOn && local {
    85  		return req.WithContext(ctx), nil
    86  	}
    87  
    88  	return req.WithContext(ctx), err
    89  }
    90  
    91  // returns true if this request is coming from a loopback address
    92  func (a *API) localhostAuthn(req *http.Request) bool {
    93  	h, _, err := net.SplitHostPort(req.RemoteAddr)
    94  	if err != nil {
    95  		return false
    96  	}
    97  	if !net.ParseIP(h).IsLoopback() {
    98  		return false
    99  	}
   100  	return true
   101  }
   102  
   103  func (a *API) tokenAuthn(req *http.Request) (string, error) {
   104  	if a.disable {
   105  		return "", nil
   106  	}
   107  
   108  	user, pw, ok := req.BasicAuth()
   109  	if !ok {
   110  		return "", ErrNoToken
   111  	}
   112  	return user, a.cachedTokenAuthnCheck(req.Context(), user, pw)
   113  }
   114  
   115  func (a *API) cachedTokenAuthnCheck(ctx context.Context, user, pw string) error {
   116  	a.tokenMu.Lock()
   117  	res, ok := a.tokenMap[user+pw]
   118  	a.tokenMu.Unlock()
   119  	if !ok || time.Now().After(res.lastLookup.Add(tokenExpiry)) {
   120  		err := a.tokens.Check(user, pw)
   121  		if err != nil {
   122  			return ErrInvalidToken
   123  		}
   124  		res = tokenResult{lastLookup: time.Now()}
   125  		a.tokenMu.Lock()
   126  		a.tokenMap[user+pw] = res
   127  		a.tokenMu.Unlock()
   128  	}
   129  	return nil
   130  }