...
1 package basic
2
3 import (
4 "bytes"
5 "context"
6 "crypto/sha256"
7 "crypto/subtle"
8 "encoding/base64"
9 "fmt"
10 "net/http"
11 "strings"
12
13 "github.com/go-kit/kit/endpoint"
14 httptransport "github.com/go-kit/kit/transport/http"
15 )
16
17
18 type AuthError struct {
19 Realm string
20 }
21
22
23 func (AuthError) StatusCode() int {
24 return http.StatusUnauthorized
25 }
26
27
28 func (AuthError) Error() string {
29 return http.StatusText(http.StatusUnauthorized)
30 }
31
32
33 func (e AuthError) Headers() http.Header {
34 return 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)},
38 }
39 }
40
41
42
43 func parseBasicAuth(auth string) (username, password []byte, ok bool) {
44 const prefix = "Basic "
45 if !strings.HasPrefix(auth, prefix) {
46 return
47 }
48 c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
49 if err != nil {
50 return
51 }
52
53 s := bytes.IndexByte(c, ':')
54 if s < 0 {
55 return
56 }
57 return c[:s], c[s+1:], true
58 }
59
60
61 func toHashSlice(s []byte) []byte {
62 hash := sha256.Sum256(s)
63 return hash[:]
64 }
65
66
67 func AuthMiddleware(requiredUser, requiredPassword, realm string) endpoint.Middleware {
68 requiredUserBytes := toHashSlice([]byte(requiredUser))
69 requiredPasswordBytes := toHashSlice([]byte(requiredPassword))
70
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)
74 if !ok {
75 return nil, AuthError{realm}
76 }
77
78 givenUser, givenPassword, ok := parseBasicAuth(auth)
79 if !ok {
80 return nil, AuthError{realm}
81 }
82
83 givenUserBytes := toHashSlice(givenUser)
84 givenPasswordBytes := toHashSlice(givenPassword)
85
86 if subtle.ConstantTimeCompare(givenUserBytes, requiredUserBytes) == 0 ||
87 subtle.ConstantTimeCompare(givenPasswordBytes, requiredPasswordBytes) == 0 {
88 return nil, AuthError{realm}
89 }
90
91 return next(ctx, request)
92 }
93 }
94 }
95
View as plain text