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)
|
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
|
return nil
|
||||||
},
|
},
|
||||||
); err != 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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
return fmt.Errorf("cannot expire pending invitations: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
membership := &coredata.Membership{}
|
var membership *coredata.Membership
|
||||||
if err := membership.LoadByIdentityIDAndOrganizationID(
|
m := &coredata.Membership{}
|
||||||
|
if err := m.LoadByIdentityIDAndOrganizationID(
|
||||||
ctx, tx, scope, profile.IdentityID, config.OrganizationID,
|
ctx, tx, scope, profile.IdentityID, config.OrganizationID,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
if !errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
return fmt.Errorf("cannot load membership: %w", err)
|
return fmt.Errorf("cannot load membership: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := membership.Delete(ctx, tx, scope, membership.ID); err != nil {
|
membership = m
|
||||||
return fmt.Errorf("cannot delete membership: %w", err)
|
}
|
||||||
}
|
|
||||||
|
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 {
|
if err := profile.Delete(ctx, tx, scope, profile.ID); err != nil {
|
||||||
return fmt.Errorf("cannot delete profile: %w", err)
|
return fmt.Errorf("cannot delete profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := webhook.InsertData(ctx, tx, scope, config.OrganizationID, coredata.WebhookEventTypeUserDeleted, webhooktypes.NewUser(profile)); err != nil {
|
if membership != nil {
|
||||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
if err := membership.Delete(ctx, tx, scope, membership.ID); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete membership: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -612,6 +612,7 @@ func (impl *Implm) Run(
|
|||||||
Interval: time.Duration(impl.cfg.Notifications.Webhook.SenderInterval) * time.Second,
|
Interval: time.Duration(impl.cfg.Notifications.Webhook.SenderInterval) * time.Second,
|
||||||
CacheTTL: time.Duration(impl.cfg.Notifications.Webhook.CacheTTL) * time.Second,
|
CacheTTL: time.Duration(impl.cfg.Notifications.Webhook.CacheTTL) * time.Second,
|
||||||
EncryptionKey: encryptionKey,
|
EncryptionKey: encryptionKey,
|
||||||
|
Host: baseURL.String(),
|
||||||
})
|
})
|
||||||
wg.Go(
|
wg.Go(
|
||||||
func() {
|
func() {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ type (
|
|||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
encryptionKey cipher.EncryptionKey
|
encryptionKey cipher.EncryptionKey
|
||||||
|
host string
|
||||||
cache sync.Map
|
cache sync.Map
|
||||||
cacheCreatedAt time.Time
|
cacheCreatedAt time.Time
|
||||||
cacheTTL time.Duration
|
cacheTTL time.Duration
|
||||||
@@ -60,6 +61,7 @@ type (
|
|||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
CacheTTL time.Duration
|
CacheTTL time.Duration
|
||||||
EncryptionKey cipher.EncryptionKey
|
EncryptionKey cipher.EncryptionKey
|
||||||
|
Host string
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingDelivery struct {
|
pendingDelivery struct {
|
||||||
@@ -88,6 +90,7 @@ func NewSender(pg *pg.Client, logger *log.Logger, cfg Config) *Sender {
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
httpClient: httpclient.DefaultPooledClient(httpclient.WithLogger(logger)),
|
httpClient: httpclient.DefaultPooledClient(httpclient.WithLogger(logger)),
|
||||||
encryptionKey: cfg.EncryptionKey,
|
encryptionKey: cfg.EncryptionKey,
|
||||||
|
host: cfg.Host,
|
||||||
cacheCreatedAt: time.Now(),
|
cacheCreatedAt: time.Now(),
|
||||||
cacheTTL: cfg.CacheTTL,
|
cacheTTL: cfg.CacheTTL,
|
||||||
interval: cfg.Interval,
|
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-Organization-Id", webhookData.OrganizationID.String())
|
||||||
req.Header.Set("X-Probo-Webhook-Timestamp", timestamp)
|
req.Header.Set("X-Probo-Webhook-Timestamp", timestamp)
|
||||||
req.Header.Set("X-Probo-Webhook-Signature", signature)
|
req.Header.Set("X-Probo-Webhook-Signature", signature)
|
||||||
|
req.Header.Set("X-Probo-Webhook-Host", s.host)
|
||||||
|
|
||||||
resp, err := s.httpClient.Do(req)
|
resp, err := s.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -22,31 +22,38 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/mail"
|
"go.probo.inc/probo/pkg/mail"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type (
|
||||||
ID gid.GID `json:"id"`
|
User struct {
|
||||||
OrganizationID gid.GID `json:"organizationId"`
|
ID gid.GID `json:"id"`
|
||||||
EmailAddress mail.Addr `json:"emailAddress"`
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
FullName string `json:"fullName"`
|
EmailAddress mail.Addr `json:"emailAddress"`
|
||||||
Kind *string `json:"kind"`
|
FullName string `json:"fullName"`
|
||||||
Source coredata.ProfileSource `json:"source"`
|
Kind *string `json:"kind"`
|
||||||
State coredata.ProfileState `json:"state"`
|
Source coredata.ProfileSource `json:"source"`
|
||||||
AdditionalEmailAddresses mail.Addrs `json:"additionalEmailAddresses"`
|
AdditionalEmailAddresses mail.Addrs `json:"additionalEmailAddresses"`
|
||||||
Position *string `json:"position"`
|
Position *string `json:"position"`
|
||||||
ContractStartDate *time.Time `json:"contractStartDate"`
|
ContractStartDate *time.Time `json:"contractStartDate"`
|
||||||
ContractEndDate *time.Time `json:"contractEndDate"`
|
ContractEndDate *time.Time `json:"contractEndDate"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
}
|
Membership *UserMembership `json:"membership"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewUser(p *coredata.MembershipProfile) *User {
|
UserMembership struct {
|
||||||
return &User{
|
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,
|
ID: p.ID,
|
||||||
OrganizationID: p.OrganizationID,
|
OrganizationID: p.OrganizationID,
|
||||||
EmailAddress: p.EmailAddress,
|
EmailAddress: p.EmailAddress,
|
||||||
FullName: p.FullName,
|
FullName: p.FullName,
|
||||||
Kind: p.Kind,
|
Kind: p.Kind,
|
||||||
Source: p.Source,
|
Source: p.Source,
|
||||||
State: p.State,
|
|
||||||
AdditionalEmailAddresses: p.AdditionalEmailAddresses,
|
AdditionalEmailAddresses: p.AdditionalEmailAddresses,
|
||||||
Position: p.Position,
|
Position: p.Position,
|
||||||
ContractStartDate: p.ContractStartDate,
|
ContractStartDate: p.ContractStartDate,
|
||||||
@@ -54,4 +61,14 @@ func NewUser(p *coredata.MembershipProfile) *User {
|
|||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
UpdatedAt: p.UpdatedAt,
|
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