Package list golang-github-go-kit-kit / b48aaea
Add basic auth middleware Dmitry Salakhov 4 years ago
3 changed file(s) with 158 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 `package auth/basic` provides a basic auth middleware [Mozilla article](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication)
1
2 ## Usage
3
4 ```go
5 import httptransport "github.com/go-kit/kit/transport/http"
6
7 httptransport.NewServer(
8 endpoint.Chain(AuthMiddleware(cfg.auth.user, cfg.auth.password, "Example Realm"))(makeUppercaseEndpoint()),
9 decodeMappingsRequest,
10 httptransport.EncodeJSONResponse,
11 httptransport.ServerBefore(httptransport.PopulateRequestContext),
12 )
13 ```
14
15 For AuthMiddleware to be able to pick up Authentication header from a http request we need to pass it through the context with something like ```httptransport.ServerBefore(httptransport.PopulateRequestContext)```
0 package basic
1
2 import (
3 "context"
4 "crypto/sha256"
5 "crypto/subtle"
6 "encoding/base64"
7 "fmt"
8 "net/http"
9 "strings"
10
11 "github.com/go-kit/kit/endpoint"
12 httptransport "github.com/go-kit/kit/transport/http"
13 )
14
15 // AuthError represents generic Authorization error
16 type AuthError struct {
17 Realm string
18 }
19
20 // StatusCode is an implemntation of StatusCoder interface in go-kit/http
21 func (AuthError) StatusCode() int {
22 return http.StatusUnauthorized
23 }
24
25 // Error is an implemntation of Error interface
26 func (AuthError) Error() string {
27 return http.StatusText(http.StatusUnauthorized)
28 }
29
30 // Headers is an implemntation of Headerer interface in go-kit/http
31 func (e AuthError) Headers() http.Header {
32 return http.Header{
33 "Content-Type": []string{"text/plain; charset=utf-8"},
34 "X-Content-Type-Options": []string{"nosniff"},
35 "WWW-Authenticate": []string{fmt.Sprintf(`Basic realm=%q`, e.Realm)}}
36 }
37
38 func credsAreValid(givenUser, givenPass, requiredUser, requiredPass string) bool {
39 // Equalize lengths of supplied and required credentials
40 // by hashing them
41 givenUserBytes := sha256.Sum256([]byte(givenUser))
42 givenPassBytes := sha256.Sum256([]byte(givenPass))
43 requiredUserBytes := sha256.Sum256([]byte(requiredUser))
44 requiredPassBytes := sha256.Sum256([]byte(requiredPass))
45
46 // Compare the supplied credentials to those set in our options
47 if subtle.ConstantTimeCompare(givenUserBytes[:], requiredUserBytes[:]) == 1 &&
48 subtle.ConstantTimeCompare(givenPassBytes[:], requiredPassBytes[:]) == 1 {
49 return true
50 }
51
52 return false
53 }
54
55 // parseBasicAuth parses an HTTP Basic Authentication string.
56 // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
57 func parseBasicAuth(auth string) (username, password string, ok bool) {
58 const prefix = "Basic "
59 if !strings.HasPrefix(auth, prefix) {
60 return
61 }
62 c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
63 if err != nil {
64 return
65 }
66 cs := string(c)
67 s := strings.IndexByte(cs, ':')
68 if s < 0 {
69 return
70 }
71 return cs[:s], cs[s+1:], true
72 }
73
74 // AuthMiddleware returns a Basic Authentication middleware for a particular user and password
75 func AuthMiddleware(requiredUser, requiredPassword, realm string) endpoint.Middleware {
76 return func(next endpoint.Endpoint) endpoint.Endpoint {
77 return func(ctx context.Context, request interface{}) (interface{}, error) {
78 auth := ctx.Value(httptransport.ContextKeyRequestAuthorization).(string)
79 givenUser, givenPass, ok := parseBasicAuth(auth)
80 if !ok {
81 return nil, AuthError{realm}
82 }
83
84 if !credsAreValid(givenUser, givenPass, requiredUser, requiredPassword) {
85 return nil, AuthError{realm}
86 }
87
88 return next(ctx, request)
89 }
90 }
91 }
0 package basic
1
2 import (
3 "context"
4 "encoding/base64"
5 "fmt"
6 "testing"
7
8 httptransport "github.com/go-kit/kit/transport/http"
9 )
10
11 func TestWithBasicAuth(t *testing.T) {
12 requiredUser := "test-user"
13 requiredPassword := "test-pass"
14 realm := "test realm"
15
16 type want struct {
17 result interface{}
18 err error
19 }
20 tests := []struct {
21 name string
22 authHeader string
23 want want
24 }{
25 {"Isn't valid without authHeader", "", want{nil, AuthError{realm}}},
26 {"Isn't valid for wrong user", makeAuthString("wrong-user", requiredPassword), want{nil, AuthError{realm}}},
27 {"Isn't valid for wrong password", makeAuthString(requiredUser, "wrong-password"), want{nil, AuthError{realm}}},
28 {"Is valid for correct creds", makeAuthString(requiredUser, requiredPassword), want{true, nil}},
29 }
30 for _, tt := range tests {
31 t.Run(tt.name, func(t *testing.T) {
32 ctx := context.WithValue(context.TODO(), httptransport.ContextKeyRequestAuthorization, tt.authHeader)
33
34 result, err := AuthMiddleware(requiredUser, requiredPassword, realm)(passedValidation)(ctx, nil)
35 if result != tt.want.result || err != tt.want.err {
36 t.Errorf("WithBasicAuth() = result: %v, err: %v, want result: %v, want error: %v", result, err, tt.want.result, tt.want.err)
37 }
38 })
39 }
40 }
41
42 func makeAuthString(user string, password string) string {
43 data := []byte(fmt.Sprintf("%s:%s", user, password))
44 return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString(data))
45 }
46
47 func passedValidation(ctx context.Context, request interface{}) (response interface{}, err error) {
48 return true, nil
49 }