From fc7aa86545f9e74c22738891af92abafe0030d7f Mon Sep 17 00:00:00 2001 From: Jonas Hungershausen Date: Tue, 6 Dec 2022 05:19:49 -0500 Subject: [PATCH] feat: store errors of courier message (#2914) BREAKING CHANGE: The `/admin/courier/messages` endpoint now uses `keysetpagination` instead. --- ...if_no_message_is_found-endpoint=admin.json | 1 + ...f_no_message_is_found-endpoint=public.json | 1 + ...parameter_is_malformed-endpoint=admin.json | 1 + ...arameter_is_malformed-endpoint=public.json | 1 + courier/courier_dispatcher.go | 48 ++- courier/courier_dispatcher_test.go | 30 +- courier/handler.go | 94 ++++- courier/handler_test.go | 151 ++++++-- courier/message.go | 54 ++- courier/message_dispatch.go | 55 +++ courier/persistence.go | 11 +- courier/sms_test.go | 2 +- courier/smtp.go | 6 +- courier/template/load_template_test.go | 4 +- courier/test/persistence.go | 51 ++- driver/config/config_test.go | 4 +- go.mod | 3 +- go.sum | 16 + identity/validator_test.go | 4 +- internal/client-go/.openapi-generator/FILES | 2 + internal/client-go/README.md | 2 + internal/client-go/api_courier.go | 172 ++++++++- .../model_courier_message_dispatch.go | 301 ++++++++++++++++ internal/client-go/model_message.go | 339 ++++++++---------- internal/client-go/model_message_dispatch.go | 265 ++++++++++++++ internal/httpclient/.openapi-generator/FILES | 2 + internal/httpclient/README.md | 2 + internal/httpclient/api_courier.go | 172 ++++++++- internal/httpclient/model_message.go | 339 ++++++++---------- internal/httpclient/model_message_dispatch.go | 265 ++++++++++++++ .../testdata/20221205095201_testdata.sql | 41 +++ ...0_add_courier_send_attempts_table.down.sql | 1 + ...d_courier_send_attempts_table.mysql.up.sql | 13 + ...000_add_courier_send_attempts_table.up.sql | 13 + persistence/sql/persister_courier.go | 66 +++- persistence/sql/persister_session.go | 2 +- selfservice/hook/web_hook_integration_test.go | 6 +- .../oidc/provider_private_net_test.go | 10 +- session/handler.go | 2 +- session/session.go | 9 +- session/test/persistence.go | 6 +- spec/api.json | 135 ++++++- spec/swagger.json | 128 ++++++- .../profiles/network/errors.spec.ts | 8 +- x/xsql/sql.go | 1 + 45 files changed, 2285 insertions(+), 554 deletions(-) create mode 100644 courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=admin.json create mode 100644 courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=public.json create mode 100644 courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=admin.json create mode 100644 courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=public.json create mode 100644 courier/message_dispatch.go create mode 100644 internal/client-go/model_courier_message_dispatch.go create mode 100644 internal/client-go/model_message_dispatch.go create mode 100644 internal/httpclient/model_message_dispatch.go create mode 100644 persistence/sql/migratest/testdata/20221205095201_testdata.sql create mode 100644 persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.down.sql create mode 100644 persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.mysql.up.sql create mode 100644 persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.up.sql diff --git a/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=admin.json b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=admin.json new file mode 100644 index 00000000000..41490e155b6 --- /dev/null +++ b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=admin.json @@ -0,0 +1 @@ +"{\"error\":{\"code\":404,\"status\":\"Not Found\",\"message\":\"Unable to locate the resource\"}}\n" diff --git a/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=public.json b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=public.json new file mode 100644 index 00000000000..41490e155b6 --- /dev/null +++ b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_no_message_is_found-endpoint=public.json @@ -0,0 +1 @@ +"{\"error\":{\"code\":404,\"status\":\"Not Found\",\"message\":\"Unable to locate the resource\"}}\n" diff --git a/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=admin.json b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=admin.json new file mode 100644 index 00000000000..babf2e28ade --- /dev/null +++ b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=admin.json @@ -0,0 +1 @@ +"{\"error\":{\"code\":400,\"status\":\"Bad Request\",\"debug\":\"could not parse parameter {id} as UUID, got \",\"message\":\"uuid: incorrect UUID length 10 in string \\\"not-a-uuid\\\"\"}}\n" diff --git a/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=public.json b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=public.json new file mode 100644 index 00000000000..babf2e28ade --- /dev/null +++ b/courier/.snapshots/TestHandler-handler=getCourierMessage-case=returns_an_error_if_parameter_is_malformed-endpoint=public.json @@ -0,0 +1 @@ +"{\"error\":{\"code\":400,\"status\":\"Bad Request\",\"debug\":\"could not parse parameter {id} as UUID, got \",\"message\":\"uuid: incorrect UUID length 10 in string \\\"not-a-uuid\\\"\"}}\n" diff --git a/courier/courier_dispatcher.go b/courier/courier_dispatcher.go index 938772c923d..41369e201eb 100644 --- a/courier/courier_dispatcher.go +++ b/courier/courier_dispatcher.go @@ -10,24 +10,6 @@ import ( ) func (c *courier) DispatchMessage(ctx context.Context, msg Message) error { - maxRetries := c.deps.CourierConfig().CourierMessageRetries(ctx) - - if msg.SendCount > maxRetries { - if err := c.deps.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusAbandoned); err != nil { - c.deps.Logger(). - WithError(err). - WithField("message_id", msg.ID). - Error(`Unable to reset the retried message's status to "abandoned".`) - return err - } - - // Skip the message - c.deps.Logger(). - WithField("message_id", msg.ID). - Warnf(`Message was abandoned because it did not deliver after %d attempts`, msg.SendCount) - return nil - } - if err := c.deps.CourierPersister().IncrementMessageSendCount(ctx, msg.ID); err != nil { c.deps.Logger(). WithError(err). @@ -68,6 +50,8 @@ func (c *courier) DispatchMessage(ctx context.Context, msg Message) error { } func (c *courier) DispatchQueue(ctx context.Context) error { + maxRetries := c.deps.CourierConfig().CourierMessageRetries(ctx) + messages, err := c.deps.CourierPersister().NextMessages(ctx, 10) if err != nil { if errors.Is(err, ErrQueueEmpty) { @@ -77,7 +61,27 @@ func (c *courier) DispatchQueue(ctx context.Context) error { } for k, msg := range messages { - if err := c.DispatchMessage(ctx, msg); err != nil { + if msg.SendCount > maxRetries { + if err := c.deps.CourierPersister().SetMessageStatus(ctx, msg.ID, MessageStatusAbandoned); err != nil { + c.deps.Logger(). + WithError(err). + WithField("message_id", msg.ID). + Error(`Unable to set the retried message's status to "abandoned".`) + return err + } + // Skip the message + c.deps.Logger(). + WithField("message_id", msg.ID). + Warnf(`Message was abandoned because it did not deliver after %d attempts`, msg.SendCount) + + } else if err := c.DispatchMessage(ctx, msg); err != nil { + + if err := c.deps.CourierPersister().RecordDispatch(ctx, msg.ID, CourierMessageDispatchStatusFailed, err); err != nil { + c.deps.Logger(). + WithError(err). + WithField("message_id", msg.ID). + Error(`Unable to record failure log entry.`) + } for _, replace := range messages[k:] { if err := c.deps.CourierPersister().SetMessageStatus(ctx, replace.ID, MessageStatusQueued); err != nil { @@ -92,6 +96,12 @@ func (c *courier) DispatchQueue(ctx context.Context) error { } return err + } else if err := c.deps.CourierPersister().RecordDispatch(ctx, msg.ID, CourierMessageDispatchStatusSuccess, nil); err != nil { + c.deps.Logger(). + WithError(err). + WithField("message_id", msg.ID). + Error(`Unable to record success log entry.`) + // continue with execution, as the message was successfully dispatched } } diff --git a/courier/courier_dispatcher_test.go b/courier/courier_dispatcher_test.go index ce784f7e0b5..8b807de581e 100644 --- a/courier/courier_dispatcher_test.go +++ b/courier/courier_dispatcher_test.go @@ -9,6 +9,7 @@ import ( "github.com/gofrs/uuid" "github.com/stretchr/testify/require" + "github.com/tidwall/gjson" "github.com/ory/kratos/courier" "github.com/ory/kratos/courier/template" @@ -54,24 +55,9 @@ func TestDispatchMessageWithInvalidSMTP(t *testing.T) { messages, err := reg.CourierPersister().NextMessages(ctx, 10) require.Len(t, messages, 1) }) - - t.Run("case=max retries reached", func(t *testing.T) { - id := queueNewMessage(t, ctx, c, reg) - message, err := reg.CourierPersister().LatestQueuedMessage(ctx) - require.NoError(t, err) - require.Equal(t, id, message.ID) - message.SendCount = 6 - - err = c.DispatchMessage(ctx, *message) - require.NoError(t, err) - - messages, err := reg.CourierPersister().NextMessages(ctx, 1) - require.Empty(t, messages) - }) - } -func TestDispatchMessage2(t *testing.T) { +func TestDispatchQueue(t *testing.T) { ctx := context.Background() conf, reg := internal.NewRegistryDefaultWithDSN(t, "") @@ -83,12 +69,7 @@ func TestDispatchMessage2(t *testing.T) { ctx, cancel := context.WithCancel(ctx) defer cancel() - id, err := c.QueueEmail(ctx, templates.NewTestStub(reg, &templates.TestStubModel{ - To: "test-recipient-1@example.org", - Subject: "test-subject-1", - Body: "test-body-1", - })) - require.NoError(t, err) + id := queueNewMessage(t, ctx, c, reg) require.NotEqual(t, uuid.Nil, id) // Fails to deliver the first time @@ -106,8 +87,13 @@ func TestDispatchMessage2(t *testing.T) { var message courier.Message err = reg.Persister().GetConnection(ctx). Where("status = ?", courier.MessageStatusAbandoned). + Eager("Dispatches"). First(&message) require.NoError(t, err) require.Equal(t, id, message.ID) + + require.Len(t, message.Dispatches, 2) + require.Contains(t, gjson.GetBytes(message.Dispatches[0].Error, "reason").String(), "failed to send email via smtp") + require.Contains(t, gjson.GetBytes(message.Dispatches[1].Error, "reason").String(), "failed to send email via smtp") } diff --git a/courier/handler.go b/courier/handler.go index 3bcd6f96e69..072bc73099b 100644 --- a/courier/handler.go +++ b/courier/handler.go @@ -4,19 +4,24 @@ package courier import ( + "fmt" "net/http" + "github.com/gofrs/uuid" + + "github.com/ory/herodot" + "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/pagination/migrationpagination" "github.com/julienschmidt/httprouter" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/x" - "github.com/ory/x/urlx" ) const AdminRouteCourier = "/courier" -const AdminRouteMessages = AdminRouteCourier + "/messages" +const AdminRouteListMessages = AdminRouteCourier + "/messages" +const AdminRouteGetMessage = AdminRouteCourier + "/messages/:msgID" type ( handlerDependencies interface { @@ -39,12 +44,14 @@ func NewHandler(r handlerDependencies) *Handler { } func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) { - h.r.CSRFHandler().IgnoreGlobs(x.AdminPrefix+AdminRouteMessages, AdminRouteMessages) - public.GET(x.AdminPrefix+AdminRouteMessages, x.RedirectToAdminRoute(h.r)) + h.r.CSRFHandler().IgnoreGlobs(x.AdminPrefix+AdminRouteListMessages, AdminRouteListMessages) + public.GET(x.AdminPrefix+AdminRouteListMessages, x.RedirectToAdminRoute(h.r)) + public.GET(x.AdminPrefix+AdminRouteGetMessage, x.RedirectToAdminRoute(h.r)) } func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) { - admin.GET(AdminRouteMessages, h.listCourierMessages) + admin.GET(AdminRouteListMessages, h.listCourierMessages) + admin.GET(AdminRouteGetMessage, h.getCourierMessage) } // Paginated Courier Message List Response @@ -62,10 +69,9 @@ type listCourierMessagesResponse struct { // Paginated List Courier Message Parameters // -// nolint:deadcode,unused // swagger:parameters listCourierMessages type ListCourierMessagesParameters struct { - migrationpagination.RequestParameters + keysetpagination.RequestParameters // Status filters out messages based on status. // If no value is provided, it doesn't take effect on filter. @@ -101,13 +107,13 @@ type ListCourierMessagesParameters struct { // 400: errorGeneric // default: errorGeneric func (h *Handler) listCourierMessages(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { - filter, err := parseMessagesFilter(r) + filter, paginator, err := parseMessagesFilter(r) if err != nil { h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, err) return } - l, tc, err := h.r.CourierPersister().ListMessages(r.Context(), filter) + l, tc, nextPage, err := h.r.CourierPersister().ListMessages(r.Context(), filter, paginator) if err != nil { h.r.Writer().WriteError(w, r, err) return @@ -119,30 +125,82 @@ func (h *Handler) listCourierMessages(w http.ResponseWriter, r *http.Request, _ } } - x.PaginationHeader(w, urlx.AppendPaths(h.r.Config().SelfAdminURL(r.Context()), AdminRouteMessages), int64(tc), filter.Page, filter.PerPage) + w.Header().Set("X-Total-Count", fmt.Sprint(tc)) + keysetpagination.Header(w, r.URL, nextPage) h.r.Writer().Write(w, r, l) } -func parseMessagesFilter(r *http.Request) (ListCourierMessagesParameters, error) { +func parseMessagesFilter(r *http.Request) (ListCourierMessagesParameters, []keysetpagination.Option, error) { var status *MessageStatus if r.URL.Query().Has("status") { ms, err := ToMessageStatus(r.URL.Query().Get("status")) if err != nil { - return ListCourierMessagesParameters{}, err + return ListCourierMessagesParameters{}, nil, err } status = &ms } - page, itemsPerPage := x.ParsePagination(r) + opts, err := keysetpagination.Parse(r.URL.Query(), keysetpagination.NewMapPageToken) + if err != nil { + return ListCourierMessagesParameters{}, nil, err + } + return ListCourierMessagesParameters{ - RequestParameters: migrationpagination.RequestParameters{ - Page: page, - PerPage: itemsPerPage, - }, Status: status, Recipient: r.URL.Query().Get("recipient"), - }, nil + }, opts, nil +} + +// Get Courier Message Parameters +// +// swagger:parameters getCourierMessage +// nolint:deadcode,unused +type getCourierMessage struct { + // MessageID is the ID of the message. + // + // required: true + // in: path + MessageID string `json:"id"` +} + +// swagger:route GET /admin/courier/messages/{id} courier getCourierMessage +// +// # Get a Message +// +// Gets a specific messages by the given ID. +// +// Produces: +// - application/json +// +// Security: +// oryAccessToken: +// +// Schemes: http, https +// +// Responses: +// 200: message +// 400: errorGeneric +// default: errorGeneric +func (h *Handler) getCourierMessage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + msgID, err := uuid.FromString(ps.ByName("msgID")) + + if err != nil { + h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError(err.Error()).WithDebugf("could not parse parameter {id} as UUID, got %s", ps.ByName("id"))) + return + } + + message, err := h.r.CourierPersister().FetchMessage(r.Context(), msgID) + if err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + + if !h.r.Config().IsInsecureDevMode(r.Context()) { + message.Body = "" + } + + h.r.Writer().Write(w, r, message) } diff --git a/courier/handler_test.go b/courier/handler_test.go index b42e0d53788..1b017f28341 100644 --- a/courier/handler_test.go +++ b/courier/handler_test.go @@ -5,13 +5,16 @@ package courier_test import ( "context" + "errors" "fmt" "io/ioutil" "net/http" "net/http/httptest" "testing" + "time" "github.com/bxcodec/faker/v3" + "github.com/gofrs/uuid" "github.com/tidwall/gjson" "github.com/ory/kratos/courier" @@ -19,18 +22,37 @@ import ( "github.com/ory/kratos/internal" "github.com/ory/kratos/internal/testhelpers" "github.com/ory/kratos/x" + "github.com/ory/x/ioutilx" + "github.com/ory/x/pagination/keysetpagination" + "github.com/ory/x/snapshotx" "github.com/ory/x/urlx" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +var defaultPageToken = new(courier.Message).DefaultPageToken().Encode() + func TestHandler(t *testing.T) { ctx := context.Background() conf, reg := internal.NewFastRegistryWithMocks(t) // Start kratos server publicTS, adminTS := testhelpers.NewKratosServerWithCSRF(t, reg) + tss := []struct { + name string + s *httptest.Server + }{ + { + name: "public", + s: publicTS, + }, + { + name: "admin", + s: adminTS, + }, + } + mockServerURL := urlx.ParseOrPanic(publicTS.URL) conf.MustSet(ctx, config.ViperKeyAdminBaseURL, adminTS.URL) conf.MustSet(ctx, config.ViperKeyPublicBaseURL, mockServerURL.String()) @@ -43,13 +65,13 @@ func TestHandler(t *testing.T) { require.NoError(t, err) require.NoError(t, res.Body.Close()) - require.EqualValues(t, expectCode, res.StatusCode, "%s", body) + assert.EqualValues(t, expectCode, res.StatusCode, "%s", body) return gjson.ParseBytes(body) } var getList = func(t *testing.T, tsName string, qs string) gjson.Result { t.Helper() - href := courier.AdminRouteMessages + qs + href := courier.AdminRouteListMessages + qs ts := adminTS if tsName == "public" { @@ -91,25 +113,27 @@ func TestHandler(t *testing.T) { require.NoError(t, reg.CourierPersister().SetMessageStatus(context.Background(), messages[i].ID, courier.MessageStatusProcessing)) } - tss := [...]string{"public", "admin"} - t.Run("paging", func(t *testing.T) { t.Run("case=should return half of the messages", func(t *testing.T) { - qs := fmt.Sprintf("?page=1&per_page=%d", msgCount/2) + qs := fmt.Sprintf("?page_token=%s&page_size=%d", defaultPageToken, msgCount/2) - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, qs) + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), msgCount/2) }) } }) t.Run("case=should return no message", func(t *testing.T) { - qs := `?page=2&per_page=250` + token := keysetpagination.MapPageToken{ + "id": "1232", + "created_at": time.Now().Add(time.Duration(-10) * time.Hour).Format("2006-01-02 15:04:05.99999-07:00"), + } + qs := fmt.Sprintf(`?page_token=%s&page_size=%s`, token.Encode(), "250") - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, qs) + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), 0) }) } @@ -117,11 +141,11 @@ func TestHandler(t *testing.T) { }) t.Run("filtering", func(t *testing.T) { t.Run("case=should return all queued messages", func(t *testing.T) { - qs := `?page=1&per_page=250&status=queued` + qs := fmt.Sprintf(`?page_token=%s&page_size=250&status=queued`, defaultPageToken) - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, qs) + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), msgCount-procCount) for _, item := range parsed.Array() { @@ -131,11 +155,11 @@ func TestHandler(t *testing.T) { } }) t.Run("case=should return all processing messages", func(t *testing.T) { - qs := `?page=1&per_page=250&status=processing` + qs := fmt.Sprintf(`?page_token=%s&page_size=250&status=processing`, defaultPageToken) - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, qs) + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), procCount) for _, item := range parsed.Array() { @@ -145,11 +169,11 @@ func TestHandler(t *testing.T) { } }) t.Run("case=should return all messages with recipient equals to noreply@ory.sh", func(t *testing.T) { - qs := `?page=1&per_page=250&recipient=noreply@ory.sh` + qs := fmt.Sprintf(`?page_token=%s&page_size=250&recipient=noreply@ory.sh`, defaultPageToken) - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, qs) + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, qs) assert.Len(t, parsed.Array(), rcptOryCount) for _, item := range parsed.Array() { @@ -161,9 +185,9 @@ func TestHandler(t *testing.T) { }) t.Run("case=body should be redacted if kratos is not in dev mode", func(t *testing.T) { conf.MustSet(ctx, "dev", false) - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, "") + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, "") require.Len(t, parsed.Array(), msgCount, "%s", parsed.Raw) for _, item := range parsed.Array() { @@ -174,9 +198,9 @@ func TestHandler(t *testing.T) { }) t.Run("case=body should not be redacted if kratos is in dev mode", func(t *testing.T) { conf.MustSet(ctx, "dev", true) - for _, name := range tss { - t.Run("endpoint="+name, func(t *testing.T) { - parsed := getList(t, name, "") + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + parsed := getList(t, tc.name, "") require.Len(t, parsed.Array(), msgCount, "%s", parsed.Raw) for _, item := range parsed.Array() { @@ -186,11 +210,74 @@ func TestHandler(t *testing.T) { } }) t.Run("case=should return with http status BadRequest when given status is invalid", func(t *testing.T) { - qs := `?page=1&status=invalid_status` - res, err := adminTS.Client().Get(adminTS.URL + courier.AdminRouteMessages + qs) + qs := fmt.Sprintf(`?page_token=%s&page_size=250&status=invalid_status`, defaultPageToken) + + res, err := adminTS.Client().Get(adminTS.URL + courier.AdminRouteListMessages + qs) require.NoError(t, err) assert.Equal(t, http.StatusBadRequest, res.StatusCode, "status code should be equal to StatusBadRequest") }) + + }) + t.Run("handler=getCourierMessage", func(t *testing.T) { + + message := courier.Message{} + require.NoError(t, faker.FakeData(&message)) + message.Type = courier.MessageTypeEmail + message.Body = "body content" + require.NoError(t, reg.CourierPersister().AddMessage(context.Background(), &message)) + require.NoError(t, reg.CourierPersister().RecordDispatch(ctx, message.ID, courier.CourierMessageDispatchStatusSuccess, errors.New("some error"))) + + getCourierMessag := func(s *httptest.Server, id string) gjson.Result { + + r, err := s.Client().Get(s.URL + "/admin/courier/messages/" + id) + require.NoError(t, err) + return gjson.ParseBytes(ioutilx.MustReadAll(r.Body)) + } + + t.Run("case=should return a message by id", func(t *testing.T) { + conf.MustSet(ctx, "dev", true) + + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + body := getCourierMessag(tc.s, message.ID.String()) + assert.Equal(t, message.ID.String(), body.Get("id").String()) + assert.Equal(t, message.Recipient, body.Get("recipient").String()) + assert.Equal(t, message.Body, body.Get("body").String()) + + // assert Eager works + assert.NotEmpty(t, body.Get("dispatches").Array()) + }) + } + }) + t.Run("case=does not contain body if not in production", func(t *testing.T) { + conf.MustSet(ctx, "dev", false) + + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + body := getCourierMessag(tc.s, message.ID.String()) + assert.Equal(t, message.ID.String(), body.Get("id").String()) + assert.Equal(t, "", body.Get("body").String()) + }) + } + }) + t.Run("case=returns an error if parameter is malformed", func(t *testing.T) { + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + body := getCourierMessag(tc.s, "not-a-uuid") + + snapshotx.SnapshotT(t, body.String()) + }) + } + }) + t.Run("case=returns an error if no message is found", func(t *testing.T) { + for _, tc := range tss { + t.Run("endpoint="+tc.name, func(t *testing.T) { + body := getCourierMessag(tc.s, uuid.Nil.String()) + + snapshotx.SnapshotT(t, body.String()) + }) + } + }) }) } diff --git a/courier/message.go b/courier/message.go index c7362415f24..4eadc0777a1 100644 --- a/courier/message.go +++ b/courier/message.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/ory/herodot" + "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/stringsx" ) @@ -113,6 +114,9 @@ const ( messageTypePhoneText = "phone" ) +// The format we need to use in the Page tokens, as it's the only format that is understood by all DBs +const dbFormat = "2006-01-02 15:04:05.99999+07:00" + func ToMessageType(str string) (MessageType, error) { switch s := stringsx.SwitchExact(str); { case s.AddCase(messageTypeEmailText): @@ -168,23 +172,53 @@ func (mt *MessageType) UnmarshalJSON(data []byte) error { // swagger:model message type Message struct { - ID uuid.UUID `json:"id" faker:"-" db:"id"` - NID uuid.UUID `json:"-" faker:"-" db:"nid"` - Status MessageStatus `json:"status" db:"status"` - Type MessageType `json:"type" db:"type"` - Recipient string `json:"recipient" db:"recipient"` - Body string `json:"body" db:"body"` - Subject string `json:"subject" db:"subject"` - TemplateType TemplateType `json:"template_type" db:"template_type"` - TemplateData []byte `json:"-" db:"template_data"` - SendCount int `json:"send_count" db:"send_count"` + // required: true + ID uuid.UUID `json:"id" faker:"-" db:"id"` + + NID uuid.UUID `json:"-" faker:"-" db:"nid"` + // required: true + Status MessageStatus `json:"status" db:"status"` + // required: true + Type MessageType `json:"type" db:"type"` + // required: true + Recipient string `json:"recipient" db:"recipient"` + // required: true + Body string `json:"body" db:"body"` + // required: true + Subject string `json:"subject" db:"subject"` + // required: true + TemplateType TemplateType `json:"template_type" db:"template_type"` + + TemplateData []byte `json:"-" db:"template_data"` + // required: true + SendCount int `json:"send_count" db:"send_count"` + + // Dispatches store information about the attempts of delivering a message + // May contain an error if any happened, or just the `success` state. + Dispatches []MessageDispatch `json:"dispatches,omitempty" has_many:"courier_message_dispatches" order_by:"created_at desc" faker:"-"` // CreatedAt is a helper struct field for gobuffalo.pop. + // required: true CreatedAt time.Time `json:"created_at" faker:"-" db:"created_at"` // UpdatedAt is a helper struct field for gobuffalo.pop. + // required: true UpdatedAt time.Time `json:"updated_at" faker:"-" db:"updated_at"` } +func (m Message) PageToken() keysetpagination.PageToken { + return keysetpagination.MapPageToken{ + "id": m.ID.String(), + "created_at": m.CreatedAt.Format(dbFormat), + } +} + +func (m Message) DefaultPageToken() keysetpagination.PageToken { + return keysetpagination.MapPageToken{ + "id": uuid.Nil.String(), + "created_at": time.Date(2200, 12, 31, 23, 59, 59, 0, time.UTC).Format(dbFormat), + } +} + func (m Message) TableName(ctx context.Context) string { return "courier_messages" } diff --git a/courier/message_dispatch.go b/courier/message_dispatch.go new file mode 100644 index 00000000000..9d88abc6b6b --- /dev/null +++ b/courier/message_dispatch.go @@ -0,0 +1,55 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +package courier + +import ( + "time" + + "github.com/gofrs/uuid" + + "github.com/ory/x/sqlxx" +) + +// swagger:enum CourierMessageDispatchStatus +type CourierMessageDispatchStatus string + +const ( + CourierMessageDispatchStatusFailed CourierMessageDispatchStatus = "failed" + CourierMessageDispatchStatusSuccess CourierMessageDispatchStatus = "success" +) + +// MessageDispatch represents an attempt of sending a courier message +// It contains the status of the attempt (failed or successful) and the error if any occured +// +// swagger:model messageDispatch +type MessageDispatch struct { + // The ID of this message dispatch + // required: true + ID uuid.UUID `json:"id" db:"id"` + + // The ID of the message being dispatched + // required: true + MessageID uuid.UUID `json:"message_id" db:"message_id"` + + // The status of this dispatch + // Either "failed" or "success" + // required: true + Status CourierMessageDispatchStatus `json:"status" db:"status"` + + // An optional error + Error sqlxx.JSONRawMessage `json:"error,omitempty" db:"error"` + + // CreatedAt is a helper struct field for gobuffalo.pop. + // required: true + CreatedAt time.Time `json:"created_at" db:"created_at"` + // UpdatedAt is a helper struct field for gobuffalo.pop. + // required: true + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + + NID uuid.UUID `json:"-" db:"nid"` +} + +func (MessageDispatch) TableName() string { + return "courier_message_dispatches" +} diff --git a/courier/persistence.go b/courier/persistence.go index 78438a0aae8..3122c806ea4 100644 --- a/courier/persistence.go +++ b/courier/persistence.go @@ -8,6 +8,8 @@ import ( "github.com/gofrs/uuid" "github.com/pkg/errors" + + "github.com/ory/x/pagination/keysetpagination" ) var ErrQueueEmpty = errors.New("queue is empty") @@ -26,7 +28,14 @@ type ( // ListMessages lists all messages in the store given the page, itemsPerPage, status and recipient. // Returns list of messages, total count of messages satisfied by given filter, and error if any - ListMessages(ctx context.Context, filter ListCourierMessagesParameters) ([]Message, int64, error) + ListMessages(context.Context, ListCourierMessagesParameters, []keysetpagination.Option) ([]Message, int64, *keysetpagination.Paginator, error) + + // FetchMessage returns a message with the id or nil and an error if not found + FetchMessage(context.Context, uuid.UUID) (*Message, error) + + // Records an attempt of sending out a courier message + // Returns an error if it fails + RecordDispatch(ctx context.Context, msgID uuid.UUID, status CourierMessageDispatchStatus, err error) error } PersistenceProvider interface { CourierPersister() Persister diff --git a/courier/sms_test.go b/courier/sms_test.go index 5a568833bce..a433b440785 100644 --- a/courier/sms_test.go +++ b/courier/sms_test.go @@ -146,5 +146,5 @@ func TestDisallowedInternalNetwork(t *testing.T) { err = c.DispatchQueue(ctx) require.Error(t, err) - assert.Contains(t, err.Error(), "is in the private, loopback, or unspecified IP range") + assert.Contains(t, err.Error(), "127.0.0.1 is not a public IP address") } diff --git a/courier/smtp.go b/courier/smtp.go index 9d374c2bd62..6411b27a3df 100644 --- a/courier/smtp.go +++ b/courier/smtp.go @@ -158,7 +158,7 @@ func (c *courier) QueueEmail(ctx context.Context, t EmailTemplate) (uuid.UUID, e func (c *courier) dispatchEmail(ctx context.Context, msg Message) error { if c.smtpClient.Host == "" { - return errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Courier tried to deliver an email but %s is not set!", config.ViperKeyCourierSMTPURL)) + return errors.WithStack(herodot.ErrInternalServerError.WithErrorf("Courier tried to deliver an email but %s is not set!", config.ViperKeyCourierSMTPURL)) } from := c.deps.CourierConfig().CourierSMTPFrom(ctx) @@ -204,7 +204,6 @@ func (c *courier) dispatchEmail(ctx context.Context, msg Message) error { WithError(err). WithField("smtp_server", fmt.Sprintf("%s:%d", c.smtpClient.Host, c.smtpClient.Port)). WithField("smtp_ssl_enabled", c.smtpClient.SSL). - // WithField("email_to", msg.Recipient). WithField("message_from", from). Error("Unable to send email using SMTP connection.") @@ -220,7 +219,8 @@ func (c *courier) dispatchEmail(ctx context.Context, msg Message) error { return err } } - return errors.WithStack(err) + return errors.WithStack(herodot.ErrInternalServerError. + WithError(err.Error()).WithReason("failed to send email via smtp")) } c.deps.Logger(). diff --git a/courier/template/load_template_test.go b/courier/template/load_template_test.go index aaa0d9ffb2b..dfaa129d14d 100644 --- a/courier/template/load_template_test.go +++ b/courier/template/load_template_test.go @@ -191,11 +191,11 @@ func TestLoadTextTemplate(t *testing.T) { _, err := template.LoadHTML(ctx, reg, nil, "", "", map[string]interface{}{}, "http://localhost:8080/1234") require.Error(t, err) - assert.Contains(t, err.Error(), "is in the") + assert.Contains(t, err.Error(), "is not a public IP address") _, err = template.LoadText(ctx, reg, nil, "", "", map[string]interface{}{}, "http://localhost:8080/1234") require.Error(t, err) - assert.Contains(t, err.Error(), "is in the") + assert.Contains(t, err.Error(), "is not a public IP address") }) diff --git a/courier/test/persistence.go b/courier/test/persistence.go index 88e9afb8490..7df77717618 100644 --- a/courier/test/persistence.go +++ b/courier/test/persistence.go @@ -5,14 +5,14 @@ package test import ( "context" + "errors" "fmt" "testing" "time" - "github.com/ory/x/pagination/migrationpagination" - "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + "github.com/tidwall/gjson" "github.com/bxcodec/faker/v3" "github.com/stretchr/testify/assert" @@ -20,6 +20,7 @@ import ( "github.com/ory/kratos/courier" "github.com/ory/kratos/x" + "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/sqlcon" ) @@ -116,21 +117,17 @@ func TestPersister(ctx context.Context, newNetworkUnlessExisting NetworkWrapper, status := courier.MessageStatusProcessing filter := courier.ListCourierMessagesParameters{ Status: &status, - RequestParameters: migrationpagination.RequestParameters{ - Page: 1, - PerPage: 100, - }, } - ms, tc, err := p.ListMessages(ctx, filter) + ms, total, _, err := p.ListMessages(ctx, filter, []keysetpagination.Option{}) require.NoError(t, err) assert.Len(t, ms, len(messages)) - assert.Equal(t, int64(len(messages)), tc) + assert.Equal(t, int64(len(messages)), total) assert.Equal(t, messages[len(messages)-1].ID, ms[0].ID) t.Run("on another network", func(t *testing.T) { _, p := newNetwork(t, ctx) - ms, tc, err := p.ListMessages(ctx, filter) + ms, tc, _, err := p.ListMessages(ctx, filter, []keysetpagination.Option{}) require.NoError(t, err) require.Len(t, ms, 0) @@ -178,5 +175,41 @@ func TestPersister(ctx context.Context, newNetworkUnlessExisting NetworkWrapper, require.ErrorIs(t, err, sqlcon.ErrNoRows) }) }) + + t.Run("case=FetchMessage", func(t *testing.T) { + msgID := messages[0].ID + + message, err := p.FetchMessage(ctx, msgID) + require.NoError(t, err) + require.Equal(t, msgID, message.ID) + + t.Run("can not get on another network", func(t *testing.T) { + _, p := newNetwork(t, ctx) + + _, err := p.FetchMessage(ctx, msgID) + require.ErrorIs(t, err, sqlcon.ErrNoRows) + }) + + }) + + t.Run("case=RecordDispatch", func(t *testing.T) { + msgID := messages[0].ID + + err := p.RecordDispatch(ctx, msgID, courier.CourierMessageDispatchStatusFailed, errors.New("testerror")) + require.NoError(t, err) + + message, err := p.FetchMessage(ctx, msgID) + require.NoError(t, err) + + require.Len(t, message.Dispatches, 1) + assert.Equal(t, "testerror", gjson.GetBytes(message.Dispatches[0].Error, "message").String()) + + t.Run("can not get on another network", func(t *testing.T) { + _, p := newNetwork(t, ctx) + + _, err := p.FetchMessage(ctx, msgID) + require.ErrorIs(t, err, sqlcon.ErrNoRows) + }) + }) } } diff --git a/driver/config/config_test.go b/driver/config/config_test.go index f61079d8ad8..d93e4ab0b07 100644 --- a/driver/config/config_test.go +++ b/driver/config/config_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/ory/x/httpx" + "github.com/ory/x/randx" "github.com/ory/x/snapshotx" @@ -946,7 +947,8 @@ func TestIdentitySchemaValidation(t *testing.T) { assert.NoError(t, os.MkdirAll(tdir, // DO NOT CHANGE THIS: https://github.com/fsnotify/fsnotify/issues/340 os.ModePerm)) - tmpConfig, err := os.Create(filepath.Join(tdir, "config.yaml")) + configFileName := randx.MustString(8, randx.Alpha) + tmpConfig, err := os.Create(filepath.Join(tdir, configFileName+".config.yaml")) assert.NoError(t, err) marshalAndWrite(t, ctx, tmpConfig, i) diff --git a/go.mod b/go.mod index ec69ec1cf12..01a7268e369 100644 --- a/go.mod +++ b/go.mod @@ -77,7 +77,7 @@ require ( github.com/ory/jsonschema/v3 v3.0.7 github.com/ory/mail/v3 v3.0.0 github.com/ory/nosurf v1.2.7 - github.com/ory/x v0.0.513 + github.com/ory/x v0.0.519 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 github.com/pkg/errors v0.9.1 github.com/pquerna/otp v1.3.0 @@ -151,6 +151,7 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fullstorydev/grpcurl v1.8.1 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/go-bindata/go-bindata v3.1.2+incompatible // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.21.4 // indirect diff --git a/go.sum b/go.sum index 5cbf6fb0ce3..73efca034ab 100644 --- a/go.sum +++ b/go.sum @@ -390,6 +390,8 @@ github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49P github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= +github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -1141,6 +1143,20 @@ github.com/ory/viper v1.7.5 h1:+xVdq7SU3e1vNaCsk/ixsfxE4zylk1TJUiJrY647jUE= github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= github.com/ory/x v0.0.513 h1:45AruNHDwqhTvNtMnQy2/wYooMv+raVhuOP454mV/Os= github.com/ory/x v0.0.513/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.517-0.20221125124208-eeecbb39902b h1:MkAOUX6klLRRtz6YIG694Q9rDADrW9LuPrVwvr5CWGE= +github.com/ory/x v0.0.517-0.20221125124208-eeecbb39902b/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.517-0.20221128091752-551ffb438eeb h1:a3U3DXqBbUr90ElIuzwRW/QI9akBZhgR49c39faItmA= +github.com/ory/x v0.0.517-0.20221128091752-551ffb438eeb/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.517-0.20221128100459-e2df1f5d26ba h1:z/ElG4rbwrxjkqaTsEDNEP3NyxrM5GW00WPuiVkgCb8= +github.com/ory/x v0.0.517-0.20221128100459-e2df1f5d26ba/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.518-0.20221130104542-b94c6f320cce h1:ZeWblm5vjgkq/R/xD+3ZU1L5UvNp93CrhZNyvlGgjZQ= +github.com/ory/x v0.0.518-0.20221130104542-b94c6f320cce/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.518-0.20221130125643-031a59af1925 h1:PvrVOQXY+VPASWyNPRqFPhUBUV8C68wmIUXZ7PEZRRA= +github.com/ory/x v0.0.518-0.20221130125643-031a59af1925/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.518 h1:vIo1zLI+HpqqjzuBVuowYWyDx1wmqPjFFvZBcXHmEdE= +github.com/ory/x v0.0.518/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= +github.com/ory/x v0.0.519 h1:T8/LbbQQqm+3P7bfI838T7eECv6+laXlvIyCp0QB+R8= +github.com/ory/x v0.0.519/go.mod h1:xUtRpoiRARyJNPVk/fcCNKzyp25Foxt9GPlj8pd7egY= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= diff --git a/identity/validator_test.go b/identity/validator_test.go index b1f5fcc6e02..bfc3d29d083 100644 --- a/identity/validator_test.go +++ b/identity/validator_test.go @@ -62,8 +62,8 @@ func TestSchemaValidatorDisallowsInternalNetworkRequests(t *testing.T) { } for _, tc := range [][2]string{ - {"localhost", "is in the private, loopback, or unspecified IP range"}, - {"privateRef", "is in the private, loopback, or unspecified IP range"}, + {"localhost", "is not a public IP address"}, + {"privateRef", "is not a public IP address"}, } { t.Run(fmt.Sprintf("case=%s", tc[0]), func(t *testing.T) { assert.Contains(t, do(t, tc[0]), tc[1]) diff --git a/internal/client-go/.openapi-generator/FILES b/internal/client-go/.openapi-generator/FILES index ce42bb177ec..856acc65ec1 100644 --- a/internal/client-go/.openapi-generator/FILES +++ b/internal/client-go/.openapi-generator/FILES @@ -47,6 +47,7 @@ docs/JsonPatch.md docs/LoginFlow.md docs/LogoutFlow.md docs/Message.md +docs/MessageDispatch.md docs/MetadataApi.md docs/NeedsPrivilegedSessionError.md docs/OAuth2Client.md @@ -147,6 +148,7 @@ model_json_patch.go model_login_flow.go model_logout_flow.go model_message.go +model_message_dispatch.go model_needs_privileged_session_error.go model_o_auth2_client.go model_o_auth2_consent_request_open_id_connect_context.go diff --git a/internal/client-go/README.md b/internal/client-go/README.md index 3fbdc4c5ca5..3fbf8f68f7b 100644 --- a/internal/client-go/README.md +++ b/internal/client-go/README.md @@ -79,6 +79,7 @@ All URIs are relative to *http://localhost* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- +*CourierApi* | [**GetCourierMessage**](docs/CourierApi.md#getcouriermessage) | **Get** /admin/courier/messages/{id} | Get a Message *CourierApi* | [**ListCourierMessages**](docs/CourierApi.md#listcouriermessages) | **Get** /admin/courier/messages | List Messages *FrontendApi* | [**CreateBrowserLoginFlow**](docs/FrontendApi.md#createbrowserloginflow) | **Get** /self-service/login/browser | Create Login Flow for Browsers *FrontendApi* | [**CreateBrowserLogoutFlow**](docs/FrontendApi.md#createbrowserlogoutflow) | **Get** /self-service/logout/browser | Create a Logout URL for Browsers @@ -168,6 +169,7 @@ Class | Method | HTTP request | Description - [LoginFlow](docs/LoginFlow.md) - [LogoutFlow](docs/LogoutFlow.md) - [Message](docs/Message.md) + - [MessageDispatch](docs/MessageDispatch.md) - [NeedsPrivilegedSessionError](docs/NeedsPrivilegedSessionError.md) - [OAuth2Client](docs/OAuth2Client.md) - [OAuth2ConsentRequestOpenIDConnectContext](docs/OAuth2ConsentRequestOpenIDConnectContext.md) diff --git a/internal/client-go/api_courier.go b/internal/client-go/api_courier.go index 5941376dd02..a7e43bcd183 100644 --- a/internal/client-go/api_courier.go +++ b/internal/client-go/api_courier.go @@ -17,6 +17,7 @@ import ( "io" "net/http" "net/url" + "strings" ) // Linger please @@ -26,6 +27,21 @@ var ( type CourierApi interface { + /* + * GetCourierMessage Get a Message + * Gets a specific messages by the given ID. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id MessageID is the ID of the message. + * @return CourierApiApiGetCourierMessageRequest + */ + GetCourierMessage(ctx context.Context, id string) CourierApiApiGetCourierMessageRequest + + /* + * GetCourierMessageExecute executes the request + * @return Message + */ + GetCourierMessageExecute(r CourierApiApiGetCourierMessageRequest) (*Message, *http.Response, error) + /* * ListCourierMessages List Messages * Lists all messages by given status and recipient. @@ -44,21 +60,157 @@ type CourierApi interface { // CourierApiService CourierApi service type CourierApiService service +type CourierApiApiGetCourierMessageRequest struct { + ctx context.Context + ApiService CourierApi + id string +} + +func (r CourierApiApiGetCourierMessageRequest) Execute() (*Message, *http.Response, error) { + return r.ApiService.GetCourierMessageExecute(r) +} + +/* + * GetCourierMessage Get a Message + * Gets a specific messages by the given ID. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id MessageID is the ID of the message. + * @return CourierApiApiGetCourierMessageRequest + */ +func (a *CourierApiService) GetCourierMessage(ctx context.Context, id string) CourierApiApiGetCourierMessageRequest { + return CourierApiApiGetCourierMessageRequest{ + ApiService: a, + ctx: ctx, + id: id, + } +} + +/* + * Execute executes the request + * @return Message + */ +func (a *CourierApiService) GetCourierMessageExecute(r CourierApiApiGetCourierMessageRequest) (*Message, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *Message + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "CourierApiService.GetCourierMessage") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/admin/courier/messages/{id}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterToString(r.id, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.ctx != nil { + // API Key Authentication + if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok { + if apiKey, ok := auth["oryAccessToken"]; ok { + var key string + if apiKey.Prefix != "" { + key = apiKey.Prefix + " " + apiKey.Key + } else { + key = apiKey.Key + } + localVarHeaderParams["Authorization"] = key + } + } + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type CourierApiApiListCourierMessagesRequest struct { ctx context.Context ApiService CourierApi - perPage *int64 - page *int64 + pageSize *int64 + pageToken *string status *CourierMessageStatus recipient *string } -func (r CourierApiApiListCourierMessagesRequest) PerPage(perPage int64) CourierApiApiListCourierMessagesRequest { - r.perPage = &perPage +func (r CourierApiApiListCourierMessagesRequest) PageSize(pageSize int64) CourierApiApiListCourierMessagesRequest { + r.pageSize = &pageSize return r } -func (r CourierApiApiListCourierMessagesRequest) Page(page int64) CourierApiApiListCourierMessagesRequest { - r.page = &page +func (r CourierApiApiListCourierMessagesRequest) PageToken(pageToken string) CourierApiApiListCourierMessagesRequest { + r.pageToken = &pageToken return r } func (r CourierApiApiListCourierMessagesRequest) Status(status CourierMessageStatus) CourierApiApiListCourierMessagesRequest { @@ -112,11 +264,11 @@ func (a *CourierApiService) ListCourierMessagesExecute(r CourierApiApiListCourie localVarQueryParams := url.Values{} localVarFormParams := url.Values{} - if r.perPage != nil { - localVarQueryParams.Add("per_page", parameterToString(*r.perPage, "")) + if r.pageSize != nil { + localVarQueryParams.Add("page_size", parameterToString(*r.pageSize, "")) } - if r.page != nil { - localVarQueryParams.Add("page", parameterToString(*r.page, "")) + if r.pageToken != nil { + localVarQueryParams.Add("page_token", parameterToString(*r.pageToken, "")) } if r.status != nil { localVarQueryParams.Add("status", parameterToString(*r.status, "")) diff --git a/internal/client-go/model_courier_message_dispatch.go b/internal/client-go/model_courier_message_dispatch.go new file mode 100644 index 00000000000..fd2ba43945e --- /dev/null +++ b/internal/client-go/model_courier_message_dispatch.go @@ -0,0 +1,301 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" + "time" +) + +// CourierMessageDispatch CourierMessageDispatch represents an attempt of sending a courier message It contains the status of the attempt (failed or successful) and the error if any occured +type CourierMessageDispatch struct { + // CreatedAt is a helper struct field for gobuffalo.pop. + CreatedAt *time.Time `json:"created_at,omitempty"` + // An optional error + Error *string `json:"error,omitempty"` + // The ID of this message dispatch + Id *string `json:"id,omitempty"` + // The ID of th message being dispatched + MessageId *string `json:"message_id,omitempty"` + // The status of this dispatch Either \"failed\" or \"success\" failed CourierMessageDispatchStatusFailed success CourierMessageDispatchStatusSuccess + Status *string `json:"status,omitempty"` + // UpdatedAt is a helper struct field for gobuffalo.pop. + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// NewCourierMessageDispatch instantiates a new CourierMessageDispatch object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewCourierMessageDispatch() *CourierMessageDispatch { + this := CourierMessageDispatch{} + return &this +} + +// NewCourierMessageDispatchWithDefaults instantiates a new CourierMessageDispatch object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewCourierMessageDispatchWithDefaults() *CourierMessageDispatch { + this := CourierMessageDispatch{} + return &this +} + +// GetCreatedAt returns the CreatedAt field value if set, zero value otherwise. +func (o *CourierMessageDispatch) GetCreatedAt() time.Time { + if o == nil || o.CreatedAt == nil { + var ret time.Time + return ret + } + return *o.CreatedAt +} + +// GetCreatedAtOk returns a tuple with the CreatedAt field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CourierMessageDispatch) GetCreatedAtOk() (*time.Time, bool) { + if o == nil || o.CreatedAt == nil { + return nil, false + } + return o.CreatedAt, true +} + +// HasCreatedAt returns a boolean if a field has been set. +func (o *CourierMessageDispatch) HasCreatedAt() bool { + if o != nil && o.CreatedAt != nil { + return true + } + + return false +} + +// SetCreatedAt gets a reference to the given time.Time and assigns it to the CreatedAt field. +func (o *CourierMessageDispatch) SetCreatedAt(v time.Time) { + o.CreatedAt = &v +} + +// GetError returns the Error field value if set, zero value otherwise. +func (o *CourierMessageDispatch) GetError() string { + if o == nil || o.Error == nil { + var ret string + return ret + } + return *o.Error +} + +// GetErrorOk returns a tuple with the Error field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CourierMessageDispatch) GetErrorOk() (*string, bool) { + if o == nil || o.Error == nil { + return nil, false + } + return o.Error, true +} + +// HasError returns a boolean if a field has been set. +func (o *CourierMessageDispatch) HasError() bool { + if o != nil && o.Error != nil { + return true + } + + return false +} + +// SetError gets a reference to the given string and assigns it to the Error field. +func (o *CourierMessageDispatch) SetError(v string) { + o.Error = &v +} + +// GetId returns the Id field value if set, zero value otherwise. +func (o *CourierMessageDispatch) GetId() string { + if o == nil || o.Id == nil { + var ret string + return ret + } + return *o.Id +} + +// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CourierMessageDispatch) GetIdOk() (*string, bool) { + if o == nil || o.Id == nil { + return nil, false + } + return o.Id, true +} + +// HasId returns a boolean if a field has been set. +func (o *CourierMessageDispatch) HasId() bool { + if o != nil && o.Id != nil { + return true + } + + return false +} + +// SetId gets a reference to the given string and assigns it to the Id field. +func (o *CourierMessageDispatch) SetId(v string) { + o.Id = &v +} + +// GetMessageId returns the MessageId field value if set, zero value otherwise. +func (o *CourierMessageDispatch) GetMessageId() string { + if o == nil || o.MessageId == nil { + var ret string + return ret + } + return *o.MessageId +} + +// GetMessageIdOk returns a tuple with the MessageId field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CourierMessageDispatch) GetMessageIdOk() (*string, bool) { + if o == nil || o.MessageId == nil { + return nil, false + } + return o.MessageId, true +} + +// HasMessageId returns a boolean if a field has been set. +func (o *CourierMessageDispatch) HasMessageId() bool { + if o != nil && o.MessageId != nil { + return true + } + + return false +} + +// SetMessageId gets a reference to the given string and assigns it to the MessageId field. +func (o *CourierMessageDispatch) SetMessageId(v string) { + o.MessageId = &v +} + +// GetStatus returns the Status field value if set, zero value otherwise. +func (o *CourierMessageDispatch) GetStatus() string { + if o == nil || o.Status == nil { + var ret string + return ret + } + return *o.Status +} + +// GetStatusOk returns a tuple with the Status field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CourierMessageDispatch) GetStatusOk() (*string, bool) { + if o == nil || o.Status == nil { + return nil, false + } + return o.Status, true +} + +// HasStatus returns a boolean if a field has been set. +func (o *CourierMessageDispatch) HasStatus() bool { + if o != nil && o.Status != nil { + return true + } + + return false +} + +// SetStatus gets a reference to the given string and assigns it to the Status field. +func (o *CourierMessageDispatch) SetStatus(v string) { + o.Status = &v +} + +// GetUpdatedAt returns the UpdatedAt field value if set, zero value otherwise. +func (o *CourierMessageDispatch) GetUpdatedAt() time.Time { + if o == nil || o.UpdatedAt == nil { + var ret time.Time + return ret + } + return *o.UpdatedAt +} + +// GetUpdatedAtOk returns a tuple with the UpdatedAt field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *CourierMessageDispatch) GetUpdatedAtOk() (*time.Time, bool) { + if o == nil || o.UpdatedAt == nil { + return nil, false + } + return o.UpdatedAt, true +} + +// HasUpdatedAt returns a boolean if a field has been set. +func (o *CourierMessageDispatch) HasUpdatedAt() bool { + if o != nil && o.UpdatedAt != nil { + return true + } + + return false +} + +// SetUpdatedAt gets a reference to the given time.Time and assigns it to the UpdatedAt field. +func (o *CourierMessageDispatch) SetUpdatedAt(v time.Time) { + o.UpdatedAt = &v +} + +func (o CourierMessageDispatch) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if o.CreatedAt != nil { + toSerialize["created_at"] = o.CreatedAt + } + if o.Error != nil { + toSerialize["error"] = o.Error + } + if o.Id != nil { + toSerialize["id"] = o.Id + } + if o.MessageId != nil { + toSerialize["message_id"] = o.MessageId + } + if o.Status != nil { + toSerialize["status"] = o.Status + } + if o.UpdatedAt != nil { + toSerialize["updated_at"] = o.UpdatedAt + } + return json.Marshal(toSerialize) +} + +type NullableCourierMessageDispatch struct { + value *CourierMessageDispatch + isSet bool +} + +func (v NullableCourierMessageDispatch) Get() *CourierMessageDispatch { + return v.value +} + +func (v *NullableCourierMessageDispatch) Set(val *CourierMessageDispatch) { + v.value = val + v.isSet = true +} + +func (v NullableCourierMessageDispatch) IsSet() bool { + return v.isSet +} + +func (v *NullableCourierMessageDispatch) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableCourierMessageDispatch(val *CourierMessageDispatch) *NullableCourierMessageDispatch { + return &NullableCourierMessageDispatch{value: val, isSet: true} +} + +func (v NullableCourierMessageDispatch) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableCourierMessageDispatch) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/client-go/model_message.go b/internal/client-go/model_message.go index 0ca65e618fb..8b385429f4e 100644 --- a/internal/client-go/model_message.go +++ b/internal/client-go/model_message.go @@ -18,26 +18,38 @@ import ( // Message struct for Message type Message struct { - Body *string `json:"body,omitempty"` + Body string `json:"body"` // CreatedAt is a helper struct field for gobuffalo.pop. - CreatedAt *time.Time `json:"created_at,omitempty"` - Id *string `json:"id,omitempty"` - Recipient *string `json:"recipient,omitempty"` - SendCount *int64 `json:"send_count,omitempty"` - Status *CourierMessageStatus `json:"status,omitempty"` - Subject *string `json:"subject,omitempty"` - TemplateType *string `json:"template_type,omitempty"` - Type *CourierMessageType `json:"type,omitempty"` + CreatedAt time.Time `json:"created_at"` + Dispatches []MessageDispatch `json:"dispatches,omitempty"` + Id string `json:"id"` + Recipient string `json:"recipient"` + SendCount int64 `json:"send_count"` + Status CourierMessageStatus `json:"status"` + Subject string `json:"subject"` + // recovery_invalid TypeRecoveryInvalid recovery_valid TypeRecoveryValid recovery_code_invalid TypeRecoveryCodeInvalid recovery_code_valid TypeRecoveryCodeValid verification_invalid TypeVerificationInvalid verification_valid TypeVerificationValid verification_code_invalid TypeVerificationCodeInvalid verification_code_valid TypeVerificationCodeValid otp TypeOTP stub TypeTestStub + TemplateType string `json:"template_type"` + Type CourierMessageType `json:"type"` // UpdatedAt is a helper struct field for gobuffalo.pop. - UpdatedAt *time.Time `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at"` } // NewMessage instantiates a new Message object // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewMessage() *Message { +func NewMessage(body string, createdAt time.Time, id string, recipient string, sendCount int64, status CourierMessageStatus, subject string, templateType string, type_ CourierMessageType, updatedAt time.Time) *Message { this := Message{} + this.Body = body + this.CreatedAt = createdAt + this.Id = id + this.Recipient = recipient + this.SendCount = sendCount + this.Status = status + this.Subject = subject + this.TemplateType = templateType + this.Type = type_ + this.UpdatedAt = updatedAt return &this } @@ -49,356 +61,311 @@ func NewMessageWithDefaults() *Message { return &this } -// GetBody returns the Body field value if set, zero value otherwise. +// GetBody returns the Body field value func (o *Message) GetBody() string { - if o == nil || o.Body == nil { + if o == nil { var ret string return ret } - return *o.Body + + return o.Body } -// GetBodyOk returns a tuple with the Body field value if set, nil otherwise +// GetBodyOk returns a tuple with the Body field value // and a boolean to check if the value has been set. func (o *Message) GetBodyOk() (*string, bool) { - if o == nil || o.Body == nil { + if o == nil { return nil, false } - return o.Body, true -} - -// HasBody returns a boolean if a field has been set. -func (o *Message) HasBody() bool { - if o != nil && o.Body != nil { - return true - } - - return false + return &o.Body, true } -// SetBody gets a reference to the given string and assigns it to the Body field. +// SetBody sets field value func (o *Message) SetBody(v string) { - o.Body = &v + o.Body = v } -// GetCreatedAt returns the CreatedAt field value if set, zero value otherwise. +// GetCreatedAt returns the CreatedAt field value func (o *Message) GetCreatedAt() time.Time { - if o == nil || o.CreatedAt == nil { + if o == nil { var ret time.Time return ret } - return *o.CreatedAt + + return o.CreatedAt } -// GetCreatedAtOk returns a tuple with the CreatedAt field value if set, nil otherwise +// GetCreatedAtOk returns a tuple with the CreatedAt field value // and a boolean to check if the value has been set. func (o *Message) GetCreatedAtOk() (*time.Time, bool) { - if o == nil || o.CreatedAt == nil { + if o == nil { + return nil, false + } + return &o.CreatedAt, true +} + +// SetCreatedAt sets field value +func (o *Message) SetCreatedAt(v time.Time) { + o.CreatedAt = v +} + +// GetDispatches returns the Dispatches field value if set, zero value otherwise. +func (o *Message) GetDispatches() []MessageDispatch { + if o == nil || o.Dispatches == nil { + var ret []MessageDispatch + return ret + } + return o.Dispatches +} + +// GetDispatchesOk returns a tuple with the Dispatches field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Message) GetDispatchesOk() ([]MessageDispatch, bool) { + if o == nil || o.Dispatches == nil { return nil, false } - return o.CreatedAt, true + return o.Dispatches, true } -// HasCreatedAt returns a boolean if a field has been set. -func (o *Message) HasCreatedAt() bool { - if o != nil && o.CreatedAt != nil { +// HasDispatches returns a boolean if a field has been set. +func (o *Message) HasDispatches() bool { + if o != nil && o.Dispatches != nil { return true } return false } -// SetCreatedAt gets a reference to the given time.Time and assigns it to the CreatedAt field. -func (o *Message) SetCreatedAt(v time.Time) { - o.CreatedAt = &v +// SetDispatches gets a reference to the given []MessageDispatch and assigns it to the Dispatches field. +func (o *Message) SetDispatches(v []MessageDispatch) { + o.Dispatches = v } -// GetId returns the Id field value if set, zero value otherwise. +// GetId returns the Id field value func (o *Message) GetId() string { - if o == nil || o.Id == nil { + if o == nil { var ret string return ret } - return *o.Id + + return o.Id } -// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// GetIdOk returns a tuple with the Id field value // and a boolean to check if the value has been set. func (o *Message) GetIdOk() (*string, bool) { - if o == nil || o.Id == nil { + if o == nil { return nil, false } - return o.Id, true -} - -// HasId returns a boolean if a field has been set. -func (o *Message) HasId() bool { - if o != nil && o.Id != nil { - return true - } - - return false + return &o.Id, true } -// SetId gets a reference to the given string and assigns it to the Id field. +// SetId sets field value func (o *Message) SetId(v string) { - o.Id = &v + o.Id = v } -// GetRecipient returns the Recipient field value if set, zero value otherwise. +// GetRecipient returns the Recipient field value func (o *Message) GetRecipient() string { - if o == nil || o.Recipient == nil { + if o == nil { var ret string return ret } - return *o.Recipient + + return o.Recipient } -// GetRecipientOk returns a tuple with the Recipient field value if set, nil otherwise +// GetRecipientOk returns a tuple with the Recipient field value // and a boolean to check if the value has been set. func (o *Message) GetRecipientOk() (*string, bool) { - if o == nil || o.Recipient == nil { + if o == nil { return nil, false } - return o.Recipient, true + return &o.Recipient, true } -// HasRecipient returns a boolean if a field has been set. -func (o *Message) HasRecipient() bool { - if o != nil && o.Recipient != nil { - return true - } - - return false -} - -// SetRecipient gets a reference to the given string and assigns it to the Recipient field. +// SetRecipient sets field value func (o *Message) SetRecipient(v string) { - o.Recipient = &v + o.Recipient = v } -// GetSendCount returns the SendCount field value if set, zero value otherwise. +// GetSendCount returns the SendCount field value func (o *Message) GetSendCount() int64 { - if o == nil || o.SendCount == nil { + if o == nil { var ret int64 return ret } - return *o.SendCount + + return o.SendCount } -// GetSendCountOk returns a tuple with the SendCount field value if set, nil otherwise +// GetSendCountOk returns a tuple with the SendCount field value // and a boolean to check if the value has been set. func (o *Message) GetSendCountOk() (*int64, bool) { - if o == nil || o.SendCount == nil { + if o == nil { return nil, false } - return o.SendCount, true -} - -// HasSendCount returns a boolean if a field has been set. -func (o *Message) HasSendCount() bool { - if o != nil && o.SendCount != nil { - return true - } - - return false + return &o.SendCount, true } -// SetSendCount gets a reference to the given int64 and assigns it to the SendCount field. +// SetSendCount sets field value func (o *Message) SetSendCount(v int64) { - o.SendCount = &v + o.SendCount = v } -// GetStatus returns the Status field value if set, zero value otherwise. +// GetStatus returns the Status field value func (o *Message) GetStatus() CourierMessageStatus { - if o == nil || o.Status == nil { + if o == nil { var ret CourierMessageStatus return ret } - return *o.Status + + return o.Status } -// GetStatusOk returns a tuple with the Status field value if set, nil otherwise +// GetStatusOk returns a tuple with the Status field value // and a boolean to check if the value has been set. func (o *Message) GetStatusOk() (*CourierMessageStatus, bool) { - if o == nil || o.Status == nil { + if o == nil { return nil, false } - return o.Status, true -} - -// HasStatus returns a boolean if a field has been set. -func (o *Message) HasStatus() bool { - if o != nil && o.Status != nil { - return true - } - - return false + return &o.Status, true } -// SetStatus gets a reference to the given CourierMessageStatus and assigns it to the Status field. +// SetStatus sets field value func (o *Message) SetStatus(v CourierMessageStatus) { - o.Status = &v + o.Status = v } -// GetSubject returns the Subject field value if set, zero value otherwise. +// GetSubject returns the Subject field value func (o *Message) GetSubject() string { - if o == nil || o.Subject == nil { + if o == nil { var ret string return ret } - return *o.Subject + + return o.Subject } -// GetSubjectOk returns a tuple with the Subject field value if set, nil otherwise +// GetSubjectOk returns a tuple with the Subject field value // and a boolean to check if the value has been set. func (o *Message) GetSubjectOk() (*string, bool) { - if o == nil || o.Subject == nil { + if o == nil { return nil, false } - return o.Subject, true -} - -// HasSubject returns a boolean if a field has been set. -func (o *Message) HasSubject() bool { - if o != nil && o.Subject != nil { - return true - } - - return false + return &o.Subject, true } -// SetSubject gets a reference to the given string and assigns it to the Subject field. +// SetSubject sets field value func (o *Message) SetSubject(v string) { - o.Subject = &v + o.Subject = v } -// GetTemplateType returns the TemplateType field value if set, zero value otherwise. +// GetTemplateType returns the TemplateType field value func (o *Message) GetTemplateType() string { - if o == nil || o.TemplateType == nil { + if o == nil { var ret string return ret } - return *o.TemplateType + + return o.TemplateType } -// GetTemplateTypeOk returns a tuple with the TemplateType field value if set, nil otherwise +// GetTemplateTypeOk returns a tuple with the TemplateType field value // and a boolean to check if the value has been set. func (o *Message) GetTemplateTypeOk() (*string, bool) { - if o == nil || o.TemplateType == nil { + if o == nil { return nil, false } - return o.TemplateType, true + return &o.TemplateType, true } -// HasTemplateType returns a boolean if a field has been set. -func (o *Message) HasTemplateType() bool { - if o != nil && o.TemplateType != nil { - return true - } - - return false -} - -// SetTemplateType gets a reference to the given string and assigns it to the TemplateType field. +// SetTemplateType sets field value func (o *Message) SetTemplateType(v string) { - o.TemplateType = &v + o.TemplateType = v } -// GetType returns the Type field value if set, zero value otherwise. +// GetType returns the Type field value func (o *Message) GetType() CourierMessageType { - if o == nil || o.Type == nil { + if o == nil { var ret CourierMessageType return ret } - return *o.Type + + return o.Type } -// GetTypeOk returns a tuple with the Type field value if set, nil otherwise +// GetTypeOk returns a tuple with the Type field value // and a boolean to check if the value has been set. func (o *Message) GetTypeOk() (*CourierMessageType, bool) { - if o == nil || o.Type == nil { + if o == nil { return nil, false } - return o.Type, true + return &o.Type, true } -// HasType returns a boolean if a field has been set. -func (o *Message) HasType() bool { - if o != nil && o.Type != nil { - return true - } - - return false -} - -// SetType gets a reference to the given CourierMessageType and assigns it to the Type field. +// SetType sets field value func (o *Message) SetType(v CourierMessageType) { - o.Type = &v + o.Type = v } -// GetUpdatedAt returns the UpdatedAt field value if set, zero value otherwise. +// GetUpdatedAt returns the UpdatedAt field value func (o *Message) GetUpdatedAt() time.Time { - if o == nil || o.UpdatedAt == nil { + if o == nil { var ret time.Time return ret } - return *o.UpdatedAt + + return o.UpdatedAt } -// GetUpdatedAtOk returns a tuple with the UpdatedAt field value if set, nil otherwise +// GetUpdatedAtOk returns a tuple with the UpdatedAt field value // and a boolean to check if the value has been set. func (o *Message) GetUpdatedAtOk() (*time.Time, bool) { - if o == nil || o.UpdatedAt == nil { + if o == nil { return nil, false } - return o.UpdatedAt, true + return &o.UpdatedAt, true } -// HasUpdatedAt returns a boolean if a field has been set. -func (o *Message) HasUpdatedAt() bool { - if o != nil && o.UpdatedAt != nil { - return true - } - - return false -} - -// SetUpdatedAt gets a reference to the given time.Time and assigns it to the UpdatedAt field. +// SetUpdatedAt sets field value func (o *Message) SetUpdatedAt(v time.Time) { - o.UpdatedAt = &v + o.UpdatedAt = v } func (o Message) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} - if o.Body != nil { + if true { toSerialize["body"] = o.Body } - if o.CreatedAt != nil { + if true { toSerialize["created_at"] = o.CreatedAt } - if o.Id != nil { + if o.Dispatches != nil { + toSerialize["dispatches"] = o.Dispatches + } + if true { toSerialize["id"] = o.Id } - if o.Recipient != nil { + if true { toSerialize["recipient"] = o.Recipient } - if o.SendCount != nil { + if true { toSerialize["send_count"] = o.SendCount } - if o.Status != nil { + if true { toSerialize["status"] = o.Status } - if o.Subject != nil { + if true { toSerialize["subject"] = o.Subject } - if o.TemplateType != nil { + if true { toSerialize["template_type"] = o.TemplateType } - if o.Type != nil { + if true { toSerialize["type"] = o.Type } - if o.UpdatedAt != nil { + if true { toSerialize["updated_at"] = o.UpdatedAt } return json.Marshal(toSerialize) diff --git a/internal/client-go/model_message_dispatch.go b/internal/client-go/model_message_dispatch.go new file mode 100644 index 00000000000..d5ad3a2b670 --- /dev/null +++ b/internal/client-go/model_message_dispatch.go @@ -0,0 +1,265 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" + "time" +) + +// MessageDispatch MessageDispatch represents an attempt of sending a courier message It contains the status of the attempt (failed or successful) and the error if any occured +type MessageDispatch struct { + // CreatedAt is a helper struct field for gobuffalo.pop. + CreatedAt time.Time `json:"created_at"` + Error map[string]interface{} `json:"error,omitempty"` + // The ID of this message dispatch + Id string `json:"id"` + // The ID of the message being dispatched + MessageId string `json:"message_id"` + // The status of this dispatch Either \"failed\" or \"success\" failed CourierMessageDispatchStatusFailed success CourierMessageDispatchStatusSuccess + Status string `json:"status"` + // UpdatedAt is a helper struct field for gobuffalo.pop. + UpdatedAt time.Time `json:"updated_at"` +} + +// NewMessageDispatch instantiates a new MessageDispatch object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewMessageDispatch(createdAt time.Time, id string, messageId string, status string, updatedAt time.Time) *MessageDispatch { + this := MessageDispatch{} + this.CreatedAt = createdAt + this.Id = id + this.MessageId = messageId + this.Status = status + this.UpdatedAt = updatedAt + return &this +} + +// NewMessageDispatchWithDefaults instantiates a new MessageDispatch object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewMessageDispatchWithDefaults() *MessageDispatch { + this := MessageDispatch{} + return &this +} + +// GetCreatedAt returns the CreatedAt field value +func (o *MessageDispatch) GetCreatedAt() time.Time { + if o == nil { + var ret time.Time + return ret + } + + return o.CreatedAt +} + +// GetCreatedAtOk returns a tuple with the CreatedAt field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetCreatedAtOk() (*time.Time, bool) { + if o == nil { + return nil, false + } + return &o.CreatedAt, true +} + +// SetCreatedAt sets field value +func (o *MessageDispatch) SetCreatedAt(v time.Time) { + o.CreatedAt = v +} + +// GetError returns the Error field value if set, zero value otherwise. +func (o *MessageDispatch) GetError() map[string]interface{} { + if o == nil || o.Error == nil { + var ret map[string]interface{} + return ret + } + return o.Error +} + +// GetErrorOk returns a tuple with the Error field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetErrorOk() (map[string]interface{}, bool) { + if o == nil || o.Error == nil { + return nil, false + } + return o.Error, true +} + +// HasError returns a boolean if a field has been set. +func (o *MessageDispatch) HasError() bool { + if o != nil && o.Error != nil { + return true + } + + return false +} + +// SetError gets a reference to the given map[string]interface{} and assigns it to the Error field. +func (o *MessageDispatch) SetError(v map[string]interface{}) { + o.Error = v +} + +// GetId returns the Id field value +func (o *MessageDispatch) GetId() string { + if o == nil { + var ret string + return ret + } + + return o.Id +} + +// GetIdOk returns a tuple with the Id field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Id, true +} + +// SetId sets field value +func (o *MessageDispatch) SetId(v string) { + o.Id = v +} + +// GetMessageId returns the MessageId field value +func (o *MessageDispatch) GetMessageId() string { + if o == nil { + var ret string + return ret + } + + return o.MessageId +} + +// GetMessageIdOk returns a tuple with the MessageId field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetMessageIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.MessageId, true +} + +// SetMessageId sets field value +func (o *MessageDispatch) SetMessageId(v string) { + o.MessageId = v +} + +// GetStatus returns the Status field value +func (o *MessageDispatch) GetStatus() string { + if o == nil { + var ret string + return ret + } + + return o.Status +} + +// GetStatusOk returns a tuple with the Status field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetStatusOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Status, true +} + +// SetStatus sets field value +func (o *MessageDispatch) SetStatus(v string) { + o.Status = v +} + +// GetUpdatedAt returns the UpdatedAt field value +func (o *MessageDispatch) GetUpdatedAt() time.Time { + if o == nil { + var ret time.Time + return ret + } + + return o.UpdatedAt +} + +// GetUpdatedAtOk returns a tuple with the UpdatedAt field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetUpdatedAtOk() (*time.Time, bool) { + if o == nil { + return nil, false + } + return &o.UpdatedAt, true +} + +// SetUpdatedAt sets field value +func (o *MessageDispatch) SetUpdatedAt(v time.Time) { + o.UpdatedAt = v +} + +func (o MessageDispatch) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["created_at"] = o.CreatedAt + } + if o.Error != nil { + toSerialize["error"] = o.Error + } + if true { + toSerialize["id"] = o.Id + } + if true { + toSerialize["message_id"] = o.MessageId + } + if true { + toSerialize["status"] = o.Status + } + if true { + toSerialize["updated_at"] = o.UpdatedAt + } + return json.Marshal(toSerialize) +} + +type NullableMessageDispatch struct { + value *MessageDispatch + isSet bool +} + +func (v NullableMessageDispatch) Get() *MessageDispatch { + return v.value +} + +func (v *NullableMessageDispatch) Set(val *MessageDispatch) { + v.value = val + v.isSet = true +} + +func (v NullableMessageDispatch) IsSet() bool { + return v.isSet +} + +func (v *NullableMessageDispatch) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableMessageDispatch(val *MessageDispatch) *NullableMessageDispatch { + return &NullableMessageDispatch{value: val, isSet: true} +} + +func (v NullableMessageDispatch) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableMessageDispatch) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/internal/httpclient/.openapi-generator/FILES b/internal/httpclient/.openapi-generator/FILES index 77a3be630a0..d11e26721f6 100644 --- a/internal/httpclient/.openapi-generator/FILES +++ b/internal/httpclient/.openapi-generator/FILES @@ -48,6 +48,7 @@ docs/JsonPatch.md docs/LoginFlow.md docs/LogoutFlow.md docs/Message.md +docs/MessageDispatch.md docs/MetadataApi.md docs/NeedsPrivilegedSessionError.md docs/OAuth2Client.md @@ -148,6 +149,7 @@ model_json_patch.go model_login_flow.go model_logout_flow.go model_message.go +model_message_dispatch.go model_needs_privileged_session_error.go model_o_auth2_client.go model_o_auth2_consent_request_open_id_connect_context.go diff --git a/internal/httpclient/README.md b/internal/httpclient/README.md index 3fbdc4c5ca5..3fbf8f68f7b 100644 --- a/internal/httpclient/README.md +++ b/internal/httpclient/README.md @@ -79,6 +79,7 @@ All URIs are relative to *http://localhost* Class | Method | HTTP request | Description ------------ | ------------- | ------------- | ------------- +*CourierApi* | [**GetCourierMessage**](docs/CourierApi.md#getcouriermessage) | **Get** /admin/courier/messages/{id} | Get a Message *CourierApi* | [**ListCourierMessages**](docs/CourierApi.md#listcouriermessages) | **Get** /admin/courier/messages | List Messages *FrontendApi* | [**CreateBrowserLoginFlow**](docs/FrontendApi.md#createbrowserloginflow) | **Get** /self-service/login/browser | Create Login Flow for Browsers *FrontendApi* | [**CreateBrowserLogoutFlow**](docs/FrontendApi.md#createbrowserlogoutflow) | **Get** /self-service/logout/browser | Create a Logout URL for Browsers @@ -168,6 +169,7 @@ Class | Method | HTTP request | Description - [LoginFlow](docs/LoginFlow.md) - [LogoutFlow](docs/LogoutFlow.md) - [Message](docs/Message.md) + - [MessageDispatch](docs/MessageDispatch.md) - [NeedsPrivilegedSessionError](docs/NeedsPrivilegedSessionError.md) - [OAuth2Client](docs/OAuth2Client.md) - [OAuth2ConsentRequestOpenIDConnectContext](docs/OAuth2ConsentRequestOpenIDConnectContext.md) diff --git a/internal/httpclient/api_courier.go b/internal/httpclient/api_courier.go index 5941376dd02..a7e43bcd183 100644 --- a/internal/httpclient/api_courier.go +++ b/internal/httpclient/api_courier.go @@ -17,6 +17,7 @@ import ( "io" "net/http" "net/url" + "strings" ) // Linger please @@ -26,6 +27,21 @@ var ( type CourierApi interface { + /* + * GetCourierMessage Get a Message + * Gets a specific messages by the given ID. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id MessageID is the ID of the message. + * @return CourierApiApiGetCourierMessageRequest + */ + GetCourierMessage(ctx context.Context, id string) CourierApiApiGetCourierMessageRequest + + /* + * GetCourierMessageExecute executes the request + * @return Message + */ + GetCourierMessageExecute(r CourierApiApiGetCourierMessageRequest) (*Message, *http.Response, error) + /* * ListCourierMessages List Messages * Lists all messages by given status and recipient. @@ -44,21 +60,157 @@ type CourierApi interface { // CourierApiService CourierApi service type CourierApiService service +type CourierApiApiGetCourierMessageRequest struct { + ctx context.Context + ApiService CourierApi + id string +} + +func (r CourierApiApiGetCourierMessageRequest) Execute() (*Message, *http.Response, error) { + return r.ApiService.GetCourierMessageExecute(r) +} + +/* + * GetCourierMessage Get a Message + * Gets a specific messages by the given ID. + * @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). + * @param id MessageID is the ID of the message. + * @return CourierApiApiGetCourierMessageRequest + */ +func (a *CourierApiService) GetCourierMessage(ctx context.Context, id string) CourierApiApiGetCourierMessageRequest { + return CourierApiApiGetCourierMessageRequest{ + ApiService: a, + ctx: ctx, + id: id, + } +} + +/* + * Execute executes the request + * @return Message + */ +func (a *CourierApiService) GetCourierMessageExecute(r CourierApiApiGetCourierMessageRequest) (*Message, *http.Response, error) { + var ( + localVarHTTPMethod = http.MethodGet + localVarPostBody interface{} + localVarFormFileName string + localVarFileName string + localVarFileBytes []byte + localVarReturnValue *Message + ) + + localBasePath, err := a.client.cfg.ServerURLWithContext(r.ctx, "CourierApiService.GetCourierMessage") + if err != nil { + return localVarReturnValue, nil, &GenericOpenAPIError{error: err.Error()} + } + + localVarPath := localBasePath + "/admin/courier/messages/{id}" + localVarPath = strings.Replace(localVarPath, "{"+"id"+"}", url.PathEscape(parameterToString(r.id, "")), -1) + + localVarHeaderParams := make(map[string]string) + localVarQueryParams := url.Values{} + localVarFormParams := url.Values{} + + // to determine the Content-Type header + localVarHTTPContentTypes := []string{} + + // set Content-Type header + localVarHTTPContentType := selectHeaderContentType(localVarHTTPContentTypes) + if localVarHTTPContentType != "" { + localVarHeaderParams["Content-Type"] = localVarHTTPContentType + } + + // to determine the Accept header + localVarHTTPHeaderAccepts := []string{"application/json"} + + // set Accept header + localVarHTTPHeaderAccept := selectHeaderAccept(localVarHTTPHeaderAccepts) + if localVarHTTPHeaderAccept != "" { + localVarHeaderParams["Accept"] = localVarHTTPHeaderAccept + } + if r.ctx != nil { + // API Key Authentication + if auth, ok := r.ctx.Value(ContextAPIKeys).(map[string]APIKey); ok { + if apiKey, ok := auth["oryAccessToken"]; ok { + var key string + if apiKey.Prefix != "" { + key = apiKey.Prefix + " " + apiKey.Key + } else { + key = apiKey.Key + } + localVarHeaderParams["Authorization"] = key + } + } + } + req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, localVarFormFileName, localVarFileName, localVarFileBytes) + if err != nil { + return localVarReturnValue, nil, err + } + + localVarHTTPResponse, err := a.client.callAPI(req) + if err != nil || localVarHTTPResponse == nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + localVarBody, err := io.ReadAll(localVarHTTPResponse.Body) + localVarHTTPResponse.Body.Close() + localVarHTTPResponse.Body = io.NopCloser(bytes.NewBuffer(localVarBody)) + if err != nil { + return localVarReturnValue, localVarHTTPResponse, err + } + + if localVarHTTPResponse.StatusCode >= 300 { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: localVarHTTPResponse.Status, + } + if localVarHTTPResponse.StatusCode == 400 { + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + var v ErrorGeneric + err = a.client.decode(&v, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr.error = err.Error() + return localVarReturnValue, localVarHTTPResponse, newErr + } + newErr.model = v + return localVarReturnValue, localVarHTTPResponse, newErr + } + + err = a.client.decode(&localVarReturnValue, localVarBody, localVarHTTPResponse.Header.Get("Content-Type")) + if err != nil { + newErr := &GenericOpenAPIError{ + body: localVarBody, + error: err.Error(), + } + return localVarReturnValue, localVarHTTPResponse, newErr + } + + return localVarReturnValue, localVarHTTPResponse, nil +} + type CourierApiApiListCourierMessagesRequest struct { ctx context.Context ApiService CourierApi - perPage *int64 - page *int64 + pageSize *int64 + pageToken *string status *CourierMessageStatus recipient *string } -func (r CourierApiApiListCourierMessagesRequest) PerPage(perPage int64) CourierApiApiListCourierMessagesRequest { - r.perPage = &perPage +func (r CourierApiApiListCourierMessagesRequest) PageSize(pageSize int64) CourierApiApiListCourierMessagesRequest { + r.pageSize = &pageSize return r } -func (r CourierApiApiListCourierMessagesRequest) Page(page int64) CourierApiApiListCourierMessagesRequest { - r.page = &page +func (r CourierApiApiListCourierMessagesRequest) PageToken(pageToken string) CourierApiApiListCourierMessagesRequest { + r.pageToken = &pageToken return r } func (r CourierApiApiListCourierMessagesRequest) Status(status CourierMessageStatus) CourierApiApiListCourierMessagesRequest { @@ -112,11 +264,11 @@ func (a *CourierApiService) ListCourierMessagesExecute(r CourierApiApiListCourie localVarQueryParams := url.Values{} localVarFormParams := url.Values{} - if r.perPage != nil { - localVarQueryParams.Add("per_page", parameterToString(*r.perPage, "")) + if r.pageSize != nil { + localVarQueryParams.Add("page_size", parameterToString(*r.pageSize, "")) } - if r.page != nil { - localVarQueryParams.Add("page", parameterToString(*r.page, "")) + if r.pageToken != nil { + localVarQueryParams.Add("page_token", parameterToString(*r.pageToken, "")) } if r.status != nil { localVarQueryParams.Add("status", parameterToString(*r.status, "")) diff --git a/internal/httpclient/model_message.go b/internal/httpclient/model_message.go index 0ca65e618fb..8b385429f4e 100644 --- a/internal/httpclient/model_message.go +++ b/internal/httpclient/model_message.go @@ -18,26 +18,38 @@ import ( // Message struct for Message type Message struct { - Body *string `json:"body,omitempty"` + Body string `json:"body"` // CreatedAt is a helper struct field for gobuffalo.pop. - CreatedAt *time.Time `json:"created_at,omitempty"` - Id *string `json:"id,omitempty"` - Recipient *string `json:"recipient,omitempty"` - SendCount *int64 `json:"send_count,omitempty"` - Status *CourierMessageStatus `json:"status,omitempty"` - Subject *string `json:"subject,omitempty"` - TemplateType *string `json:"template_type,omitempty"` - Type *CourierMessageType `json:"type,omitempty"` + CreatedAt time.Time `json:"created_at"` + Dispatches []MessageDispatch `json:"dispatches,omitempty"` + Id string `json:"id"` + Recipient string `json:"recipient"` + SendCount int64 `json:"send_count"` + Status CourierMessageStatus `json:"status"` + Subject string `json:"subject"` + // recovery_invalid TypeRecoveryInvalid recovery_valid TypeRecoveryValid recovery_code_invalid TypeRecoveryCodeInvalid recovery_code_valid TypeRecoveryCodeValid verification_invalid TypeVerificationInvalid verification_valid TypeVerificationValid verification_code_invalid TypeVerificationCodeInvalid verification_code_valid TypeVerificationCodeValid otp TypeOTP stub TypeTestStub + TemplateType string `json:"template_type"` + Type CourierMessageType `json:"type"` // UpdatedAt is a helper struct field for gobuffalo.pop. - UpdatedAt *time.Time `json:"updated_at,omitempty"` + UpdatedAt time.Time `json:"updated_at"` } // NewMessage instantiates a new Message object // This constructor will assign default values to properties that have it defined, // and makes sure properties required by API are set, but the set of arguments // will change when the set of required properties is changed -func NewMessage() *Message { +func NewMessage(body string, createdAt time.Time, id string, recipient string, sendCount int64, status CourierMessageStatus, subject string, templateType string, type_ CourierMessageType, updatedAt time.Time) *Message { this := Message{} + this.Body = body + this.CreatedAt = createdAt + this.Id = id + this.Recipient = recipient + this.SendCount = sendCount + this.Status = status + this.Subject = subject + this.TemplateType = templateType + this.Type = type_ + this.UpdatedAt = updatedAt return &this } @@ -49,356 +61,311 @@ func NewMessageWithDefaults() *Message { return &this } -// GetBody returns the Body field value if set, zero value otherwise. +// GetBody returns the Body field value func (o *Message) GetBody() string { - if o == nil || o.Body == nil { + if o == nil { var ret string return ret } - return *o.Body + + return o.Body } -// GetBodyOk returns a tuple with the Body field value if set, nil otherwise +// GetBodyOk returns a tuple with the Body field value // and a boolean to check if the value has been set. func (o *Message) GetBodyOk() (*string, bool) { - if o == nil || o.Body == nil { + if o == nil { return nil, false } - return o.Body, true -} - -// HasBody returns a boolean if a field has been set. -func (o *Message) HasBody() bool { - if o != nil && o.Body != nil { - return true - } - - return false + return &o.Body, true } -// SetBody gets a reference to the given string and assigns it to the Body field. +// SetBody sets field value func (o *Message) SetBody(v string) { - o.Body = &v + o.Body = v } -// GetCreatedAt returns the CreatedAt field value if set, zero value otherwise. +// GetCreatedAt returns the CreatedAt field value func (o *Message) GetCreatedAt() time.Time { - if o == nil || o.CreatedAt == nil { + if o == nil { var ret time.Time return ret } - return *o.CreatedAt + + return o.CreatedAt } -// GetCreatedAtOk returns a tuple with the CreatedAt field value if set, nil otherwise +// GetCreatedAtOk returns a tuple with the CreatedAt field value // and a boolean to check if the value has been set. func (o *Message) GetCreatedAtOk() (*time.Time, bool) { - if o == nil || o.CreatedAt == nil { + if o == nil { + return nil, false + } + return &o.CreatedAt, true +} + +// SetCreatedAt sets field value +func (o *Message) SetCreatedAt(v time.Time) { + o.CreatedAt = v +} + +// GetDispatches returns the Dispatches field value if set, zero value otherwise. +func (o *Message) GetDispatches() []MessageDispatch { + if o == nil || o.Dispatches == nil { + var ret []MessageDispatch + return ret + } + return o.Dispatches +} + +// GetDispatchesOk returns a tuple with the Dispatches field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Message) GetDispatchesOk() ([]MessageDispatch, bool) { + if o == nil || o.Dispatches == nil { return nil, false } - return o.CreatedAt, true + return o.Dispatches, true } -// HasCreatedAt returns a boolean if a field has been set. -func (o *Message) HasCreatedAt() bool { - if o != nil && o.CreatedAt != nil { +// HasDispatches returns a boolean if a field has been set. +func (o *Message) HasDispatches() bool { + if o != nil && o.Dispatches != nil { return true } return false } -// SetCreatedAt gets a reference to the given time.Time and assigns it to the CreatedAt field. -func (o *Message) SetCreatedAt(v time.Time) { - o.CreatedAt = &v +// SetDispatches gets a reference to the given []MessageDispatch and assigns it to the Dispatches field. +func (o *Message) SetDispatches(v []MessageDispatch) { + o.Dispatches = v } -// GetId returns the Id field value if set, zero value otherwise. +// GetId returns the Id field value func (o *Message) GetId() string { - if o == nil || o.Id == nil { + if o == nil { var ret string return ret } - return *o.Id + + return o.Id } -// GetIdOk returns a tuple with the Id field value if set, nil otherwise +// GetIdOk returns a tuple with the Id field value // and a boolean to check if the value has been set. func (o *Message) GetIdOk() (*string, bool) { - if o == nil || o.Id == nil { + if o == nil { return nil, false } - return o.Id, true -} - -// HasId returns a boolean if a field has been set. -func (o *Message) HasId() bool { - if o != nil && o.Id != nil { - return true - } - - return false + return &o.Id, true } -// SetId gets a reference to the given string and assigns it to the Id field. +// SetId sets field value func (o *Message) SetId(v string) { - o.Id = &v + o.Id = v } -// GetRecipient returns the Recipient field value if set, zero value otherwise. +// GetRecipient returns the Recipient field value func (o *Message) GetRecipient() string { - if o == nil || o.Recipient == nil { + if o == nil { var ret string return ret } - return *o.Recipient + + return o.Recipient } -// GetRecipientOk returns a tuple with the Recipient field value if set, nil otherwise +// GetRecipientOk returns a tuple with the Recipient field value // and a boolean to check if the value has been set. func (o *Message) GetRecipientOk() (*string, bool) { - if o == nil || o.Recipient == nil { + if o == nil { return nil, false } - return o.Recipient, true + return &o.Recipient, true } -// HasRecipient returns a boolean if a field has been set. -func (o *Message) HasRecipient() bool { - if o != nil && o.Recipient != nil { - return true - } - - return false -} - -// SetRecipient gets a reference to the given string and assigns it to the Recipient field. +// SetRecipient sets field value func (o *Message) SetRecipient(v string) { - o.Recipient = &v + o.Recipient = v } -// GetSendCount returns the SendCount field value if set, zero value otherwise. +// GetSendCount returns the SendCount field value func (o *Message) GetSendCount() int64 { - if o == nil || o.SendCount == nil { + if o == nil { var ret int64 return ret } - return *o.SendCount + + return o.SendCount } -// GetSendCountOk returns a tuple with the SendCount field value if set, nil otherwise +// GetSendCountOk returns a tuple with the SendCount field value // and a boolean to check if the value has been set. func (o *Message) GetSendCountOk() (*int64, bool) { - if o == nil || o.SendCount == nil { + if o == nil { return nil, false } - return o.SendCount, true -} - -// HasSendCount returns a boolean if a field has been set. -func (o *Message) HasSendCount() bool { - if o != nil && o.SendCount != nil { - return true - } - - return false + return &o.SendCount, true } -// SetSendCount gets a reference to the given int64 and assigns it to the SendCount field. +// SetSendCount sets field value func (o *Message) SetSendCount(v int64) { - o.SendCount = &v + o.SendCount = v } -// GetStatus returns the Status field value if set, zero value otherwise. +// GetStatus returns the Status field value func (o *Message) GetStatus() CourierMessageStatus { - if o == nil || o.Status == nil { + if o == nil { var ret CourierMessageStatus return ret } - return *o.Status + + return o.Status } -// GetStatusOk returns a tuple with the Status field value if set, nil otherwise +// GetStatusOk returns a tuple with the Status field value // and a boolean to check if the value has been set. func (o *Message) GetStatusOk() (*CourierMessageStatus, bool) { - if o == nil || o.Status == nil { + if o == nil { return nil, false } - return o.Status, true -} - -// HasStatus returns a boolean if a field has been set. -func (o *Message) HasStatus() bool { - if o != nil && o.Status != nil { - return true - } - - return false + return &o.Status, true } -// SetStatus gets a reference to the given CourierMessageStatus and assigns it to the Status field. +// SetStatus sets field value func (o *Message) SetStatus(v CourierMessageStatus) { - o.Status = &v + o.Status = v } -// GetSubject returns the Subject field value if set, zero value otherwise. +// GetSubject returns the Subject field value func (o *Message) GetSubject() string { - if o == nil || o.Subject == nil { + if o == nil { var ret string return ret } - return *o.Subject + + return o.Subject } -// GetSubjectOk returns a tuple with the Subject field value if set, nil otherwise +// GetSubjectOk returns a tuple with the Subject field value // and a boolean to check if the value has been set. func (o *Message) GetSubjectOk() (*string, bool) { - if o == nil || o.Subject == nil { + if o == nil { return nil, false } - return o.Subject, true -} - -// HasSubject returns a boolean if a field has been set. -func (o *Message) HasSubject() bool { - if o != nil && o.Subject != nil { - return true - } - - return false + return &o.Subject, true } -// SetSubject gets a reference to the given string and assigns it to the Subject field. +// SetSubject sets field value func (o *Message) SetSubject(v string) { - o.Subject = &v + o.Subject = v } -// GetTemplateType returns the TemplateType field value if set, zero value otherwise. +// GetTemplateType returns the TemplateType field value func (o *Message) GetTemplateType() string { - if o == nil || o.TemplateType == nil { + if o == nil { var ret string return ret } - return *o.TemplateType + + return o.TemplateType } -// GetTemplateTypeOk returns a tuple with the TemplateType field value if set, nil otherwise +// GetTemplateTypeOk returns a tuple with the TemplateType field value // and a boolean to check if the value has been set. func (o *Message) GetTemplateTypeOk() (*string, bool) { - if o == nil || o.TemplateType == nil { + if o == nil { return nil, false } - return o.TemplateType, true + return &o.TemplateType, true } -// HasTemplateType returns a boolean if a field has been set. -func (o *Message) HasTemplateType() bool { - if o != nil && o.TemplateType != nil { - return true - } - - return false -} - -// SetTemplateType gets a reference to the given string and assigns it to the TemplateType field. +// SetTemplateType sets field value func (o *Message) SetTemplateType(v string) { - o.TemplateType = &v + o.TemplateType = v } -// GetType returns the Type field value if set, zero value otherwise. +// GetType returns the Type field value func (o *Message) GetType() CourierMessageType { - if o == nil || o.Type == nil { + if o == nil { var ret CourierMessageType return ret } - return *o.Type + + return o.Type } -// GetTypeOk returns a tuple with the Type field value if set, nil otherwise +// GetTypeOk returns a tuple with the Type field value // and a boolean to check if the value has been set. func (o *Message) GetTypeOk() (*CourierMessageType, bool) { - if o == nil || o.Type == nil { + if o == nil { return nil, false } - return o.Type, true + return &o.Type, true } -// HasType returns a boolean if a field has been set. -func (o *Message) HasType() bool { - if o != nil && o.Type != nil { - return true - } - - return false -} - -// SetType gets a reference to the given CourierMessageType and assigns it to the Type field. +// SetType sets field value func (o *Message) SetType(v CourierMessageType) { - o.Type = &v + o.Type = v } -// GetUpdatedAt returns the UpdatedAt field value if set, zero value otherwise. +// GetUpdatedAt returns the UpdatedAt field value func (o *Message) GetUpdatedAt() time.Time { - if o == nil || o.UpdatedAt == nil { + if o == nil { var ret time.Time return ret } - return *o.UpdatedAt + + return o.UpdatedAt } -// GetUpdatedAtOk returns a tuple with the UpdatedAt field value if set, nil otherwise +// GetUpdatedAtOk returns a tuple with the UpdatedAt field value // and a boolean to check if the value has been set. func (o *Message) GetUpdatedAtOk() (*time.Time, bool) { - if o == nil || o.UpdatedAt == nil { + if o == nil { return nil, false } - return o.UpdatedAt, true + return &o.UpdatedAt, true } -// HasUpdatedAt returns a boolean if a field has been set. -func (o *Message) HasUpdatedAt() bool { - if o != nil && o.UpdatedAt != nil { - return true - } - - return false -} - -// SetUpdatedAt gets a reference to the given time.Time and assigns it to the UpdatedAt field. +// SetUpdatedAt sets field value func (o *Message) SetUpdatedAt(v time.Time) { - o.UpdatedAt = &v + o.UpdatedAt = v } func (o Message) MarshalJSON() ([]byte, error) { toSerialize := map[string]interface{}{} - if o.Body != nil { + if true { toSerialize["body"] = o.Body } - if o.CreatedAt != nil { + if true { toSerialize["created_at"] = o.CreatedAt } - if o.Id != nil { + if o.Dispatches != nil { + toSerialize["dispatches"] = o.Dispatches + } + if true { toSerialize["id"] = o.Id } - if o.Recipient != nil { + if true { toSerialize["recipient"] = o.Recipient } - if o.SendCount != nil { + if true { toSerialize["send_count"] = o.SendCount } - if o.Status != nil { + if true { toSerialize["status"] = o.Status } - if o.Subject != nil { + if true { toSerialize["subject"] = o.Subject } - if o.TemplateType != nil { + if true { toSerialize["template_type"] = o.TemplateType } - if o.Type != nil { + if true { toSerialize["type"] = o.Type } - if o.UpdatedAt != nil { + if true { toSerialize["updated_at"] = o.UpdatedAt } return json.Marshal(toSerialize) diff --git a/internal/httpclient/model_message_dispatch.go b/internal/httpclient/model_message_dispatch.go new file mode 100644 index 00000000000..d5ad3a2b670 --- /dev/null +++ b/internal/httpclient/model_message_dispatch.go @@ -0,0 +1,265 @@ +/* + * Ory Identities API + * + * This is the API specification for Ory Identities with features such as registration, login, recovery, account verification, profile settings, password reset, identity management, session management, email and sms delivery, and more. + * + * API version: + * Contact: office@ory.sh + */ + +// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT. + +package client + +import ( + "encoding/json" + "time" +) + +// MessageDispatch MessageDispatch represents an attempt of sending a courier message It contains the status of the attempt (failed or successful) and the error if any occured +type MessageDispatch struct { + // CreatedAt is a helper struct field for gobuffalo.pop. + CreatedAt time.Time `json:"created_at"` + Error map[string]interface{} `json:"error,omitempty"` + // The ID of this message dispatch + Id string `json:"id"` + // The ID of the message being dispatched + MessageId string `json:"message_id"` + // The status of this dispatch Either \"failed\" or \"success\" failed CourierMessageDispatchStatusFailed success CourierMessageDispatchStatusSuccess + Status string `json:"status"` + // UpdatedAt is a helper struct field for gobuffalo.pop. + UpdatedAt time.Time `json:"updated_at"` +} + +// NewMessageDispatch instantiates a new MessageDispatch object +// This constructor will assign default values to properties that have it defined, +// and makes sure properties required by API are set, but the set of arguments +// will change when the set of required properties is changed +func NewMessageDispatch(createdAt time.Time, id string, messageId string, status string, updatedAt time.Time) *MessageDispatch { + this := MessageDispatch{} + this.CreatedAt = createdAt + this.Id = id + this.MessageId = messageId + this.Status = status + this.UpdatedAt = updatedAt + return &this +} + +// NewMessageDispatchWithDefaults instantiates a new MessageDispatch object +// This constructor will only assign default values to properties that have it defined, +// but it doesn't guarantee that properties required by API are set +func NewMessageDispatchWithDefaults() *MessageDispatch { + this := MessageDispatch{} + return &this +} + +// GetCreatedAt returns the CreatedAt field value +func (o *MessageDispatch) GetCreatedAt() time.Time { + if o == nil { + var ret time.Time + return ret + } + + return o.CreatedAt +} + +// GetCreatedAtOk returns a tuple with the CreatedAt field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetCreatedAtOk() (*time.Time, bool) { + if o == nil { + return nil, false + } + return &o.CreatedAt, true +} + +// SetCreatedAt sets field value +func (o *MessageDispatch) SetCreatedAt(v time.Time) { + o.CreatedAt = v +} + +// GetError returns the Error field value if set, zero value otherwise. +func (o *MessageDispatch) GetError() map[string]interface{} { + if o == nil || o.Error == nil { + var ret map[string]interface{} + return ret + } + return o.Error +} + +// GetErrorOk returns a tuple with the Error field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetErrorOk() (map[string]interface{}, bool) { + if o == nil || o.Error == nil { + return nil, false + } + return o.Error, true +} + +// HasError returns a boolean if a field has been set. +func (o *MessageDispatch) HasError() bool { + if o != nil && o.Error != nil { + return true + } + + return false +} + +// SetError gets a reference to the given map[string]interface{} and assigns it to the Error field. +func (o *MessageDispatch) SetError(v map[string]interface{}) { + o.Error = v +} + +// GetId returns the Id field value +func (o *MessageDispatch) GetId() string { + if o == nil { + var ret string + return ret + } + + return o.Id +} + +// GetIdOk returns a tuple with the Id field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Id, true +} + +// SetId sets field value +func (o *MessageDispatch) SetId(v string) { + o.Id = v +} + +// GetMessageId returns the MessageId field value +func (o *MessageDispatch) GetMessageId() string { + if o == nil { + var ret string + return ret + } + + return o.MessageId +} + +// GetMessageIdOk returns a tuple with the MessageId field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetMessageIdOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.MessageId, true +} + +// SetMessageId sets field value +func (o *MessageDispatch) SetMessageId(v string) { + o.MessageId = v +} + +// GetStatus returns the Status field value +func (o *MessageDispatch) GetStatus() string { + if o == nil { + var ret string + return ret + } + + return o.Status +} + +// GetStatusOk returns a tuple with the Status field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetStatusOk() (*string, bool) { + if o == nil { + return nil, false + } + return &o.Status, true +} + +// SetStatus sets field value +func (o *MessageDispatch) SetStatus(v string) { + o.Status = v +} + +// GetUpdatedAt returns the UpdatedAt field value +func (o *MessageDispatch) GetUpdatedAt() time.Time { + if o == nil { + var ret time.Time + return ret + } + + return o.UpdatedAt +} + +// GetUpdatedAtOk returns a tuple with the UpdatedAt field value +// and a boolean to check if the value has been set. +func (o *MessageDispatch) GetUpdatedAtOk() (*time.Time, bool) { + if o == nil { + return nil, false + } + return &o.UpdatedAt, true +} + +// SetUpdatedAt sets field value +func (o *MessageDispatch) SetUpdatedAt(v time.Time) { + o.UpdatedAt = v +} + +func (o MessageDispatch) MarshalJSON() ([]byte, error) { + toSerialize := map[string]interface{}{} + if true { + toSerialize["created_at"] = o.CreatedAt + } + if o.Error != nil { + toSerialize["error"] = o.Error + } + if true { + toSerialize["id"] = o.Id + } + if true { + toSerialize["message_id"] = o.MessageId + } + if true { + toSerialize["status"] = o.Status + } + if true { + toSerialize["updated_at"] = o.UpdatedAt + } + return json.Marshal(toSerialize) +} + +type NullableMessageDispatch struct { + value *MessageDispatch + isSet bool +} + +func (v NullableMessageDispatch) Get() *MessageDispatch { + return v.value +} + +func (v *NullableMessageDispatch) Set(val *MessageDispatch) { + v.value = val + v.isSet = true +} + +func (v NullableMessageDispatch) IsSet() bool { + return v.isSet +} + +func (v *NullableMessageDispatch) Unset() { + v.value = nil + v.isSet = false +} + +func NewNullableMessageDispatch(val *MessageDispatch) *NullableMessageDispatch { + return &NullableMessageDispatch{value: val, isSet: true} +} + +func (v NullableMessageDispatch) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +func (v *NullableMessageDispatch) UnmarshalJSON(src []byte) error { + v.isSet = true + return json.Unmarshal(src, &v.value) +} diff --git a/persistence/sql/migratest/testdata/20221205095201_testdata.sql b/persistence/sql/migratest/testdata/20221205095201_testdata.sql new file mode 100644 index 00000000000..de20fc952a7 --- /dev/null +++ b/persistence/sql/migratest/testdata/20221205095201_testdata.sql @@ -0,0 +1,41 @@ +INSERT INTO + courier_messages ( + id, + type, + status, + body, + subject, + recipient, + created_at, + updated_at, + template_type, + nid, + send_count + ) +VALUES + ( + 'd9d4401c-08a1-434c-8ab5-4a7edefde351', + 1, + 2, + 'Hi, please verify your account by clicking the following link: http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/u9ZcBr5HbRTR8f53Qj2Ng3KR8Mv1Zjdb', + 'Please verify your email address', + 'foobar@ory.sh', + '2013-10-07 08:23:19', + '2013-10-07 08:23:19', + 'verification_valid', + '884f556e-eb3a-4b9f-bee3-11345642c6c0', + 4 + ); + +INSERT INTO + courier_message_dispatches +VALUES + ( + 'ea0c9a03-44ce-43a8-8c5d-52f468991cb9', + 'd9d4401c-08a1-434c-8ab5-4a7edefde351', + 'success', + '{}', + '884f556e-eb3a-4b9f-bee3-11345642c6c0', + '2013-10-07 08:23:19', + '2013-10-07 08:23:19' + ) \ No newline at end of file diff --git a/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.down.sql b/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.down.sql new file mode 100644 index 00000000000..2154ad8ef03 --- /dev/null +++ b/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.down.sql @@ -0,0 +1 @@ +DROP TABLE courier_message_dispatches; \ No newline at end of file diff --git a/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.mysql.up.sql b/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.mysql.up.sql new file mode 100644 index 00000000000..20f9fbcb40b --- /dev/null +++ b/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.mysql.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE courier_message_dispatches ( + id CHAR(36) PRIMARY KEY, + message_id CHAR(36) NOT NULL, + status VARCHAR(7) NOT NULL, + error JSON, + nid CHAR(36) NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT courier_message_dispatches_message_id_fk FOREIGN KEY (message_id) REFERENCES courier_messages (id) ON DELETE cascade, + CONSTRAINT courier_message_dispatches_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON DELETE cascade +); + +CREATE INDEX courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches (id, message_id, nid); \ No newline at end of file diff --git a/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.up.sql b/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.up.sql new file mode 100644 index 00000000000..27c21135338 --- /dev/null +++ b/persistence/sql/migrations/sql/20221205092803000000_add_courier_send_attempts_table.up.sql @@ -0,0 +1,13 @@ +CREATE TABLE courier_message_dispatches ( + id UUID PRIMARY KEY, + message_id UUID NOT NULL, + status VARCHAR(7) NOT NULL, + error JSON, + nid UUID NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT courier_message_dispatches_message_id_fk FOREIGN KEY (message_id) REFERENCES courier_messages (id) ON DELETE cascade, + CONSTRAINT courier_message_dispatches_nid_fk FOREIGN KEY (nid) REFERENCES networks (id) ON DELETE cascade +); + +CREATE INDEX courier_message_dispatches_id_message_id_nid_idx ON courier_message_dispatches (id, message_id, nid); \ No newline at end of file diff --git a/persistence/sql/persister_courier.go b/persistence/sql/persister_courier.go index c3b4e68bebd..e4c08e87be3 100644 --- a/persistence/sql/persister_courier.go +++ b/persistence/sql/persister_courier.go @@ -6,13 +6,17 @@ package sql import ( "context" "database/sql" + "encoding/json" "fmt" "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" "github.com/pkg/errors" + "github.com/ory/herodot" + "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/sqlcon" + "github.com/ory/x/uuidx" "github.com/ory/kratos/courier" ) @@ -28,7 +32,7 @@ func (p *Persister) AddMessage(ctx context.Context, m *courier.Message) error { return sqlcon.HandleError(p.GetConnection(ctx).Create(m)) // do not create eager to avoid identity injection. } -func (p *Persister) ListMessages(ctx context.Context, filter courier.ListCourierMessagesParameters) ([]courier.Message, int64, error) { +func (p *Persister) ListMessages(ctx context.Context, filter courier.ListCourierMessagesParameters, opts []keysetpagination.Option) ([]courier.Message, int64, *keysetpagination.Paginator, error) { ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.ListMessages") defer span.End() @@ -42,17 +46,24 @@ func (p *Persister) ListMessages(ctx context.Context, filter courier.ListCourier q = q.Where("recipient=?", filter.Recipient) } - messages := make([]courier.Message, 0) - if err := q.Paginate(filter.Page, filter.PerPage).Order("created_at DESC").All(&messages); err != nil { - return nil, 0, sqlcon.HandleError(err) + opts = append(opts, keysetpagination.WithDefaultToken(new(courier.Message).DefaultPageToken())) + opts = append(opts, keysetpagination.WithDefaultSize(10)) + opts = append(opts, keysetpagination.WithColumn("created_at", "DESC")) + paginator := keysetpagination.GetPaginator(opts...) + + messages := make([]courier.Message, paginator.Size()) + if err := q.Scope(keysetpagination.Paginate[courier.Message](paginator)). + All(&messages); err != nil { + return nil, 0, nil, sqlcon.HandleError(err) } count, err := q.Count(&courier.Message{}) if err != nil { - return nil, 0, sqlcon.HandleError(err) + return nil, 0, nil, sqlcon.HandleError(err) } - return messages, int64(count), nil + messages, nextPage := keysetpagination.Result(messages, paginator) + return messages, int64(count), nextPage, nil } func (p *Persister) NextMessages(ctx context.Context, limit uint8) (messages []courier.Message, err error) { @@ -170,3 +181,46 @@ func (p *Persister) IncrementMessageSendCount(ctx context.Context, id uuid.UUID) return nil } + +func (p *Persister) FetchMessage(ctx context.Context, msgID uuid.UUID) (*courier.Message, error) { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.FetchMessage") + defer span.End() + + var message courier.Message + if err := p.GetConnection(ctx). + Where("id = ? AND nid = ?", msgID, p.NetworkID(ctx)). + Eager(). + First(&message); err != nil { + return nil, sqlcon.HandleError(err) + } + + return &message, nil +} + +func (p *Persister) RecordDispatch(ctx context.Context, msgID uuid.UUID, status courier.CourierMessageDispatchStatus, err error) error { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.RecordDispatch") + defer span.End() + + dispatch := courier.MessageDispatch{ + ID: uuidx.NewV4(), + MessageID: msgID, + Status: status, + NID: p.NetworkID(ctx), + } + + if err != nil { + // We use herodot as a carrier for the error's data + her := herodot.ToDefaultError(err, "") + content, mErr := json.Marshal(her) + if mErr != nil { + return errors.WithStack(mErr) + } + dispatch.Error = content + } + + if err := p.GetConnection(ctx).Create(&dispatch); err != nil { + return sqlcon.HandleError(err) + } + + return nil +} diff --git a/persistence/sql/persister_session.go b/persistence/sql/persister_session.go index 4ee12c5a483..259b7fcdfb4 100644 --- a/persistence/sql/persister_session.go +++ b/persistence/sql/persister_session.go @@ -71,7 +71,7 @@ func (p *Persister) ListSessions(ctx context.Context, active *bool, paginatorOpt paginatorOpts = append(paginatorOpts, keysetpagination.WithDefaultSize(paginationDefaultItemsSize)) paginatorOpts = append(paginatorOpts, keysetpagination.WithMaxSize(paginationMaxItemsSize)) - paginatorOpts = append(paginatorOpts, keysetpagination.WithDefaultToken(uuid.Nil.String())) + paginatorOpts = append(paginatorOpts, keysetpagination.WithDefaultToken(new(session.Session).DefaultPageToken())) paginator := keysetpagination.GetPaginator(paginatorOpts...) if err := p.Transaction(ctx, func(ctx context.Context, c *pop.Connection) error { diff --git a/selfservice/hook/web_hook_integration_test.go b/selfservice/hook/web_hook_integration_test.go index 091e73d2462..1c7ddb21df0 100644 --- a/selfservice/hook/web_hook_integration_test.go +++ b/selfservice/hook/web_hook_integration_test.go @@ -805,7 +805,7 @@ func TestDisallowPrivateIPRanges(t *testing.T) { }`)) err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s) require.Error(t, err) - require.Contains(t, err.Error(), "private, loopback, or unspecified IP range") + require.Contains(t, err.Error(), "is not a public IP address") }) t.Run("allowed to call exempt url", func(t *testing.T) { @@ -816,7 +816,7 @@ func TestDisallowPrivateIPRanges(t *testing.T) { }`)) err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s) require.Error(t, err, "the target does not exist and we still receive an error") - require.NotContains(t, err.Error(), "is in the private, loopback, or unspecified IP range", "but the error is not related to the IP range.") + require.NotContains(t, err.Error(), "is not a public IP address", "but the error is not related to the IP range.") }) t.Run("not allowed to load from source", func(t *testing.T) { @@ -836,6 +836,6 @@ func TestDisallowPrivateIPRanges(t *testing.T) { }`)) err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s) require.Error(t, err) - require.Contains(t, err.Error(), "ip 192.168.178.0 is in the private, loopback, or unspecified IP range") + require.Contains(t, err.Error(), "192.168.178.0 is not a public IP address") }) } diff --git a/selfservice/strategy/oidc/provider_private_net_test.go b/selfservice/strategy/oidc/provider_private_net_test.go index 06527980c3c..49da743a6fd 100644 --- a/selfservice/strategy/oidc/provider_private_net_test.go +++ b/selfservice/strategy/oidc/provider_private_net_test.go @@ -50,16 +50,16 @@ func TestProviderPrivateIP(t *testing.T) { }{ // Apple uses a fixed token URL and does not use the issuer. - {p: auth0, c: &oidc.Configuration{IssuerURL: "http://127.0.0.2/"}, e: "ip 127.0.0.2 is in the private, loopback, or unspecified IP range"}, + {p: auth0, c: &oidc.Configuration{IssuerURL: "http://127.0.0.2/"}, e: "127.0.0.2 is not a public IP address"}, // The TokenURL is fixed in Auth0 to {issuer_url}/token. Since the issuer is called first, any local token fails also. // If the issuer URL is local, we fail - {p: generic, c: &oidc.Configuration{IssuerURL: "http://127.0.0.2/"}, e: "ip 127.0.0.2 is in the private, loopback, or unspecified IP range", id: fakeJWTJWKS}, + {p: generic, c: &oidc.Configuration{IssuerURL: "http://127.0.0.2/"}, e: "127.0.0.2 is not a public IP address", id: fakeJWTJWKS}, // If the issuer URL has a local JWKs URL, we fail - {p: generic, c: &oidc.Configuration{ClientID: "abcd", IssuerURL: wellknownJWKs}, e: "ip 127.0.1.0 is in the private, loopback, or unspecified IP range", id: fakeJWTJWKS}, + {p: generic, c: &oidc.Configuration{ClientID: "abcd", IssuerURL: wellknownJWKs}, e: "is not a public IP address", id: fakeJWTJWKS}, // The next call does not fail because the provider uses only the ID JSON Web Token to verify this call and does // not use the TokenURL at all! - // {p: generic, c: &oidc.Configuration{ClientID: "abcd", IssuerURL: wellknownToken, TokenURL: "http://127.0.0.3/"}, e: "ip 127.0.0.3 is in the private, loopback, or unspecified IP range", id: fakeJWTToken}, + // {p: generic, c: &oidc.Configuration{ClientID: "abcd", IssuerURL: wellknownToken, TokenURL: "http://127.0.0.3/"}, e: "127.0.0.3 is not a public IP address", id: fakeJWTToken}, // Discord uses a fixed token URL and does not use the issuer. // Facebook uses a fixed token URL and does not use the issuer. @@ -67,7 +67,7 @@ func TestProviderPrivateIP(t *testing.T) { // GitHub App uses a fixed token URL and does not use the issuer. // GitHub App uses a fixed token URL and does not use the issuer. - {p: gitlab, c: &oidc.Configuration{IssuerURL: "http://127.0.0.2/"}, e: "ip 127.0.0.2 is in the private, loopback, or unspecified IP range"}, + {p: gitlab, c: &oidc.Configuration{IssuerURL: "http://127.0.0.2/"}, e: "127.0.0.2 is not a public IP address"}, // The TokenURL is fixed in GitLab to {issuer_url}/token. Since the issuer is called first, any local token fails also. // Google uses a fixed token URL and does not use the issuer. diff --git a/session/handler.go b/session/handler.go index d7896c0b530..db5e284e378 100644 --- a/session/handler.go +++ b/session/handler.go @@ -338,7 +338,7 @@ func (h *Handler) adminListSessions(w http.ResponseWriter, r *http.Request, ps h } // Parse request pagination parameters - opts, err := keysetpagination.Parse(r.URL.Query()) + opts, err := keysetpagination.Parse(r.URL.Query(), keysetpagination.NewStringPageToken) if err != nil { h.r.Writer().WriteError(w, r, herodot.ErrBadRequest.WithError("could not parse parameter page_size")) return diff --git a/session/session.go b/session/session.go index 411ea31c1cd..74edf61a046 100644 --- a/session/session.go +++ b/session/session.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ory/x/httpx" + "github.com/ory/x/pagination/keysetpagination" "github.com/ory/x/stringsx" "github.com/pkg/errors" @@ -140,8 +141,12 @@ type Session struct { NID uuid.UUID `json:"-" faker:"-" db:"nid"` } -func (s Session) PageToken() string { - return s.ID.String() +func (s Session) PageToken() keysetpagination.PageToken { + return keysetpagination.StringPageToken(s.ID.String()) +} + +func (s Session) DefaultPageToken() keysetpagination.PageToken { + return keysetpagination.StringPageToken(uuid.Nil.String()) } func (s Session) TableName(ctx context.Context) string { diff --git a/session/test/persistence.go b/session/test/persistence.go index cf401b71532..22a4e2b9bde 100644 --- a/session/test/persistence.go +++ b/session/test/persistence.go @@ -245,7 +245,7 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { require.Equal(t, len(tc.expected), len(actual)) require.Equal(t, int64(len(tc.expected)), total) assert.Equal(t, true, nextPage.IsLast()) - assert.Equal(t, uuid.Nil.String(), nextPage.Token()) + assert.Equal(t, uuid.Nil.String(), nextPage.Token().Encode()) assert.Equal(t, 250, nextPage.Size()) for _, es := range tc.expected { found := false @@ -269,7 +269,7 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { require.Equal(t, 5, len(actual)) require.Equal(t, int64(5), total) assert.Equal(t, true, page.IsLast()) - assert.Equal(t, uuid.Nil.String(), page.Token()) + assert.Equal(t, uuid.Nil.String(), page.Token().Encode()) assert.Equal(t, 250, page.Size()) }) @@ -308,7 +308,7 @@ func TestPersister(ctx context.Context, conf *config.Config, p interface { assert.Len(t, firstPageItems, 3) assert.Equal(t, false, page1.IsLast()) - assert.Equal(t, firstPageItems[len(firstPageItems)-1].ID.String(), page1.Token()) + assert.Equal(t, firstPageItems[len(firstPageItems)-1].ID.String(), page1.Token().Encode()) assert.Equal(t, 3, page1.Size()) // Validate secondPageItems page diff --git a/spec/api.json b/spec/api.json index 233967efa8c..ab6664c5fa9 100755 --- a/spec/api.json +++ b/spec/api.json @@ -1214,6 +1214,12 @@ "format": "date-time", "type": "string" }, + "dispatches": { + "items": { + "$ref": "#/components/schemas/messageDispatch" + }, + "type": "array" + }, "id": { "format": "uuid", "type": "string" @@ -1232,6 +1238,7 @@ "type": "string" }, "template_type": { + "description": "\nrecovery_invalid TypeRecoveryInvalid\nrecovery_valid TypeRecoveryValid\nrecovery_code_invalid TypeRecoveryCodeInvalid\nrecovery_code_valid TypeRecoveryCodeValid\nverification_invalid TypeVerificationInvalid\nverification_valid TypeVerificationValid\nverification_code_invalid TypeVerificationCodeInvalid\nverification_code_valid TypeVerificationCodeValid\notp TypeOTP\nstub TypeTestStub", "enum": [ "recovery_invalid", "recovery_valid", @@ -1256,6 +1263,63 @@ "type": "string" } }, + "required": [ + "id", + "status", + "type", + "recipient", + "body", + "subject", + "template_type", + "send_count", + "created_at", + "updated_at" + ], + "type": "object" + }, + "messageDispatch": { + "description": "MessageDispatch represents an attempt of sending a courier message\nIt contains the status of the attempt (failed or successful) and the error if any occured", + "properties": { + "created_at": { + "description": "CreatedAt is a helper struct field for gobuffalo.pop.", + "format": "date-time", + "type": "string" + }, + "error": { + "$ref": "#/components/schemas/JSONRawMessage" + }, + "id": { + "description": "The ID of this message dispatch", + "format": "uuid", + "type": "string" + }, + "message_id": { + "description": "The ID of the message being dispatched", + "format": "uuid", + "type": "string" + }, + "status": { + "description": "The status of this dispatch\nEither \"failed\" or \"success\"\nfailed CourierMessageDispatchStatusFailed\nsuccess CourierMessageDispatchStatusSuccess", + "enum": [ + "failed", + "success" + ], + "type": "string", + "x-go-enum-desc": "failed CourierMessageDispatchStatusFailed\nsuccess CourierMessageDispatchStatusSuccess" + }, + "updated_at": { + "description": "UpdatedAt is a helper struct field for gobuffalo.pop.", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "message_id", + "status", + "created_at", + "updated_at" + ], "type": "object" }, "needsPrivilegedSessionError": { @@ -3019,9 +3083,9 @@ "operationId": "listCourierMessages", "parameters": [ { - "description": "Items per Page\n\nThis is the number of items per page.", + "description": "Items per Page\n\nThis is the number of items per page to return.\nFor details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination).", "in": "query", - "name": "per_page", + "name": "page_size", "schema": { "default": 250, "format": "int64", @@ -3031,14 +3095,11 @@ } }, { - "description": "Pagination Page\n\nThis value is currently an integer, but it is not sequential. The value is not the page number, but a\nreference. The next page can be any number and some numbers might return an empty list.\n\nFor example, page 2 might not follow after page 1. And even if page 3 and 5 exist, but page 4 might not exist.", + "description": "Next Page Token\n\nThe next page token.\nFor details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination).", "in": "query", - "name": "page", + "name": "page_token", "schema": { - "default": 1, - "format": "int64", - "minimum": 1, - "type": "integer" + "type": "string" } }, { @@ -3094,6 +3155,64 @@ ] } }, + "/admin/courier/messages/{id}": { + "get": { + "description": "Gets a specific messages by the given ID.", + "operationId": "getCourierMessage", + "parameters": [ + { + "description": "MessageID is the ID of the message.", + "in": "path", + "name": "id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/message" + } + } + }, + "description": "message" + }, + "400": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorGeneric" + } + } + }, + "description": "errorGeneric" + }, + "default": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/errorGeneric" + } + } + }, + "description": "errorGeneric" + } + }, + "security": [ + { + "oryAccessToken": [] + } + ], + "summary": "Get a Message", + "tags": [ + "courier" + ] + } + }, "/admin/identities": { "get": { "description": "Lists all [identities](https://www.ory.sh/docs/kratos/concepts/identity-user-model) in the system.", diff --git a/spec/swagger.json b/spec/swagger.json index 1b2ac4bc46c..ff65f263d58 100755 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -70,17 +70,14 @@ "type": "integer", "format": "int64", "default": 250, - "description": "Items per Page\n\nThis is the number of items per page.", - "name": "per_page", + "description": "Items per Page\n\nThis is the number of items per page to return.\nFor details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination).", + "name": "page_size", "in": "query" }, { - "minimum": 1, - "type": "integer", - "format": "int64", - "default": 1, - "description": "Pagination Page\n\nThis value is currently an integer, but it is not sequential. The value is not the page number, but a\nreference. The next page can be any number and some numbers might return an empty list.\n\nFor example, page 2 might not follow after page 1. And even if page 3 and 5 exist, but page 4 might not exist.", - "name": "page", + "type": "string", + "description": "Next Page Token\n\nThe next page token.\nFor details on pagination please head over to the [pagination documentation](https://www.ory.sh/docs/ecosystem/api-design#pagination).", + "name": "page_token", "in": "query" }, { @@ -116,6 +113,57 @@ } } }, + "/admin/courier/messages/{id}": { + "get": { + "security": [ + { + "oryAccessToken": [] + } + ], + "description": "Gets a specific messages by the given ID.", + "produces": [ + "application/json" + ], + "schemes": [ + "http", + "https" + ], + "tags": [ + "courier" + ], + "summary": "Get a Message", + "operationId": "getCourierMessage", + "parameters": [ + { + "type": "string", + "description": "MessageID is the ID of the message.", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "message", + "schema": { + "$ref": "#/definitions/message" + } + }, + "400": { + "description": "errorGeneric", + "schema": { + "$ref": "#/definitions/errorGeneric" + } + }, + "default": { + "description": "errorGeneric", + "schema": { + "$ref": "#/definitions/errorGeneric" + } + } + } + } + }, "/admin/identities": { "get": { "security": [ @@ -3859,6 +3907,18 @@ }, "message": { "type": "object", + "required": [ + "id", + "status", + "type", + "recipient", + "body", + "subject", + "template_type", + "send_count", + "created_at", + "updated_at" + ], "properties": { "body": { "type": "string" @@ -3868,6 +3928,12 @@ "type": "string", "format": "date-time" }, + "dispatches": { + "type": "array", + "items": { + "$ref": "#/definitions/messageDispatch" + } + }, "id": { "type": "string", "format": "uuid" @@ -3886,6 +3952,7 @@ "type": "string" }, "template_type": { + "description": "\nrecovery_invalid TypeRecoveryInvalid\nrecovery_valid TypeRecoveryValid\nrecovery_code_invalid TypeRecoveryCodeInvalid\nrecovery_code_valid TypeRecoveryCodeValid\nverification_invalid TypeVerificationInvalid\nverification_valid TypeVerificationValid\nverification_code_invalid TypeVerificationCodeInvalid\nverification_code_valid TypeVerificationCodeValid\notp TypeOTP\nstub TypeTestStub", "type": "string", "enum": [ "recovery_invalid", @@ -3911,6 +3978,51 @@ } } }, + "messageDispatch": { + "description": "MessageDispatch represents an attempt of sending a courier message\nIt contains the status of the attempt (failed or successful) and the error if any occured", + "type": "object", + "required": [ + "id", + "message_id", + "status", + "created_at", + "updated_at" + ], + "properties": { + "created_at": { + "description": "CreatedAt is a helper struct field for gobuffalo.pop.", + "type": "string", + "format": "date-time" + }, + "error": { + "$ref": "#/definitions/JSONRawMessage" + }, + "id": { + "description": "The ID of this message dispatch", + "type": "string", + "format": "uuid" + }, + "message_id": { + "description": "The ID of the message being dispatched", + "type": "string", + "format": "uuid" + }, + "status": { + "description": "The status of this dispatch\nEither \"failed\" or \"success\"\nfailed CourierMessageDispatchStatusFailed\nsuccess CourierMessageDispatchStatusSuccess", + "type": "string", + "enum": [ + "failed", + "success" + ], + "x-go-enum-desc": "failed CourierMessageDispatchStatusFailed\nsuccess CourierMessageDispatchStatusSuccess" + }, + "updated_at": { + "description": "UpdatedAt is a helper struct field for gobuffalo.pop.", + "type": "string", + "format": "date-time" + } + } + }, "needsPrivilegedSessionError": { "type": "object", "title": "Is sent when a privileged session is required to perform the settings update.", diff --git a/test/e2e/cypress/integration/profiles/network/errors.spec.ts b/test/e2e/cypress/integration/profiles/network/errors.spec.ts index 6274356ee8c..90633fcbf47 100644 --- a/test/e2e/cypress/integration/profiles/network/errors.spec.ts +++ b/test/e2e/cypress/integration/profiles/network/errors.spec.ts @@ -15,7 +15,7 @@ describe("Registration failures with email profile", () => { cy.visit(express.registration, { failOnStatusCode: false }) cy.get('[data-testid="code-box"]').should( "contain.text", - "is in the private, loopback, or unspecified IP range", // could be ::1 or 127.0.0.1 + "is not a public IP address", // could be ::1 or 127.0.0.1 ) }) @@ -24,7 +24,7 @@ describe("Registration failures with email profile", () => { cy.visit(express.registration, { failOnStatusCode: false }) cy.get('[data-testid="code-box"]').should( "contain.text", - "ip 192.168.178.1 is in the private, loopback, or unspecified IP range", + "192.168.178.1 is not a public IP address", ) }) @@ -33,7 +33,7 @@ describe("Registration failures with email profile", () => { cy.visit(express.login, { failOnStatusCode: false }) cy.get('[data-testid="code-box"]').should( "contain.text", - "ip 192.168.178.2 is in the private, loopback, or unspecified IP range", + "192.168.178.2 is not a public IP address", ) }) @@ -46,7 +46,7 @@ describe("Registration failures with email profile", () => { cy.get('[type="submit"]').click() cy.get('[data-testid="code-box"]').should( "contain.text", - "ip 192.168.178.3 is in the private, loopback, or unspecified IP range", + "192.168.178.3 is not a public IP address", ) }) }) diff --git a/x/xsql/sql.go b/x/xsql/sql.go index 06c58b12c4e..517b42d8e87 100644 --- a/x/xsql/sql.go +++ b/x/xsql/sql.go @@ -28,6 +28,7 @@ func CleanSQL(t *testing.T, c *pop.Connection) { ctx := context.Background() for _, table := range []string{ new(continuity.Container).TableName(ctx), + new(courier.MessageDispatch).TableName(), new(courier.Message).TableName(ctx), new(session.Device).TableName(ctx),