Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,3 @@ Describe how reviewers can test this change to be sure that it works correctly.
- [ ] I have added new test fixtures as needed to support added tests
- [ ] I have added or updated the documentation
- [ ] Check this box if a reviewer can merge this pull request after approval (leave it unchecked if you want to do it yourself)

### Reviewer(s) checklist

- [ ] Any new user-facing content that has been added for this PR has been QA'ed to ensure correct grammar, spelling, and understandability.
- [ ] To the best of my ability, I believe that this PR represents a good solution to the specified problem and that it should be merged into the main code base.


2 changes: 1 addition & 1 deletion pkg/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func New(conf config.Config) (node *Node, err error) {
audit.UseKeyChain(kc)

// Create the admin web ui server if it is enabled
if node.admin, err = web.New(conf, node.store, node.network); err != nil {
if node.admin, err = web.New(conf, node.store, node.network, node.webhook); err != nil {
return nil, err
}

Expand Down
24 changes: 24 additions & 0 deletions pkg/postman/sunrise.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/trisacrypto/envoy/pkg/emails"
"github.com/trisacrypto/envoy/pkg/enum"
"github.com/trisacrypto/envoy/pkg/store/models"
"github.com/trisacrypto/envoy/pkg/webhook"
"github.com/trisacrypto/trisa/pkg/ivms101"
trisa "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1"
generic "github.com/trisacrypto/trisa/pkg/trisa/data/generic/v1beta1"
Expand Down Expand Up @@ -123,6 +124,29 @@ func ReceiveSunriseReject(envelopeID uuid.UUID, reject *trisa.Error) (packet *Su
return packet, nil
}

// WebhookRequest creates a notification for the result of a Sunrise review.
// Unlike a network reply, the webhook should receive the state of the outgoing
// response because the incoming envelope is synthetic.
func (s *SunrisePacket) WebhookRequest() (request *webhook.Request, err error) {
request = s.In.WebhookRequest()
request.TransferState = s.Out.Envelope.TransferState().String()

if s.In.Envelope.IsError() {
return request, nil
}

var payload *trisa.Payload
if payload, err = s.In.Envelope.Payload(); err != nil {
return nil, fmt.Errorf("could not retrieve sunrise webhook payload: %w", err)
}

if err = request.AddPayload(payload); err != nil {
return nil, fmt.Errorf("could not add sunrise webhook payload: %w", err)
}

return request, nil
}

// Returns the email contacts of the compliance officers associated with the counterparty.
func (s *SunrisePacket) Contacts() (contacts []*models.Contact, err error) {
if contacts, err = s.Counterparty.Contacts(); err != nil {
Expand Down
77 changes: 77 additions & 0 deletions pkg/postman/sunrise_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package postman_test

import (
"context"
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/trisacrypto/envoy/pkg/postman"
"github.com/trisacrypto/envoy/pkg/store/models"
"github.com/trisacrypto/envoy/pkg/webhook"
trisa "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1"
)

func TestSunriseWebhookRequest(t *testing.T) {
// Use the same payload shape that a Sunrise recipient submits during review.
payload, err := loadPayloadFixture("testdata/identity.pb.json", "testdata/transaction.pb.json")
require.NoError(t, err)

// An accepted Sunrise review becomes an accepted incoming webhook message
// containing the identity and transaction payload.
t.Run("Accept", func(t *testing.T) {
packet, err := postman.ReceiveSunriseAccept(uuid.New(), payload)
require.NoError(t, err)

packet.Counterparty = &models.Counterparty{Name: "Example VASP"}
request, err := packet.WebhookRequest()
require.NoError(t, err)

var received *webhook.Request
mock := webhook.NewMock()
mock.OnCallback = func(_ context.Context, request *webhook.Request) (*webhook.Reply, error) {
received = request
return &webhook.Reply{TransactionID: request.TransactionID}, nil
}

_, err = mock.Callback(context.Background(), request)
require.NoError(t, err)
require.Equal(t, 1, mock.Callbacks)
require.Same(t, request, received)
require.Equal(t, trisa.TransferAccepted.String(), received.TransferState)
require.NotNil(t, received.Payload)
require.NotNil(t, received.Payload.Identity)
require.NotNil(t, received.Payload.Transaction)
})

// A rejected review is delivered as an error-only webhook message, with
// the transfer state determined by the rejection's retry flag.
t.Run("Reject", func(t *testing.T) {
reject := &trisa.Error{
Code: trisa.ComplianceCheckFail,
Message: "transaction rejected",
Retry: false,
}
packet, err := postman.ReceiveSunriseReject(uuid.New(), reject)
require.NoError(t, err)

packet.Counterparty = &models.Counterparty{Name: "Example VASP"}
request, err := packet.WebhookRequest()
require.NoError(t, err)

mock := webhook.NewMock()
var received *webhook.Request
mock.OnCallback = func(_ context.Context, request *webhook.Request) (*webhook.Reply, error) {
received = request
return &webhook.Reply{TransactionID: request.TransactionID}, nil
}

_, err = mock.Callback(context.Background(), request)
require.NoError(t, err)
require.Equal(t, 1, mock.Callbacks)
require.Same(t, request, received)
require.Equal(t, trisa.TransferRejected.String(), received.TransferState)
require.Equal(t, reject, received.Error)
require.Nil(t, received.Payload)
})
}
2 changes: 2 additions & 0 deletions pkg/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/trisacrypto/envoy/pkg/store/models"
"github.com/trisacrypto/envoy/pkg/trisa/network"
"github.com/trisacrypto/envoy/pkg/web/auth"
"github.com/trisacrypto/envoy/pkg/webhook"

"github.com/gin-gonic/gin"
"github.com/rs/zerolog/log"
Expand All @@ -33,6 +34,7 @@ type Server struct {
url *url.URL
vasp *models.Counterparty
trisa network.Network
webhook webhook.Handler
started time.Time
healthy bool
ready bool
Expand Down
36 changes: 36 additions & 0 deletions pkg/web/sunrise.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/trisacrypto/envoy/pkg/web/auth"
"github.com/trisacrypto/envoy/pkg/web/htmx"
"github.com/trisacrypto/envoy/pkg/web/scene"
"github.com/trisacrypto/envoy/pkg/webhook"
trisa "github.com/trisacrypto/trisa/pkg/trisa/api/v1beta1"
"github.com/trisacrypto/trisa/pkg/trisa/envelope"
"github.com/trisacrypto/trisa/pkg/trisa/keys"
Expand Down Expand Up @@ -71,6 +72,19 @@ func (s *Server) SendSunrise(ctx context.Context, packet *postman.SunrisePacket)
return nil
}

// Sends a webhook request to the configured webhook handler for the result of
// a sunrise review.
func (s *Server) notifySunriseWebhook(ctx context.Context, request *webhook.Request) {
if s.webhook == nil {
return
}

if _, err := s.webhook.Callback(ctx, request); err != nil {
log := logger.Tracing(ctx)
log.Error().Err(err).Msg("could not notify webhook of sunrise message")
}
}

//===========================================================================
// Sunrise Pages
//===========================================================================
Expand Down Expand Up @@ -312,6 +326,7 @@ func (s *Server) SunriseMessageReject(c *gin.Context) {
sunriseID ulid.ULID
sunriseMsg *models.Sunrise
packet *postman.SunrisePacket
hookReq *webhook.Request
)

in = &api.Rejection{}
Expand Down Expand Up @@ -377,6 +392,13 @@ func (s *Server) SunriseMessageReject(c *gin.Context) {
return
}

// Build the webhook request before Save seals the incoming envelope.
if hookReq, err = packet.WebhookRequest(); err != nil {
c.Error(err)
c.JSON(http.StatusInternalServerError, api.Error("could not complete request"))
return
}

// Create a prepared transaction to create secure envelopes
if packet.DB, err = s.store.PrepareTransaction(ctx, packet.Transaction.ID, &models.ComplianceAuditLog{
ChangeNotes: sql.NullString{Valid: true, String: "Server.SunriseMessageReject()"},
Expand Down Expand Up @@ -409,6 +431,9 @@ func (s *Server) SunriseMessageReject(c *gin.Context) {
return
}

// Notify the webhook only after the review has been committed.
s.notifySunriseWebhook(ctx, hookReq)

// If successful, then redirect to the sunrise message complete page.
htmx.Redirect(c, http.StatusTemporaryRedirect, "/sunrise/complete")
}
Expand All @@ -422,6 +447,7 @@ func (s *Server) SunriseMessageAccept(c *gin.Context) {
sunriseMsg *models.Sunrise
payload *trisa.Payload
packet *postman.SunrisePacket
hookReq *webhook.Request
)

in = &api.Envelope{}
Expand Down Expand Up @@ -500,6 +526,13 @@ func (s *Server) SunriseMessageAccept(c *gin.Context) {
return
}

// Build the webhook request before Save seals the incoming envelope.
if hookReq, err = packet.WebhookRequest(); err != nil {
c.Error(err)
c.JSON(http.StatusInternalServerError, api.Error("could not complete request"))
return
}

// Create a prepared transaction to create secure envelopes
if packet.DB, err = s.store.PrepareTransaction(ctx, packet.Transaction.ID, &models.ComplianceAuditLog{
ChangeNotes: sql.NullString{Valid: true, String: "Server.SunriseMessageAccept()"},
Expand Down Expand Up @@ -538,6 +571,9 @@ func (s *Server) SunriseMessageAccept(c *gin.Context) {
return
}

// Notify the webhook only after the review has been committed.
s.notifySunriseWebhook(ctx, hookReq)

// If successful, then redirect to the sunrise message complete page.
htmx.Redirect(c, http.StatusTemporaryRedirect, "/sunrise/complete")
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,23 @@ import (
"github.com/trisacrypto/envoy/pkg/trisa/network"
"github.com/trisacrypto/envoy/pkg/web/auth"
"github.com/trisacrypto/envoy/pkg/web/scene"
"github.com/trisacrypto/envoy/pkg/webhook"

"github.com/gin-gonic/gin"
"go.rtnl.ai/ulid"
)

// Create a new web server that serves the compliance and admin web user interface.
func New(conf config.Config, store store.Store, network network.Network) (s *Server, err error) {
func New(conf config.Config, store store.Store, network network.Network, callback webhook.Handler) (s *Server, err error) {
if err = conf.Web.Validate(); err != nil {
return nil, err
}

s = &Server{
conf: conf,
store: store,
trisa: network,
conf: conf,
store: store,
trisa: network,
webhook: callback,
}

// If not enabled, return just the server stub
Expand Down Expand Up @@ -70,7 +72,7 @@ func New(conf config.Config, store store.Store, network network.Network) (s *Ser
// Debug returns a server that uses the specified http server instead of creating one.
// This function is primarily used to create test servers easily.
func Debug(conf config.Config, store store.Store, network network.Network, srv *http.Server) (s *Server, err error) {
if s, err = New(conf, store, network); err != nil {
if s, err = New(conf, store, network, nil); err != nil {
return nil, err
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/web/web_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestServerEnabled(t *testing.T) {
UIEnabled: false,
}}

_, err := web.New(conf, nil, nil)
_, err := web.New(conf, nil, nil, nil)
require.EqualError(t, err, "invalid configuration: if enabled, either the api, ui, or both need to be enabled")
})

Expand Down Expand Up @@ -73,7 +73,7 @@ func TestServerEnabled(t *testing.T) {
Origin: "http://locahost:57132",
}}

srv, err := web.New(conf, store, network)
srv, err := web.New(conf, store, network, nil)
require.NoError(t, err, "could not start web server")

err = srv.Serve(nil)
Expand Down Expand Up @@ -166,7 +166,7 @@ func TestServerEnabled(t *testing.T) {
Origin: "http://locahost:57132",
}}

srv, err := web.New(conf, store, network)
srv, err := web.New(conf, store, network, nil)
require.NoError(t, err, "could not start web server")

err = srv.Serve(nil)
Expand Down
Loading