-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
middleware.go
94 lines (79 loc) · 2.49 KB
/
middleware.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
package basic
import (
"bytes"
"context"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"net/http"
"strings"
"github.com/go-kit/kit/endpoint"
httptransport "github.com/go-kit/kit/transport/http"
)
// AuthError represents an authorization error.
type AuthError struct {
Realm string
}
// StatusCode is an implementation of the StatusCoder interface in go-kit/http.
func (AuthError) StatusCode() int {
return http.StatusUnauthorized
}
// Error is an implementation of the Error interface.
func (AuthError) Error() string {
return http.StatusText(http.StatusUnauthorized)
}
// Headers is an implementation of the Headerer interface in go-kit/http.
func (e AuthError) Headers() http.Header {
return http.Header{
"Content-Type": []string{"text/plain; charset=utf-8"},
"X-Content-Type-Options": []string{"nosniff"},
"WWW-Authenticate": []string{fmt.Sprintf(`Basic realm=%q`, e.Realm)},
}
}
// parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ([]byte("Aladdin"), []byte("open sesame"), true).
func parseBasicAuth(auth string) (username, password []byte, ok bool) {
const prefix = "Basic "
if !strings.HasPrefix(auth, prefix) {
return
}
c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return
}
s := bytes.IndexByte(c, ':')
if s < 0 {
return
}
return c[:s], c[s+1:], true
}
// Returns a hash of a given slice.
func toHashSlice(s []byte) []byte {
hash := sha256.Sum256(s)
return hash[:]
}
// AuthMiddleware returns a Basic Authentication middleware for a particular user and password.
func AuthMiddleware(requiredUser, requiredPassword, realm string) endpoint.Middleware {
requiredUserBytes := toHashSlice([]byte(requiredUser))
requiredPasswordBytes := toHashSlice([]byte(requiredPassword))
return func(next endpoint.Endpoint) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
auth, ok := ctx.Value(httptransport.ContextKeyRequestAuthorization).(string)
if !ok {
return nil, AuthError{realm}
}
givenUser, givenPassword, ok := parseBasicAuth(auth)
if !ok {
return nil, AuthError{realm}
}
givenUserBytes := toHashSlice(givenUser)
givenPasswordBytes := toHashSlice(givenPassword)
if subtle.ConstantTimeCompare(givenUserBytes, requiredUserBytes) == 0 ||
subtle.ConstantTimeCompare(givenPasswordBytes, requiredPasswordBytes) == 0 {
return nil, AuthError{realm}
}
return next(ctx, request)
}
}
}