Add slack integration
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
@@ -218,7 +219,12 @@ func NewMux(
|
||||
r.Post("/auth/reset-password", ResetPasswordHandler(authSvc, authCfg))
|
||||
|
||||
r.Get("/connectors/initiate", WithSession(authSvc, authzSvc, authCfg, func(w http.ResponseWriter, r *http.Request) {
|
||||
connectorID := r.URL.Query().Get("connector_id")
|
||||
provider := r.URL.Query().Get("provider")
|
||||
if provider != "SLACK" {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported provider"))
|
||||
return
|
||||
}
|
||||
|
||||
organizationID, err := gid.ParseGID(r.URL.Query().Get("organization_id"))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse organization id: %w", err))
|
||||
@@ -226,34 +232,53 @@ func NewMux(
|
||||
|
||||
_ = GetTenantService(r.Context(), proboSvc, organizationID.TenantID())
|
||||
|
||||
redirectURL, err := connectorRegistry.Initiate(r.Context(), connectorID, organizationID, r)
|
||||
redirectURL, err := connectorRegistry.Initiate(r.Context(), provider, organizationID, r)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot initiate connector: %w", err))
|
||||
}
|
||||
|
||||
http.Redirect(w, r, redirectURL, http.StatusSeeOther)
|
||||
// Allow external redirects for Slack OAuth only for now
|
||||
slackSafeRedirect := &saferedirect.SafeRedirect{AllowedHost: "slack.com"}
|
||||
slackSafeRedirect.Redirect(w, r, redirectURL, "/", http.StatusSeeOther)
|
||||
}))
|
||||
|
||||
r.Get("/connectors/complete", WithSession(authSvc, authzSvc, authCfg, func(w http.ResponseWriter, r *http.Request) {
|
||||
connectorID := r.URL.Query().Get("connector_id")
|
||||
organizationID, err := gid.ParseGID(r.URL.Query().Get("organization_id"))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to parse organization id: %w", err))
|
||||
r.Get("/connectors/complete", func(w http.ResponseWriter, r *http.Request) {
|
||||
provider := r.URL.Query().Get("provider")
|
||||
if provider == "" {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("missing provider parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
connection, err := connectorRegistry.Complete(r.Context(), connectorID, organizationID, r)
|
||||
var connectorProvider coredata.ConnectorProvider
|
||||
switch provider {
|
||||
case "SLACK":
|
||||
connectorProvider = coredata.ConnectorProviderSlack
|
||||
default:
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("unsupported provider"))
|
||||
return
|
||||
}
|
||||
|
||||
stateToken := r.URL.Query().Get("state")
|
||||
if stateToken == "" {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("missing state parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
connection, organizationID, err := connectorRegistry.Complete(r.Context(), provider, r)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to complete connector: %w", err))
|
||||
}
|
||||
|
||||
svc := GetTenantService(r.Context(), proboSvc, organizationID.TenantID())
|
||||
continueURL := r.URL.Query().Get("continue")
|
||||
|
||||
_, err = svc.Connectors.CreateOrUpdate(
|
||||
svc := proboSvc.WithTenant(organizationID.TenantID())
|
||||
|
||||
_, err = svc.Connectors.Create(
|
||||
r.Context(),
|
||||
probo.CreateOrUpdateConnectorRequest{
|
||||
OrganizationID: organizationID,
|
||||
Name: connectorID,
|
||||
Type: connector.ProtocolType(connection.Type()),
|
||||
probo.CreateConnectorRequest{
|
||||
OrganizationID: *organizationID,
|
||||
Provider: connectorProvider,
|
||||
Protocol: coredata.ConnectorProtocol(connection.Type()),
|
||||
Connection: connection,
|
||||
},
|
||||
)
|
||||
@@ -261,8 +286,13 @@ func NewMux(
|
||||
panic(fmt.Errorf("failed to create or update connector: %w", err))
|
||||
}
|
||||
|
||||
safeRedirect.RedirectFromQuery(w, r, "continue", "/", http.StatusSeeOther)
|
||||
}))
|
||||
if continueURL != "" {
|
||||
safeRedirect.Redirect(w, r, continueURL, "/", http.StatusSeeOther)
|
||||
} else {
|
||||
redirectURL := fmt.Sprintf("/organizations/%s", organizationID.String())
|
||||
safeRedirect.Redirect(w, r, redirectURL, "/", http.StatusSeeOther)
|
||||
}
|
||||
})
|
||||
|
||||
r.Get("/", playground.Handler("GraphQL", "/api/console/v1/query"))
|
||||
r.Post("/query", graphqlHandler(logger, proboSvc, authSvc, authzSvc, authCfg, customDomainCname))
|
||||
|
||||
@@ -555,20 +555,6 @@ enum OrganizationOrderField
|
||||
)
|
||||
}
|
||||
|
||||
enum ConnectorOrderField
|
||||
@goModel(
|
||||
model: "github.com/getprobo/probo/pkg/coredata.ConnectorOrderField"
|
||||
) {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.ConnectorOrderFieldCreatedAt"
|
||||
)
|
||||
NAME
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.ConnectorOrderFieldName"
|
||||
)
|
||||
}
|
||||
|
||||
enum DataSensitivity
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.DataSensitivity") {
|
||||
NONE
|
||||
@@ -1473,11 +1459,6 @@ input OrganizationOrder {
|
||||
field: OrganizationOrderField!
|
||||
}
|
||||
|
||||
input ConnectorOrder {
|
||||
field: ConnectorOrderField!
|
||||
direction: OrderDirection!
|
||||
}
|
||||
|
||||
input DocumentVersionOrder
|
||||
@goModel(
|
||||
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.DocumentVersionOrderBy"
|
||||
@@ -1621,13 +1602,12 @@ type Organization implements Node {
|
||||
filter: InvitationFilter
|
||||
): InvitationConnection! @goField(forceResolver: true)
|
||||
|
||||
connectors(
|
||||
slackConnections(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: ConnectorOrder
|
||||
): ConnectorConnection! @goField(forceResolver: true)
|
||||
): SlackConnectionConnection! @goField(forceResolver: true)
|
||||
|
||||
frameworks(
|
||||
first: Int
|
||||
@@ -1808,14 +1788,24 @@ type Invitation implements Node {
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Connector implements Node {
|
||||
type SlackConnection {
|
||||
id: ID!
|
||||
name: String!
|
||||
type: String!
|
||||
channel: String
|
||||
channelId: String
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type SlackConnectionConnection {
|
||||
edges: [SlackConnectionEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type SlackConnectionEdge {
|
||||
cursor: CursorKey!
|
||||
node: SlackConnection!
|
||||
}
|
||||
|
||||
type People implements Node {
|
||||
id: ID!
|
||||
fullName: String!
|
||||
@@ -2635,15 +2625,6 @@ type VendorServiceEdge {
|
||||
node: VendorService!
|
||||
}
|
||||
|
||||
type ConnectorConnection {
|
||||
edges: [ConnectorEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type ConnectorEdge {
|
||||
cursor: CursorKey!
|
||||
node: Connector!
|
||||
}
|
||||
|
||||
type VendorRiskAssessmentConnection {
|
||||
edges: [VendorRiskAssessmentEdge!]!
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,36 +19,42 @@ import (
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
ConnectorOrderBy OrderBy[coredata.ConnectorOrderField]
|
||||
)
|
||||
|
||||
func NewConnectorConnection(p *page.Page[*coredata.Connector, coredata.ConnectorOrderField]) *ConnectorConnection {
|
||||
var edges = make([]*ConnectorEdge, len(p.Data))
|
||||
func NewSlackConnectionConnection(p *page.Page[*coredata.Connector, coredata.ConnectorOrderField]) *SlackConnectionConnection {
|
||||
var edges = make([]*SlackConnectionEdge, len(p.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewConnectorEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
edges[i] = NewSlackConnectionEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &ConnectorConnection{
|
||||
return &SlackConnectionConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
|
||||
func NewConnectorEdge(c *coredata.Connector, orderBy coredata.ConnectorOrderField) *ConnectorEdge {
|
||||
return &ConnectorEdge{
|
||||
func NewSlackConnectionEdge(c *coredata.Connector, orderBy coredata.ConnectorOrderField) *SlackConnectionEdge {
|
||||
return &SlackConnectionEdge{
|
||||
Cursor: c.CursorKey(orderBy),
|
||||
Node: NewConnector(c),
|
||||
Node: NewSlackConnection(c),
|
||||
}
|
||||
}
|
||||
|
||||
func NewConnector(c *coredata.Connector) *Connector {
|
||||
return &Connector{
|
||||
func NewSlackConnection(c *coredata.Connector) *SlackConnection {
|
||||
conn := &SlackConnection{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Type: string(c.Type),
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
}
|
||||
|
||||
// Extract channel information from settings
|
||||
if len(c.Settings) > 0 {
|
||||
if channel, ok := c.Settings["channel"].(string); ok && channel != "" {
|
||||
conn.Channel = &channel
|
||||
}
|
||||
if channelID, ok := c.Settings["channel_id"].(string); ok && channelID != "" {
|
||||
conn.ChannelID = &channelID
|
||||
}
|
||||
}
|
||||
|
||||
return conn
|
||||
}
|
||||
@@ -146,32 +146,6 @@ type ConfirmEmailPayload struct {
|
||||
Success bool `json:"success"`
|
||||
}
|
||||
|
||||
type Connector struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (Connector) IsNode() {}
|
||||
func (this Connector) GetID() gid.GID { return this.ID }
|
||||
|
||||
type ConnectorConnection struct {
|
||||
Edges []*ConnectorEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type ConnectorEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Connector `json:"node"`
|
||||
}
|
||||
|
||||
type ConnectorOrder struct {
|
||||
Field coredata.ConnectorOrderField `json:"field"`
|
||||
Direction page.OrderDirection `json:"direction"`
|
||||
}
|
||||
|
||||
type ContinualImprovement struct {
|
||||
ID gid.GID `json:"id"`
|
||||
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
|
||||
@@ -1374,7 +1348,7 @@ type Organization struct {
|
||||
HeadquarterAddress *string `json:"headquarterAddress,omitempty"`
|
||||
Memberships *MembershipConnection `json:"memberships"`
|
||||
Invitations *InvitationConnection `json:"invitations"`
|
||||
Connectors *ConnectorConnection `json:"connectors"`
|
||||
SlackConnections *SlackConnectionConnection `json:"slackConnections"`
|
||||
Frameworks *FrameworkConnection `json:"frameworks"`
|
||||
Controls *ControlConnection `json:"controls"`
|
||||
Vendors *VendorConnection `json:"vendors"`
|
||||
@@ -1590,6 +1564,24 @@ type Session struct {
|
||||
ExpiresAt time.Time `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type SlackConnection struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Channel *string `json:"channel,omitempty"`
|
||||
ChannelID *string `json:"channelId,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type SlackConnectionConnection struct {
|
||||
Edges []*SlackConnectionEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type SlackConnectionEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *SlackConnection `json:"node"`
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Organization *Organization `json:"organization"`
|
||||
|
||||
@@ -3666,29 +3666,27 @@ func (r *organizationResolver) Invitations(ctx context.Context, obj *types.Organ
|
||||
return types.NewInvitationConnection(page, r, obj.ID, filter), nil
|
||||
}
|
||||
|
||||
// Connectors is the resolver for the connectors field.
|
||||
func (r *organizationResolver) Connectors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) (*types.ConnectorConnection, error) {
|
||||
// SlackConnections is the resolver for the slackConnections field.
|
||||
func (r *organizationResolver) SlackConnections(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.SlackConnectionConnection, error) {
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
// Filter for Slack connectors only
|
||||
slackProvider := coredata.ConnectorProviderSlack
|
||||
filter := coredata.NewConnectorProviderFilter(&slackProvider)
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.ConnectorOrderField]{
|
||||
Field: coredata.ConnectorOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.ConnectorOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
page, err := prb.Connectors.ListForOrganizationID(ctx, obj.ID, cursor)
|
||||
page, err := prb.Connectors.ListForOrganizationID(ctx, obj.ID, cursor, filter)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list organization connectors: %w", err))
|
||||
panic(fmt.Errorf("cannot list organization slack connections: %w", err))
|
||||
}
|
||||
|
||||
return types.NewConnectorConnection(page), nil
|
||||
return types.NewSlackConnectionConnection(page), nil
|
||||
}
|
||||
|
||||
// Frameworks is the resolver for the frameworks field.
|
||||
|
||||
Reference in New Issue
Block a user