Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 84 additions & 14 deletions pkg/dimoauth/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
35 changes: 35 additions & 0 deletions pkg/dimoauth/settings.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading