13 "github.com/go-kit/kit/endpoint"
14 httptransport "github.com/go-kit/kit/transport/http"
17 // AuthError represents an authorization error.
18 type AuthError struct {
22 // StatusCode is an implementation of the StatusCoder interface in go-kit/http.
23 func (AuthError) StatusCode() int {
24 return http.StatusUnauthorized
27 // Error is an implementation of the Error interface.
28 func (AuthError) Error() string {
29 return http.StatusText(http.StatusUnauthorized)
32 // Headers is an implementation of the Headerer interface in go-kit/http.
33 func (e AuthError) Headers() http.Header {
35 "Content-Type": []string{"text/plain; charset=utf-8"},
36 "X-Content-Type-Options": []string{"nosniff"},
37 "WWW-Authenticate": []string{fmt.Sprintf(`Basic realm=%q`, e.Realm)},
41 // parseBasicAuth parses an HTTP Basic Authentication string.
42 // "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ([]byte("Aladdin"), []byte("open sesame"), true).
43 func parseBasicAuth(auth string) (username, password []byte, ok bool) {
44 const prefix = "Basic "
45 if !strings.HasPrefix(auth, prefix) {
48 c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
53 s := bytes.IndexByte(c, ':')
57 return c[:s], c[s+1:], true
60 // Returns a hash of a given slice.
61 func toHashSlice(s []byte) []byte {
62 hash := sha256.Sum256(s)
66 // AuthMiddleware returns a Basic Authentication middleware for a particular user and password.
67 func AuthMiddleware(requiredUser, requiredPassword, realm string) endpoint.Middleware {
68 requiredUserBytes := toHashSlice([]byte(requiredUser))
69 requiredPasswordBytes := toHashSlice([]byte(requiredPassword))
71 return func(next endpoint.Endpoint) endpoint.Endpoint {
72 return func(ctx context.Context, request interface{}) (interface{}, error) {
73 auth, ok := ctx.Value(httptransport.ContextKeyRequestAuthorization).(string)
75 return nil, AuthError{realm}
78 givenUser, givenPassword, ok := parseBasicAuth(auth)
80 return nil, AuthError{realm}
83 givenUserBytes := toHashSlice(givenUser)
84 givenPasswordBytes := toHashSlice(givenPassword)
86 if subtle.ConstantTimeCompare(givenUserBytes, requiredUserBytes) == 0 ||
87 subtle.ConstantTimeCompare(givenPasswordBytes, requiredPasswordBytes) == 0 {
88 return nil, AuthError{realm}
91 return next(ctx, request)