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
|
package oauth
import (
"fmt"
"sync"
"time"
"github.com/go-errors/errors"
"github.com/eduvpn/eduvpn-common/internal/log"
)
// TokenResponse defines the OAuth response from the server that includes the tokens.
type TokenResponse struct {
// Access is the access token returned by the server
Access string `json:"access_token"`
// Refresh token is the refresh token returned by the server
Refresh string `json:"refresh_token"`
// Type indicates which type of tokens we have
Type string `json:"token_type"`
// Expires is the expires time returned by the server
Expires int64 `json:"expires_in"`
}
// The public type that can be passed to an update function
// It contains our access and refresh tokens with a timestamp
type Token struct {
// Access is the Access token returned by the server
Access string
// Refresh token is the Refresh token returned by the server
Refresh string
// ExpiredTimestamp is the Expires field but converted to a Go timestamp
ExpiredTimestamp time.Time
}
// tokenRefresher is a structure that contains our access and refresh tokens and a timestamp when they expire.
// Additionally, it contains the refresher to get new tokens
type tokenRefresher struct {
Token
// Refresher is the function that refreshes the token
Refresher func(string) (*TokenResponse, time.Time, error)
}
// tokenLock is a wrapper around token that protects it with a lock
type tokenLock struct {
// Protects t
mu sync.Mutex
// The token fields protected by the lock
// This token struct contains a refresher
t *tokenRefresher
}
// Access gets the OAuth access token used for contacting the server API
// It returns the access token as a string, possibly obtained fresh using the refresher
// If the token cannot be obtained, an error is returned and the token is an empty string.
func (l *tokenLock) Access() (string, error) {
log.Logger.Debugf("Getting access token")
l.mu.Lock()
defer l.mu.Unlock()
// The tokens are not expired yet
// So they should be valid, re-login not neede
if !l.expired() {
log.Logger.Debugf("Access token is not expired, returning")
return l.t.Access, nil
}
// Check if refresh is even possible by doing a simple check if the refresh token is empty
// This is not needed but reduces API calls to the server
if l.t.Refresh == "" {
log.Logger.Debugf("Refresh token is empty, returning error")
return "", errors.Wrap(&TokensInvalidError{Cause: "no refresh token is present"}, 0)
}
// Otherwise refresh and then later return the access token if we are successful
tr, s, err := l.t.Refresher(l.t.Refresh)
if err != nil {
log.Logger.Debugf("Got a refresh token error: %v", err)
// We have failed to ensure the tokens due to refresh not working
return "", errors.Wrap(
&TokensInvalidError{Cause: fmt.Sprintf("tokens failed refresh with error: %v", err)}, 0)
}
if tr == nil {
log.Logger.Debugf("No token response after refreshing")
return "", errors.New("No token response after refreshing")
}
r := *tr
e := s.Add(time.Second * time.Duration(r.Expires))
t := Token{Access: r.Access, Refresh: r.Refresh, ExpiredTimestamp: e}
l.updateInternal(t)
return l.t.Access, nil
}
// UpdateResponse updates the structure using the server response and locks
func (l *tokenLock) UpdateResponse(r TokenResponse, s time.Time) {
l.mu.Lock()
e := s.Add(time.Second * time.Duration(r.Expires))
t := Token{Access: r.Access, Refresh: r.Refresh, ExpiredTimestamp: e}
l.updateInternal(t)
l.mu.Unlock()
}
// updateInternal updates the token structure internally but does not lock
func (l *tokenLock) updateInternal(r Token) {
l.t.Access = r.Access
l.t.Refresh = r.Refresh
l.t.ExpiredTimestamp = r.ExpiredTimestamp
}
// Update updates the token structure using the internal function but locks
func (l *tokenLock) Update(r Token) {
l.mu.Lock()
l.updateInternal(r)
l.mu.Unlock()
}
// Get gets the tokens into a public struct
func (l *tokenLock) Get() Token {
// TODO: Check nil?
l.mu.Lock()
defer l.mu.Unlock()
return l.t.Token
}
// SetExpired overrides the timestamp to the current time
// This marks the tokens as expired
func (l *tokenLock) SetExpired() {
l.mu.Lock()
l.t.ExpiredTimestamp = time.Now()
l.mu.Unlock()
}
// expired checks if the access token is expired.
// This is only called internally and thus does not lock
func (l *tokenLock) expired() bool {
now := time.Now()
return !now.Before(l.t.ExpiredTimestamp)
}
|