summaryrefslogtreecommitdiff
path: root/internal/oauth/token.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/oauth/token.go')
-rw-r--r--internal/oauth/token.go94
1 files changed, 88 insertions, 6 deletions
diff --git a/internal/oauth/token.go b/internal/oauth/token.go
index 31c2c74..855677c 100644
--- a/internal/oauth/token.go
+++ b/internal/oauth/token.go
@@ -1,6 +1,12 @@
package oauth
-import "time"
+import (
+ "fmt"
+ "sync"
+ "time"
+
+ "github.com/go-errors/errors"
+)
// TokenResponse defines the OAuth response from the server that includes the tokens.
type TokenResponse struct {
@@ -17,8 +23,8 @@ type TokenResponse struct {
Expires int64 `json:"expires_in"`
}
-// Token is a structure that contains our access and refresh tokens and a timestamp when they expire.
-type Token struct {
+// token is a structure that contains our access and refresh tokens and a timestamp when they expire.
+type token struct {
// Access is the access token returned by the server
access string
@@ -27,10 +33,86 @@ type Token struct {
// ExpiredTimestamp is the Expires field but converted to a Go timestamp
expiredTimestamp time.Time
+
+ // 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
+ t *token
+}
+
+// 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) {
+ 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() {
+ 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 == "" {
+ 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 {
+ // 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 {
+ return "", errors.New("No token response after refreshing")
+ }
+ l.updateInternal(*tr, s)
+ return l.t.access, nil
+}
+
+// Clear completely clears the token structure
+// This is useful for forcing re-authorization
+func (l *tokenLock) Clear() {
+ l.mu.Lock()
+ l.t = &token{}
+ l.mu.Unlock()
+}
+
+// updateInternal updates the structure using the response without locking
+func (l *tokenLock) updateInternal(r TokenResponse, s time.Time) {
+ l.t.access = r.Access
+ l.t.refresh = r.Refresh
+ l.t.expiredTimestamp = s.Add(time.Second * time.Duration(r.Expires))
+}
+
+// Update updates the structure usign the response and locks
+func (l *tokenLock) Update(r TokenResponse, s time.Time) {
+ l.mu.Lock()
+ l.updateInternal(r, s)
+ l.mu.Unlock()
+}
+
+// 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.
-func (tokens *Token) Expired() bool {
+// 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(tokens.expiredTimestamp)
+ return !now.Before(l.t.expiredTimestamp)
}