+++ /dev/null
-package authn
-
-import (
- "context"
- "net"
- "net/http"
- "strings"
- "sync"
- "time"
-
- "github.com/vapor/accesstoken"
- "github.com/vapor/errors"
-)
-
-const tokenExpiry = time.Minute * 5
-
-var loopbackOn = true
-
-var (
- //ErrInvalidToken is returned when authenticate is called with invalid token.
- ErrInvalidToken = errors.New("invalid token")
- //ErrNoToken is returned when authenticate is called with no token.
- ErrNoToken = errors.New("no token")
-)
-
-//API describe the token authenticate.
-type API struct {
- disable bool
- tokens *accesstoken.CredentialStore
- tokenMu sync.Mutex // protects the following
- tokenMap map[string]tokenResult
-}
-
-type tokenResult struct {
- lastLookup time.Time
-}
-
-//NewAPI create a token authenticate object.
-func NewAPI(tokens *accesstoken.CredentialStore, disable bool) *API {
- return &API{
- disable: disable,
- tokens: tokens,
- tokenMap: make(map[string]tokenResult),
- }
-}
-
-// Authenticate returns the request, with added tokens and/or localhost
-// flags in the context, as appropriate.
-func (a *API) Authenticate(req *http.Request) (*http.Request, error) {
- ctx := req.Context()
-
- token, err := a.tokenAuthn(req)
- if err == nil && token != "" {
- // if this request was successfully authenticated with a token, pass the token along
- ctx = newContextWithToken(ctx, token)
- }
- local := a.localhostAuthn(req)
- if local {
- ctx = newContextWithLocalhost(ctx)
- }
-
- if !local && strings.HasPrefix(req.URL.Path, "/backup-wallet") {
- return req.WithContext(ctx), errors.New("only local can get access backup-wallets")
- }
-
- if !local && strings.HasPrefix(req.URL.Path, "/restore-wallet") {
- return req.WithContext(ctx), errors.New("only local can get access restore-wallet")
- }
-
- if !local && strings.HasPrefix(req.URL.Path, "/list-access-tokens") {
- return req.WithContext(ctx), errors.New("only local can get access token list")
- }
-
- // Temporary workaround. Dashboard is always ok.
- // See loopbackOn comment above.
- if strings.HasPrefix(req.URL.Path, "/dashboard/") || req.URL.Path == "/dashboard" {
- return req.WithContext(ctx), nil
- }
- // Adding this workaround for Equity Playground.
- if strings.HasPrefix(req.URL.Path, "/equity/") || req.URL.Path == "/equity" {
- return req.WithContext(ctx), nil
- }
- if loopbackOn && local {
- return req.WithContext(ctx), nil
- }
-
- return req.WithContext(ctx), err
-}
-
-// returns true if this request is coming from a loopback address
-func (a *API) localhostAuthn(req *http.Request) bool {
- h, _, err := net.SplitHostPort(req.RemoteAddr)
- if err != nil {
- return false
- }
- if !net.ParseIP(h).IsLoopback() {
- return false
- }
- return true
-}
-
-func (a *API) tokenAuthn(req *http.Request) (string, error) {
- if a.disable {
- return "", nil
- }
-
- user, pw, ok := req.BasicAuth()
- if !ok {
- return "", ErrNoToken
- }
- return user, a.cachedTokenAuthnCheck(req.Context(), user, pw)
-}
-
-func (a *API) cachedTokenAuthnCheck(ctx context.Context, user, pw string) error {
- a.tokenMu.Lock()
- res, ok := a.tokenMap[user+pw]
- a.tokenMu.Unlock()
- if !ok || time.Now().After(res.lastLookup.Add(tokenExpiry)) {
- err := a.tokens.Check(user, pw)
- if err != nil {
- return ErrInvalidToken
- }
- res = tokenResult{lastLookup: time.Now()}
- a.tokenMu.Lock()
- a.tokenMap[user+pw] = res
- a.tokenMu.Unlock()
- }
- return nil
-}