github.com/futurehomeno/fimpgo@v1.14.0/edgeapp/auth.go (about)

     1  package edgeapp
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"github.com/futurehomeno/fimpgo"
     9  	"github.com/futurehomeno/fimpgo/utils"
    10  	log "github.com/sirupsen/logrus"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"time"
    14  )
    15  
    16  type OAuth2TokenResponse struct {
    17  	AccessToken  string      `json:"access_token"`
    18  	TokenType    string      `json:"token_type"`
    19  	ExpiresIn    int64       `json:"expires_in"`
    20  	RefreshToken string      `json:"refresh_token"`
    21  	Scope        interface{} `json:"scope"`
    22  }
    23  
    24  type OAuth2RefreshProxyRequest struct {
    25  	RefreshToken string `json:"refreshToken"`
    26  	PartnerCode  string `json:"partnerCode"`
    27  }
    28  
    29  type OAuth2AuthCodeProxyRequest struct {
    30  	AuthCode    string `json:"code"`
    31  	PartnerCode string `json:"partnerCode"`
    32  }
    33  
    34  type OAuth2PasswordProxyRequest struct {
    35  	PartnerCode string `json:"partnerCode"`
    36  	Username    string `json:"username"`
    37  	Password    string `json:"password"`
    38  }
    39  
    40  type FhOAuth2Client struct {
    41  	hubToken           string
    42  	syncClient         *fimpgo.SyncClient
    43  	appName            string
    44  	partnerName        string
    45  	mqt                *fimpgo.MqttTransport
    46  	mqttServerURI      string
    47  	mqttClientID       string
    48  	refreshTokenApiUrl string
    49  	authCodeApiUrl     string
    50  	refreshRetry       int
    51  	retryDelay         time.Duration // delay in seconds
    52  	cbRetry            int
    53  	cbRetryDelay       time.Duration
    54  }
    55  
    56  func (oac *FhOAuth2Client) SetHubToken(hubToken string) {
    57  	oac.hubToken = hubToken
    58  }
    59  
    60  func (oac *FhOAuth2Client) AuthCodeApiUrl() string {
    61  	return oac.authCodeApiUrl
    62  }
    63  
    64  func (oac *FhOAuth2Client) SetAuthCodeApiUrl(authCodeApiUrl string) {
    65  	oac.authCodeApiUrl = authCodeApiUrl
    66  }
    67  
    68  func (oac *FhOAuth2Client) RefreshTokenApiUrl() string {
    69  	return oac.refreshTokenApiUrl
    70  }
    71  
    72  func (oac *FhOAuth2Client) SetRefreshTokenApiUrl(refreshTokenApiUrl string) {
    73  	oac.refreshTokenApiUrl = refreshTokenApiUrl
    74  }
    75  
    76  //NewFhOAuth2Client implements OAuth client which communicates to 3rd party API over FH Auth proxy.
    77  func NewFhOAuth2Client(partnerName string, appName string, env string) *FhOAuth2Client {
    78  	client := &FhOAuth2Client{partnerName: partnerName, mqttServerURI: "tcp://localhost:1883", mqttClientID: "auth_client_" + appName}
    79  	if env == utils.EnvBeta {
    80  		client.refreshTokenApiUrl = "https://partners-beta.futurehome.io/api/control/edge/proxy/refresh"
    81  		client.authCodeApiUrl = "https://partners-beta.futurehome.io/api/control/edge/proxy/auth-code"
    82  	} else {
    83  		client.refreshTokenApiUrl = "https://partners.futurehome.io/api/control/edge/proxy/refresh"
    84  		client.authCodeApiUrl = "https://partners.futurehome.io/api/control/edge/proxy/auth-code"
    85  	}
    86  	client.retryDelay = 60
    87  	client.refreshRetry = 5
    88  	client.cbRetryDelay = 30
    89  	client.cbRetry = 7
    90  	client.appName = appName
    91  	return client
    92  }
    93  
    94  // Init has to be invoked before requesting access token
    95  func (oac *FhOAuth2Client) Init() error {
    96  	return oac.LoadHubTokenFromCB()
    97  }
    98  
    99  // SetParameters can be used to change default configuration parameter parameters. Parameters which are set to null values will be ignored
   100  func (oac *FhOAuth2Client) SetParameters(mqttServerUri, authCodeApiUrl, refreshTokenApiUrl string, retryDelay time.Duration, refreshRetry int, cbRetry int, cbRetryDelay time.Duration) {
   101  	if mqttServerUri != "" {
   102  		oac.mqttServerURI = mqttServerUri
   103  	}
   104  	if authCodeApiUrl != "" {
   105  		oac.authCodeApiUrl = authCodeApiUrl
   106  	}
   107  	if refreshTokenApiUrl != "" {
   108  		oac.refreshTokenApiUrl = refreshTokenApiUrl
   109  	}
   110  	if retryDelay != 0 {
   111  		oac.retryDelay = retryDelay
   112  	}
   113  	if refreshRetry != 0 {
   114  		oac.refreshRetry = refreshRetry
   115  	}
   116  	if cbRetry != 0 {
   117  		oac.cbRetry = cbRetry
   118  	}
   119  	if cbRetryDelay != 0 {
   120  		oac.cbRetryDelay = cbRetryDelay
   121  	}
   122  }
   123  
   124  // ConfigureFimpSyncClient configures fimp sync client , which is used to obtain Hub token from cloud bridge.
   125  func (oac *FhOAuth2Client) ConfigureFimpSyncClient() error {
   126  	if oac.mqt == nil {
   127  		oac.mqt = fimpgo.NewMqttTransport(oac.mqttServerURI, oac.mqttClientID, "", "", true, 1, 1)
   128  		err := oac.mqt.Start()
   129  		if err != nil {
   130  			log.Error("Error connecting to broker ", err)
   131  			return err
   132  		}
   133  		log.Debug("Auth mqtt client connected")
   134  		oac.syncClient = fimpgo.NewSyncClient(oac.mqt)
   135  	} else {
   136  		log.Error("Mqtt client is not configured")
   137  	}
   138  	return nil
   139  }
   140  
   141  // LoadHubTokenFromCB - requests hub token from CloudBridge
   142  func (oac *FhOAuth2Client) LoadHubTokenFromCB() error {
   143  	if oac.mqt == nil || oac.syncClient == nil {
   144  		if err := oac.ConfigureFimpSyncClient(); err != nil {
   145  			log.Error(err)
   146  		}
   147  	}
   148  	responseTopic := fmt.Sprintf("pt:j1/mt:rsp/rt:app/rn:%s/ad:1", oac.appName)
   149  	oac.syncClient.AddSubscription(responseTopic)
   150  	reqMsg := fimpgo.NewStringMessage("cmd.clbridge.get_auth_token", "clbridge", "", nil, nil, nil)
   151  	reqMsg.ResponseToTopic = responseTopic
   152  	var err error
   153  	var response *fimpgo.FimpMessage
   154  	for i := 0; i < oac.cbRetry; i++ {
   155  		response, err = oac.syncClient.SendFimp("pt:j1/mt:cmd/rt:app/rn:clbridge/ad:1", reqMsg, 5)
   156  		if err == nil {
   157  			break
   158  		}
   159  		log.Error("CB is not responding.Retrying")
   160  		time.Sleep(time.Second * oac.cbRetryDelay)
   161  	}
   162  
   163  	oac.syncClient.Stop()
   164  	oac.mqt.Stop()
   165  	if err != nil {
   166  		return err
   167  	}
   168  	if response.Type != "evt.clbridge.auth_token_report" {
   169  		return errors.New("wrong response msg type")
   170  	}
   171  	oac.hubToken, err = response.GetStringValue()
   172  	return err
   173  }
   174  
   175  // ExchangeCodeForTokens - exchanging code for access token
   176  func (oac *FhOAuth2Client) ExchangeCodeForTokens(code string) (*OAuth2TokenResponse, error) {
   177  	req := OAuth2AuthCodeProxyRequest{AuthCode: code, PartnerCode: oac.partnerName}
   178  	return oac.postMsg(req, oac.refreshTokenApiUrl)
   179  }
   180  
   181  // ExchangeRefreshToken - exchange refresh token for new access
   182  func (oac *FhOAuth2Client) ExchangeRefreshToken(refreshToken string) (*OAuth2TokenResponse, error) {
   183  	req := OAuth2RefreshProxyRequest{RefreshToken: refreshToken, PartnerCode: oac.partnerName}
   184  	return oac.postMsg(req, oac.refreshTokenApiUrl)
   185  }
   186  
   187  func (oac *FhOAuth2Client) postMsg(req interface{}, url string) (*OAuth2TokenResponse, error) {
   188  	if oac.hubToken == "" {
   189  		log.Info("Empty token.Re-requesting new token")
   190  		err := oac.LoadHubTokenFromCB()
   191  		if err != nil {
   192  			return nil, errors.New("empty hub token.operation aborted")
   193  		}
   194  	}
   195  	reqB, err := json.Marshal(req)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	client := &http.Client{Timeout: time.Second * 60}
   200  	r, _ := http.NewRequest("POST", url, bytes.NewBuffer(reqB))
   201  	r.Header.Add("Content-Type", "application/json")
   202  	r.Header.Add("Authorization", "Bearer "+oac.hubToken)
   203  	//log.Info("Sending using token :",oac.hubToken
   204  	var resp *http.Response
   205  	for i := 0; i < oac.refreshRetry; i++ {
   206  		resp, err = client.Do(r)
   207  		if err == nil && resp.StatusCode < 400 {
   208  			break
   209  		}
   210  		log.Error("Error response from auth endpoint.Retrying...")
   211  		time.Sleep(time.Second * oac.retryDelay)
   212  	}
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	if resp.StatusCode >= 400 {
   217  		return nil, fmt.Errorf("error %s response from server", resp.Status)
   218  	}
   219  
   220  	bData, err := ioutil.ReadAll(resp.Body)
   221  	if err != nil {
   222  		return nil, err
   223  	}
   224  	tResp := &OAuth2TokenResponse{}
   225  	err = json.Unmarshal(bData, tResp)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	return tResp, nil
   230  }