Add membership and host to user webhook payload
Nest membership ID, role, and state into a membership sub-object in the user webhook payload. Also emit user:updated webhook when the membership role is changed. Add X-Probo-Webhook-Host header to webhook HTTP calls. Skip delete webhook when membership is not found in SCIM user deletion. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -273,6 +273,15 @@ func (s *OrganizationService) UpdateMempership(
|
||||
return fmt.Errorf("cannot update membership: %w", err)
|
||||
}
|
||||
|
||||
profile := &coredata.MembershipProfile{}
|
||||
if err := profile.LoadByIdentityIDAndOrganizationID(ctx, tx, scope, membership.IdentityID, membership.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load profile: %w", err)
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, tx, scope, organizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile, &membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
); err != nil {
|
||||
@@ -323,7 +332,7 @@ func (s *OrganizationService) RemoveUser(
|
||||
}
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, tx, scope, organizationID, coredata.WebhookEventTypeUserDeleted, webhooktypes.NewUser(&profile)); err != nil {
|
||||
if err := webhook.InsertData(ctx, tx, scope, organizationID, coredata.WebhookEventTypeUserDeleted, webhooktypes.NewUser(&profile, membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
@@ -946,7 +955,7 @@ func (s *OrganizationService) CreateUser(ctx context.Context, req *CreateUserReq
|
||||
return fmt.Errorf("cannot insert membership: %w", err)
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, conn, scope, req.OrganizationID, coredata.WebhookEventTypeUserCreated, webhooktypes.NewUser(profile)); err != nil {
|
||||
if err := webhook.InsertData(ctx, conn, scope, req.OrganizationID, coredata.WebhookEventTypeUserCreated, webhooktypes.NewUser(profile, membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
@@ -999,7 +1008,12 @@ func (s *OrganizationService) UpdateUser(ctx context.Context, req *UpdateUserReq
|
||||
return fmt.Errorf("cannot update profile: %w", err)
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, conn, scope, profile.OrganizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile)); err != nil {
|
||||
membership := &coredata.Membership{}
|
||||
if err := membership.LoadByIdentityIDAndOrganizationID(ctx, conn, scope, profile.IdentityID, profile.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, conn, scope, profile.OrganizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile, membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -330,7 +330,7 @@ func (s *Service) CreateUser(
|
||||
}
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, eventType, webhooktypes.NewUser(profile)); err != nil {
|
||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, eventType, webhooktypes.NewUser(profile, membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
@@ -748,7 +748,7 @@ func (s *Service) updateUser(
|
||||
}
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile)); err != nil {
|
||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, coredata.WebhookEventTypeUserUpdated, webhooktypes.NewUser(profile, membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
@@ -797,25 +797,30 @@ func (s *Service) DeleteUser(
|
||||
return fmt.Errorf("cannot expire pending invitations: %w", err)
|
||||
}
|
||||
|
||||
membership := &coredata.Membership{}
|
||||
if err := membership.LoadByIdentityIDAndOrganizationID(
|
||||
var membership *coredata.Membership
|
||||
m := &coredata.Membership{}
|
||||
if err := m.LoadByIdentityIDAndOrganizationID(
|
||||
ctx, tx, scope, profile.IdentityID, config.OrganizationID,
|
||||
); err != nil {
|
||||
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return fmt.Errorf("cannot load membership: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := membership.Delete(ctx, tx, scope, membership.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete membership: %w", err)
|
||||
}
|
||||
membership = m
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, coredata.WebhookEventTypeUserDeleted, webhooktypes.NewUser(profile, membership)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
|
||||
if err := profile.Delete(ctx, tx, scope, profile.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete profile: %w", err)
|
||||
}
|
||||
|
||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, coredata.WebhookEventTypeUserDeleted, webhooktypes.NewUser(profile)); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
if membership != nil {
|
||||
if err := membership.Delete(ctx, tx, scope, membership.ID); err != nil {
|
||||
return fmt.Errorf("cannot delete membership: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -612,6 +612,7 @@ func (impl *Implm) Run(
|
||||
Interval: time.Duration(impl.cfg.Notifications.Webhook.SenderInterval) * time.Second,
|
||||
CacheTTL: time.Duration(impl.cfg.Notifications.Webhook.CacheTTL) * time.Second,
|
||||
EncryptionKey: encryptionKey,
|
||||
Host: baseURL.String(),
|
||||
})
|
||||
wg.Go(
|
||||
func() {
|
||||
|
||||
@@ -43,6 +43,7 @@ type (
|
||||
logger *log.Logger
|
||||
httpClient *http.Client
|
||||
encryptionKey cipher.EncryptionKey
|
||||
host string
|
||||
cache sync.Map
|
||||
cacheCreatedAt time.Time
|
||||
cacheTTL time.Duration
|
||||
@@ -60,6 +61,7 @@ type (
|
||||
Timeout time.Duration
|
||||
CacheTTL time.Duration
|
||||
EncryptionKey cipher.EncryptionKey
|
||||
Host string
|
||||
}
|
||||
|
||||
pendingDelivery struct {
|
||||
@@ -88,6 +90,7 @@ func NewSender(pg *pg.Client, logger *log.Logger, cfg Config) *Sender {
|
||||
logger: logger,
|
||||
httpClient: httpclient.DefaultPooledClient(httpclient.WithLogger(logger)),
|
||||
encryptionKey: cfg.EncryptionKey,
|
||||
host: cfg.Host,
|
||||
cacheCreatedAt: time.Now(),
|
||||
cacheTTL: cfg.CacheTTL,
|
||||
interval: cfg.Interval,
|
||||
@@ -310,6 +313,7 @@ func (s *Sender) doHTTPCall(
|
||||
req.Header.Set("X-Probo-Webhook-Organization-Id", webhookData.OrganizationID.String())
|
||||
req.Header.Set("X-Probo-Webhook-Timestamp", timestamp)
|
||||
req.Header.Set("X-Probo-Webhook-Signature", signature)
|
||||
req.Header.Set("X-Probo-Webhook-Host", s.host)
|
||||
|
||||
resp, err := s.httpClient.Do(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -22,31 +22,38 @@ import (
|
||||
"go.probo.inc/probo/pkg/mail"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
EmailAddress mail.Addr `json:"emailAddress"`
|
||||
FullName string `json:"fullName"`
|
||||
Kind *string `json:"kind"`
|
||||
Source coredata.ProfileSource `json:"source"`
|
||||
State coredata.ProfileState `json:"state"`
|
||||
AdditionalEmailAddresses mail.Addrs `json:"additionalEmailAddresses"`
|
||||
Position *string `json:"position"`
|
||||
ContractStartDate *time.Time `json:"contractStartDate"`
|
||||
ContractEndDate *time.Time `json:"contractEndDate"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
type (
|
||||
User struct {
|
||||
ID gid.GID `json:"id"`
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
EmailAddress mail.Addr `json:"emailAddress"`
|
||||
FullName string `json:"fullName"`
|
||||
Kind *string `json:"kind"`
|
||||
Source coredata.ProfileSource `json:"source"`
|
||||
AdditionalEmailAddresses mail.Addrs `json:"additionalEmailAddresses"`
|
||||
Position *string `json:"position"`
|
||||
ContractStartDate *time.Time `json:"contractStartDate"`
|
||||
ContractEndDate *time.Time `json:"contractEndDate"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Membership *UserMembership `json:"membership"`
|
||||
}
|
||||
|
||||
func NewUser(p *coredata.MembershipProfile) *User {
|
||||
return &User{
|
||||
UserMembership struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Role coredata.MembershipRole `json:"role"`
|
||||
State coredata.ProfileState `json:"state"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewUser(p *coredata.MembershipProfile, m *coredata.Membership) *User {
|
||||
u := &User{
|
||||
ID: p.ID,
|
||||
OrganizationID: p.OrganizationID,
|
||||
EmailAddress: p.EmailAddress,
|
||||
FullName: p.FullName,
|
||||
Kind: p.Kind,
|
||||
Source: p.Source,
|
||||
State: p.State,
|
||||
AdditionalEmailAddresses: p.AdditionalEmailAddresses,
|
||||
Position: p.Position,
|
||||
ContractStartDate: p.ContractStartDate,
|
||||
@@ -54,4 +61,14 @@ func NewUser(p *coredata.MembershipProfile) *User {
|
||||
CreatedAt: p.CreatedAt,
|
||||
UpdatedAt: p.UpdatedAt,
|
||||
}
|
||||
|
||||
if m != nil {
|
||||
u.Membership = &UserMembership{
|
||||
ID: m.ID,
|
||||
Role: m.Role,
|
||||
State: p.State,
|
||||
}
|
||||
}
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user