diff --git a/backends/rapidpro/backend_test.go b/backends/rapidpro/backend_test.go index c84f5c8b1..d5e7faf8a 100644 --- a/backends/rapidpro/backend_test.go +++ b/backends/rapidpro/backend_test.go @@ -962,14 +962,14 @@ func (ts *BackendTestSuite) TestWriteAttachment() { content := "" switch r.URL.Path { case "/test.jpg": - content = "malformedjpegbody" + content = "\xFF\xD8\xFF" case "/giffy": content = "GIF87aandstuff" case "/header": w.Header().Add("Content-Type", "image/png") - content = "nothingbody" + content = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" default: content = "unknown" diff --git a/backends/rapidpro/msg.go b/backends/rapidpro/msg.go index 341395563..f9ba55cf8 100644 --- a/backends/rapidpro/msg.go +++ b/backends/rapidpro/msg.go @@ -642,3 +642,8 @@ func (m *DBMsg) WithURNAuth(auth string) courier.Msg { m.URNAuth_ = auth return m } + +func (m *DBMsg) WithPresignedURL(urls []string) courier.Msg { + m.Attachments_ = urls + return m +} diff --git a/config.go b/config.go index c91ce19ea..88c55a3a2 100644 --- a/config.go +++ b/config.go @@ -57,8 +57,8 @@ func NewConfig() *Config { S3MediaPrefix: "/media/", S3DisableSSL: false, S3ForcePathStyle: false, - AWSAccessKeyID: "", - AWSSecretAccessKey: "", + AWSAccessKeyID: "missing_aws_access_key_id", + AWSSecretAccessKey: "missing_secret_key_id", FacebookApplicationSecret: "missing_facebook_app_secret", FacebookWebhookSecret: "missing_facebook_webhook_secret", WhatsappAdminSystemUserToken: "missing_whatsapp_admin_system_user_token", diff --git a/handlers/facebookapp/facebookapp.go b/handlers/facebookapp/facebookapp.go index a0364f77e..2ef9c721e 100644 --- a/handlers/facebookapp/facebookapp.go +++ b/handlers/facebookapp/facebookapp.go @@ -80,7 +80,6 @@ func init() { courier.RegisterHandler(newHandler("IG", "Instagram", false)) courier.RegisterHandler(newHandler("FBA", "Facebook", false)) courier.RegisterHandler(newHandler("WAC", "WhatsApp Cloud", false)) - } type handler struct { @@ -1324,6 +1323,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if err != nil { return status, err } + if attType == "application" { attType = "document" } @@ -1424,6 +1424,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if err != nil { return status, err } + if attType == "application" { attType = "document" } @@ -1460,6 +1461,7 @@ func (h *handler) sendCloudAPIWhatsappMsg(ctx context.Context, msg courier.Msg) if len(msg.Attachments()) > 0 { attType, attURL := handlers.SplitAttachment(msg.Attachments()[i]) attType = strings.Split(attType, "/")[0] + if attType == "application" { attType = "document" } diff --git a/handlers/facebookapp/facebookapp_test.go b/handlers/facebookapp/facebookapp_test.go index 26c43c18e..1f7db7561 100644 --- a/handlers/facebookapp/facebookapp_test.go +++ b/handlers/facebookapp/facebookapp_test.go @@ -532,12 +532,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Text: "audio caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"audio/mpeg:https://foo.bar/audio.mp3"}, + Attachments: []string{"audio/mpeg:https://foo.bar/attachments/audio.mp3"}, Responses: map[MockedRequest]MockedResponse{ MockedRequest{ Method: "POST", Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/attachments/audio.mp3"}}`, }: MockedResponse{ Status: 201, Body: `{ "messages": [{"id": "157b5e14568e8"}] }`, @@ -556,25 +556,25 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Text: "document caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"application/pdf:https://foo.bar/document.pdf"}, ResponseStatus: 201, + Attachments: []string{"application/pdf:https://foo.bar/attachments/document.pdf"}, ResponseStatus: 201, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/document.pdf","caption":"document caption","filename":"document.pdf"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"document","document":{"link":"https://foo.bar/attachments/document.pdf","caption":"document caption","filename":"document.pdf"}}`, SendPrep: setSendURL}, {Label: "Image Send", Text: "image caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"image caption"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/attachments/image.jpg","caption":"image caption"}}`, SendPrep: setSendURL}, {Label: "Video Send", Text: "video caption", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + Attachments: []string{"video/mp4:https://foo.bar/attachments/video.mp4"}, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/video.mp4","caption":"video caption"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"video","video":{"link":"https://foo.bar/attachments/video.mp4","caption":"video caption"}}`, SendPrep: setSendURL}, {Label: "Template Send", Text: "templated message", @@ -614,19 +614,19 @@ var SendTestCasesWAC = []ChannelSendTestCase{ {Label: "Interactive Button Message Send with attachment", Text: "Interactive Button Msg", URN: "whatsapp:250788123123", QuickReplies: []string{"BUTTON1"}, Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","video":{},"image":{"link":"https://foo.bar/image.jpg"},"document":{}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"interactive","interactive":{"type":"button","header":{"type":"image","video":{},"image":{"link":"https://foo.bar/attachments/image.jpg"},"document":{}},"body":{"text":"Interactive Button Msg"},"action":{"buttons":[{"type":"reply","reply":{"id":"0","title":"BUTTON1"}}]}}}`, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, SendPrep: setSendURL}, {Label: "Interactive List Message Send with attachment", Text: "Interactive List Msg", URN: "whatsapp:250788123123", QuickReplies: []string{"ROW1", "ROW2", "ROW3", "ROW4"}, Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, Responses: map[MockedRequest]MockedResponse{ MockedRequest{ Method: "POST", Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/image.jpg"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"image","image":{"link":"https://foo.bar/attachments/image.jpg"}}`, }: MockedResponse{ Status: 201, Body: `{ "messages": [{"id": "157b5e14568e8"}] }`, @@ -644,12 +644,12 @@ var SendTestCasesWAC = []ChannelSendTestCase{ {Label: "Interactive Button Message Send with audio attachment", Text: "Interactive Button Msg", URN: "whatsapp:250788123123", QuickReplies: []string{"BUTTON0", "BUTTON1", "BUTTON2"}, Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"audio/mp3:https://foo.bar/audio.mp3"}, + Attachments: []string{"audio/mp3:https://foo.bar/attachments/audio.mp3"}, Responses: map[MockedRequest]MockedResponse{ MockedRequest{ Method: "POST", Path: "/12345_ID/messages", - Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/audio.mp3"}}`, + Body: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"audio","audio":{"link":"https://foo.bar/attachments/audio.mp3"}}`, }: MockedResponse{ Status: 201, Body: `{ "messages": [{"id": "157b5e14568e8"}] }`, @@ -668,25 +668,25 @@ var SendTestCasesWAC = []ChannelSendTestCase{ Text: "Media Message Msg", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"image","image":{"link":"https://foo.bar/image.jpg"}}]}]}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"image","image":{"link":"https://foo.bar/attachments/image.jpg"}}]}]}}`, SendPrep: setSendURL}, {Label: "Media Message Template Send - Video", Text: "Media Message Msg", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), - Attachments: []string{"video/mp4:https://foo.bar/video.mp4"}, + Attachments: []string{"video/mp4:https://foo.bar/attachments/video.mp4"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"video","video":{"link":"https://foo.bar/video.mp4"}}]}]}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"video","video":{"link":"https://foo.bar/attachments/video.mp4"}}]}]}}`, SendPrep: setSendURL}, {Label: "Media Message Template Send - Document", Text: "Media Message Msg", URN: "whatsapp:250788123123", Status: "W", ExternalID: "157b5e14568e8", Metadata: json.RawMessage(`{ "templating": { "template": { "name": "revive_issue", "uuid": "171f8a4d-f725-46d7-85a6-11aceff0bfe3" }, "namespace": "wa_template_namespace", "language": "eng", "country": "US", "variables": ["Chef", "tomorrow"]}}`), - Attachments: []string{"application/pdf:https://foo.bar/document.pdf"}, + Attachments: []string{"application/pdf:https://foo.bar/attachments/document.pdf"}, ResponseBody: `{ "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"document","document":{"link":"https://foo.bar/document.pdf","filename":"document.pdf"}}]}]}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"250788123123","type":"template","template":{"name":"revive_issue","language":{"policy":"deterministic","code":"en_US"},"components":[{"type":"body","parameters":[{"type":"text","text":"Chef"},{"type":"text","text":"tomorrow"}]},{"type":"header","parameters":[{"type":"document","document":{"link":"https://foo.bar/attachments/document.pdf","filename":"document.pdf"}}]}]}}`, SendPrep: setSendURL}, {Label: "Link Sending", Text: "Link Sending https://link.com", URN: "whatsapp:250788123123", Path: "/12345_ID/messages", @@ -704,9 +704,9 @@ var SendTestCasesWAC = []ChannelSendTestCase{ {Label: "Attachment with Caption", Text: "Simple Message", URN: "whatsapp:5511987654321", Path: "/12345_ID/messages", Status: "W", ExternalID: "157b5e14568e8", - Attachments: []string{"image/jpeg:https://foo.bar/image.jpg"}, + Attachments: []string{"image/jpeg:https://foo.bar/attachments/image.jpg"}, ResponseBody: `{ "contacts":[{"input":"5511987654321", "wa_id":"551187654321"}], "messages": [{"id": "157b5e14568e8"}] }`, ResponseStatus: 201, - RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"5511987654321","type":"image","image":{"link":"https://foo.bar/image.jpg","caption":"Simple Message"}}`, + RequestBody: `{"messaging_product":"whatsapp","recipient_type":"individual","to":"5511987654321","type":"image","image":{"link":"https://foo.bar/attachments/image.jpg","caption":"Simple Message"}}`, SendPrep: setSendURL, NewURN: "whatsapp:551187654321"}, } diff --git a/msg.go b/msg.go index c94dd6491..79111185a 100644 --- a/msg.go +++ b/msg.go @@ -123,6 +123,7 @@ type Msg interface { WithURNAuth(auth string) Msg WithMetadata(metadata json.RawMessage) Msg WithFlow(flow *FlowReference) Msg + WithPresignedURL(urls []string) Msg EventID() int64 SessionStatus() string diff --git a/presigned_url.go b/presigned_url.go new file mode 100644 index 000000000..fd1bc498d --- /dev/null +++ b/presigned_url.go @@ -0,0 +1,52 @@ +package courier + +import ( + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" +) + +func PresignedURL(link string, accessKey string, secretKey string, region string) (string, error) { + + splitURL := strings.Split(link, ".") + bucketName := strings.TrimPrefix(splitURL[0], "https://") + + splitURL = strings.Split(link, "attachments") + objectKey := "/attachments" + splitURL[1] + + sess, err := session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), + Region: aws.String(region), + }) + if err != nil { + return "", err + } + + svc := s3.New(sess) + + req, _ := svc.GetObjectRequest(&s3.GetObjectInput{ + Bucket: aws.String(bucketName), + Key: aws.String(objectKey), + }) + urlStr, err := req.Presign((24 * time.Hour) * 7) + + if err != nil { + return "", err + } + + return urlStr, nil + +} + +// SplitAttachment takes an attachment string and returns the media type and URL for the attachment +func SplitAttachment(attachment string) (string, string) { + parts := strings.SplitN(attachment, ":", 2) + if len(parts) < 2 { + return "", parts[0] + } + return parts[0], parts[1] +} diff --git a/sender.go b/sender.go index 094e1b437..21d22e1b3 100644 --- a/sender.go +++ b/sender.go @@ -164,7 +164,19 @@ func (w *Sender) sendMessage(msg Msg) { log = log.WithField("msg_id", msg.ID().String()).WithField("msg_text", msg.Text()).WithField("msg_urn", msg.URN().Identity()) if len(msg.Attachments()) > 0 { + var attachments []string log = log.WithField("attachments", msg.Attachments()) + for _, att := range msg.Attachments() { + attType, attURL := SplitAttachment(att) + url, err := PresignedURL(attURL, server.Config().AWSAccessKeyID, server.Config().AWSSecretAccessKey, server.Config().S3Region) + if err != nil { + log.WithError(err).Error("error converting attachment for pre-signed url") + } + att = attType + ":" + url + attachments = append(attachments, att) + } + msg = msg.WithPresignedURL(attachments) + } if len(msg.QuickReplies()) > 0 { log = log.WithField("quick_replies", msg.QuickReplies()) diff --git a/test.go b/test.go index 86240b5df..e468ce969 100644 --- a/test.go +++ b/test.go @@ -628,6 +628,10 @@ func (m *mockMsg) WithAttachment(url string) Msg { func (m *mockMsg) WithMetadata(metadata json.RawMessage) Msg { m.metadata = metadata; return m } func (m *mockMsg) WithFlow(flow *FlowReference) Msg { m.flow = flow; return m } +func (m *mockMsg) WithPresignedURL(urls []string) Msg { + m.attachments = urls + return m +} //----------------------------------------------------------------------------- // Mock status implementation