From 4ed12c2484d0bce19e50d5864aff47c226bcf431 Mon Sep 17 00:00:00 2001 From: "mojo-machine[bot]" <111131124+mojo-machine[bot]@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:39:05 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20Sync=20from=20monorepo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/wearemojo/mojo/commit/8d099a1ce925460ed2d07c9585ca40a85125ec19 --- lib/discourse/roundtripper.go | 6 +- lib/googleiid/googleiid.go | 92 +++++++++++++++++++++++++ lib/googleiid/roundtripper.go | 50 ++++++++++++++ lib/postmark/{client.go => postmark.go} | 36 +++------- lib/postmark/roundtripper.go | 16 ++++- 5 files changed, 169 insertions(+), 31 deletions(-) create mode 100644 lib/googleiid/googleiid.go create mode 100644 lib/googleiid/roundtripper.go rename lib/postmark/{client.go => postmark.go} (70%) diff --git a/lib/discourse/roundtripper.go b/lib/discourse/roundtripper.go index 2e3aabd..0d51d76 100644 --- a/lib/discourse/roundtripper.go +++ b/lib/discourse/roundtripper.go @@ -8,12 +8,14 @@ type roundTripper struct { header http.Header } -func (h roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { +func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + req = req.Clone(req.Context()) + if req.Header == nil { req.Header = http.Header{} } - for k, v := range h.header { + for k, v := range r.header { req.Header[k] = v } diff --git a/lib/googleiid/googleiid.go b/lib/googleiid/googleiid.go new file mode 100644 index 0000000..65a82c2 --- /dev/null +++ b/lib/googleiid/googleiid.go @@ -0,0 +1,92 @@ +package googleiid + +import ( + "context" + "time" + + "github.com/wearemojo/mojo-public-go/lib/errgroup" + "github.com/wearemojo/mojo-public-go/lib/httpclient" + "github.com/wearemojo/mojo-public-go/lib/jsonclient" + "github.com/wearemojo/mojo-public-go/lib/secret" +) + +const baseURL = "https://iid.googleapis.com" + +type Client struct { + client *jsonclient.Client +} + +func NewClient(ctx context.Context, serverKeySecretID, vapidPublicKeySecretID string) (*Client, error) { + g := errgroup.WithContext(ctx) + + g.Go(func(ctx context.Context) (err error) { + _, err = secret.Get(ctx, serverKeySecretID) + return + }) + + g.Go(func(ctx context.Context) (err error) { + _, err = secret.Get(ctx, vapidPublicKeySecretID) + return + }) + + if err := g.Wait(); err != nil { + return nil, err + } + + return &Client{ + client: jsonclient.NewClient( + baseURL, + httpclient.NewClient(5*time.Second, roundTripper{ + ServerKeySecretID: serverKeySecretID, + VAPIDPublicKeySecretID: vapidPublicKeySecretID, + }), + ), + }, nil +} + +type APNSRequest struct { + Application string `json:"application"` + Sandbox bool `json:"sandbox"` + APNSTokens []string `json:"apns_tokens"` +} + +type APNSResponse struct { + // order does not relate to the request ordering - match by token + Results []APNSResponseResult `json:"results"` +} + +type APNSResponseResult struct { + RegistrationToken string `json:"registration_token"` + APNSToken string `json:"apns_token"` + Status string `json:"status"` +} + +func (r APNSResponseResult) Valid() bool { + // we've also noticed `INVALID_ARGUMENT` and `INTERNAL` on the status field + return r.Status == "OK" && r.APNSToken != "" && r.RegistrationToken != "" +} + +type WebPushRequest struct { + Endpoint string `json:"endpoint"` + + Keys WebPushRequestKeys `json:"keys"` +} + +type WebPushRequestKeys struct { + Auth string `json:"auth"` + P256DH string `json:"p256dh"` +} + +type WebPushResponse struct { + Token string `json:"token"` +} + +func (c *Client) ImportAPNSTokens(ctx context.Context, req *APNSRequest) (res *APNSResponse, err error) { + // https://web.archive.org/web/20220407013020/https://developers.google.com/instance-id/reference/server#create_registration_tokens_for_apns_tokens + return res, c.client.Do(ctx, "POST", "iid/v1:batchImport", nil, req, &res) +} + +func (c *Client) ImportWebPushSubscription(ctx context.Context, req *WebPushRequest) (res *WebPushResponse, err error) { + // https://web.archive.org/web/20220407013020/https://developers.google.com/instance-id/reference/server#import_push_subscriptions + return res, c.client.Do(ctx, "POST", "v1/web/iid", nil, req, &res) +} diff --git a/lib/googleiid/roundtripper.go b/lib/googleiid/roundtripper.go new file mode 100644 index 0000000..ce2f8f4 --- /dev/null +++ b/lib/googleiid/roundtripper.go @@ -0,0 +1,50 @@ +package googleiid + +import ( + "context" + "net/http" + + "github.com/wearemojo/mojo-public-go/lib/errgroup" + "github.com/wearemojo/mojo-public-go/lib/secret" +) + +type roundTripper struct { + ServerKeySecretID string + VAPIDPublicKeySecretID string +} + +func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + ctx := req.Context() + req = req.Clone(ctx) + + var serverKey string + var vapidPublicKey string + + g := errgroup.WithContext(ctx) + + g.Go(func(ctx context.Context) (err error) { + serverKey, err = secret.Get(ctx, r.ServerKeySecretID) + return + }) + + g.Go(func(ctx context.Context) (err error) { + vapidPublicKey, err = secret.Get(ctx, r.VAPIDPublicKeySecretID) + return + }) + + if err := g.Wait(); err != nil { + return nil, err + } + + if req.Header == nil { + req.Header = http.Header{} + } + + // https://web.archive.org/web/20221206045856/https://firebase.google.com/docs/cloud-messaging/auth-server#authorize-http-requests + req.Header.Set("Authorization", "key="+serverKey) + + // https://developers.google.com/instance-id/reference/server#parameters_5 + req.Header.Set("Crypto-Key", "p256ecdsa="+vapidPublicKey) + + return http.DefaultTransport.RoundTrip(req) +} diff --git a/lib/postmark/client.go b/lib/postmark/postmark.go similarity index 70% rename from lib/postmark/client.go rename to lib/postmark/postmark.go index 8c87007..6b98263 100644 --- a/lib/postmark/client.go +++ b/lib/postmark/postmark.go @@ -13,6 +13,8 @@ import ( "github.com/wearemojo/mojo-public-go/lib/secret" ) +const baseURL = "https://api.postmarkapp.com" + //nolint:tagliatelle // postmark uses title case type Response struct { To string `json:"To"` @@ -32,42 +34,24 @@ type EmailWithTemplate struct { } type Client struct { - BaseURL string - - secretID string + client *jsonclient.Client } -func NewClient(ctx context.Context, baseURL, secretID string) (*Client, error) { - if _, err := secret.Get(ctx, secretID); err != nil { +func NewClient(ctx context.Context, serverTokenSecretID string) (*Client, error) { + if _, err := secret.Get(ctx, serverTokenSecretID); err != nil { return nil, err } return &Client{ - BaseURL: baseURL, - - secretID: secretID, + client: jsonclient.NewClient( + baseURL, + httpclient.NewClient(5*time.Second, roundTripper{serverTokenSecretID}), + ), }, nil } -func (c *Client) client(ctx context.Context) (*jsonclient.Client, error) { - apiKey, err := secret.Get(ctx, c.secretID) - if err != nil { - return nil, err - } - - return jsonclient.NewClient( - c.BaseURL, - httpclient.NewClient(5*time.Second, roundTripper{apiKey}), - ), nil -} - func (c *Client) SendWithTemplate(ctx context.Context, req *EmailWithTemplate) (res *Response, err error) { - jsonClient, err := c.client(ctx) - if err != nil { - return nil, err - } - - err = jsonClient.Do(ctx, "POST", "email/withTemplate", nil, req, &res) + err = c.client.Do(ctx, "POST", "email/withTemplate", nil, req, &res) if cerr, ok := gerrors.As[cher.E](err); ok { cerr.Code = fmt.Sprintf("postmark_%s", cerr.Code) return nil, cerr diff --git a/lib/postmark/roundtripper.go b/lib/postmark/roundtripper.go index 19ed338..f920fb8 100644 --- a/lib/postmark/roundtripper.go +++ b/lib/postmark/roundtripper.go @@ -2,18 +2,28 @@ package postmark import ( "net/http" + + "github.com/wearemojo/mojo-public-go/lib/secret" ) type roundTripper struct { - serverToken string + SecretID string } -func (h roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { +func (r roundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + ctx := req.Context() + req = req.Clone(ctx) + + serverToken, err := secret.Get(ctx, r.SecretID) + if err != nil { + return nil, err + } + if req.Header == nil { req.Header = http.Header{} } - req.Header.Set("X-Postmark-Server-Token", h.serverToken) + req.Header.Set("X-Postmark-Server-Token", serverToken) return http.DefaultTransport.RoundTrip(req) }