github.com/go-kivik/kivik/v4@v4.3.2/couchdb/chttp/proxyauth.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 12 13 package chttp 14 15 import ( 16 "crypto/hmac" 17 "crypto/sha1" 18 "encoding/hex" 19 "fmt" 20 "net/http" 21 "strings" 22 23 "github.com/go-kivik/kivik/v4" 24 ) 25 26 type proxyAuth struct { 27 Username string 28 Secret string 29 Roles []string 30 Headers http.Header 31 32 transport http.RoundTripper 33 token string 34 } 35 36 var ( 37 _ authenticator = &proxyAuth{} 38 _ kivik.Option = (*proxyAuth)(nil) 39 ) 40 41 func (a *proxyAuth) Apply(target interface{}) { 42 if auth, ok := target.(*authenticator); ok { 43 // Clone this so that it's safe to re-use the same option to multiple 44 // client connections. TODO: This can no doubt be refactored. 45 *auth = &proxyAuth{ 46 Username: a.Username, 47 Secret: a.Secret, 48 Roles: a.Roles, 49 Headers: a.Headers, 50 } 51 } 52 } 53 54 func (a *proxyAuth) String() string { 55 return fmt.Sprintf("[ProxyAuth{username:%s,secret:%s}]", a.Username, strings.Repeat("*", len(a.Secret))) 56 } 57 58 func (a *proxyAuth) header(header string) string { 59 if h := a.Headers.Get(header); h != "" { 60 return http.CanonicalHeaderKey(h) 61 } 62 return header 63 } 64 65 func (a *proxyAuth) genToken() string { 66 if a.Secret == "" { 67 return "" 68 } 69 if a.token != "" { 70 return a.token 71 } 72 // Generate auth token 73 // https://docs.couchdb.org/en/stable/config/auth.html#couch_httpd_auth/x_auth_token 74 h := hmac.New(sha1.New, []byte(a.Secret)) 75 _, _ = h.Write([]byte(a.Username)) 76 a.token = hex.EncodeToString(h.Sum(nil)) 77 return a.token 78 } 79 80 // RoundTrip implements the http.RoundTripper interface. 81 func (a *proxyAuth) RoundTrip(req *http.Request) (*http.Response, error) { 82 if token := a.genToken(); token != "" { 83 req.Header.Set(a.header("X-Auth-CouchDB-Token"), token) 84 } 85 86 req.Header.Set(a.header("X-Auth-CouchDB-UserName"), a.Username) 87 req.Header.Set(a.header("X-Auth-CouchDB-Roles"), strings.Join(a.Roles, ",")) 88 89 return a.transport.RoundTrip(req) 90 } 91 92 // Authenticate allows authentication via ProxyAuth. 93 func (a *proxyAuth) Authenticate(c *Client) error { 94 a.transport = c.Transport 95 if a.transport == nil { 96 a.transport = http.DefaultTransport 97 } 98 c.Transport = a 99 return nil 100 }