OSDN Git Service

Merge pull request #41 from Bytom/dev
[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         local := a.localhostAuthn(req)
58         if local {
59                 ctx = newContextWithLocalhost(ctx)
60         }
61
62         if !local && strings.HasPrefix(req.URL.Path, "/backup-wallet") {
63                 return req.WithContext(ctx), errors.New("only local can get access backup-wallets")
64         }
65
66         if !local && strings.HasPrefix(req.URL.Path, "/restore-wallet") {
67                 return req.WithContext(ctx), errors.New("only local can get access restore-wallet")
68         }
69
70         if !local && strings.HasPrefix(req.URL.Path, "/list-access-tokens") {
71                 return req.WithContext(ctx), errors.New("only local can get access token list")
72         }
73
74         // Temporary workaround. Dashboard is always ok.
75         // See loopbackOn comment above.
76         if strings.HasPrefix(req.URL.Path, "/dashboard/") || req.URL.Path == "/dashboard" {
77                 return req.WithContext(ctx), nil
78         }
79         // Adding this workaround for Equity Playground.
80         if strings.HasPrefix(req.URL.Path, "/equity/") || req.URL.Path == "/equity" {
81                 return req.WithContext(ctx), nil
82         }
83         if loopbackOn && local {
84                 return req.WithContext(ctx), nil
85         }
86
87         return req.WithContext(ctx), err
88 }
89
90 // returns true if this request is coming from a loopback address
91 func (a *API) localhostAuthn(req *http.Request) bool {
92         h, _, err := net.SplitHostPort(req.RemoteAddr)
93         if err != nil {
94                 return false
95         }
96         if !net.ParseIP(h).IsLoopback() {
97                 return false
98         }
99         return true
100 }
101
102 func (a *API) tokenAuthn(req *http.Request) (string, error) {
103         if a.disable {
104                 return "", nil
105         }
106
107         user, pw, ok := req.BasicAuth()
108         if !ok {
109                 return "", ErrNoToken
110         }
111         return user, a.cachedTokenAuthnCheck(req.Context(), user, pw)
112 }
113
114 func (a *API) cachedTokenAuthnCheck(ctx context.Context, user, pw string) error {
115         a.tokenMu.Lock()
116         res, ok := a.tokenMap[user+pw]
117         a.tokenMu.Unlock()
118         if !ok || time.Now().After(res.lastLookup.Add(tokenExpiry)) {
119                 err := a.tokens.Check(user, pw)
120                 if err != nil {
121                         return ErrInvalidToken
122                 }
123                 res = tokenResult{lastLookup: time.Now()}
124                 a.tokenMu.Lock()
125                 a.tokenMap[user+pw] = res
126                 a.tokenMu.Unlock()
127         }
128         return nil
129 }