Skip to content

Commit

Permalink
Merge pull request #8 from ellisvalentiner/feat/add-pregenerated-jwt-…
Browse files Browse the repository at this point in the history
…support

feat: add support for using a pre-generated JWT for authorization
  • Loading branch information
ellisvalentiner committed Jul 15, 2022
2 parents 94f6f87 + e1dcb06 commit 7d73616
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 14 deletions.
5 changes: 5 additions & 0 deletions config/weatherkit.spc
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ connection "weatherkit" {
plugin = "ellisvalentiner/weatherkit"

# WeatherKit requires authorization using a signed developer token
# You must either provide the information to generate a signed JSON web token (JWT) or supply a pre-generated JWT.
# See the Apple Developer documentation
# https://developer.apple.com/documentation/weatherkitrestapi/request_authentication_for_weatherkit_rest_api

# Option 1: Generate an JWT
# The 10-character key identifier from your developer account.
# key_id = "STJY7HX969"

Expand All @@ -16,4 +18,7 @@ connection "weatherkit" {

# Path to your private key for signing the JWT.
# private_key_path = "~/.auth/AuthKey_STJY7HX969.p8"

# Option 2: Use a pre-generated JWT
# token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
}
27 changes: 17 additions & 10 deletions weatherkit/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ type Client struct {
config *weatherKitConfig
}

func NewClient(httpClient *http.Client, config weatherKitConfig) *Client {
func NewClient(httpClient *http.Client, config *weatherKitConfig) *Client {
return &Client{
httpClient: httpClient,
config: &config,
config: config,
}
}

Expand All @@ -44,7 +44,14 @@ func (c *Client) NewRequest(ctx context.Context, method, url string) (*http.Requ
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
token := c.createJwt(*c.config)

var token string
if c.config.Token != nil {
token = *c.config.Token
} else {
token = c.createJwt()
}

req.Header.Add("Authorization", "Bearer "+token)

req = req.WithContext(ctx)
Expand Down Expand Up @@ -81,23 +88,23 @@ func (c *Client) DoRequest(r *http.Request, v interface{}) error {
return nil
}

func (c *Client) loadPrivateKey(keyPath string) *ecdsa.PrivateKey {
func (c *Client) loadPrivateKey() *ecdsa.PrivateKey {
// Read, decode, and parse the private key
fileBytes, _ := ioutil.ReadFile(keyPath)
fileBytes, _ := ioutil.ReadFile(*c.config.PrivateKeyPath)
x509Encoded, _ := pem.Decode(fileBytes)
parsedKey, _ := x509.ParsePKCS8PrivateKey(x509Encoded.Bytes)
ecdsaPrivateKey, _ := parsedKey.(*ecdsa.PrivateKey)
return ecdsaPrivateKey
}

func (c *Client) createJwt(config weatherKitConfig) string {
func (c *Client) createJwt() string {

// Define standard claims
claims := jwt.StandardClaims{
Issuer: *config.TeamId,
Issuer: *c.config.TeamId,
IssuedAt: time.Now().UTC().Unix(),
ExpiresAt: time.Now().Add(time.Minute * 5).UTC().Unix(),
Subject: *config.ServiceId,
Subject: *c.config.ServiceId,
}

// Create the JWT
Expand All @@ -106,12 +113,12 @@ func (c *Client) createJwt(config weatherKitConfig) string {
// Add header information
token.Header = map[string]interface{}{
"alg": "ES256",
"kid": config.KeyId,
"kid": c.config.KeyId,
"id": claims.Issuer + "." + claims.Subject,
}

// Sign and get the complete encoded token as a string using the secret
ecdsaPrivateKey := c.loadPrivateKey(*config.PrivateKeyPath)
ecdsaPrivateKey := c.loadPrivateKey()
tokenString, _ := token.SignedString(ecdsaPrivateKey)

return tokenString
Expand Down
4 changes: 4 additions & 0 deletions weatherkit/connection_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type weatherKitConfig struct {
ServiceId *string `cty:"service_id"`
TeamId *string `cty:"team_id"`
PrivateKeyPath *string `cty:"private_key_path"`
Token *string `cty:"token"`
}

var ConfigSchema = map[string]*schema.Attribute{
Expand All @@ -25,6 +26,9 @@ var ConfigSchema = map[string]*schema.Attribute{
"private_key_path": {
Type: schema.TypeString,
},
"token": {
Type: schema.TypeString,
},
}

func ConfigInstance() interface{} {
Expand Down
4 changes: 2 additions & 2 deletions weatherkit/table_weatherkit_availability.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ func tableWeatherKitAvailability() *plugin.Table {
Columns: []*plugin.Column{
{
Name: "latitude",
Type: proto.ColumnType_DOUBLE,
Type: proto.ColumnType_STRING,
Description: "A numeric value indicating the latitude of the coordinate between -90 and 90.",
Transform: transform.FromQual("latitude"),
},
{
Name: "longitude",
Type: proto.ColumnType_DOUBLE,
Type: proto.ColumnType_STRING,
Description: "A numeric value indicating the longitude of the coordinate between -180 and 180.",
Transform: transform.FromQual("longitude"),
},
Expand Down
4 changes: 2 additions & 2 deletions weatherkit/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@ func connect(_ context.Context, d *plugin.QueryData) (*Client, error) {

// Prefer config options given in Steampipe
weatherKitConfig := GetConfig(d.Connection)
if weatherKitConfig.KeyId == nil || weatherKitConfig.ServiceId == nil || weatherKitConfig.TeamId == nil || weatherKitConfig.PrivateKeyPath == nil {
if (weatherKitConfig.KeyId == nil || weatherKitConfig.ServiceId == nil || weatherKitConfig.TeamId == nil || weatherKitConfig.PrivateKeyPath == nil) && (weatherKitConfig.Token == nil) {
return nil, errors.New("invalid configuration from ~/.steampipe/config/weatherkit.spc")
}

// Make a new client that can hold the JWT
client := NewClient(http.DefaultClient, weatherKitConfig)
client := NewClient(http.DefaultClient, &weatherKitConfig)

// Save to cache
d.ConnectionManager.Cache.Set(cacheKey, client)
Expand Down

0 comments on commit 7d73616

Please sign in to comment.