12 "github.com/bytom/accesstoken"
13 "github.com/bytom/errors"
16 const tokenExpiry = time.Minute * 5
21 //ErrInvalidToken is returned when authenticate is called with invalid token.
22 ErrInvalidToken = errors.New("invalid token")
23 //ErrNoToken is returned when authenticate is called with no token.
24 ErrNoToken = errors.New("no token")
27 //API describe the token authenticate.
29 tokens *accesstoken.CredentialStore
30 crosscoreRPCPrefix string
31 rootCAs *x509.CertPool
33 tokenMu sync.Mutex // protects the following
34 tokenMap map[string]tokenResult
37 type tokenResult struct {
41 //NewAPI create a token authenticate object.
42 func NewAPI(tokens *accesstoken.CredentialStore) *API {
45 tokenMap: make(map[string]tokenResult),
49 // Authenticate returns the request, with added tokens and/or localhost
50 // flags in the context, as appropriate.
51 func (a *API) Authenticate(req *http.Request) (*http.Request, error) {
54 token, err := a.tokenAuthn(req)
55 if err == nil && token != "" {
56 // if this request was successfully authenticated with a token, pass the token along
57 ctx = newContextWithToken(ctx, token)
59 local := a.localhostAuthn(req)
61 ctx = newContextWithLocalhost(ctx)
64 if !local && strings.HasPrefix(req.URL.Path, "/list-access-tokens") {
65 return req.WithContext(ctx), errors.New("only local can get access token list")
68 // Temporary workaround. Dashboard is always ok.
69 // See loopbackOn comment above.
70 if strings.HasPrefix(req.URL.Path, "/dashboard/") || req.URL.Path == "/dashboard" {
71 return req.WithContext(ctx), nil
73 if loopbackOn && local {
74 return req.WithContext(ctx), nil
77 return req.WithContext(ctx), err
80 // checks the request for a valid client cert list.
81 // If found, it is added to the request's context.
82 // Note that an *invalid* client cert is treated the
83 // same as no client cert -- it is omitted from the
84 // returned context, but the connection may proceed.
85 func certAuthn(req *http.Request, rootCAs *x509.CertPool) context.Context {
86 if req.TLS != nil && len(req.TLS.PeerCertificates) > 0 {
87 certs := req.TLS.PeerCertificates
89 // Same logic as serverHandshakeState.processCertsFromClient
90 // in $GOROOT/src/crypto/tls/handshake_server.go.
91 opts := x509.VerifyOptions{
93 CurrentTime: time.Now(),
94 Intermediates: x509.NewCertPool(),
95 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
97 for _, cert := range certs[1:] {
98 opts.Intermediates.AddCert(cert)
101 if _, err := certs[0].Verify(opts); err != nil {
102 // crypto/tls treats this as an error:
103 // errors.New("tls: failed to verify client's certificate: " + err.Error())
104 // For us, it is ok; we want to treat it the same as if there
105 // were no client cert presented.
109 return context.WithValue(req.Context(), x509CertsKey, certs)
114 // returns true if this request is coming from a loopback address
115 func (a *API) localhostAuthn(req *http.Request) bool {
116 h, _, err := net.SplitHostPort(req.RemoteAddr)
120 if !net.ParseIP(h).IsLoopback() {
126 func (a *API) tokenAuthn(req *http.Request) (string, error) {
127 user, pw, ok := req.BasicAuth()
129 return "", ErrNoToken
131 return user, a.cachedTokenAuthnCheck(req.Context(), user, pw)
134 func (a *API) cachedTokenAuthnCheck(ctx context.Context, user, pw string) error {
136 res, ok := a.tokenMap[user+pw]
138 if !ok || time.Now().After(res.lastLookup.Add(tokenExpiry)) {
139 err := a.tokens.Check(ctx, user, pw)
141 return ErrInvalidToken
143 res = tokenResult{lastLookup: time.Now()}
145 a.tokenMap[user+pw] = res