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 }