Refactor token server initialization

- Move state "gen server" to the top of main/0
- Initialize it as empty
- Ensure that persistTokens/2 is called whenever the state changes
- Support setState/2 (similar in spirit to getState/0)
This commit is contained in:
William Carroll 2020-02-09 23:11:45 +00:00
parent d35eb5c8f9
commit bd88f40224

View file

@ -56,6 +56,7 @@ type readMsg struct {
type writeMsg struct { type writeMsg struct {
state state state state
sender chan bool
} }
type channels struct { type channels struct {
@ -102,10 +103,10 @@ func scheduleTokenRefresh(expiresIn int, refreshToken string) {
log.Printf("Scheduling token refresh for %v\n", timestamp) log.Printf("Scheduling token refresh for %v\n", timestamp)
time.Sleep(duration) time.Sleep(duration)
log.Println("Refreshing tokens now...") log.Println("Refreshing tokens now...")
access, refresh := refreshTokens(refreshToken) accessToken, refreshToken := refreshTokens(refreshToken)
log.Println("Successfully refreshed tokens.") log.Println("Successfully refreshed tokens.")
logTokens(access, refresh) logTokens(accessToken, refreshToken)
chans.writes <- writeMsg{state{access, refresh}} setState(accessToken, refreshToken)
} }
// Exchange existing credentials for a new access token and `refreshToken`. Also // Exchange existing credentials for a new access token and `refreshToken`. Also
@ -177,6 +178,13 @@ func handleInterrupts() {
os.Exit(0) os.Exit(0)
} }
// Set `accessToken` and `refreshToken` on application state.
func setState(accessToken string, refreshToken string) {
msg := writeMsg{state{accessToken, refreshToken}, make(chan bool)}
chans.writes <- msg
<-msg.sender
}
// Return our application state. // Return our application state.
func getState() state { func getState() state {
msg := readMsg{make(chan state)} msg := readMsg{make(chan state)}
@ -189,24 +197,9 @@ func getState() state {
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
func main() { func main() {
// Retrieve cached tokens from store.
accessToken := fmt.Sprintf("%v", kv.Get("monzoAccessToken"))
refreshToken := fmt.Sprintf("%v", kv.Get("monzoRefreshToken"))
log.Println("Attempting to retrieve cached credentials...")
logTokens(accessToken, refreshToken)
if accessToken == "" || refreshToken == "" {
log.Println("Cached credentials are absent. Authorizing client...")
authCode := auth.GetAuthCode(monzoClientId)
tokens := auth.GetTokensFromAuthCode(authCode, monzoClientId, monzoClientSecret)
accessToken, refreshToken = tokens.AccessToken, tokens.RefreshToken
go persistTokens(accessToken, refreshToken)
go scheduleTokenRefresh(tokens.ExpiresIn, refreshToken)
}
// Manage application state. // Manage application state.
go func() { go func() {
state := &state{accessToken, refreshToken} state := &state{}
for { for {
select { select {
case msg := <-chans.reads: case msg := <-chans.reads:
@ -218,18 +211,39 @@ func main() {
log.Printf("Old: %s\n", state) log.Printf("Old: %s\n", state)
*state = msg.state *state = msg.state
log.Printf("New: %s\n", state) log.Printf("New: %s\n", state)
// As an attempt to maintain consistency between application
// state and persisted state, everytime we write to the
// application state, we will write to the store.
persistTokens(state.accessToken, state.refreshToken)
msg.sender <- true
} }
} }
}() }()
// Gracefully shutdown. // Retrieve cached tokens from store.
go handleInterrupts() accessToken := fmt.Sprintf("%v", kv.Get("monzoAccessToken"))
refreshToken := fmt.Sprintf("%v", kv.Get("monzoRefreshToken"))
log.Println("Attempting to retrieve cached credentials...")
logTokens(accessToken, refreshToken)
if accessToken == "" || refreshToken == "" {
log.Println("Cached credentials are absent. Authorizing client...")
authCode := auth.GetAuthCode(monzoClientId)
tokens := auth.GetTokensFromAuthCode(authCode, monzoClientId, monzoClientSecret)
setState(tokens.AccessToken, tokens.RefreshToken)
go scheduleTokenRefresh(tokens.ExpiresIn, tokens.RefreshToken)
} else {
setState(accessToken, refreshToken)
// If we have tokens, they may be expiring soon. We don't know because // If we have tokens, they may be expiring soon. We don't know because
// we aren't storing the expiration timestamp in the state or in the // we aren't storing the expiration timestamp in the state or in the
// store. Until we have that information, and to be safe, let's refresh // store. Until we have that information, and to be safe, let's refresh
// the tokens. // the tokens.
scheduleTokenRefresh(0, refreshToken) go scheduleTokenRefresh(0, refreshToken)
}
// Gracefully handle shutdowns.
go handleInterrupts()
// Listen to inbound requests. // Listen to inbound requests.
fmt.Println("Listening on http://localhost:4242 ...") fmt.Println("Listening on http://localhost:4242 ...")
@ -239,7 +253,6 @@ func main() {
state := getState() state := getState()
go scheduleTokenRefresh(0, state.refreshToken) go scheduleTokenRefresh(0, state.refreshToken)
fmt.Fprintf(w, "Done.") fmt.Fprintf(w, "Done.")
} else if req.URL.Path == "/set-tokens" && req.Method == "POST" { } else if req.URL.Path == "/set-tokens" && req.Method == "POST" {
// Parse // Parse
payload := &setTokensRequest{} payload := &setTokensRequest{}
@ -249,8 +262,7 @@ func main() {
} }
// Update application state // Update application state
msg := writeMsg{state{payload.AccessToken, payload.RefreshToken}} setState(payload.AccessToken, payload.RefreshToken)
chans.writes <- msg
// Refresh tokens // Refresh tokens
go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken) go scheduleTokenRefresh(payload.ExpiresIn, payload.RefreshToken)