1
1
package entraid
2
2
3
3
import (
4
+ "errors"
4
5
"fmt"
6
+ "log"
7
+ "net"
5
8
"sync"
6
9
"time"
7
10
)
@@ -15,27 +18,56 @@ type TokenManagerOptions struct {
15
18
// The value should be between 0 and 1.
16
19
// For example, if the expiration time is 1 hour and the ratio is 0.5,
17
20
// the token will be refreshed after 30 minutes.
21
+ //
22
+ // default: 0.7
18
23
ExpirationRefreshRatio float64
19
24
25
+ // LowerRefreshBoundMs is the lower bound for the refresh time in milliseconds.
26
+ // It is used to determine when to refresh the token.
27
+ // The value should be greater than 0.
28
+ // For example, if the expiration time is 1 hour and the lower bound is 30 minutes,
29
+ // the token will be refreshed after 30 minutes.
30
+ //
31
+ // default: 0 ms (no lower bound, refresh based on ExpirationRefreshRatio)
32
+ LowerRefreshBoundMs int
33
+
20
34
// TokenParser is a function that parses the raw token and returns a Token object.
21
35
// The function takes the raw token as a string and returns a Token object and an error.
22
36
// If this function is not provided, the default implementation will be used.
37
+ //
38
+ // required: true
23
39
TokenParser TokenParserFunc
24
40
25
41
// RetryOptions is a struct that contains the options for retrying the token request.
26
42
// It contains the maximum number of attempts, initial delay, maximum delay, and backoff multiplier.
43
+ //
44
+ // The default values are 3 attempts, 1000 ms initial delay, 10000 ms maximum delay, and 2.0 backoff multiplier.
27
45
RetryOptions RetryOptions
28
46
}
29
47
30
48
// RetryOptions is a struct that contains the options for retrying the token request.
31
49
type RetryOptions struct {
50
+ // IsRetryable is a function that checks if the error is retryable.
51
+ // It takes an error as an argument and returns a boolean value.
52
+ //
53
+ // default: defaultRetryableFunc
54
+ IsRetryable func (err error ) bool
32
55
// MaxAttempts is the maximum number of attempts to retry the token request.
56
+ //
57
+ // default: 3
33
58
MaxAttempts int
34
59
// InitialDelayMs is the initial delay in milliseconds before retrying the token request.
60
+ //
61
+ // default: 1000 ms
35
62
InitialDelayMs int
63
+
36
64
// MaxDelayMs is the maximum delay in milliseconds between retry attempts.
65
+ //
66
+ // default: 10000 ms
37
67
MaxDelayMs int
68
+
38
69
// BackoffMultiplier is the multiplier for the backoff delay.
70
+ // default: 2.0
39
71
BackoffMultiplier float64
40
72
}
41
73
@@ -55,12 +87,30 @@ type TokenManager interface {
55
87
// defaultTokenParser is a function that parses the raw token and returns Token object.
56
88
var defaultTokenParser = func (rawToken string , expiresOn time.Time ) (* Token , error ) {
57
89
// Parse the token and return the username and password.
58
- // This is a placeholder implementation.
90
+ // In this example, we are just returning the raw token as the password.
91
+ // In a real implementation, you would parse the token and extract the username and password.
92
+ // For example, if the token is a JWT, you would decode the JWT and extract the claims.
93
+ // This is just a placeholder implementation.
94
+ // You should replace this with your own implementation.
95
+ if rawToken == "" {
96
+ return nil , fmt .Errorf ("raw token is empty" )
97
+ }
98
+ if expiresOn .IsZero () {
99
+ return nil , fmt .Errorf ("expires on is zero" )
100
+ }
101
+ if expiresOn .Before (time .Now ()) {
102
+ return nil , fmt .Errorf ("expires on is in the past" )
103
+ }
104
+ if expiresOn .Sub (time .Now ()) < MinTokenTTL {
105
+ return nil , fmt .Errorf ("expires on is less than minimum token TTL" )
106
+ }
107
+ // parse token as jwt token and get claims
108
+
59
109
return & Token {
60
110
Username : "username" ,
61
- Password : "password" ,
62
- ExpiresOn : expiresOn ,
63
- TTL : expiresOn .Unix () - time .Now ().Unix (),
111
+ Password : rawToken ,
112
+ ExpiresOn : expiresOn . UTC () ,
113
+ TTL : expiresOn .UTC (). Unix () - time .Now (). UTC ().Unix (),
64
114
RawToken : rawToken ,
65
115
}, nil
66
116
}
@@ -70,18 +120,22 @@ var defaultTokenParser = func(rawToken string, expiresOn time.Time) (*Token, err
70
120
// The IdentityProvider is used to obtain the token, and the TokenManagerOptions contains options for the TokenManager.
71
121
// The TokenManager is responsible for managing the token and refreshing it when necessary.
72
122
func NewTokenManager (idp IdentityProvider , options TokenManagerOptions ) (TokenManager , error ) {
73
- tokenParser := defaultTokenParserOr (options .TokenParser )
74
- retryOptions := defaultRetryOptionsOr (options .RetryOptions )
123
+ options = defaultTokenManagerOptionsOr (options )
124
+ if options .ExpirationRefreshRatio <= 0 || options .ExpirationRefreshRatio > 1 {
125
+ return nil , fmt .Errorf ("expiration refresh ratio must be between 0 and 1" )
126
+ }
127
+
75
128
if idp == nil {
76
129
return nil , fmt .Errorf ("identity provider is required" )
77
130
}
78
131
79
132
return & entraidTokenManager {
80
- idp : idp ,
81
- token : nil ,
82
- closed : make (chan struct {}),
83
- tokenParser : tokenParser ,
84
- retryOptions : retryOptions ,
133
+ idp : idp ,
134
+ token : nil ,
135
+ closed : make (chan struct {}),
136
+ expirationRefreshRatio : options .ExpirationRefreshRatio ,
137
+ tokenParser : options .TokenParser ,
138
+ retryOptions : options .RetryOptions ,
85
139
}, nil
86
140
}
87
141
@@ -108,6 +162,10 @@ type entraidTokenManager struct {
108
162
// lock locks the listener to prevent concurrent access.
109
163
lock sync.Mutex
110
164
165
+ // expirationRefreshRatio is the ratio of the token expiration time to refresh the token.
166
+ // It is used to determine when to refresh the token.
167
+ expirationRefreshRatio float64
168
+
111
169
closed chan struct {}
112
170
}
113
171
@@ -169,7 +227,7 @@ func (e *entraidTokenManager) Start(listener TokenListener) (cancelFunc, error)
169
227
// Simulate token refresh
170
228
for {
171
229
select {
172
- case <- time .After (time .Duration ( e . token .TTL ) * time .Second ):
230
+ case <- time .After (time .Until ( token .ExpiresOn ) * time .Duration ( e . expirationRefreshRatio ) ):
173
231
// Token is about to expire, refresh it
174
232
for i := 0 ; i < e .retryOptions .MaxAttempts ; i ++ {
175
233
token , err := e .GetToken ()
@@ -178,8 +236,8 @@ func (e *entraidTokenManager) Start(listener TokenListener) (cancelFunc, error)
178
236
break
179
237
}
180
238
// check if err is retryable
181
- if err . Error () == "retryable error" {
182
- // retry
239
+ if e . retryOptions . IsRetryable ( err ) {
240
+ // retryable error, continue to next attempt
183
241
continue
184
242
} else {
185
243
// not retryable
@@ -216,6 +274,12 @@ func (e *entraidTokenManager) Start(listener TokenListener) (cancelFunc, error)
216
274
}
217
275
218
276
func (e * entraidTokenManager ) Close () error {
277
+ defer func () {
278
+ if r := recover (); r != nil {
279
+ // handle panic
280
+ log .Printf ("Recovered from panic: %v" , r )
281
+ }
282
+ }()
219
283
e .lock .Lock ()
220
284
defer e .lock .Unlock ()
221
285
if e .listener != nil {
@@ -225,11 +289,30 @@ func (e *entraidTokenManager) Close() error {
225
289
return nil
226
290
}
227
291
292
+ // defaultRetryableFunc is a function that checks if the error is retryable.
293
+ // It takes an error as an argument and returns a boolean value.
294
+ // The function checks if the error is a net.Error and if it is a timeout or temporary error.
295
+ var defaultRetryableFunc = func (err error ) bool {
296
+ var netErr net.Error
297
+ if err == nil {
298
+ return true
299
+ }
300
+
301
+ if ok := errors .As (err , netErr ); ok {
302
+ return netErr .Timeout () || netErr .Temporary ()
303
+ }
304
+ return false
305
+ }
306
+
228
307
// defaultRetryOptionsOr returns the default retry options if the provided options are not set.
229
308
// It sets the maximum number of attempts, initial delay, maximum delay, and backoff multiplier.
230
309
// The default values are 3 attempts, 1000 ms initial delay, 10000 ms maximum delay, and 2.0 backoff multiplier.
231
310
// The values can be overridden by the user.
232
311
func defaultRetryOptionsOr (retryOptions RetryOptions ) RetryOptions {
312
+ if retryOptions .IsRetryable == nil {
313
+ retryOptions .IsRetryable = defaultRetryableFunc
314
+ }
315
+
233
316
if retryOptions .MaxAttempts <= 0 {
234
317
retryOptions .MaxAttempts = 3
235
318
}
@@ -254,3 +337,12 @@ func defaultTokenParserOr(tokenParser TokenParserFunc) TokenParserFunc {
254
337
}
255
338
return tokenParser
256
339
}
340
+
341
+ func defaultTokenManagerOptionsOr (options TokenManagerOptions ) TokenManagerOptions {
342
+ options .RetryOptions = defaultRetryOptionsOr (options .RetryOptions )
343
+ options .TokenParser = defaultTokenParserOr (options .TokenParser )
344
+ if options .ExpirationRefreshRatio <= 0 || options .ExpirationRefreshRatio > 1 {
345
+ options .ExpirationRefreshRatio = 0.7
346
+ }
347
+ return options
348
+ }
0 commit comments