-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathauth.go
147 lines (128 loc) · 3.21 KB
/
auth.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package priv
import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
var ErrNotAuthorized = errors.New("not authorized")
type Authenticator interface {
Authenticate(r *http.Request) (account string, err error)
}
type NoAuth struct {
}
func (a *NoAuth) Authenticate(r *http.Request) (account string, err error) {
return "default", nil
}
type PasswordAuth struct {
Username string
Password string
}
func (a *PasswordAuth) Authenticate(r *http.Request) (account string, err error) {
username, password := ParseBasicAuth(r)
if username == a.Username && password == a.Password {
return "default", nil
}
return "", ErrNotAuthorized
}
type AcoustidBizAuth struct {
Cache Cache
Endpoint string
Username string
Tag string
}
func NewAcoustidBizAuth(tag string) *AcoustidBizAuth {
auth := &AcoustidBizAuth{}
auth.Endpoint = "https://acoustid.biz/internal/validate-api-key"
auth.Username = "x-acoustid-api-key"
auth.Tag = tag
return auth
}
func (a *AcoustidBizAuth) Authenticate(r *http.Request) (account string, err error) {
username, password := ParseBasicAuth(r)
if strings.ToLower(username) == a.Username && password != "" {
return a.check(password)
}
return "", ErrNotAuthorized
}
func (a *AcoustidBizAuth) check(apiKey string) (account string, err error) {
cacheKey := fmt.Sprintf("acoustid-biz-api-key:%s", apiKey)
if a.Cache != nil {
result, found := a.Cache.Get(cacheKey)
if found {
account = result.(string)
if account == "" {
return "", ErrNotAuthorized
}
return account, nil
}
}
account, err = a.validateApiKey(apiKey)
if err != nil {
return "", errors.WithMessage(err, "failed to check remote API key")
}
if a.Cache != nil {
var expiration time.Duration
if account == "" {
expiration = time.Minute
} else {
expiration = time.Hour
}
a.Cache.Set(cacheKey, account, expiration)
}
if account == "" {
return "", ErrNotAuthorized
}
return account, nil
}
func (a *AcoustidBizAuth) validateApiKey(apiKey string) (account string, err error) {
params := url.Values{"api_key": {apiKey}, "tag": {a.Tag}}
resp, err := http.Get(a.Endpoint + "?" + params.Encode())
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("HTTP error %v", resp.StatusCode)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var doc struct {
Valid bool `json:"valid"`
AccountID int `json:"account_id"`
}
err = json.Unmarshal(body, &doc)
if err != nil {
return "", err
}
if !doc.Valid {
return "", nil
}
return fmt.Sprintf("acoustid-biz:%v", doc.AccountID), nil
}
func ParseBasicAuth(r *http.Request) (username string, password string) {
header := r.Header.Get("Authorization")
if header == "" {
return "", ""
}
headerParts := strings.SplitN(header, " ", 2)
if strings.ToLower(headerParts[0]) != "basic" || len(headerParts) != 2 {
return "", ""
}
auth, err := base64.StdEncoding.DecodeString(headerParts[1])
if err != nil {
return "", ""
}
decodedAuthParts := strings.SplitN(string(auth), ":", 2)
if len(decodedAuthParts) != 2 {
return "", ""
}
return decodedAuthParts[0], decodedAuthParts[1]
}