-
Notifications
You must be signed in to change notification settings - Fork 0
/
authenticator.go
184 lines (158 loc) · 3.97 KB
/
authenticator.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package api
import (
"context"
"errors"
"fmt"
"net/http"
"sync"
)
const (
AccessTokenKey = "access_token"
)
type (
AuthCAS uint64
Authenticator interface {
Decorate(context.Context, *Request) (AuthCAS, error)
Refresh(context.Context, *Client, AuthCAS) (AuthCAS, error)
Invalidate(context.Context, AuthCAS) (AuthCAS, error)
}
)
type PasswordAuthenticator struct {
mu sync.RWMutex
username string
password string
cas uint64
token string
cookie *http.Cookie
}
func NewPasswordAuthenticator(username, password string) *PasswordAuthenticator {
pa := &PasswordAuthenticator{
username: username,
password: password,
}
return pa
}
func (pa *PasswordAuthenticator) Username() string {
return pa.username
}
func (pa *PasswordAuthenticator) Password() string {
return pa.password
}
func (pa *PasswordAuthenticator) Decorate(ctx context.Context, request *Request) (AuthCAS, error) {
if debug {
log.Printf("[pw-auth-%s] Decorate called for request %d", pa.username, request.ID())
}
pa.mu.RLock()
cas := pa.cas
if request == nil {
pa.mu.RUnlock()
return AuthCAS(cas), errors.New("request cannot be nil")
}
if err := ctx.Err(); err != nil {
pa.mu.RUnlock()
return AuthCAS(cas), err
}
token := pa.token
cookie := pa.cookie
if token != "" && cookie != nil {
request.AddQueryParameter(AccessTokenKey, token)
request.SetCookies([]*http.Cookie{cookie})
pa.mu.RUnlock()
return AuthCAS(cas), nil
}
pa.mu.RUnlock()
return AuthCAS(cas), errors.New("token requires refresh")
}
func (pa *PasswordAuthenticator) Refresh(ctx context.Context, client *Client, cas AuthCAS) (AuthCAS, error) {
if debug {
log.Printf("[pw-auth-%s] Refresh called", pa.username)
}
pa.mu.Lock()
ccas := pa.cas
if client == nil {
pa.mu.Unlock()
return AuthCAS(ccas), errors.New("client cannot be nil")
}
if err := ctx.Err(); err != nil {
pa.mu.Unlock()
return AuthCAS(ccas), err
}
if ccas < uint64(cas) {
pa.mu.Unlock()
return AuthCAS(ccas), errors.New("provided cas value is greater than possible")
}
if ccas > uint64(cas) {
pa.mu.Unlock()
return AuthCAS(ccas), nil
}
// build new login request
request := NewRequest("POST", "/users/login", false)
err := request.SetBodyModel(&UsersLoginRequest{Username: pa.username, Password: pa.password})
if err != nil {
pa.token = ""
pa.cookie = nil
pa.cas++
ncas := pa.cas
pa.mu.Unlock()
return AuthCAS(ncas), err
}
// response model
out := new(UsersLoginResponse)
// execute login request
httpResponse, _, err := client.Ensure(ctx, request, 200, out)
if err != nil {
pa.token = ""
pa.cookie = nil
pa.cas++
ncas := pa.cas
pa.mu.Unlock()
return AuthCAS(ncas), err
}
// attempt to locate appropriate cookie...
if cookie := TryExtractAccessTokenCookie(httpResponse); cookie != nil {
// cookie found, update state and cas
pa.token = out.ID
pa.cookie = cookie
if debug {
log.Printf("[pw-auth-%s] Token %s; Cookie: %+v", pa.username, pa.token, pa.cookie)
}
// update internal cas
pa.cas++
ncas := pa.cas
pa.mu.Unlock()
return AuthCAS(ncas), nil
}
// if we were unable to find a cookie, reset state, iterate cas, and return error
pa.token = ""
pa.cookie = nil
pa.cas++
ncas := pa.cas
log.Printf("[pw-auth-%s] Unable to locate cookie \"%s\" in response", pa.username, AccessTokenKey)
pa.mu.Unlock()
return AuthCAS(ncas), fmt.Errorf("unable to locate cookie \"%s\" in response", AccessTokenKey)
}
func (pa *PasswordAuthenticator) Invalidate(ctx context.Context, cas AuthCAS) (AuthCAS, error) {
if debug {
log.Printf("[pw-auth-%s] Invalidate called", pa.username)
}
pa.mu.Lock()
ccas := pa.cas
if err := ctx.Err(); err != nil {
pa.mu.Unlock()
return AuthCAS(ccas), err
}
if ccas < uint64(cas) {
pa.mu.Unlock()
return AuthCAS(ccas), errors.New("provided cas value is greater than possible")
}
if ccas > uint64(cas) {
pa.mu.Unlock()
return AuthCAS(ccas), nil
}
pa.cas++
ncas := pa.cas
pa.token = ""
pa.cookie = nil
pa.mu.Unlock()
return AuthCAS(ncas), nil
}