OSDN Git Service

f3f8897e12024136b492a1d3682d3758ec6d8f80
[bytom/vapor.git] / net / http / authn / authn.go
1 package authn
2
3 import (
4         "context"
5         "net"
6         "net/http"
7         "strings"
8         "sync"
9         "time"
10
11         "github.com/vapor/accesstoken"
12         "github.com/vapor/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 }