package http import ( "encoding/json" "fmt" "io" "net/http" "github.com/jandedobbeleer/oh-my-posh/src/platform" "github.com/jandedobbeleer/oh-my-posh/src/properties" ) const ( Timeout = "timeout" InvalidRefreshToken = "invalid refresh token" TokenRefreshFailed = "token refresh error" DefaultRefreshToken = "111111111111111111111111111111" ) type tokenExchange struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` ExpiresIn int `json:"expires_in"` } type OAuthError struct { message string } func (a *OAuthError) Error() string { return a.message } type OAuthRequest struct { Request AccessTokenKey string RefreshTokenKey string SegmentName string } func (o *OAuthRequest) getAccessToken() (string, error) { // get directly from cache if acccessToken, OK := o.env.Cache().Get(o.AccessTokenKey); OK && len(acccessToken) != 0 { return acccessToken, nil } // use cached refresh token to get new access token if refreshToken, OK := o.env.Cache().Get(o.RefreshTokenKey); OK && len(refreshToken) != 0 { if acccessToken, err := o.refreshToken(refreshToken); err == nil { return acccessToken, nil } } // use initial refresh token from property refreshToken := o.props.GetString(properties.RefreshToken, "") // ignore an empty or default refresh token if len(refreshToken) == 0 || refreshToken == DefaultRefreshToken { return "", &OAuthError{ message: InvalidRefreshToken, } } // no need to let the user provide access token, we'll always verify the refresh token acccessToken, err := o.refreshToken(refreshToken) return acccessToken, err } func (o *OAuthRequest) refreshToken(refreshToken string) (string, error) { httpTimeout := o.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout) url := fmt.Sprintf("https://ohmyposh.dev/api/refresh?segment=%s&token=%s", o.SegmentName, refreshToken) body, err := o.env.HTTPRequest(url, nil, httpTimeout) if err != nil { return "", &OAuthError{ // This might happen if /api was asleep. Assume the user will just retry message: Timeout, } } tokens := &tokenExchange{} err = json.Unmarshal(body, &tokens) if err != nil { return "", &OAuthError{ message: TokenRefreshFailed, } } // add tokens to cache o.env.Cache().Set(o.AccessTokenKey, tokens.AccessToken, tokens.ExpiresIn/60) o.env.Cache().Set(o.RefreshTokenKey, tokens.RefreshToken, 2*525960) // it should never expire unless revoked, default to 2 year return tokens.AccessToken, nil } func OauthResult[a any](o *OAuthRequest, url string, body io.Reader, requestModifiers ...platform.HTTPRequestModifier) (a, error) { if data, err := getCacheValue[a](&o.Request, url); err == nil { return data, nil } accessToken, err := o.getAccessToken() if err != nil { var data a return data, err } // add token to header for authentication addAuthHeader := func(request *http.Request) { request.Header.Add("Authorization", "Bearer "+accessToken) } if requestModifiers == nil { requestModifiers = []platform.HTTPRequestModifier{} } requestModifiers = append(requestModifiers, addAuthHeader) return do[a](&o.Request, url, body, requestModifiers...) }