feat:pull the slack message service out to slack pkg

Signed-off-by: manish-singh-bisht <mthefool218@gmail.com>
This commit is contained in:
manish-singh-bisht
2025-12-09 13:42:21 +05:30
committed by Émile Ré
parent 7cb6edcbb5
commit 8eeab0a6d0
16 changed files with 353 additions and 101 deletions

View File

@@ -92,6 +92,8 @@ INSERT INTO slack_messages (
requester_email, requester_email,
metadata, metadata,
initial_slack_message_id, initial_slack_message_id,
message_ts,
channel_id,
created_at, created_at,
updated_at updated_at
) )
@@ -104,6 +106,8 @@ VALUES (
@requester_email, @requester_email,
@metadata, @metadata,
@initial_slack_message_id, @initial_slack_message_id,
@message_ts,
@channel_id,
@created_at, @created_at,
@updated_at @updated_at
) )
@@ -118,6 +122,8 @@ VALUES (
"requester_email": s.RequesterEmail, "requester_email": s.RequesterEmail,
"metadata": s.Metadata, "metadata": s.Metadata,
"initial_slack_message_id": s.InitialSlackMessageID, "initial_slack_message_id": s.InitialSlackMessageID,
"message_ts": s.MessageTS,
"channel_id": s.ChannelID,
"created_at": s.CreatedAt, "created_at": s.CreatedAt,
"updated_at": s.UpdatedAt, "updated_at": s.UpdatedAt,
} }

View File

@@ -33,6 +33,7 @@ import (
"go.probo.inc/probo/pkg/filevalidation" "go.probo.inc/probo/pkg/filevalidation"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/html2pdf"
"go.probo.inc/probo/pkg/slack"
) )
const ( const (
@@ -68,6 +69,7 @@ type (
auth *auth.Service auth *auth.Service
authz *authz.Service authz *authz.Service
logger *log.Logger logger *log.Logger
slack *slack.Service
} }
TenantService struct { TenantService struct {
@@ -113,6 +115,7 @@ type (
ProcessingActivities *ProcessingActivityService ProcessingActivities *ProcessingActivityService
Files *FileService Files *FileService
CustomDomains *CustomDomainService CustomDomains *CustomDomainService
Slack *slack.TenantService
} }
) )
@@ -132,6 +135,7 @@ func NewService(
authService *auth.Service, authService *auth.Service,
authzService *authz.Service, authzService *authz.Service,
logger *log.Logger, logger *log.Logger,
slackService *slack.Service,
) (*Service, error) { ) (*Service, error) {
if bucket == "" { if bucket == "" {
return nil, fmt.Errorf("bucket is required") return nil, fmt.Errorf("bucket is required")
@@ -152,6 +156,7 @@ func NewService(
auth: authService, auth: authService,
authz: authzService, authz: authzService,
logger: logger, logger: logger,
slack: slackService,
} }
return svc, nil return svc, nil
@@ -169,6 +174,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
trustConfig: s.trustConfig, trustConfig: s.trustConfig,
agent: agents.NewAgent(nil, s.agentConfig), agent: agents.NewAgent(nil, s.agentConfig),
fileManager: s.fileManager, fileManager: s.fileManager,
Slack: s.slack.WithTenant(tenantID),
} }
tenantService.Frameworks = &FrameworkService{ tenantService.Frameworks = &FrameworkService{

View File

@@ -273,6 +273,8 @@ func (s TrustCenterAccessService) Update(
now := time.Now() now := time.Now()
var access *coredata.TrustCenterAccess var access *coredata.TrustCenterAccess
var trustCenterAcessActivated bool
var shouldUpdateSlackMessage bool
err := s.svc.pg.WithTx( err := s.svc.pg.WithTx(
ctx, ctx,
func(tx pg.Conn) error { func(tx pg.Conn) error {
@@ -282,7 +284,7 @@ func (s TrustCenterAccessService) Update(
return fmt.Errorf("cannot load trust center access: %w", err) return fmt.Errorf("cannot load trust center access: %w", err)
} }
shouldSendEmail := req.Active != nil && *req.Active && !access.Active trustCenterAcessActivated = req.Active != nil && *req.Active && !access.Active
if req.Name != nil { if req.Name != nil {
access.Name = *req.Name access.Name = *req.Name
} }
@@ -339,12 +341,18 @@ func (s TrustCenterAccessService) Update(
} }
} }
if shouldSendEmail { if trustCenterAcessActivated {
if err := s.sendAccessEmail(ctx, tx, access); err != nil { if err := s.sendAccessEmail(ctx, tx, access); err != nil {
return fmt.Errorf("cannot send access email: %w", err) return fmt.Errorf("cannot send access email: %w", err)
} }
} }
shouldUpdateSlackMessage = trustCenterAcessActivated ||
len(req.DocumentAccesses) > 0 ||
len(req.ReportAccesses) > 0 ||
len(req.TrustCenterFileAccesses) > 0 ||
req.Name != nil
return nil return nil
}, },
) )
@@ -353,6 +361,12 @@ func (s TrustCenterAccessService) Update(
return nil, err return nil, err
} }
if shouldUpdateSlackMessage {
if err := s.svc.Slack.GetSlackMessageService().QueueSlackAccessMessageUpdate(ctx, access.Email, access.TrustCenterID); err != nil {
return nil, fmt.Errorf("cannot queue slack access message update: %w", err)
}
}
return access, nil return access, nil
} }

View File

@@ -342,6 +342,15 @@ func (impl *Implm) Run(
return fmt.Errorf("cannot initialize ACME service: %w", err) return fmt.Errorf("cannot initialize ACME service: %w", err)
} }
slackService := slack.NewService(
pgClient,
impl.cfg.GetSlackSigningSecret(),
impl.cfg.BaseURL.String(),
impl.cfg.EncryptionKey,
impl.cfg.Auth.Cookie.Secret,
l.Named("slack"),
)
proboService, err := probo.NewService( proboService, err := probo.NewService(
ctx, ctx,
impl.cfg.EncryptionKey, impl.cfg.EncryptionKey,
@@ -358,6 +367,7 @@ func (impl *Implm) Run(
authService, authService,
authzService, authzService,
l.Named("probo"), l.Named("probo"),
slackService,
) )
if err != nil { if err != nil {
return fmt.Errorf("cannot create probo service: %w", err) return fmt.Errorf("cannot create probo service: %w", err)
@@ -370,7 +380,6 @@ func (impl *Implm) Run(
impl.cfg.BaseURL.String(), impl.cfg.BaseURL.String(),
impl.cfg.EncryptionKey, impl.cfg.EncryptionKey,
impl.cfg.TrustAuth.TokenSecret, impl.cfg.TrustAuth.TokenSecret,
impl.cfg.GetSlackSigningSecret(),
authService, authService,
html2pdfConverter, html2pdfConverter,
fileManagerService, fileManagerService,
@@ -380,6 +389,7 @@ func (impl *Implm) Run(
TokenDuration: time.Duration(impl.cfg.TrustAuth.TokenDuration) * time.Hour, TokenDuration: time.Duration(impl.cfg.TrustAuth.TokenDuration) * time.Hour,
TokenType: impl.cfg.TrustAuth.TokenType, TokenType: impl.cfg.TrustAuth.TokenType,
}, },
slackService,
) )
serverHandler, err := server.NewServer( serverHandler, err := server.NewServer(
@@ -390,6 +400,7 @@ func (impl *Implm) Run(
Auth: authService, Auth: authService,
Authz: authzService, Authz: authzService,
Trust: trustService, Trust: trustService,
Slack: slackService,
SAML: samlService, SAML: samlService,
ConnectorRegistry: defaultConnectorRegistry, ConnectorRegistry: defaultConnectorRegistry,
Agent: agent, Agent: agent,

View File

@@ -31,7 +31,9 @@ import (
"go.probo.inc/probo/pkg/saferedirect" "go.probo.inc/probo/pkg/saferedirect"
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1" console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
mcp_v1 "go.probo.inc/probo/pkg/server/api/mcp/v1" mcp_v1 "go.probo.inc/probo/pkg/server/api/mcp/v1"
slack_v1 "go.probo.inc/probo/pkg/server/api/slack/v1"
trust_v1 "go.probo.inc/probo/pkg/server/api/trust/v1" trust_v1 "go.probo.inc/probo/pkg/server/api/trust/v1"
"go.probo.inc/probo/pkg/slack"
"go.probo.inc/probo/pkg/trust" "go.probo.inc/probo/pkg/trust"
) )
@@ -62,6 +64,7 @@ type (
Auth *auth.Service Auth *auth.Service
Authz *authz.Service Authz *authz.Service
Trust *trust.Service Trust *trust.Service
Slack *slack.Service
SAML *auth.SAMLService SAML *auth.SAMLService
ConsoleAuth ConsoleAuthConfig ConsoleAuth ConsoleAuthConfig
TrustAuth TrustAuthConfig TrustAuth TrustAuthConfig
@@ -83,6 +86,7 @@ type (
trustAPIHandler http.Handler trustAPIHandler http.Handler
consoleAPIHandler http.Handler consoleAPIHandler http.Handler
mcpAPIHandler http.Handler mcpAPIHandler http.Handler
slackAPIHandler http.Handler
} }
) )
@@ -157,6 +161,7 @@ func NewServer(cfg Config) (*Server, error) {
TokenType: cfg.TrustAuth.TokenType, TokenType: cfg.TrustAuth.TokenType,
CookieSecure: cfg.TrustAuth.CookieSecure, CookieSecure: cfg.TrustAuth.CookieSecure,
}, },
cfg.Slack,
) )
consoleAPIHandler := console_v1.NewMux( consoleAPIHandler := console_v1.NewMux(
@@ -189,11 +194,18 @@ func NewServer(cfg Config) (*Server, error) {
}, },
) )
slackAPIHandler := slack_v1.NewMux(
cfg.Logger.Named("slack.v1"),
cfg.Slack,
cfg.Trust,
)
return &Server{ return &Server{
cfg: cfg, cfg: cfg,
trustAPIHandler: trustAPIHandler, trustAPIHandler: trustAPIHandler,
consoleAPIHandler: consoleAPIHandler, consoleAPIHandler: consoleAPIHandler,
mcpAPIHandler: mcpAPIHandler, mcpAPIHandler: mcpAPIHandler,
slackAPIHandler: slackAPIHandler,
}, nil }, nil
} }
@@ -229,6 +241,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
router.Mount("/console/v1", s.consoleAPIHandler) router.Mount("/console/v1", s.consoleAPIHandler)
router.Mount("/trust/v1", s.trustAPIHandler) router.Mount("/trust/v1", s.trustAPIHandler)
router.Mount("/mcp/v1", s.mcpAPIHandler) router.Mount("/mcp/v1", s.mcpAPIHandler)
router.Mount("/slack/v1", s.slackAPIHandler)
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
} }

View File

@@ -0,0 +1,26 @@
package slack_v1
import (
"github.com/go-chi/chi/v5"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/slack"
"go.probo.inc/probo/pkg/trust"
)
func NewMux(
logger *log.Logger,
slackSvc *slack.Service,
trustSvc *trust.Service,
) *chi.Mux {
r := chi.NewMux()
logger.Info("Registering Slack interactive endpoint")
r.Post("/interactive", SlackHandler(
slackSvc,
slackSvc.GetSlackSigningSecret(),
logger,
trustSvc,
))
return r
}

View File

@@ -12,7 +12,7 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE. // PERFORMANCE OF THIS SOFTWARE.
package trust_v1 package slack_v1
import ( import (
"bytes" "bytes"
@@ -57,7 +57,7 @@ const (
StatusReject = "reject" StatusReject = "reject"
) )
func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *log.Logger) http.HandlerFunc { func SlackHandler(slackSvc *slack.Service, slackSigningSecret string, logger *log.Logger, trustSvc *trust.Service) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@@ -132,7 +132,7 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
return return
} }
initialSlackMessage, err := trustSvc.GetInitialSlackMessageByChannelAndTS(ctx, slackPayload.Container.ChannelID, slackPayload.Container.MessageTS) initialSlackMessage, err := slackSvc.GetInitialSlackMessageByChannelAndTS(ctx, slackPayload.Container.ChannelID, slackPayload.Container.MessageTS)
if err != nil { if err != nil {
logger.ErrorCtx(ctx, "cannot load slack message", log.Error(err)) logger.ErrorCtx(ctx, "cannot load slack message", log.Error(err))
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"}) httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
@@ -168,7 +168,7 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
return return
} }
documentIDs, reportIDs, fileIDs, err = tenantSvc.SlackMessages.GetSlackMessageDocumentIDs(ctx, currentMessageId) documentIDs, reportIDs, fileIDs, err = slackSvc.WithTenant(initialSlackMessage.OrganizationID.TenantID()).GetSlackMessageService().GetSlackMessageDocumentIDs(ctx, currentMessageId)
if err != nil { if err != nil {
logger.ErrorCtx(ctx, "cannot load slack message document ids", log.Error(err)) logger.ErrorCtx(ctx, "cannot load slack message document ids", log.Error(err))
httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"}) httpserver.RenderJSON(w, http.StatusInternalServerError, SlackInteractiveResponse{Success: false, Message: "internal server error"})
@@ -261,7 +261,7 @@ func slackHandler(trustSvc *trust.Service, slackSigningSecret string, logger *lo
return return
} }
if err := tenantSvc.SlackMessages.UpdateSlackAccessMessage( if err := slackSvc.WithTenant(initialSlackMessage.OrganizationID.TenantID()).GetSlackMessageService().UpdateSlackAccessMessage(
ctx, ctx,
initialSlackMessage.ID, initialSlackMessage.ID,
slackPayload.ResponseURL, slackPayload.ResponseURL,

View File

@@ -34,10 +34,12 @@ import (
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/probo"
console_v1 "go.probo.inc/probo/pkg/server/api/console/v1" console_v1 "go.probo.inc/probo/pkg/server/api/console/v1"
slack_v1 "go.probo.inc/probo/pkg/server/api/slack/v1"
"go.probo.inc/probo/pkg/server/api/trust/v1/schema" "go.probo.inc/probo/pkg/server/api/trust/v1/schema"
"go.probo.inc/probo/pkg/server/api/trust/v1/trustauth" "go.probo.inc/probo/pkg/server/api/trust/v1/trustauth"
"go.probo.inc/probo/pkg/server/gqlutils" "go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/server/session" "go.probo.inc/probo/pkg/server/session"
"go.probo.inc/probo/pkg/slack"
"go.probo.inc/probo/pkg/statelesstoken" "go.probo.inc/probo/pkg/statelesstoken"
"go.probo.inc/probo/pkg/trust" "go.probo.inc/probo/pkg/trust"
) )
@@ -103,6 +105,9 @@ func NewMux(
trustSvc *trust.Service, trustSvc *trust.Service,
authCfg console_v1.AuthConfig, authCfg console_v1.AuthConfig,
trustAuthCfg TrustAuthConfig, trustAuthCfg TrustAuthConfig,
// TODO: Remove this after successful migration to /slack/v1.
slackSvc *slack.Service,
) *chi.Mux { ) *chi.Mux {
r := chi.NewMux() r := chi.NewMux()
@@ -111,7 +116,9 @@ func NewMux(
r.Post("/auth/authenticate", authTokenHandler(trustSvc, trustAuthCfg)) r.Post("/auth/authenticate", authTokenHandler(trustSvc, trustAuthCfg))
r.Delete("/auth/logout", trustCenterLogoutHandler(authCfg, trustAuthCfg)) r.Delete("/auth/logout", trustCenterLogoutHandler(authCfg, trustAuthCfg))
r.Post("/slack", slackHandler(trustSvc, trustSvc.GetSlackSigningSecret(), logger)) // Backward compatibility: support old /trust/v1/slack endpoint
// TODO: Remove this after successful migration to /slack/v1 and then make SlackHandler PRIVATE in slack_v1 package.
r.Post("/slack", slack_v1.SlackHandler(slackSvc, slackSvc.GetSlackSigningSecret(), logger, trustSvc))
return r return r
} }

View File

@@ -38,6 +38,7 @@ import (
authz_server "go.probo.inc/probo/pkg/server/authz" authz_server "go.probo.inc/probo/pkg/server/authz"
"go.probo.inc/probo/pkg/server/trust" "go.probo.inc/probo/pkg/server/trust"
"go.probo.inc/probo/pkg/server/web" "go.probo.inc/probo/pkg/server/web"
"go.probo.inc/probo/pkg/slack"
trust_pkg "go.probo.inc/probo/pkg/trust" trust_pkg "go.probo.inc/probo/pkg/trust"
) )
@@ -48,6 +49,7 @@ type Config struct {
Auth *auth.Service Auth *auth.Service
Authz *authz.Service Authz *authz.Service
Trust *trust_pkg.Service Trust *trust_pkg.Service
Slack *slack.Service
SAML *auth.SAMLService SAML *auth.SAMLService
ConsoleAuth api.ConsoleAuthConfig ConsoleAuth api.ConsoleAuthConfig
TrustAuth api.TrustAuthConfig TrustAuth api.TrustAuthConfig
@@ -80,6 +82,7 @@ func NewServer(cfg Config) (*Server, error) {
Auth: cfg.Auth, Auth: cfg.Auth,
Authz: cfg.Authz, Authz: cfg.Authz,
Trust: cfg.Trust, Trust: cfg.Trust,
Slack: cfg.Slack,
SAML: cfg.SAML, SAML: cfg.SAML,
ConsoleAuth: cfg.ConsoleAuth, ConsoleAuth: cfg.ConsoleAuth,
TrustAuth: cfg.TrustAuth, TrustAuth: cfg.TrustAuth,

View File

@@ -0,0 +1,31 @@
package slack
import (
"encoding/json"
"strings"
"text/template"
)
var (
accessRequestTemplate = template.Must(
template.New("access-request.json.tmpl").
Funcs(template.FuncMap{
"jsonEscape": func(s string) string {
b, _ := json.Marshal(s)
return string(b[1 : len(b)-1])
},
"buildAcceptAllValue": func(docIDs, repIDs []string) string {
value := map[string][]string{
"document_ids": docIDs,
"report_ids": repIDs,
}
b, _ := json.Marshal(value)
s := string(b)
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
return s
},
}).
ParseFS(Templates, "templates/access-request.json.tmpl"),
)
)

95
pkg/slack/service.go Normal file
View File

@@ -0,0 +1,95 @@
package slack
import (
"context"
"fmt"
"go.gearno.de/kit/log"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/crypto/cipher"
"go.probo.inc/probo/pkg/gid"
)
type Service struct {
pg *pg.Client
logger *log.Logger
slackSigningSecret string
baseURL string
encryptionKey cipher.EncryptionKey
tokenSecret string
}
type TenantService struct {
pg *pg.Client
scope coredata.Scoper
logger *log.Logger
baseURL string
encryptionKey cipher.EncryptionKey
tokenSecret string
}
func NewService(
pg *pg.Client,
slackSigningSecret string,
baseURL string,
encryptionKey cipher.EncryptionKey,
tokenSecret string,
logger *log.Logger,
) *Service {
return &Service{
pg: pg,
logger: logger,
slackSigningSecret: slackSigningSecret,
baseURL: baseURL,
encryptionKey: encryptionKey,
tokenSecret: tokenSecret,
}
}
func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService := &TenantService{
pg: s.pg,
scope: coredata.NewScope(tenantID),
logger: s.logger,
baseURL: s.baseURL,
encryptionKey: s.encryptionKey,
tokenSecret: s.tokenSecret,
}
return tenantService
}
func (s *TenantService) GetSlackClient() *Client {
return NewClient(s.logger)
}
func (s *TenantService) GetSlackMessageService() *SlackMessageService {
return &SlackMessageService{svc: s}
}
func (s *Service) GetSlackSigningSecret() string {
return s.slackSigningSecret
}
func (s *Service) GetInitialSlackMessageByChannelAndTS(
ctx context.Context,
channelID string,
messageTS string,
) (*coredata.SlackMessage, error) {
var slackMessage coredata.SlackMessage
err := s.pg.WithConn(ctx, func(conn pg.Conn) error {
if err := slackMessage.LoadInitialByChannelAndTS(ctx, conn, coredata.NewNoScope(), channelID, messageTS); err != nil {
return fmt.Errorf("cannot load slack message: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return &slackMessage, nil
}

View File

@@ -12,7 +12,7 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE. // PERFORMANCE OF THIS SOFTWARE.
package trust package slack
import ( import (
"bytes" "bytes"
@@ -26,7 +26,6 @@ import (
"go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/baseurl"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/slack"
) )
const ( const (
@@ -36,7 +35,6 @@ const (
type ( type (
SlackMessageService struct { SlackMessageService struct {
svc *TenantService svc *TenantService
slackClient *slack.Client
} }
SlackMessageDocument struct { SlackMessageDocument struct {
@@ -74,28 +72,6 @@ func (m SlackMessageMetadata) toMap() map[string]any {
} }
} }
func (s *Service) GetInitialSlackMessageByChannelAndTS(
ctx context.Context,
channelID string,
messageTS string,
) (*coredata.SlackMessage, error) {
var slackMessage coredata.SlackMessage
err := s.pg.WithConn(ctx, func(conn pg.Conn) error {
if err := slackMessage.LoadInitialByChannelAndTS(ctx, conn, coredata.NewNoScope(), channelID, messageTS); err != nil {
return fmt.Errorf("cannot load slack message: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
return &slackMessage, nil
}
func (s *SlackMessageService) GetSlackMessageDocumentIDs( func (s *SlackMessageService) GetSlackMessageDocumentIDs(
ctx context.Context, ctx context.Context,
slackMessageID gid.GID, slackMessageID gid.GID,
@@ -132,7 +108,6 @@ func (s *SlackMessageService) UpdateSlackAccessMessage(
if err := slackMessage.LoadById(ctx, tx, s.svc.scope, slackMessageID); err != nil { if err := slackMessage.LoadById(ctx, tx, s.svc.scope, slackMessageID); err != nil {
return fmt.Errorf("cannot load slack message: %w", err) return fmt.Errorf("cannot load slack message: %w", err)
} }
var trustCenter coredata.TrustCenter var trustCenter coredata.TrustCenter
if err := trustCenter.LoadByOrganizationID(ctx, tx, s.svc.scope, slackMessage.OrganizationID); err != nil { if err := trustCenter.LoadByOrganizationID(ctx, tx, s.svc.scope, slackMessage.OrganizationID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err) return fmt.Errorf("cannot load trust center: %w", err)
@@ -189,7 +164,7 @@ func (s *SlackMessageService) UpdateSlackAccessMessage(
return fmt.Errorf("cannot insert slack message: %w", err) return fmt.Errorf("cannot insert slack message: %w", err)
} }
if err := s.slackClient.UpdateInteractiveMessage(ctx, responseURL, updatedBody); err != nil { if err := s.svc.GetSlackClient().UpdateInteractiveMessage(ctx, responseURL, updatedBody); err != nil {
return fmt.Errorf("cannot update Slack message: %w", err) return fmt.Errorf("cannot update Slack message: %w", err)
} }
@@ -310,6 +285,102 @@ func (s *SlackMessageService) QueueSlackNotification(
}) })
} }
func (s *SlackMessageService) QueueSlackAccessMessageUpdate(
ctx context.Context,
requesterEmail string,
trustCenterID gid.GID,
) error {
return s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
var trustCenter coredata.TrustCenter
if err := trustCenter.LoadByID(ctx, tx, s.svc.scope, trustCenterID); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
var trustCenterAccess coredata.TrustCenterAccess
if err := trustCenterAccess.LoadByTrustCenterIDAndEmail(ctx, tx, s.svc.scope, trustCenterID, requesterEmail); err != nil {
return fmt.Errorf("cannot load trust center access: %w", err)
}
var existingMessage coredata.SlackMessage
err := existingMessage.LoadLatestByRequesterEmailAndType(
ctx,
tx,
s.svc.scope,
trustCenter.OrganizationID,
requesterEmail,
coredata.SlackMessageTypeTrustCenterAccessRequest,
time.Time{},
)
if err != nil {
var notFoundErr coredata.ErrSlackMessageNotFound
if errors.Is(err, notFoundErr) {
return nil
}
return fmt.Errorf("cannot load existing slack message: %w", err)
}
if existingMessage.MessageTS == nil || existingMessage.ChannelID == nil {
return nil
}
if existingMessage.ID != existingMessage.InitialSlackMessageID {
var originalMessage coredata.SlackMessage
if err := originalMessage.LoadById(ctx, tx, s.svc.scope, existingMessage.InitialSlackMessageID); err != nil {
return nil
}
existingMessage = originalMessage
}
documents, reports, files, err := s.loadDocumentsReportsAndFilesFromAccesses(ctx, tx, trustCenterAccess.ID)
if err != nil {
return fmt.Errorf("cannot load documents, reports and files: %w", err)
}
newSlackMessageID := gid.New(s.svc.scope.GetTenantID(), coredata.SlackMessageEntityType)
updatedBody, err := s.buildAccessRequestMessage(
newSlackMessageID,
trustCenterAccess.Name,
requesterEmail,
trustCenter.OrganizationID,
documents,
reports,
files,
)
if err != nil {
return fmt.Errorf("cannot build access request message: %w", err)
}
metadata := SlackMessageMetadata{
Documents: documents,
Reports: reports,
Files: files,
}
now := time.Now()
newSlackMessage := &coredata.SlackMessage{
ID: newSlackMessageID,
OrganizationID: existingMessage.OrganizationID,
Type: existingMessage.Type,
Body: updatedBody,
MessageTS: existingMessage.MessageTS,
ChannelID: existingMessage.ChannelID,
RequesterEmail: existingMessage.RequesterEmail,
Metadata: metadata.toMap(),
InitialSlackMessageID: existingMessage.InitialSlackMessageID,
CreatedAt: now,
UpdatedAt: now,
SentAt: nil,
}
if err := newSlackMessage.Insert(ctx, tx, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert slack message: %w", err)
}
return nil
})
}
func (s *SlackMessageService) loadDocumentsReportsAndFilesFromAccesses( func (s *SlackMessageService) loadDocumentsReportsAndFilesFromAccesses(
ctx context.Context, ctx context.Context,
conn pg.Conn, conn pg.Conn,

View File

@@ -12,7 +12,7 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE. // PERFORMANCE OF THIS SOFTWARE.
package trust package slack
import ( import (
"embed" "embed"

View File

@@ -18,6 +18,8 @@ import (
"time" "time"
"github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3"
"go.gearno.de/kit/log"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/auth" "go.probo.inc/probo/pkg/auth"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/crypto/cipher"
@@ -26,8 +28,6 @@ import (
"go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/html2pdf"
"go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/slack" "go.probo.inc/probo/pkg/slack"
"go.gearno.de/kit/log"
"go.gearno.de/kit/pg"
) )
type ( type (
@@ -44,13 +44,13 @@ type (
proboSvc *probo.Service proboSvc *probo.Service
encryptionKey cipher.EncryptionKey encryptionKey cipher.EncryptionKey
tokenSecret string tokenSecret string
slackSigningSecret string
baseURL string baseURL string
auth *auth.Service auth *auth.Service
html2pdfConverter *html2pdf.Converter html2pdfConverter *html2pdf.Converter
fileManager *filemanager.Service fileManager *filemanager.Service
logger *log.Logger logger *log.Logger
trustConfig TrustConfig trustConfig TrustConfig
slack *slack.Service
} }
TenantService struct { TenantService struct {
@@ -77,7 +77,7 @@ type (
TrustCenterFiles *TrustCenterFileService TrustCenterFiles *TrustCenterFileService
Reports *ReportService Reports *ReportService
Organizations *OrganizationService Organizations *OrganizationService
SlackMessages *SlackMessageService slack *slack.TenantService
} }
) )
@@ -88,12 +88,12 @@ func NewService(
baseURL string, baseURL string,
encryptionKey cipher.EncryptionKey, encryptionKey cipher.EncryptionKey,
tokenSecret string, tokenSecret string,
slackSigningSecret string,
auth *auth.Service, auth *auth.Service,
html2pdfConverter *html2pdf.Converter, html2pdfConverter *html2pdf.Converter,
fileManagerService *filemanager.Service, fileManagerService *filemanager.Service,
logger *log.Logger, logger *log.Logger,
trustConfig TrustConfig, trustConfig TrustConfig,
slack *slack.Service,
) *Service { ) *Service {
return &Service{ return &Service{
pg: pgClient, pg: pgClient,
@@ -101,13 +101,13 @@ func NewService(
bucket: bucket, bucket: bucket,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
tokenSecret: tokenSecret, tokenSecret: tokenSecret,
slackSigningSecret: slackSigningSecret,
baseURL: baseURL, baseURL: baseURL,
auth: auth, auth: auth,
html2pdfConverter: html2pdfConverter, html2pdfConverter: html2pdfConverter,
fileManager: fileManagerService, fileManager: fileManagerService,
logger: logger, logger: logger,
trustConfig: trustConfig, trustConfig: trustConfig,
slack: slack,
} }
} }
@@ -126,10 +126,9 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
fileManager: s.fileManager, fileManager: s.fileManager,
logger: s.logger, logger: s.logger,
trustConfig: s.trustConfig, trustConfig: s.trustConfig,
slack: s.slack.WithTenant(tenantID),
} }
slackClient := slack.NewClient(s.logger)
tenantService.TrustCenters = &TrustCenterService{svc: tenantService} tenantService.TrustCenters = &TrustCenterService{svc: tenantService}
tenantService.Documents = &DocumentService{svc: tenantService, html2pdfConverter: s.html2pdfConverter} tenantService.Documents = &DocumentService{svc: tenantService, html2pdfConverter: s.html2pdfConverter}
tenantService.Audits = &AuditService{svc: tenantService} tenantService.Audits = &AuditService{svc: tenantService}
@@ -140,7 +139,6 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService.TrustCenterFiles = &TrustCenterFileService{svc: tenantService} tenantService.TrustCenterFiles = &TrustCenterFileService{svc: tenantService}
tenantService.Reports = &ReportService{svc: tenantService} tenantService.Reports = &ReportService{svc: tenantService}
tenantService.Organizations = &OrganizationService{svc: tenantService} tenantService.Organizations = &OrganizationService{svc: tenantService}
tenantService.SlackMessages = &SlackMessageService{svc: tenantService, slackClient: slackClient}
return tenantService return tenantService
} }
@@ -148,7 +146,3 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
func (s *Service) GetTokenSecret() string { func (s *Service) GetTokenSecret() string {
return s.tokenSecret return s.tokenSecret
} }
func (s *Service) GetSlackSigningSecret() string {
return s.slackSigningSecret
}

View File

@@ -21,8 +21,6 @@ import (
"fmt" "fmt"
"net/mail" "net/mail"
"net/url" "net/url"
"strings"
"text/template"
"time" "time"
"go.gearno.de/kit/log" "go.gearno.de/kit/log"
@@ -35,30 +33,6 @@ import (
"go.probo.inc/probo/pkg/statelesstoken" "go.probo.inc/probo/pkg/statelesstoken"
) )
var (
accessRequestTemplate = template.Must(
template.New("access-request.json.tmpl").
Funcs(template.FuncMap{
"jsonEscape": func(s string) string {
b, _ := json.Marshal(s)
return string(b[1 : len(b)-1])
},
"buildAcceptAllValue": func(docIDs, repIDs []string) string {
value := map[string][]string{
"document_ids": docIDs,
"report_ids": repIDs,
}
b, _ := json.Marshal(value)
s := string(b)
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
return s
},
}).
ParseFS(Templates, "templates/access-request.json.tmpl"),
)
)
type ( type (
TrustCenterAccessService struct { TrustCenterAccessService struct {
svc *TenantService svc *TenantService
@@ -258,7 +232,8 @@ func (s TrustCenterAccessService) Request(
return nil, err return nil, err
} }
if err := s.svc.SlackMessages.QueueSlackNotification(ctx, access.Email, req.TrustCenterID); err != nil { slackMessageService := s.svc.slack.GetSlackMessageService()
if err := slackMessageService.QueueSlackNotification(ctx, access.Email, req.TrustCenterID); err != nil {
s.logger.ErrorCtx(ctx, "cannot queue slack notification", log.Error(err)) s.logger.ErrorCtx(ctx, "cannot queue slack notification", log.Error(err))
} }