-
Notifications
You must be signed in to change notification settings - Fork 0
/
auth.go
129 lines (108 loc) · 2.98 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
package auth
import (
"encoding/json"
"net/http"
"net/url"
"strings"
"time"
"github.com/24COMS/go.isempty"
"github.com/24COMS/go.openid/authenticator"
"github.com/24COMS/go.openid/validator"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Config is used to provide dependencies to New() and create Authenticator instance
type Config struct {
Logger logrus.FieldLogger
Validator access.Validator
GrantType grantType
// Other fields could be added for different grant types
ClientID string
ClientSecret string
}
type grantType string
var (
// ClientCredentials is for "client_credentials"
ClientCredentials grantType = "client_credentials"
)
// New will create instance of authenticator using provided Config
func New(cfg Config) (authenticator.Authenticator, error) {
if isEmpty.Values(cfg.ClientSecret, cfg.ClientID, cfg.Logger, cfg.Validator) {
return nil, errors.New("some configs are empty")
}
if cfg.GrantType != ClientCredentials {
return nil, errors.New("grant type is not supported")
}
a := &auth{
grantType: string(cfg.GrantType),
clientID: cfg.ClientID,
clientSecret: cfg.ClientSecret,
validator: cfg.Validator,
logger: cfg.Logger,
cache: make(map[string]cachedToken),
}
return a, nil
}
type auth struct {
grantType string
clientID string
clientSecret string
validator access.Validator
logger logrus.FieldLogger
cache map[string]cachedToken
}
const contentType = "application/x-www-form-urlencoded"
func (a *auth) GetToken(scope string) (string, error) {
// First trying to get cached token
var token string
if cached, ok := a.cache[scope]; ok {
if time.Now().Add(time.Minute).Before(cached.ExpiresAt) {
token = cached.AccessToken
} else {
delete(a.cache, scope)
}
}
if token != "" {
return token, nil
}
// If cached token was not found or it was expired
endpoint := a.validator.GetOpenIDConfig().TokenEndpoint
if endpoint == "" {
return "", errors.New("failed to get token endpoint")
}
data := url.Values{
"grant_type": []string{a.grantType},
"client_id": []string{a.clientID},
"client_secret": []string{a.clientSecret},
"scope": []string{scope},
}
resp, err := http.Post(endpoint, contentType, strings.NewReader(data.Encode()))
if err != nil {
return "", errors.Wrap(err, "failed to execute post request")
}
defer func() {
err = resp.Body.Close()
if err != nil {
a.logger.Warn(errors.Wrap(err, "failed to close response body"))
}
}()
var r authResp
err = json.NewDecoder(resp.Body).Decode(&r)
if err != nil {
return "", errors.Wrap(err, "failed to decode response")
}
a.cache[scope] = cachedToken{
AccessToken: r.AccessToken,
ExpiresAt: time.Now().Add(time.Second * time.Duration(r.ExpiresIn)),
}
return r.AccessToken, nil
}
type authResp struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
}
type cachedToken struct {
AccessToken string
ExpiresAt time.Time
}