Description
IBEX Hub authentication is based on the double JWT system.
This means that every system based on top of it should call the Refresh Access Token endpoint once any endpoint returns a 401 on a request, and call the Sign in endpoint every time a request to refresh-access-token returns a 401.
Code example
package ibexhubclient
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"os"
"strings"
"time"
"github.com/hashicorp/go-retryablehttp"
)
type IbexHubClient struct {
ibexHubUrl string
ibexHubUser string
ibexHubPassword string
client *retryablehttp.Client
accessToken string
refreshToken string
}
type NewProviderInput struct {
IbexHubUrl string
IbexHubUser string
IbexHubPassword string
}
func NewPovider(input *NewProviderInput) (*IbexHubClient, error) {
client := &IbexHubClient{
ibexHubUrl: input.IbexHubUrl,
ibexHubUser: input.IbexHubUser,
ibexHubPassword: input.IbexHubPassword,
client: newHttpClient(),
}
if err := client.signIn(); err != nil {
return nil, err
}
return client, nil
}
func newHttpClient() *retryablehttp.Client {
retryClient := retryablehttp.NewClient()
retryClient.RetryMax = 6
retryClient.RetryWaitMin = time.Second * 3
retryClient.RetryWaitMax = time.Second * 10
return retryClient
}
package ibexhubclient
import (
"bytes"
"encoding/json"
"errors"
"net/http"
"os"
"strings"
"time"
"github.com/hashicorp/go-retryablehttp"
)
func (client *IbexHubClient) call(method string, url string, body interface{}) (*http.Response, error) {
var request *retryablehttp.Request
var err error
if method == http.MethodGet || method == http.MethodDelete {
request, err = retryablehttp.NewRequest(method, url, nil)
if err != nil {
return nil, err
}
} else {
requestBody, err := marshalBody(body)
if err != nil {
return nil, err
}
request, err = retryablehttp.NewRequest(method, url, requestBody)
if err != nil {
return nil, err
}
}
request.Header.Set("Authorization", client.accessToken)
response, err := client.client.Do(request)
if err != nil {
return nil, err
}
return client.checkUnAuthorized(request, response)
}
func marshalBody(body interface{}) (*bytes.Buffer, error) {
var requestBody *bytes.Buffer = nil
if body != nil {
marshaledBytes, err := json.Marshal(body)
if err != nil {
return nil, err
}
requestBody = bytes.NewBuffer(marshaledBytes)
}
return requestBody, nil
}
// check unauthorized
func (client *IbexHubClient) checkUnAuthorized(req *retryablehttp.Request, response *http.Response) (*http.Response, error) {
if response.StatusCode != http.StatusUnauthorized {
return response, nil
}
refreshAccessTokenResponse, err := client.refreshAccessToken()
if err != nil {
return nil, err
} else if refreshAccessTokenResponse.StatusCode == http.StatusUnauthorized {
if err := client.signIn(); err != nil {
return nil, err
}
}
req.Header.Set("Authorization", client.accessToken)
resp, err := client.client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
package ibexhubclient
const (
accountGroup = "/account"
)
// Create Account
func (client *IbexHubClient) CreateAccount(name string, currencyID int) (*forms.AccountResponse, error) {
requestBody := &CreateAccountBody{Name: name, CurrencyID: currencyID}
response, err := client.call(http.MethodPost, client.ibexHubUrl+accountGroup+"/create", requestBody)
if err != nil {
return nil, err
}
defer response.Body.Close()
if response.StatusCode > 299 {
var errResponse forms.ErrorResponse
if err := json.NewDecoder(response.Body).Decode(&errResponse); err != nil {
return nil, err
}
return nil, errors.New(errResponse.Error)
}
createAccountResponse := &forms.AccountResponse{}
err = json.NewDecoder(response.Body).Decode(createAccountResponse)
return createAccountResponse, err
}
package ibexhubclient
type HubError struct {
ErrorResponse *ErrorResponse
StatusCode int
}
type ErrorResponse struct {
Error string `json:"error"`
}
type CreateAccountBody struct {
Name string `json:"name" type:"string" binding:"required" sensitive:"true"`
CurrencyID int `json:"currencyId"`
}
type SignUpBody struct {
Email string `json:"email" type:"string" binding:"required" sensitive:"true"`
TemporaryPassword string `json:"temporaryPassword" type:"string" binding:"required" sensitive:"true"`
NewPassword string `json:"newPassword" type:"string" binding:"required" sensitive:"true"`
}
type SignInBody struct {
Email string `json:"email" type:"string" binding:"required" sensitive:"true"`
Password string `json:"password" type:"string" binding:"required" sensitive:"true"`
}
type TokensResponse struct {
AccessToken string `json:"accessToken" type:"string" sensitive:"true"`
AccessTokenExpiresAt int64 `json:"accessTokenExpiresAt" type:"integer"`
RefreshToken string `json:"refreshToken" type:"string" sensitive:"true"`
RefreshTokenExpiresAt int64 `json:"refreshTokenExpiresAt" type:"integer"`
}
type RefreshTokenBody struct {
RefreshToken string `json:"refreshToken" type:"string" binding:"required" sensitive:"true"`
}
type RefreshAccessTokenResponse struct {
AccessToken string `json:"accessToken" type:"string" sensitive:"true"`
ExpiresAt int64 `json:"expiresAt" type:"integer"`
}
package ibexhubclient
import (
"encoding/json"
"errors"
"net/http"
"github.com/IBEXDWM/IBEXHub-Client/ibexhubclient/forms"
"github.com/hashicorp/go-retryablehttp"
)
const (
authGroup = "/auth"
)
func (client *IbexHubClient) signIn() error {
requestBody := forms.SignInBody{
Email: client.ibexHubUser,
Password: client.ibexHubPassword,
}
response, err := client.call(http.MethodPost, client.ibexHubUrl+authGroup+"/signin", requestBody)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
errorResponse := &forms.ErrorResponse{}
if err = json.NewDecoder(response.Body).Decode(errorResponse); err != nil {
return err
}
return errors.New(errorResponse.Error)
}
tokensResponse := &TokensResponse{}
if err = json.NewDecoder(response.Body).Decode(tokensResponse); err != nil {
return err
}
client.accessToken = tokensResponse.AccessToken
client.refreshToken = tokensResponse.RefreshToken
return nil
}
// Refresh Access Token
func (client *IbexHubClient) refreshAccessToken() (*http.Response, error) {
requestBody := forms.RefreshTokenBody{RefreshToken: client.refreshToken}
bodyMarshaled, err := marshalBody(requestBody)
if err != nil {
return nil, err
}
response, err := http.Post(client.ibexHubUrl+authGroup+"/refresh-access-token", "application/json", bodyMarshaled)
if err != nil {
return nil, err
}
defer response.Body.Close()
refreshTokensResponse := &RefreshAccessTokenResponse{}
err = json.NewDecoder(response.Body).Decode(refreshTokensResponse)
if err != nil {
return nil, err
}
client.accessToken = refreshTokensResponse.AccessToken
return response, nil
}
Usage
package main
func main() {
config := ibexhubclient.NewProviderInput{
IbexHubUrl: "",
IbexHubUser: "",
IbexHubPassword: "",
}
hubClient, err := ibexhubclient.NewProvider()
if err != nil {
log.Println(err.Error())
return
}
newAccount, err := hubClient.CreateAccount("zozo", 0)
if err != nil {
log.Println(err.Error())
return
}
log.Println(newAccount)
}