oh-my-posh/src/http/oauth.go

113 lines
3.1 KiB
Go

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...)
}