// Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 package courier import ( "net/http" "github.com/gofrs/uuid" "github.com/pkg/errors" "github.com/ory/herodot" "github.com/ory/kratos/driver/config" "github.com/ory/kratos/x/nosurfx" "github.com/ory/kratos/x/redir" "github.com/ory/x/httprouterx" "github.com/ory/x/httpx" "github.com/ory/x/logrusx" keysetpagination "github.com/ory/x/pagination/keysetpagination_v2" ) const ( AdminRouteCourier = "/courier" AdminRouteListMessages = AdminRouteCourier + "/messages" AdminRouteGetMessage = AdminRouteCourier + "/messages/{msgID}" ) type ( handlerDependencies interface { httpx.WriterProvider logrusx.Provider nosurfx.CSRFProvider PersistenceProvider config.Provider } Handler struct { r handlerDependencies } HandlerProvider interface { CourierHandler() *Handler } ) func NewHandler(r handlerDependencies) *Handler { return &Handler{r: r} } func (h *Handler) RegisterPublicRoutes(public *httprouterx.RouterPublic) { h.r.CSRFHandler().IgnoreGlobs(httprouterx.AdminPrefix+AdminRouteListMessages, AdminRouteListMessages) public.GET(httprouterx.AdminPrefix+AdminRouteListMessages, redir.RedirectToAdminRoute(h.r)) public.GET(httprouterx.AdminPrefix+AdminRouteGetMessage, redir.RedirectToAdminRoute(h.r)) } func (h *Handler) RegisterAdminRoutes(admin *httprouterx.RouterAdmin) { admin.GET(AdminRouteListMessages, h.listCourierMessages) admin.GET(AdminRouteGetMessage, h.getCourierMessage) } // Paginated Courier Message List Response // // swagger:response listCourierMessages // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions type listCourierMessagesResponse struct { keysetpagination.ResponseHeaders // List of identities // // in:body Body []Message } // Paginated List Courier Message Parameters // // swagger:parameters listCourierMessages type ListCourierMessagesParameters struct { keysetpagination.RequestParameters // Status filters out messages based on status. // If no value is provided, it doesn't take effect on filter. // // required: false // in: query Status *MessageStatus `json:"status"` // Recipient filters out messages based on recipient. // If no value is provided, it doesn't take effect on filter. // // required: false // in: query Recipient string `json:"recipient"` } // swagger:route GET /admin/courier/messages courier listCourierMessages // // # List Messages // // Lists all messages by given status and recipient. // // Produces: // - application/json // // Security: // oryAccessToken: // // Schemes: http, https // // Responses: // 200: listCourierMessages // 400: errorGeneric // default: errorGeneric // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-low func (h *Handler) listCourierMessages(w http.ResponseWriter, r *http.Request) { keys := h.r.Config().SecretsPagination(r.Context()) filter, paginator, err := parseMessagesFilter(r, keys) if err != nil { h.r.Writer().WriteErrorCode(w, r, http.StatusBadRequest, err) return } messages, nextPage, err := h.r.CourierPersister().ListMessages(r.Context(), filter, paginator) if err != nil { h.r.Writer().WriteError(w, r, err) return } if !h.r.Config().IsInsecureDevMode(r.Context()) { for i := range messages { messages[i].Body = "" messages[i].Subject = "" } } u := *r.URL keysetpagination.SetLinkHeader(w, keys, &u, nextPage) h.r.Writer().Write(w, r, messages) } func parseMessagesFilter(r *http.Request, keys [][32]byte) (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{}, nil, errors.WithStack(err) } status = &ms } opts, err := keysetpagination.ParseQueryParams(keys, r.URL.Query()) if err != nil { return ListCourierMessagesParameters{}, nil, errors.WithStack(err) } return ListCourierMessagesParameters{ Status: status, Recipient: r.URL.Query().Get("recipient"), }, opts, nil } // Get Courier Message Parameters // // swagger:parameters getCourierMessage // //nolint:deadcode,unused //lint:ignore U1000 Used to generate Swagger and OpenAPI definitions 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 // // Extensions: // x-ory-ratelimit-bucket: kratos-admin-medium func (h *Handler) getCourierMessage(w http.ResponseWriter, r *http.Request) { msgID, err := uuid.FromString(r.PathValue("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", r.PathValue("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 = "" message.Subject = "" } h.r.Writer().Write(w, r, message) }