-
Notifications
You must be signed in to change notification settings - Fork 0
/
authn.go
130 lines (110 loc) · 3.21 KB
/
authn.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package authn
import (
"context"
"net"
"net/http"
"strings"
"sync"
"time"
"github.com/bytom-gm/accesstoken"
"github.com/bytom-gm/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
}