-
Notifications
You must be signed in to change notification settings - Fork 5
/
token.go
157 lines (134 loc) · 4.1 KB
/
token.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
148
149
150
151
152
153
154
155
156
157
package wallabago
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"time"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
var token *Token
func setToken(newToken *Token) {
token = newToken
}
// Token represents the object being returned from the oauth process at the API
// containing the access token, expire time (after converting it from the
// number of seconds the token is valid to the point in time where it will
// expires), type of token, scope and a refresh token
type Token struct {
AccessToken string
ExpirationTime time.Time
TokenType string
Scope string
RefreshToken string
}
type tokenResponse struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
RefreshToken string `json:"refresh_token"`
}
// parseTokenResponse consumes a stream (typically a http.Response.Body) that
// is expected to contain JSON that unmarshals into a tokenResponse struct
func parseTokenResponse(reader io.ReadCloser) (*tokenResponse, error) {
var tokenResponse tokenResponse
err := json.NewDecoder(reader).Decode(&tokenResponse)
if err != nil {
return nil, err
}
return &tokenResponse, nil
}
// responseToToken converts a tokenResponse into a Token, computing the point
// in time where the token will expire using the ExpiresIn field in
// tokenResponse and the moment in which the function is called
func responseToToken(tokenResponse *tokenResponse) *Token {
expiresIn := time.Duration(tokenResponse.ExpiresIn) * time.Second
expirationTime := time.Now().Add(expiresIn)
return &Token{
AccessToken: tokenResponse.AccessToken,
ExpirationTime: expirationTime,
TokenType: tokenResponse.TokenType,
Scope: tokenResponse.Scope,
RefreshToken: tokenResponse.RefreshToken,
}
}
// getToken will use the credentials set in the configuration to
// request an access token from the wallabag API
func getToken() (*tokenResponse, error) {
tokenURL := LibConfig.WallabagURL + "/oauth/v2/token"
resp, err := http.PostForm(tokenURL,
url.Values{
"grant_type": {"password"},
"client_id": {LibConfig.ClientID},
"client_secret": {LibConfig.ClientSecret},
"username": {LibConfig.UserName},
"password": {LibConfig.UserPassword},
})
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(
"getToken: bad response from server: %v", resp.StatusCode)
}
defer resp.Body.Close()
return parseTokenResponse(resp.Body)
}
// refreshToken will use the credentials set in the configuration to
// refresh the token stored in a previous request. It errors if there is no
// token stored or if the refresh request fails
func refreshToken() (*tokenResponse, error) {
if token == nil {
return nil, fmt.Errorf("a nil token cannot be refreshed")
}
tokenURL := LibConfig.WallabagURL + "/oauth/v2/token"
resp, err := http.PostForm(tokenURL,
url.Values{
"grant_type": {"refresh_token"},
"client_id": {LibConfig.ClientID},
"client_secret": {LibConfig.ClientSecret},
"refresh_token": {token.RefreshToken},
})
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(
"refreshToken: bad response from server: %v", resp.StatusCode)
}
defer resp.Body.Close()
return parseTokenResponse(resp.Body)
}
func checkForToken() error {
if token == nil {
tokenResponse, err := getToken()
if err != nil {
return err
}
setToken(responseToToken(tokenResponse))
return nil
}
if token.ExpirationTime.Before(time.Now()) {
tokenResponse, err := refreshToken()
if err != nil {
return err
}
setToken(responseToToken(tokenResponse))
return err
}
return nil
}
// GetAuthTokenHeader will make sure there's a working token and
// return a valid string to be used as an Authentication: header
func GetAuthTokenHeader() (string, error) {
err := checkForToken()
if err != nil {
return "", err
}
caser := cases.Title(language.Und, cases.NoLower)
authTokeHeader := caser.String(token.TokenType) + " " + token.AccessToken
return authTokeHeader, nil
}