Skip to content

Commit

Permalink
feat: add webhook (#96)
Browse files Browse the repository at this point in the history
* feat: add webhook

* feat: switch to non deprecated implementation

* feat: upgrade go

* feat: install golangci-lint from source

* feat: use go version from mod file

---------

Co-authored-by: Federico Sordillo <[email protected]>
  • Loading branch information
fes300 and Federico Sordillo authored Jun 8, 2023
1 parent 70d5d72 commit fcfd47d
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 31 deletions.
39 changes: 13 additions & 26 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,18 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.17
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
- uses: actions/setup-go@v4
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: v1.29

# Optional: working directory, useful for monorepos
# working-directory: somedir

# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

# Optional: if set to true then the all caching functionality will be complete disabled,
# takes precedence over all other caching options.
# skip-cache: true

# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true

# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true
go-version-file: go.mod
# The builtin cache feature ensures that installing golangci-lint
# is consistently fast.
cache: true
cache-dependency-path: go.sum
- name: install-golangci-lint
# Install golangci-lint from source instead of using
# golangci-lint-action to ensure the golangci-lint binary is built with
# the same Go version we're targeting.
run: go install github.com/golangci/golangci-lint/cmd/[email protected]
- name: golangci-lint
run: golangci-lint run --version --verbose --out-format=github-actions
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ go 1.18

require github.com/google/uuid v1.3.0

require github.com/go-resty/resty/v2 v2.7.0
require (
github.com/go-resty/resty/v2 v2.7.0
github.com/golang-jwt/jwt/v5 v5.0.0
)

require golang.org/x/net v0.7.0 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
Expand Down
20 changes: 16 additions & 4 deletions lago.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,18 @@ func (c *Client) SetDebug(debug bool) *Client {
}

func (c *Client) Get(ctx context.Context, cr *ClientRequest) (interface{}, *Error) {
resp, err := c.HttpClient.R().
hasResult := cr.Result != nil

request := c.HttpClient.R().
SetContext(ctx).
SetError(&Error{}).
SetResult(cr.Result).
SetQueryParams(cr.QueryParams).
SetQueryParams(cr.QueryParams)

if hasResult {
request.SetResult(cr.Result)
}

resp, err := request.
Get(cr.Path)
if err != nil {
return nil, &Error{Err: err}
Expand All @@ -87,7 +94,11 @@ func (c *Client) Get(ctx context.Context, cr *ClientRequest) (interface{}, *Erro
return nil, err
}

return resp.Result(), nil
if hasResult {
return resp.Result(), nil
}

return resp.String(), nil
}

func (c *Client) Post(ctx context.Context, cr *ClientRequest) (interface{}, *Error) {
Expand All @@ -97,6 +108,7 @@ func (c *Client) Post(ctx context.Context, cr *ClientRequest) (interface{}, *Err
SetResult(cr.Result).
SetBody(cr.Body).
Post(cr.Path)

if err != nil {
return nil, &Error{Err: err}
}
Expand Down
110 changes: 110 additions & 0 deletions webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package lago

import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"

jwt "github.com/golang-jwt/jwt/v5"
)

type WebhookRequest struct {
client *Client
}

func (c *Client) Webhook() *WebhookRequest {
return &WebhookRequest{
client: c,
}
}

func (wr *WebhookRequest) GetPublicKey(ctx context.Context) (*rsa.PublicKey, *Error) {
clientRequest := &ClientRequest{
Path: "webhooks/public_key",
}

result, err := wr.client.Get(ctx, clientRequest)
if err != nil {
return nil, err
}

validatedResult, ok := result.(string)
if !ok {
return nil, &Error{
Err: errors.New("response is not a string"),
HTTPStatusCode: http.StatusInternalServerError,
Msg: "response is not a string",
}
}

// Decode the base64-encoded key
bytesResult, decodeErr := base64.StdEncoding.DecodeString(validatedResult)
if err != nil {
return nil, &Error{
Err: decodeErr,
HTTPStatusCode: http.StatusInternalServerError,
Msg: "cannot decode the key",
}
}

// Parse the PEM block
block, _ := pem.Decode(bytesResult)
if block == nil || block.Type != "PUBLIC KEY" {
return nil, &Error{
Err: errors.New("Failed to decode PEM block containing public key"),
HTTPStatusCode: http.StatusInternalServerError,
Msg: "Failed to decode PEM block containing public key",
}
}

// Parse the DER-encoded public key
publicKey, parseErr := x509.ParsePKIXPublicKey(block.Bytes)
if parseErr != nil {
return nil, &Error{
Err: parseErr,
HTTPStatusCode: http.StatusInternalServerError,
Msg: "Failed to to parse the public key",
}
}

rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return nil, &Error{
Err: errors.New("Unexpected type of public key"),
HTTPStatusCode: http.StatusInternalServerError,
Msg: "Unexpected type of public key",
}
}

return rsaPublicKey, nil
}

func (wr *WebhookRequest) ValidateSignature(ctx context.Context, signature string) (bool, *Error) {
publicKey, err := wr.GetPublicKey(ctx)
if err != nil {
return false, err
}

token, parseErr := jwt.Parse(signature, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return publicKey, nil
})
if parseErr != nil {
return false, &Error{
Err: parseErr,
HTTPStatusCode: http.StatusInternalServerError,
Msg: "cannot parse token",
}
}

println(fmt.Printf("token: %+v", token))

return token.Valid, nil
}

0 comments on commit fcfd47d

Please sign in to comment.