From 823cb35dc7a846685e71e32a1ef14db33d473782 Mon Sep 17 00:00:00 2001 From: James Reategui Date: Tue, 3 Feb 2026 13:39:12 -0500 Subject: [PATCH] refactor settings, add func for vehicle jwt --- pkg/dimoauth/service.go | 98 ++++++++++++++++++++++++++++++++++------ pkg/dimoauth/settings.go | 35 ++++++++++++++ 2 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 pkg/dimoauth/settings.go diff --git a/pkg/dimoauth/service.go b/pkg/dimoauth/service.go index d801452..912b449 100644 --- a/pkg/dimoauth/service.go +++ b/pkg/dimoauth/service.go @@ -20,27 +20,36 @@ import ( var ErrBadRequest = fmt.Errorf("bad request") type AuthService struct { - authURL url.URL - clientID common.Address - domain string - privateKey *ecdsa.PrivateKey - token *jwt.Token - logger zerolog.Logger - m sync.RWMutex + authURL url.URL + tokenExchangeURL url.URL + nftContractAddress common.Address + clientID common.Address + domain string + privateKey *ecdsa.PrivateKey + token *jwt.Token + logger zerolog.Logger + m sync.RWMutex } -func NewAuthService(logger zerolog.Logger, authURL url.URL, clientID common.Address, domain string, privateKeyHex string) (*AuthService, error) { - ecdsaPrivateKey, err := crypto.HexToECDSA(privateKeyHex) +func NewAuthService(logger zerolog.Logger, settings *Settings) (*AuthService, error) { + authURL, tokenExchangeURL, nftContractAddress, clientID, err := settings.ParseURLs() + if err != nil { + return nil, err + } + + ecdsaPrivateKey, err := crypto.HexToECDSA(settings.PrivateKeyHex) if err != nil { return nil, err } return &AuthService{ - authURL: authURL, - clientID: clientID, - domain: domain, - privateKey: ecdsaPrivateKey, - logger: logger, + authURL: authURL, + tokenExchangeURL: tokenExchangeURL, + nftContractAddress: nftContractAddress, + clientID: clientID, + domain: settings.Domain, + privateKey: ecdsaPrivateKey, + logger: logger, }, nil } @@ -255,3 +264,64 @@ type AuthSubmitChallengeResponse struct { ExpiresIn int `json:"expires_in"` IDToken string `json:"id_token"` } + +type TokenExchangeRequest struct { + NFTContractAddress string `json:"nftContractAddress"` + Privileges []int `json:"privileges"` + TokenID int `json:"tokenId"` +} + +type TokenExchangeResponse struct { + Token string `json:"token"` +} + +// GetVehicleJWT exchanges a developer JWT for a vehicle-specific JWT token +func (a *AuthService) GetVehicleJWT(devJWT string, privileges []int, tokenID int) (string, error) { + h := map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + devJWT, + } + hcw, _ := shttp.NewClientWrapper(a.tokenExchangeURL.String(), "", 10*time.Second, h, false, shttp.WithRetry(3)) + + requestPayload := TokenExchangeRequest{ + NFTContractAddress: a.nftContractAddress.String(), + Privileges: privileges, + TokenID: tokenID, + } + + payloadBytes, err := json.Marshal(requestPayload) + if err != nil { + a.logger.Err(err).Msg("Failed to marshal token exchange request") + return "", err + } + + resp, err := hcw.ExecuteRequest("/v1/tokens/exchange", "POST", payloadBytes) + if err != nil { + a.logger.Err(err).Msg("Failed to send token exchange request") + return "", err + } + + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + a.logger.Err(err).Msg("Failed to close response body") + } + }(resp.Body) + + if resp.StatusCode != 200 { + return "", ErrBadRequest + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + a.logger.Err(err).Msg("Failed to read response body") + return "", err + } + + var decoded TokenExchangeResponse + if err = json.Unmarshal(body, &decoded); err != nil { + return "", err + } + + return decoded.Token, nil +} diff --git a/pkg/dimoauth/settings.go b/pkg/dimoauth/settings.go new file mode 100644 index 0000000..bc1b8a7 --- /dev/null +++ b/pkg/dimoauth/settings.go @@ -0,0 +1,35 @@ +package dimoauth + +import ( + "net/url" + + "github.com/ethereum/go-ethereum/common" +) + +// Settings contains configuration for DIMO authentication service +type Settings struct { + AuthURL string `env:"AUTH_URL" yaml:"AUTH_URL"` + TokenExchangeURL string `env:"TOKEN_EXCHANGE_URL" yaml:"TOKEN_EXCHANGE_URL"` + NFTContractAddress string `env:"NFT_CONTRACT_ADDRESS" yaml:"NFT_CONTRACT_ADDRESS"` + ClientID string `env:"CLIENT_ID" yaml:"CLIENT_ID"` + Domain string `env:"DOMAIN" yaml:"DOMAIN"` + PrivateKeyHex string `env:"PRIVATE_KEY_HEX" yaml:"PRIVATE_KEY_HEX"` +} + +// ParseURLs converts string URLs to url.URL and validates addresses +func (s *Settings) ParseURLs() (authURL url.URL, tokenExchangeURL url.URL, nftContractAddress common.Address, clientID common.Address, err error) { + parsedAuthURL, err := url.Parse(s.AuthURL) + if err != nil { + return url.URL{}, url.URL{}, common.Address{}, common.Address{}, err + } + + parsedTokenExchangeURL, err := url.Parse(s.TokenExchangeURL) + if err != nil { + return url.URL{}, url.URL{}, common.Address{}, common.Address{}, err + } + + nftContractAddress = common.HexToAddress(s.NFTContractAddress) + clientID = common.HexToAddress(s.ClientID) + + return *parsedAuthURL, *parsedTokenExchangeURL, nftContractAddress, clientID, nil +}