Add trust center front
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -18,10 +18,14 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/connector"
|
||||
"github.com/getprobo/probo/pkg/probo"
|
||||
"github.com/getprobo/probo/pkg/saferedirect"
|
||||
console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1"
|
||||
trust_v1 "github.com/getprobo/probo/pkg/server/api/trust/v1"
|
||||
"github.com/getprobo/probo/pkg/trust"
|
||||
"github.com/getprobo/probo/pkg/usrmgr"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
@@ -30,11 +34,19 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
AuthConfig struct {
|
||||
CookieName string
|
||||
CookieDomain string
|
||||
SessionDuration time.Duration
|
||||
CookieSecret string
|
||||
}
|
||||
|
||||
Config struct {
|
||||
AllowedOrigins []string
|
||||
Probo *probo.Service
|
||||
Usrmgr *usrmgr.Service
|
||||
Auth console_v1.AuthConfig
|
||||
Trust *trust.Service
|
||||
Auth AuthConfig
|
||||
ConnectorRegistry *connector.ConnectorRegistry
|
||||
SafeRedirect *saferedirect.SafeRedirect
|
||||
Logger *log.Logger
|
||||
@@ -122,11 +134,32 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.cfg.Logger.Named("console.v1"),
|
||||
s.cfg.Probo,
|
||||
s.cfg.Usrmgr,
|
||||
s.cfg.Auth,
|
||||
console_v1.AuthConfig{
|
||||
CookieName: s.cfg.Auth.CookieName,
|
||||
CookieDomain: s.cfg.Auth.CookieDomain,
|
||||
SessionDuration: s.cfg.Auth.SessionDuration,
|
||||
CookieSecret: s.cfg.Auth.CookieSecret,
|
||||
},
|
||||
s.cfg.ConnectorRegistry,
|
||||
s.cfg.SafeRedirect,
|
||||
),
|
||||
)
|
||||
|
||||
// Mount the trust API with authentication
|
||||
router.Mount(
|
||||
"/trust/v1",
|
||||
trust_v1.NewMux(
|
||||
s.cfg.Logger.Named("trust.v1"),
|
||||
s.cfg.Usrmgr,
|
||||
s.cfg.Trust,
|
||||
trust_v1.AuthConfig{
|
||||
CookieName: s.cfg.Auth.CookieName,
|
||||
CookieDomain: s.cfg.Auth.CookieDomain,
|
||||
SessionDuration: s.cfg.Auth.SessionDuration,
|
||||
CookieSecret: s.cfg.Auth.CookieSecret,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
@@ -566,6 +566,14 @@ enum AuditOrderField
|
||||
)
|
||||
}
|
||||
|
||||
enum TrustCenterAccessOrderField
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.TrustCenterAccessOrderField") {
|
||||
CREATED_AT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.TrustCenterAccessOrderFieldCreatedAt"
|
||||
)
|
||||
}
|
||||
|
||||
# Input Types
|
||||
input UserOrder
|
||||
@goModel(
|
||||
@@ -647,6 +655,14 @@ input AuditOrder
|
||||
field: AuditOrderField!
|
||||
}
|
||||
|
||||
input TrustCenterAccessOrder
|
||||
@goModel(
|
||||
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.TrustCenterAccessOrderBy"
|
||||
) {
|
||||
direction: OrderDirection!
|
||||
field: TrustCenterAccessOrderField!
|
||||
}
|
||||
|
||||
input EvidenceOrder
|
||||
@goModel(
|
||||
model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.EvidenceOrderBy"
|
||||
@@ -702,6 +718,14 @@ input RiskFilter {
|
||||
query: String
|
||||
}
|
||||
|
||||
input OrganizationFilter {
|
||||
trustCenterSlug: String
|
||||
}
|
||||
|
||||
input TrustCenterFilter {
|
||||
slug: String
|
||||
}
|
||||
|
||||
# Core Types
|
||||
type TrustCenter implements Node {
|
||||
id: ID!
|
||||
@@ -709,6 +733,15 @@ type TrustCenter implements Node {
|
||||
slug: String!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
accesses(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: TrustCenterAccessOrder
|
||||
): TrustCenterAccessConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
@@ -1177,6 +1210,7 @@ type Viewer {
|
||||
last: Int
|
||||
before: CursorKey
|
||||
orderBy: OrganizationOrder
|
||||
filter: OrganizationFilter
|
||||
): OrganizationConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
@@ -1191,6 +1225,35 @@ type OrganizationEdge {
|
||||
node: Organization!
|
||||
}
|
||||
|
||||
type TrustCenterConnection {
|
||||
edges: [TrustCenterEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type TrustCenterEdge {
|
||||
cursor: CursorKey!
|
||||
node: TrustCenter!
|
||||
}
|
||||
|
||||
type TrustCenterAccess implements Node {
|
||||
id: ID!
|
||||
email: String!
|
||||
name: String!
|
||||
active: Boolean!
|
||||
createdAt: Datetime!
|
||||
updatedAt: Datetime!
|
||||
}
|
||||
|
||||
type TrustCenterAccessConnection {
|
||||
edges: [TrustCenterAccessEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type TrustCenterAccessEdge {
|
||||
cursor: CursorKey!
|
||||
node: TrustCenterAccess!
|
||||
}
|
||||
|
||||
type UserConnection {
|
||||
edges: [UserEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
@@ -1399,6 +1462,13 @@ type AuditEdge {
|
||||
type Query {
|
||||
node(id: ID!): Node!
|
||||
viewer: Viewer!
|
||||
trustCenters(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
filter: TrustCenterFilter
|
||||
): TrustCenterConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
@@ -1414,6 +1484,23 @@ type Mutation {
|
||||
input: UpdateTrustCenterInput!
|
||||
): UpdateTrustCenterPayload!
|
||||
|
||||
revokeTrustCenterAccess(
|
||||
input: RevokeTrustCenterAccessInput!
|
||||
): RevokeTrustCenterAccessPayload!
|
||||
|
||||
# Trust Center Access CRUD mutations
|
||||
createTrustCenterAccess(
|
||||
input: CreateTrustCenterAccessInput!
|
||||
): CreateTrustCenterAccessPayload!
|
||||
|
||||
updateTrustCenterAccess(
|
||||
input: UpdateTrustCenterAccessInput!
|
||||
): UpdateTrustCenterAccessPayload!
|
||||
|
||||
deleteTrustCenterAccess(
|
||||
input: DeleteTrustCenterAccessInput!
|
||||
): DeleteTrustCenterAccessPayload!
|
||||
|
||||
# User mutations
|
||||
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
|
||||
inviteUser(input: InviteUserInput!): InviteUserPayload!
|
||||
@@ -1586,6 +1673,29 @@ input UpdateTrustCenterInput {
|
||||
slug: String
|
||||
}
|
||||
|
||||
input RevokeTrustCenterAccessInput {
|
||||
accessId: ID!
|
||||
}
|
||||
|
||||
input CreateTrustCenterAccessInput {
|
||||
trustCenterId: ID!
|
||||
email: String!
|
||||
name: String!
|
||||
sendEmail: Boolean! = true
|
||||
}
|
||||
|
||||
input UpdateTrustCenterAccessInput {
|
||||
accessId: ID!
|
||||
email: String
|
||||
name: String
|
||||
active: Boolean
|
||||
sendEmail: Boolean! = false
|
||||
}
|
||||
|
||||
input DeleteTrustCenterAccessInput {
|
||||
accessId: ID!
|
||||
}
|
||||
|
||||
input CreateVendorInput {
|
||||
organizationId: ID!
|
||||
name: String!
|
||||
@@ -1951,6 +2061,24 @@ type UpdateTrustCenterPayload {
|
||||
trustCenter: TrustCenter!
|
||||
}
|
||||
|
||||
|
||||
|
||||
type RevokeTrustCenterAccessPayload {
|
||||
trustCenterAccess: TrustCenterAccess!
|
||||
}
|
||||
|
||||
type CreateTrustCenterAccessPayload {
|
||||
trustCenterAccessEdge: TrustCenterAccessEdge!
|
||||
}
|
||||
|
||||
type UpdateTrustCenterAccessPayload {
|
||||
trustCenterAccess: TrustCenterAccess!
|
||||
}
|
||||
|
||||
type DeleteTrustCenterAccessPayload {
|
||||
deletedTrustCenterAccessId: ID!
|
||||
}
|
||||
|
||||
type CreateControlPayload {
|
||||
controlEdge: ControlEdge!
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
57
pkg/server/api/console/v1/types/trust_center_access.go
Normal file
57
pkg/server/api/console/v1/types/trust_center_access.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
type TrustCenterAccessOrderBy = OrderBy[coredata.TrustCenterAccessOrderField]
|
||||
|
||||
func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess {
|
||||
return &TrustCenterAccess{
|
||||
ID: tca.ID,
|
||||
Email: tca.Email,
|
||||
Name: tca.Name,
|
||||
Active: tca.Active,
|
||||
CreatedAt: tca.CreatedAt,
|
||||
UpdatedAt: tca.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTrustCenterAccessConnection(
|
||||
page *page.Page[*coredata.TrustCenterAccess, coredata.TrustCenterAccessOrderField],
|
||||
) *TrustCenterAccessConnection {
|
||||
var edges = make([]*TrustCenterAccessEdge, len(page.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewTrustCenterAccessEdge(page.Data[i], page.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &TrustCenterAccessConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(page),
|
||||
}
|
||||
}
|
||||
|
||||
func NewTrustCenterAccessEdge(tca *coredata.TrustCenterAccess, orderBy coredata.TrustCenterAccessOrderField) *TrustCenterAccessEdge {
|
||||
return &TrustCenterAccessEdge{
|
||||
Cursor: tca.CursorKey(orderBy),
|
||||
Node: NewTrustCenterAccess(tca),
|
||||
}
|
||||
}
|
||||
|
||||
// Types are auto-generated in types.go - only helper functions remain here
|
||||
@@ -367,6 +367,17 @@ type CreateTaskPayload struct {
|
||||
TaskEdge *TaskEdge `json:"taskEdge"`
|
||||
}
|
||||
|
||||
type CreateTrustCenterAccessInput struct {
|
||||
TrustCenterID gid.GID `json:"trustCenterId"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
SendEmail bool `json:"sendEmail"`
|
||||
}
|
||||
|
||||
type CreateTrustCenterAccessPayload struct {
|
||||
TrustCenterAccessEdge *TrustCenterAccessEdge `json:"trustCenterAccessEdge"`
|
||||
}
|
||||
|
||||
type CreateVendorInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
Name string `json:"name"`
|
||||
@@ -561,6 +572,14 @@ type DeleteTaskPayload struct {
|
||||
DeletedTaskID gid.GID `json:"deletedTaskId"`
|
||||
}
|
||||
|
||||
type DeleteTrustCenterAccessInput struct {
|
||||
AccessID gid.GID `json:"accessId"`
|
||||
}
|
||||
|
||||
type DeleteTrustCenterAccessPayload struct {
|
||||
DeletedTrustCenterAccessID gid.GID `json:"deletedTrustCenterAccessId"`
|
||||
}
|
||||
|
||||
type DeleteVendorComplianceReportInput struct {
|
||||
ReportID gid.GID `json:"reportId"`
|
||||
}
|
||||
@@ -836,6 +855,10 @@ type OrganizationEdge struct {
|
||||
Node *Organization `json:"node"`
|
||||
}
|
||||
|
||||
type OrganizationFilter struct {
|
||||
TrustCenterSlug *string `json:"trustCenterSlug,omitempty"`
|
||||
}
|
||||
|
||||
type OrganizationOrder struct {
|
||||
Direction page.OrderDirection `json:"direction"`
|
||||
Field coredata.OrganizationOrderField `json:"field"`
|
||||
@@ -925,6 +948,14 @@ type RequestSignaturePayload struct {
|
||||
DocumentVersionSignatureEdge *DocumentVersionSignatureEdge `json:"documentVersionSignatureEdge"`
|
||||
}
|
||||
|
||||
type RevokeTrustCenterAccessInput struct {
|
||||
AccessID gid.GID `json:"accessId"`
|
||||
}
|
||||
|
||||
type RevokeTrustCenterAccessPayload struct {
|
||||
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
|
||||
}
|
||||
|
||||
type Risk struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -996,16 +1027,54 @@ type TaskEdge struct {
|
||||
}
|
||||
|
||||
type TrustCenter struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Active bool `json:"active"`
|
||||
Slug string `json:"slug"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
ID gid.GID `json:"id"`
|
||||
Active bool `json:"active"`
|
||||
Slug string `json:"slug"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Accesses *TrustCenterAccessConnection `json:"accesses"`
|
||||
}
|
||||
|
||||
func (TrustCenter) IsNode() {}
|
||||
func (this TrustCenter) GetID() gid.GID { return this.ID }
|
||||
|
||||
type TrustCenterAccess struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
Active bool `json:"active"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (TrustCenterAccess) IsNode() {}
|
||||
func (this TrustCenterAccess) GetID() gid.GID { return this.ID }
|
||||
|
||||
type TrustCenterAccessConnection struct {
|
||||
Edges []*TrustCenterAccessEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type TrustCenterAccessEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *TrustCenterAccess `json:"node"`
|
||||
}
|
||||
|
||||
type TrustCenterConnection struct {
|
||||
Edges []*TrustCenterEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type TrustCenterEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *TrustCenter `json:"node"`
|
||||
}
|
||||
|
||||
type TrustCenterFilter struct {
|
||||
Slug *string `json:"slug,omitempty"`
|
||||
}
|
||||
|
||||
type UnassignTaskInput struct {
|
||||
TaskID gid.GID `json:"taskId"`
|
||||
}
|
||||
@@ -1167,6 +1236,18 @@ type UpdateTaskPayload struct {
|
||||
Task *Task `json:"task"`
|
||||
}
|
||||
|
||||
type UpdateTrustCenterAccessInput struct {
|
||||
AccessID gid.GID `json:"accessId"`
|
||||
Email *string `json:"email,omitempty"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
SendEmail bool `json:"sendEmail"`
|
||||
}
|
||||
|
||||
type UpdateTrustCenterAccessPayload struct {
|
||||
TrustCenterAccess *TrustCenterAccess `json:"trustCenterAccess"`
|
||||
}
|
||||
|
||||
type UpdateTrustCenterInput struct {
|
||||
TrustCenterID gid.GID `json:"trustCenterId"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
|
||||
@@ -998,6 +998,77 @@ func (r *mutationResolver) UpdateTrustCenter(ctx context.Context, input types.Up
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RevokeTrustCenterAccess is the resolver for the revokeTrustCenterAccess field.
|
||||
func (r *mutationResolver) RevokeTrustCenterAccess(ctx context.Context, input types.RevokeTrustCenterAccessInput) (*types.RevokeTrustCenterAccessPayload, error) {
|
||||
prb := r.ProboService(ctx, input.AccessID.TenantID())
|
||||
|
||||
access, err := prb.TrustCenterAccesses.RevokeAccess(ctx, &probo.RevokeTrustCenterAccessRequest{
|
||||
AccessID: input.AccessID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot revoke trust center access: %w", err)
|
||||
}
|
||||
|
||||
return &types.RevokeTrustCenterAccessPayload{
|
||||
TrustCenterAccess: types.NewTrustCenterAccess(access),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateTrustCenterAccess is the resolver for the createTrustCenterAccess field.
|
||||
func (r *mutationResolver) CreateTrustCenterAccess(ctx context.Context, input types.CreateTrustCenterAccessInput) (*types.CreateTrustCenterAccessPayload, error) {
|
||||
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
|
||||
|
||||
access, err := prb.TrustCenterAccesses.Create(ctx, &probo.CreateTrustCenterAccessRequest{
|
||||
TrustCenterID: input.TrustCenterID,
|
||||
Email: input.Email,
|
||||
Name: input.Name,
|
||||
SendEmail: input.SendEmail,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create trust center access: %w", err)
|
||||
}
|
||||
|
||||
return &types.CreateTrustCenterAccessPayload{
|
||||
TrustCenterAccessEdge: types.NewTrustCenterAccessEdge(access, coredata.TrustCenterAccessOrderFieldCreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateTrustCenterAccess is the resolver for the updateTrustCenterAccess field.
|
||||
func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input types.UpdateTrustCenterAccessInput) (*types.UpdateTrustCenterAccessPayload, error) {
|
||||
prb := r.ProboService(ctx, input.AccessID.TenantID())
|
||||
|
||||
access, err := prb.TrustCenterAccesses.Update(ctx, &probo.UpdateTrustCenterAccessRequest{
|
||||
AccessID: input.AccessID,
|
||||
Email: input.Email,
|
||||
Name: input.Name,
|
||||
Active: input.Active,
|
||||
SendEmail: input.SendEmail,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot update trust center access: %w", err)
|
||||
}
|
||||
|
||||
return &types.UpdateTrustCenterAccessPayload{
|
||||
TrustCenterAccess: types.NewTrustCenterAccess(access),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteTrustCenterAccess is the resolver for the deleteTrustCenterAccess field.
|
||||
func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) {
|
||||
prb := r.ProboService(ctx, input.AccessID.TenantID())
|
||||
|
||||
err := prb.TrustCenterAccesses.Delete(ctx, &probo.DeleteTrustCenterAccessRequest{
|
||||
AccessID: input.AccessID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot delete trust center access: %w", err)
|
||||
}
|
||||
|
||||
return &types.DeleteTrustCenterAccessPayload{
|
||||
DeletedTrustCenterAccessID: input.AccessID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ConfirmEmail is the resolver for the confirmEmail field.
|
||||
func (r *mutationResolver) ConfirmEmail(ctx context.Context, input types.ConfirmEmailInput) (*types.ConfirmEmailPayload, error) {
|
||||
err := r.usrmgrSvc.ConfirmEmail(ctx, input.Token)
|
||||
@@ -2883,7 +2954,7 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error
|
||||
}
|
||||
return types.NewReport(report), nil
|
||||
case coredata.TrustCenterEntityType:
|
||||
trustCenter, err := prb.TrustCenters.GetByOrganizationID(ctx, id)
|
||||
trustCenter, err := prb.TrustCenters.Get(ctx, id)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot get trust center: %w", err))
|
||||
}
|
||||
@@ -2905,6 +2976,11 @@ func (r *queryResolver) Viewer(ctx context.Context) (*types.Viewer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TrustCenters is the resolver for the trustCenters field.
|
||||
func (r *queryResolver) TrustCenters(ctx context.Context, first *int, after *page.CursorKey, last *int, before *page.CursorKey, filter *types.TrustCenterFilter) (*types.TrustCenterConnection, error) {
|
||||
panic(fmt.Errorf("not implemented: TrustCenters - trustCenters"))
|
||||
}
|
||||
|
||||
// DownloadURL is the resolver for the downloadUrl field.
|
||||
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
@@ -3167,6 +3243,43 @@ func (r *taskConnectionResolver) TotalCount(ctx context.Context, obj *types.Task
|
||||
panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver))
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
organization, err := prb.Organizations.Get(ctx, obj.Organization.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||
}
|
||||
|
||||
return types.NewOrganization(organization), nil
|
||||
}
|
||||
|
||||
// Accesses is the resolver for the accesses field.
|
||||
func (r *trustCenterResolver) Accesses(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrderBy[coredata.TrustCenterAccessOrderField]) (*types.TrustCenterAccessConnection, error) {
|
||||
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.TrustCenterAccessOrderField]{
|
||||
Field: coredata.TrustCenterAccessOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
if orderBy != nil {
|
||||
pageOrderBy = page.OrderBy[coredata.TrustCenterAccessOrderField]{
|
||||
Field: orderBy.Field,
|
||||
Direction: orderBy.Direction,
|
||||
}
|
||||
}
|
||||
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
result, err := prb.TrustCenterAccesses.ListForTrustCenterID(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot list trust center accesses: %w", err))
|
||||
}
|
||||
|
||||
return types.NewTrustCenterAccessConnection(result), nil
|
||||
}
|
||||
|
||||
// People is the resolver for the people field.
|
||||
func (r *userResolver) People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) {
|
||||
prb := r.ProboService(ctx, organizationID.TenantID())
|
||||
@@ -3369,7 +3482,7 @@ func (r *vendorRiskAssessmentResolver) AssessedBy(ctx context.Context, obj *type
|
||||
}
|
||||
|
||||
// Organizations is the resolver for the organizations field.
|
||||
func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrganizationOrder) (*types.OrganizationConnection, error) {
|
||||
func (r *viewerResolver) Organizations(ctx context.Context, obj *types.Viewer, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.OrganizationOrder, filter *types.OrganizationFilter) (*types.OrganizationConnection, error) {
|
||||
user := UserFromContext(ctx)
|
||||
|
||||
// For now, we're not using cursor pagination since we're loading all organizations
|
||||
@@ -3496,6 +3609,9 @@ func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} }
|
||||
// TaskConnection returns schema.TaskConnectionResolver implementation.
|
||||
func (r *Resolver) TaskConnection() schema.TaskConnectionResolver { return &taskConnectionResolver{r} }
|
||||
|
||||
// TrustCenter returns schema.TrustCenterResolver implementation.
|
||||
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
|
||||
|
||||
// User returns schema.UserResolver implementation.
|
||||
func (r *Resolver) User() schema.UserResolver { return &userResolver{r} }
|
||||
|
||||
@@ -3547,6 +3663,7 @@ type riskResolver struct{ *Resolver }
|
||||
type riskConnectionResolver struct{ *Resolver }
|
||||
type taskResolver struct{ *Resolver }
|
||||
type taskConnectionResolver struct{ *Resolver }
|
||||
type trustCenterResolver struct{ *Resolver }
|
||||
type userResolver struct{ *Resolver }
|
||||
type vendorResolver struct{ *Resolver }
|
||||
type vendorComplianceReportResolver struct{ *Resolver }
|
||||
|
||||
29
pkg/server/api/trust/v1/gqlgen.yaml
Normal file
29
pkg/server/api/trust/v1/gqlgen.yaml
Normal file
@@ -0,0 +1,29 @@
|
||||
schema: ["schema.graphql"]
|
||||
|
||||
exec:
|
||||
filename: "schema/schema.go"
|
||||
package: "schema"
|
||||
|
||||
model:
|
||||
filename: "types/types.go"
|
||||
package: "types"
|
||||
|
||||
resolver:
|
||||
layout: "follow-schema"
|
||||
dir: "."
|
||||
package: "trust_v1"
|
||||
filename_template: "v1_resolver.go"
|
||||
|
||||
autobind: []
|
||||
call_argument_directives_with_null: true
|
||||
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- "github.com/getprobo/probo/pkg/server/api/trust/v1/types.GIDScalar"
|
||||
Datetime:
|
||||
model:
|
||||
- "github.com/99designs/gqlgen/graphql.Time"
|
||||
CursorKey:
|
||||
model:
|
||||
- "github.com/getprobo/probo/pkg/server/api/trust/v1/types.CursorKeyScalar"
|
||||
277
pkg/server/api/trust/v1/resolver.go
Normal file
277
pkg/server/api/trust/v1/resolver.go
Normal file
@@ -0,0 +1,277 @@
|
||||
//go:generate go run github.com/99designs/gqlgen generate
|
||||
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package trust_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/99designs/gqlgen/graphql/handler"
|
||||
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/securecookie"
|
||||
"github.com/getprobo/probo/pkg/server/api/trust/v1/schema"
|
||||
"github.com/getprobo/probo/pkg/server/api/trust/v1/types"
|
||||
"github.com/getprobo/probo/pkg/trust"
|
||||
"github.com/getprobo/probo/pkg/usrmgr"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
type (
|
||||
AuthConfig struct {
|
||||
CookieName string
|
||||
CookieDomain string
|
||||
SessionDuration time.Duration
|
||||
CookieSecret string
|
||||
}
|
||||
|
||||
Resolver struct {
|
||||
trustCenterSvc *trust.Service
|
||||
authCfg AuthConfig
|
||||
}
|
||||
|
||||
ctxKey struct{ name string }
|
||||
|
||||
TokenAccessData struct {
|
||||
TrustCenterID gid.GID
|
||||
Email string
|
||||
TenantID gid.TenantID
|
||||
Scope string
|
||||
}
|
||||
|
||||
TrustCenterTokenData struct {
|
||||
TrustCenterID gid.GID `json:"trust_center_id"`
|
||||
Email string `json:"email"`
|
||||
TenantID gid.TenantID `json:"tenant_id"`
|
||||
Scope string `json:"scope"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
TokenScopeTrustCenterReadOnly = "trust_center_readonly"
|
||||
TokenCookieName = "trust_center_token"
|
||||
)
|
||||
|
||||
var (
|
||||
sessionContextKey = &ctxKey{name: "session"}
|
||||
userContextKey = &ctxKey{name: "user"}
|
||||
userTenantContextKey = &ctxKey{name: "user_tenants"}
|
||||
tokenAccessContextKey = &ctxKey{name: "token_access"}
|
||||
)
|
||||
|
||||
func SessionFromContext(ctx context.Context) *coredata.Session {
|
||||
session, _ := ctx.Value(sessionContextKey).(*coredata.Session)
|
||||
return session
|
||||
}
|
||||
|
||||
func UserFromContext(ctx context.Context) *coredata.User {
|
||||
user, _ := ctx.Value(userContextKey).(*coredata.User)
|
||||
return user
|
||||
}
|
||||
|
||||
func TokenAccessFromContext(ctx context.Context) *TokenAccessData {
|
||||
tokenAccess, _ := ctx.Value(tokenAccessContextKey).(*TokenAccessData)
|
||||
return tokenAccess
|
||||
}
|
||||
|
||||
func GetCurrentUserRole(ctx context.Context) types.Role {
|
||||
user := UserFromContext(ctx)
|
||||
tokenAccess := TokenAccessFromContext(ctx)
|
||||
|
||||
if user != nil || tokenAccess != nil {
|
||||
return types.RoleUser
|
||||
}
|
||||
return types.RoleNone
|
||||
}
|
||||
|
||||
func NewMux(
|
||||
logger *log.Logger,
|
||||
usrmgrSvc *usrmgr.Service,
|
||||
trustSvc *trust.Service,
|
||||
authCfg AuthConfig,
|
||||
) *chi.Mux {
|
||||
r := chi.NewMux()
|
||||
|
||||
encryptionKey := trustSvc.GetEncryptionKey()
|
||||
|
||||
r.Handle("/graphql", graphqlHandler(logger, usrmgrSvc, trustSvc, authCfg, encryptionKey))
|
||||
|
||||
r.Handle("/playground", playground.Handler("GraphQL Playground", "/api/trust/v1/graphql"))
|
||||
|
||||
r.Post("/trust-center-access/authenticate", authTokenHandler(trustSvc, authCfg, encryptionKey))
|
||||
r.Delete("/trust-center-access/logout", trustCenterLogoutHandler(authCfg))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func graphqlHandler(logger *log.Logger, usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg AuthConfig, encryptionKey cipher.EncryptionKey) http.HandlerFunc {
|
||||
var mb int64 = 1 << 20
|
||||
|
||||
c := schema.Config{
|
||||
Resolvers: &Resolver{
|
||||
trustCenterSvc: trustSvc,
|
||||
authCfg: authCfg,
|
||||
},
|
||||
}
|
||||
|
||||
c.Directives.MustBeAuthenticated = func(ctx context.Context, obj interface{}, next graphql.Resolver, role *types.Role) (interface{}, error) {
|
||||
currentRole := GetCurrentUserRole(ctx)
|
||||
|
||||
if role != nil && *role == types.RoleUser && currentRole == types.RoleNone {
|
||||
return nil, fmt.Errorf("access denied: authentication required")
|
||||
}
|
||||
|
||||
return next(ctx)
|
||||
}
|
||||
|
||||
es := schema.NewExecutableSchema(c)
|
||||
|
||||
srv := handler.New(es)
|
||||
|
||||
srv.AddTransport(transport.POST{})
|
||||
srv.AddTransport(transport.GET{})
|
||||
srv.AddTransport(transport.Options{})
|
||||
srv.AddTransport(
|
||||
transport.MultipartForm{
|
||||
MaxMemory: 32 * mb,
|
||||
MaxUploadSize: 50 * mb,
|
||||
},
|
||||
)
|
||||
|
||||
srv.Use(extension.Introspection{})
|
||||
|
||||
srv.SetRecoverFunc(func(ctx context.Context, err any) error {
|
||||
logger := httpserver.LoggerFromContext(ctx)
|
||||
logger.Error("resolver panic", log.Any("error", err), log.Any("stack", string(debug.Stack())))
|
||||
|
||||
return errors.New("internal server error")
|
||||
})
|
||||
|
||||
return WithSession(usrmgrSvc, trustSvc, authCfg, encryptionKey, srv.ServeHTTP)
|
||||
}
|
||||
|
||||
// TrustService returns a trust service scoped to the given tenant
|
||||
func (r *Resolver) TrustService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
|
||||
return r.trustCenterSvc.WithTenant(tenantID)
|
||||
}
|
||||
|
||||
// GetTenantService returns a tenant service for the given tenant ID
|
||||
func (r *Resolver) GetTenantService(ctx context.Context, tenantID gid.TenantID) *trust.TenantService {
|
||||
return r.trustCenterSvc.WithTenant(tenantID)
|
||||
}
|
||||
|
||||
func WithSession(usrmgrSvc *usrmgr.Service, trustSvc *trust.Service, authCfg AuthConfig, encryptionKey cipher.EncryptionKey, next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
cookieValue, err := securecookie.Get(r, securecookie.DefaultConfig(
|
||||
authCfg.CookieName,
|
||||
authCfg.CookieSecret,
|
||||
))
|
||||
|
||||
if err == nil {
|
||||
sessionID, err := gid.ParseGID(cookieValue)
|
||||
if err == nil {
|
||||
session, err := usrmgrSvc.GetSession(ctx, sessionID)
|
||||
if err == nil {
|
||||
user, err := usrmgrSvc.GetUserBySession(ctx, sessionID)
|
||||
if err == nil {
|
||||
tenantIDs, err := usrmgrSvc.ListTenantsForUserID(ctx, user.ID)
|
||||
if err == nil {
|
||||
ctx = context.WithValue(ctx, sessionContextKey, session)
|
||||
ctx = context.WithValue(ctx, userContextKey, user)
|
||||
ctx = context.WithValue(ctx, userTenantContextKey, &tenantIDs)
|
||||
|
||||
next(w, r.WithContext(ctx))
|
||||
|
||||
if err := usrmgrSvc.UpdateSession(ctx, session); err != nil {
|
||||
panic(fmt.Errorf("failed to update session: %w", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
securecookie.Clear(w, securecookie.DefaultConfig(
|
||||
authCfg.CookieName,
|
||||
authCfg.CookieSecret,
|
||||
))
|
||||
}
|
||||
|
||||
tokenCookieValue, err := securecookie.Get(r, securecookie.Config{
|
||||
Name: TokenCookieName,
|
||||
Secret: authCfg.CookieSecret,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
encryptedData, err := base64.StdEncoding.DecodeString(tokenCookieValue)
|
||||
if err == nil {
|
||||
decryptedData, err := cipher.Decrypt(encryptedData, encryptionKey)
|
||||
if err == nil {
|
||||
var tokenData TrustCenterTokenData
|
||||
if err := json.Unmarshal(decryptedData, &tokenData); err == nil {
|
||||
if time.Now().Before(tokenData.ExpiresAt) {
|
||||
tenantSvc := trustSvc.WithTenant(tokenData.TenantID)
|
||||
isActive, err := tenantSvc.TrustCenterAccesses.IsAccessActive(ctx, tokenData.TrustCenterID, tokenData.Email)
|
||||
|
||||
if err == nil && isActive {
|
||||
tokenAccess := &TokenAccessData{
|
||||
TrustCenterID: tokenData.TrustCenterID,
|
||||
Email: tokenData.Email,
|
||||
TenantID: tokenData.TenantID,
|
||||
Scope: tokenData.Scope,
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, tokenAccessContextKey, tokenAccess)
|
||||
next(w, r.WithContext(ctx))
|
||||
return
|
||||
} else {
|
||||
securecookie.Clear(w, securecookie.Config{
|
||||
Name: TokenCookieName,
|
||||
Secret: authCfg.CookieSecret,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
securecookie.Clear(w, securecookie.Config{
|
||||
Name: TokenCookieName,
|
||||
Secret: authCfg.CookieSecret,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Continue without authentication for public access
|
||||
next(w, r.WithContext(ctx))
|
||||
}
|
||||
}
|
||||
263
pkg/server/api/trust/v1/schema.graphql
Normal file
263
pkg/server/api/trust/v1/schema.graphql
Normal file
@@ -0,0 +1,263 @@
|
||||
# Directives
|
||||
directive @goField(
|
||||
forceResolver: Boolean
|
||||
name: String
|
||||
omittable: Boolean
|
||||
) on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||
|
||||
directive @goModel(
|
||||
model: String
|
||||
models: [String!]
|
||||
) on OBJECT | INPUT_OBJECT | SCALAR | ENUM | INTERFACE | UNION
|
||||
|
||||
directive @goEnum(value: String) on ENUM_VALUE
|
||||
|
||||
directive @mustBeAuthenticated(role: Role = NONE) on FIELD_DEFINITION | OBJECT
|
||||
|
||||
enum Role {
|
||||
NONE
|
||||
USER
|
||||
}
|
||||
|
||||
scalar Datetime
|
||||
scalar CursorKey
|
||||
|
||||
interface Node {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
hasNextPage: Boolean!
|
||||
hasPreviousPage: Boolean!
|
||||
startCursor: CursorKey
|
||||
endCursor: CursorKey
|
||||
}
|
||||
|
||||
type Organization implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
logoUrl: String @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
enum DocumentType
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.DocumentType") {
|
||||
OTHER
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeOther")
|
||||
ISMS @goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypeISMS")
|
||||
POLICY
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.DocumentTypePolicy")
|
||||
}
|
||||
|
||||
type DocumentVersion implements Node {
|
||||
id: ID!
|
||||
}
|
||||
|
||||
type Document implements Node {
|
||||
id: ID!
|
||||
title: String!
|
||||
documentType: DocumentType!
|
||||
versions(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): DocumentVersionConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
type DocumentConnection {
|
||||
edges: [DocumentEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type DocumentEdge {
|
||||
cursor: CursorKey!
|
||||
node: Document!
|
||||
}
|
||||
|
||||
type DocumentVersionConnection {
|
||||
edges: [DocumentVersionEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type DocumentVersionEdge {
|
||||
cursor: CursorKey!
|
||||
node: DocumentVersion!
|
||||
}
|
||||
|
||||
|
||||
type Framework implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
}
|
||||
|
||||
type Report implements Node {
|
||||
id: ID!
|
||||
filename: String!
|
||||
downloadUrl: String @goField(forceResolver: true) @mustBeAuthenticated(role: USER)
|
||||
}
|
||||
|
||||
type Audit implements Node {
|
||||
id: ID!
|
||||
framework: Framework! @goField(forceResolver: true)
|
||||
report: Report @goField(forceResolver: true)
|
||||
reportUrl: String @goField(forceResolver: true) @mustBeAuthenticated(role: USER)
|
||||
}
|
||||
|
||||
type AuditConnection {
|
||||
edges: [AuditEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type AuditEdge {
|
||||
cursor: CursorKey!
|
||||
node: Audit!
|
||||
}
|
||||
|
||||
enum VendorCategory
|
||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.VendorCategory") {
|
||||
ANALYTICS
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryAnalytics"
|
||||
)
|
||||
CLOUD_MONITORING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
||||
)
|
||||
CLOUD_PROVIDER
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCloudProvider"
|
||||
)
|
||||
COLLABORATION
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCollaboration"
|
||||
)
|
||||
CUSTOMER_SUPPORT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryCustomerSupport"
|
||||
)
|
||||
DATA_STORAGE_AND_PROCESSING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDataStorageAndProcessing"
|
||||
)
|
||||
DOCUMENT_MANAGEMENT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryDocumentManagement"
|
||||
)
|
||||
EMPLOYEE_MANAGEMENT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
||||
)
|
||||
ENGINEERING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryEngineering"
|
||||
)
|
||||
FINANCE
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryFinance"
|
||||
)
|
||||
IDENTITY_PROVIDER
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryIdentityProvider"
|
||||
)
|
||||
IT @goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryIT")
|
||||
MARKETING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryMarketing"
|
||||
)
|
||||
OFFICE_OPERATIONS
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
||||
)
|
||||
OTHER
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryOther")
|
||||
PASSWORD_MANAGEMENT
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
||||
)
|
||||
PRODUCT_AND_DESIGN
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProductAndDesign"
|
||||
)
|
||||
PROFESSIONAL_SERVICES
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
||||
)
|
||||
RECRUITING
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryRecruiting"
|
||||
)
|
||||
SALES
|
||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySales")
|
||||
SECURITY
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategorySecurity"
|
||||
)
|
||||
VERSION_CONTROL
|
||||
@goEnum(
|
||||
value: "github.com/getprobo/probo/pkg/coredata.VendorCategoryVersionControl"
|
||||
)
|
||||
}
|
||||
|
||||
type Vendor implements Node {
|
||||
id: ID!
|
||||
name: String!
|
||||
category: VendorCategory!
|
||||
websiteUrl: String
|
||||
privacyPolicyUrl: String
|
||||
}
|
||||
|
||||
type VendorConnection {
|
||||
edges: [VendorEdge!]!
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type VendorEdge {
|
||||
cursor: CursorKey!
|
||||
node: Vendor!
|
||||
}
|
||||
|
||||
type TrustCenter implements Node {
|
||||
id: ID!
|
||||
active: Boolean!
|
||||
slug: String!
|
||||
organization: Organization! @goField(forceResolver: true)
|
||||
|
||||
documents(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): DocumentConnection! @goField(forceResolver: true)
|
||||
|
||||
audits(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): AuditConnection! @goField(forceResolver: true)
|
||||
|
||||
vendors(
|
||||
first: Int
|
||||
after: CursorKey
|
||||
last: Int
|
||||
before: CursorKey
|
||||
): VendorConnection! @goField(forceResolver: true)
|
||||
}
|
||||
|
||||
input ExportDocumentVersionPDFInput {
|
||||
documentVersionId: ID!
|
||||
}
|
||||
|
||||
type ExportDocumentVersionPDFPayload {
|
||||
data: String!
|
||||
}
|
||||
|
||||
type Query {
|
||||
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
exportDocumentVersionPDF(
|
||||
input: ExportDocumentVersionPDFInput!
|
||||
): ExportDocumentVersionPDFPayload! @mustBeAuthenticated(role: USER)
|
||||
}
|
||||
9013
pkg/server/api/trust/v1/schema/schema.go
Normal file
9013
pkg/server/api/trust/v1/schema/schema.go
Normal file
File diff suppressed because it is too large
Load Diff
153
pkg/server/api/trust/v1/trust_center_access_handler.go
Normal file
153
pkg/server/api/trust/v1/trust_center_access_handler.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package trust_v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/crypto/cipher"
|
||||
"github.com/getprobo/probo/pkg/probo"
|
||||
"github.com/getprobo/probo/pkg/securecookie"
|
||||
"github.com/getprobo/probo/pkg/statelesstoken"
|
||||
"github.com/getprobo/probo/pkg/trust"
|
||||
"go.gearno.de/kit/httpserver"
|
||||
)
|
||||
|
||||
type (
|
||||
AuthTokenRequest struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
AuthTokenResponse struct {
|
||||
Success bool `json:"success"`
|
||||
TrustCenterID string `json:"trust_center_id,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
func authTokenHandler(trustSvc *trust.Service, authCfg AuthConfig, encryptionKey cipher.EncryptionKey) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req AuthTokenRequest
|
||||
// Limit request body size to 1KB to prevent DoS attacks
|
||||
limitedReader := http.MaxBytesReader(w, r.Body, 1024)
|
||||
if err := json.NewDecoder(limitedReader).Decode(&req); err != nil {
|
||||
httpserver.RenderError(w, http.StatusBadRequest, fmt.Errorf("cannot decode body: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if req.Token == "" {
|
||||
httpserver.RenderJSON(w, http.StatusBadRequest, AuthTokenResponse{
|
||||
Success: false,
|
||||
Message: "Token is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
accessData, err := validateTrustCenterAccessToken(r.Context(), trustSvc, authCfg, req.Token)
|
||||
if err != nil {
|
||||
httpserver.RenderJSON(w, http.StatusUnauthorized, AuthTokenResponse{
|
||||
Success: false,
|
||||
Message: "Invalid or expired token",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tokenData := TrustCenterTokenData{
|
||||
TrustCenterID: accessData.TrustCenterID,
|
||||
Email: accessData.Email,
|
||||
TenantID: accessData.TrustCenterID.TenantID(),
|
||||
Scope: TokenScopeTrustCenterReadOnly,
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour),
|
||||
}
|
||||
|
||||
tokenBytes, err := json.Marshal(tokenData)
|
||||
if err != nil {
|
||||
httpserver.RenderError(w, http.StatusInternalServerError, fmt.Errorf("failed to serialize token data: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
encryptedTokenData, err := cipher.Encrypt(tokenBytes, encryptionKey)
|
||||
if err != nil {
|
||||
httpserver.RenderError(w, http.StatusInternalServerError, fmt.Errorf("failed to encrypt token data: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
encryptedTokenString := base64.StdEncoding.EncodeToString(encryptedTokenData)
|
||||
|
||||
cookieConfig := securecookie.Config{
|
||||
Name: TokenCookieName,
|
||||
Secret: authCfg.CookieSecret,
|
||||
Domain: authCfg.CookieDomain,
|
||||
Path: "/",
|
||||
MaxAge: int(24 * time.Hour / time.Second), // 24 hours
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
}
|
||||
|
||||
if err := securecookie.Set(w, cookieConfig, encryptedTokenString); err != nil {
|
||||
httpserver.RenderError(w, http.StatusInternalServerError, fmt.Errorf("failed to set cookie: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
httpserver.RenderJSON(w, http.StatusOK, AuthTokenResponse{
|
||||
Success: true,
|
||||
TrustCenterID: accessData.TrustCenterID.String(),
|
||||
Message: "Authentication successful",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func validateTrustCenterAccessToken(ctx context.Context, trustSvc *trust.Service, authCfg AuthConfig, tokenString string) (*probo.TrustCenterAccessData, error) {
|
||||
token, err := statelesstoken.ValidateToken[probo.TrustCenterAccessData](
|
||||
authCfg.CookieSecret,
|
||||
probo.TokenTypeTrustCenterAccess,
|
||||
tokenString,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot validate trust center access token: %w", err)
|
||||
}
|
||||
|
||||
tenantID := token.Data.TrustCenterID.TenantID()
|
||||
tenantSvc := trustSvc.WithTenant(tenantID)
|
||||
|
||||
return tenantSvc.TrustCenterAccesses.ValidateToken(ctx, tokenString)
|
||||
}
|
||||
|
||||
func trustCenterLogoutHandler(authCfg AuthConfig) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cookieConfig := securecookie.Config{
|
||||
Name: TokenCookieName,
|
||||
Secret: authCfg.CookieSecret,
|
||||
Domain: authCfg.CookieDomain,
|
||||
Path: "/",
|
||||
MaxAge: -1,
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
}
|
||||
|
||||
securecookie.Clear(w, cookieConfig)
|
||||
|
||||
w.Header().Set("Clear-Site-Data", "*")
|
||||
|
||||
httpserver.RenderJSON(w, http.StatusOK, map[string]bool{"success": true})
|
||||
}
|
||||
}
|
||||
47
pkg/server/api/trust/v1/types/audit.go
Normal file
47
pkg/server/api/trust/v1/types/audit.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewAuditConnection(
|
||||
p *page.Page[*coredata.Audit, coredata.AuditOrderField],
|
||||
) *AuditConnection {
|
||||
edges := make([]*AuditEdge, len(p.Data))
|
||||
for i, audit := range p.Data {
|
||||
edges[i] = NewAuditEdge(audit, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &AuditConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
|
||||
func NewAudit(a *coredata.Audit) *Audit {
|
||||
return &Audit{
|
||||
ID: a.ID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAuditEdge(a *coredata.Audit, orderField coredata.AuditOrderField) *AuditEdge {
|
||||
return &AuditEdge{
|
||||
Node: NewAudit(a),
|
||||
Cursor: a.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
70
pkg/server/api/trust/v1/types/cursorkey.go
Normal file
70
pkg/server/api/trust/v1/types/cursorkey.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewCursor[O page.OrderField](
|
||||
first *int,
|
||||
after *page.CursorKey,
|
||||
last *int,
|
||||
before *page.CursorKey,
|
||||
orderBy page.OrderBy[O],
|
||||
) *page.Cursor[O] {
|
||||
var (
|
||||
size int
|
||||
from *page.CursorKey
|
||||
direction = page.Head
|
||||
)
|
||||
|
||||
if first != nil {
|
||||
size = *first
|
||||
direction = page.Head
|
||||
from = after
|
||||
} else if last != nil {
|
||||
size = *last
|
||||
direction = page.Tail
|
||||
from = before
|
||||
}
|
||||
|
||||
return page.NewCursor(size, from, direction, orderBy)
|
||||
}
|
||||
|
||||
func MarshalCursorKeyScalar(ck page.CursorKey) graphql.Marshaler {
|
||||
return graphql.WriterFunc(func(w io.Writer) {
|
||||
_, _ = w.Write([]byte(strconv.Quote(ck.String())))
|
||||
})
|
||||
}
|
||||
|
||||
func UnmarshalCursorKeyScalar(v interface{}) (page.CursorKey, error) {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return page.CursorKeyNil, errors.New("must be a string")
|
||||
}
|
||||
|
||||
ck, err := page.ParseCursorKey(s)
|
||||
if err != nil {
|
||||
return page.CursorKeyNil, err
|
||||
}
|
||||
|
||||
return ck, nil
|
||||
}
|
||||
49
pkg/server/api/trust/v1/types/document.go
Normal file
49
pkg/server/api/trust/v1/types/document.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewDocumentConnection(
|
||||
p *page.Page[*coredata.Document, coredata.DocumentOrderField],
|
||||
) *DocumentConnection {
|
||||
edges := make([]*DocumentEdge, len(p.Data))
|
||||
for i, document := range p.Data {
|
||||
edges[i] = NewDocumentEdge(document, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &DocumentConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDocument(d *coredata.Document) *Document {
|
||||
return &Document{
|
||||
ID: d.ID,
|
||||
Title: d.Title,
|
||||
DocumentType: d.DocumentType,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDocumentEdge(d *coredata.Document, orderField coredata.DocumentOrderField) *DocumentEdge {
|
||||
return &DocumentEdge{
|
||||
Node: NewDocument(d),
|
||||
Cursor: d.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
47
pkg/server/api/trust/v1/types/document_version.go
Normal file
47
pkg/server/api/trust/v1/types/document_version.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewDocumentVersionConnection(
|
||||
p *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField],
|
||||
) *DocumentVersionConnection {
|
||||
edges := make([]*DocumentVersionEdge, len(p.Data))
|
||||
for i, documentVersion := range p.Data {
|
||||
edges[i] = NewDocumentVersionEdge(documentVersion, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &DocumentVersionConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
|
||||
func NewDocumentVersion(dv *coredata.DocumentVersion) *DocumentVersion {
|
||||
return &DocumentVersion{
|
||||
ID: dv.ID,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDocumentVersionEdge(dv *coredata.DocumentVersion, orderField coredata.DocumentVersionOrderField) *DocumentVersionEdge {
|
||||
return &DocumentVersionEdge{
|
||||
Node: NewDocumentVersion(dv),
|
||||
Cursor: dv.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
26
pkg/server/api/trust/v1/types/framework.go
Normal file
26
pkg/server/api/trust/v1/types/framework.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewFramework(f *coredata.Framework) *Framework {
|
||||
return &Framework{
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
}
|
||||
}
|
||||
44
pkg/server/api/trust/v1/types/gid.go
Normal file
44
pkg/server/api/trust/v1/types/gid.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
)
|
||||
|
||||
func MarshalGIDScalar(id gid.GID) graphql.Marshaler {
|
||||
return graphql.WriterFunc(func(w io.Writer) {
|
||||
w.Write([]byte(strconv.Quote(id.String())))
|
||||
})
|
||||
}
|
||||
|
||||
func UnmarshalGIDScalar(v interface{}) (gid.GID, error) {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return gid.Nil, errors.New("must be a string")
|
||||
}
|
||||
|
||||
id, err := gid.ParseGID(s)
|
||||
if err != nil {
|
||||
return gid.Nil, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
26
pkg/server/api/trust/v1/types/organization.go
Normal file
26
pkg/server/api/trust/v1/types/organization.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewOrganization(o *coredata.Organization) *Organization {
|
||||
return &Organization{
|
||||
ID: o.ID,
|
||||
Name: o.Name,
|
||||
}
|
||||
}
|
||||
39
pkg/server/api/trust/v1/types/pageinfo.go
Normal file
39
pkg/server/api/trust/v1/types/pageinfo.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"go.gearno.de/x/ref"
|
||||
)
|
||||
|
||||
func NewPageInfo[T page.Paginable[O], O page.OrderField](p *page.Page[T, O]) *PageInfo {
|
||||
var (
|
||||
startCursor *page.CursorKey
|
||||
endCursor *page.CursorKey
|
||||
)
|
||||
|
||||
if len(p.Data) > 0 {
|
||||
startCursor = ref.Ref(p.First().CursorKey(p.Cursor.OrderBy.Field))
|
||||
endCursor = ref.Ref(p.Last().CursorKey(p.Cursor.OrderBy.Field))
|
||||
}
|
||||
|
||||
return &PageInfo{
|
||||
HasNextPage: p.Info.HasNext,
|
||||
HasPreviousPage: p.Info.HasPrev,
|
||||
StartCursor: startCursor,
|
||||
EndCursor: endCursor,
|
||||
}
|
||||
}
|
||||
26
pkg/server/api/trust/v1/types/report.go
Normal file
26
pkg/server/api/trust/v1/types/report.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewReport(r *coredata.Report) *Report {
|
||||
return &Report{
|
||||
ID: r.ID,
|
||||
Filename: r.Filename,
|
||||
}
|
||||
}
|
||||
27
pkg/server/api/trust/v1/types/trust_center.go
Normal file
27
pkg/server/api/trust/v1/types/trust_center.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
func NewTrustCenter(tc *coredata.TrustCenter) *TrustCenter {
|
||||
return &TrustCenter{
|
||||
ID: tc.ID,
|
||||
Active: tc.Active,
|
||||
Slug: tc.Slug,
|
||||
}
|
||||
}
|
||||
212
pkg/server/api/trust/v1/types/types.go
Normal file
212
pkg/server/api/trust/v1/types/types.go
Normal file
@@ -0,0 +1,212 @@
|
||||
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
type Node interface {
|
||||
IsNode()
|
||||
GetID() gid.GID
|
||||
}
|
||||
|
||||
type Audit struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Framework *Framework `json:"framework"`
|
||||
Report *Report `json:"report,omitempty"`
|
||||
ReportURL *string `json:"reportUrl,omitempty"`
|
||||
}
|
||||
|
||||
func (Audit) IsNode() {}
|
||||
func (this Audit) GetID() gid.GID { return this.ID }
|
||||
|
||||
type AuditConnection struct {
|
||||
Edges []*AuditEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type AuditEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Audit `json:"node"`
|
||||
}
|
||||
|
||||
type Document struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
DocumentType coredata.DocumentType `json:"documentType"`
|
||||
Versions *DocumentVersionConnection `json:"versions"`
|
||||
}
|
||||
|
||||
func (Document) IsNode() {}
|
||||
func (this Document) GetID() gid.GID { return this.ID }
|
||||
|
||||
type DocumentConnection struct {
|
||||
Edges []*DocumentEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type DocumentEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Document `json:"node"`
|
||||
}
|
||||
|
||||
type DocumentVersion struct {
|
||||
ID gid.GID `json:"id"`
|
||||
}
|
||||
|
||||
func (DocumentVersion) IsNode() {}
|
||||
func (this DocumentVersion) GetID() gid.GID { return this.ID }
|
||||
|
||||
type DocumentVersionConnection struct {
|
||||
Edges []*DocumentVersionEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type DocumentVersionEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *DocumentVersion `json:"node"`
|
||||
}
|
||||
|
||||
type ExportDocumentVersionPDFInput struct {
|
||||
DocumentVersionID gid.GID `json:"documentVersionId"`
|
||||
}
|
||||
|
||||
type ExportDocumentVersionPDFPayload struct {
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type Framework struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (Framework) IsNode() {}
|
||||
func (this Framework) GetID() gid.GID { return this.ID }
|
||||
|
||||
type Mutation struct {
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LogoURL *string `json:"logoUrl,omitempty"`
|
||||
}
|
||||
|
||||
func (Organization) IsNode() {}
|
||||
func (this Organization) GetID() gid.GID { return this.ID }
|
||||
|
||||
type PageInfo struct {
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||
StartCursor *page.CursorKey `json:"startCursor,omitempty"`
|
||||
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
}
|
||||
|
||||
type Report struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Filename string `json:"filename"`
|
||||
DownloadURL *string `json:"downloadUrl,omitempty"`
|
||||
}
|
||||
|
||||
func (Report) IsNode() {}
|
||||
func (this Report) GetID() gid.GID { return this.ID }
|
||||
|
||||
type TrustCenter struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Active bool `json:"active"`
|
||||
Slug string `json:"slug"`
|
||||
Organization *Organization `json:"organization"`
|
||||
Documents *DocumentConnection `json:"documents"`
|
||||
Audits *AuditConnection `json:"audits"`
|
||||
Vendors *VendorConnection `json:"vendors"`
|
||||
}
|
||||
|
||||
func (TrustCenter) IsNode() {}
|
||||
func (this TrustCenter) GetID() gid.GID { return this.ID }
|
||||
|
||||
type Vendor struct {
|
||||
ID gid.GID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Category coredata.VendorCategory `json:"category"`
|
||||
WebsiteURL *string `json:"websiteUrl,omitempty"`
|
||||
PrivacyPolicyURL *string `json:"privacyPolicyUrl,omitempty"`
|
||||
}
|
||||
|
||||
func (Vendor) IsNode() {}
|
||||
func (this Vendor) GetID() gid.GID { return this.ID }
|
||||
|
||||
type VendorConnection struct {
|
||||
Edges []*VendorEdge `json:"edges"`
|
||||
PageInfo *PageInfo `json:"pageInfo"`
|
||||
}
|
||||
|
||||
type VendorEdge struct {
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Vendor `json:"node"`
|
||||
}
|
||||
|
||||
type Role string
|
||||
|
||||
const (
|
||||
RoleNone Role = "NONE"
|
||||
RoleUser Role = "USER"
|
||||
)
|
||||
|
||||
var AllRole = []Role{
|
||||
RoleNone,
|
||||
RoleUser,
|
||||
}
|
||||
|
||||
func (e Role) IsValid() bool {
|
||||
switch e {
|
||||
case RoleNone, RoleUser:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Role) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *Role) UnmarshalGQL(v any) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = Role(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid Role", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Role) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
func (e *Role) UnmarshalJSON(b []byte) error {
|
||||
s, err := strconv.Unquote(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return e.UnmarshalGQL(s)
|
||||
}
|
||||
|
||||
func (e Role) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
e.MarshalGQL(&buf)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
51
pkg/server/api/trust/v1/types/vendor.go
Normal file
51
pkg/server/api/trust/v1/types/vendor.go
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
)
|
||||
|
||||
func NewVendorConnection(
|
||||
p *page.Page[*coredata.Vendor, coredata.VendorOrderField],
|
||||
) *VendorConnection {
|
||||
edges := make([]*VendorEdge, len(p.Data))
|
||||
for i, vendor := range p.Data {
|
||||
edges[i] = NewVendorEdge(vendor, p.Cursor.OrderBy.Field)
|
||||
}
|
||||
|
||||
return &VendorConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
|
||||
func NewVendor(v *coredata.Vendor) *Vendor {
|
||||
return &Vendor{
|
||||
ID: v.ID,
|
||||
Name: v.Name,
|
||||
Category: v.Category,
|
||||
WebsiteURL: v.WebsiteURL,
|
||||
PrivacyPolicyURL: v.PrivacyPolicyURL,
|
||||
}
|
||||
}
|
||||
|
||||
func NewVendorEdge(v *coredata.Vendor, orderField coredata.VendorOrderField) *VendorEdge {
|
||||
return &VendorEdge{
|
||||
Node: NewVendor(v),
|
||||
Cursor: v.CursorKey(orderField),
|
||||
}
|
||||
}
|
||||
258
pkg/server/api/trust/v1/v1_resolver.go
Normal file
258
pkg/server/api/trust/v1/v1_resolver.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package trust_v1
|
||||
|
||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||
// will be copied through when generating and any unknown code will be moved to the end.
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.76
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/coredata"
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/server/api/trust/v1/schema"
|
||||
"github.com/getprobo/probo/pkg/server/api/trust/v1/types"
|
||||
)
|
||||
|
||||
// Framework is the resolver for the framework field.
|
||||
func (r *auditResolver) Framework(ctx context.Context, obj *types.Audit) (*types.Framework, error) {
|
||||
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
audit, err := trust.Audits.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
framework, err := trust.Frameworks.Get(ctx, audit.FrameworkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
return types.NewFramework(framework), nil
|
||||
}
|
||||
|
||||
// Report is the resolver for the report field.
|
||||
func (r *auditResolver) Report(ctx context.Context, obj *types.Audit) (*types.Report, error) {
|
||||
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
audit, err := trust.Audits.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
if audit.ReportID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
report, err := trust.Reports.Get(ctx, *audit.ReportID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load report: %w", err)
|
||||
}
|
||||
|
||||
return types.NewReport(report), nil
|
||||
}
|
||||
|
||||
// ReportURL is the resolver for the reportUrl field.
|
||||
func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) {
|
||||
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
audit, err := trust.Audits.Get(ctx, obj.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load audit: %w", err)
|
||||
}
|
||||
|
||||
if audit.ReportID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
url, err := trust.Audits.GenerateReportURL(ctx, obj.ID, 15*time.Minute)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate report URL: %w", err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Versions is the resolver for the versions field.
|
||||
func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentVersionConnection, error) {
|
||||
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.DocumentVersionOrderField]{
|
||||
Field: coredata.DocumentVersionOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
// For the public trust API, only return published versions
|
||||
page, err := trust.Documents.ListVersions(ctx, obj.ID, cursor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list document versions: %w", err)
|
||||
}
|
||||
|
||||
// Filter to only published versions and create edges directly
|
||||
publishedEdges := make([]*types.DocumentVersionEdge, 0)
|
||||
for _, version := range page.Data {
|
||||
if version.Status == coredata.DocumentStatusPublished {
|
||||
edge := &types.DocumentVersionEdge{
|
||||
Cursor: version.CursorKey(pageOrderBy.Field),
|
||||
Node: types.NewDocumentVersion(version),
|
||||
}
|
||||
publishedEdges = append(publishedEdges, edge)
|
||||
}
|
||||
}
|
||||
|
||||
return &types.DocumentVersionConnection{
|
||||
Edges: publishedEdges,
|
||||
PageInfo: types.NewPageInfo(page),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ExportDocumentVersionPDF is the resolver for the exportDocumentVersionPDF field.
|
||||
func (r *mutationResolver) ExportDocumentVersionPDF(ctx context.Context, input types.ExportDocumentVersionPDFInput) (*types.ExportDocumentVersionPDFPayload, error) {
|
||||
trust := r.trustCenterSvc.WithTenant(input.DocumentVersionID.TenantID())
|
||||
|
||||
pdf, err := trust.Documents.ExportPDF(ctx, input.DocumentVersionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot export document version PDF: %w", err)
|
||||
}
|
||||
|
||||
return &types.ExportDocumentVersionPDFPayload{
|
||||
Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(pdf)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LogoURL is the resolver for the logoUrl field.
|
||||
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
|
||||
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
return trust.Organizations.GenerateLogoURL(ctx, obj.ID, 1*time.Hour)
|
||||
}
|
||||
|
||||
// TrustCenterBySlug is the resolver for the trustCenterBySlug field.
|
||||
func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error) {
|
||||
trust := r.trustCenterSvc.WithTenant(gid.NewTenantID())
|
||||
|
||||
trustCenter, err := trust.TrustCenters.GetBySlug(ctx, slug)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !trustCenter.Active {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
result := types.NewTrustCenter(trustCenter)
|
||||
|
||||
orgTrust := r.trustCenterSvc.WithTenant(trustCenter.TenantID)
|
||||
org, err := orgTrust.Organizations.Get(ctx, trustCenter.OrganizationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot get organization: %w", err)
|
||||
}
|
||||
|
||||
result.Organization = types.NewOrganization(org)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DownloadURL is the resolver for the downloadUrl field.
|
||||
func (r *reportResolver) DownloadURL(ctx context.Context, obj *types.Report) (*string, error) {
|
||||
trust := r.TrustService(ctx, obj.ID.TenantID())
|
||||
|
||||
url, err := trust.Reports.GenerateDownloadURL(ctx, obj.ID, 5*time.Minute)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate download URL: %w", err)
|
||||
}
|
||||
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *trustCenterResolver) Organization(ctx context.Context, obj *types.TrustCenter) (*types.Organization, error) {
|
||||
return obj.Organization, nil
|
||||
}
|
||||
|
||||
// Documents is the resolver for the documents field.
|
||||
func (r *trustCenterResolver) Documents(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.DocumentConnection, error) {
|
||||
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.DocumentOrderField]{
|
||||
Field: coredata.DocumentOrderFieldTitle,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
}
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
documentPage, err := trust.Documents.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list public documents: %w", err)
|
||||
}
|
||||
|
||||
return types.NewDocumentConnection(documentPage), nil
|
||||
}
|
||||
|
||||
// Audits is the resolver for the audits field.
|
||||
func (r *trustCenterResolver) Audits(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.AuditConnection, error) {
|
||||
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.AuditOrderField]{
|
||||
Field: coredata.AuditOrderFieldValidFrom,
|
||||
Direction: page.OrderDirectionDesc,
|
||||
}
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
auditPage, err := trust.Audits.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list public audits: %w", err)
|
||||
}
|
||||
|
||||
return types.NewAuditConnection(auditPage), nil
|
||||
}
|
||||
|
||||
// Vendors is the resolver for the vendors field.
|
||||
func (r *trustCenterResolver) Vendors(ctx context.Context, obj *types.TrustCenter, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) {
|
||||
trust := r.trustCenterSvc.WithTenant(obj.Organization.ID.TenantID())
|
||||
|
||||
pageOrderBy := page.OrderBy[coredata.VendorOrderField]{
|
||||
Field: coredata.VendorOrderFieldName,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
}
|
||||
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||
|
||||
vendorPage, err := trust.Vendors.ListForOrganizationId(ctx, obj.Organization.ID, cursor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list public vendors: %w", err)
|
||||
}
|
||||
|
||||
return types.NewVendorConnection(vendorPage), nil
|
||||
}
|
||||
|
||||
// Audit returns schema.AuditResolver implementation.
|
||||
func (r *Resolver) Audit() schema.AuditResolver { return &auditResolver{r} }
|
||||
|
||||
// Document returns schema.DocumentResolver implementation.
|
||||
func (r *Resolver) Document() schema.DocumentResolver { return &documentResolver{r} }
|
||||
|
||||
// Mutation returns schema.MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
// Organization returns schema.OrganizationResolver implementation.
|
||||
func (r *Resolver) Organization() schema.OrganizationResolver { return &organizationResolver{r} }
|
||||
|
||||
// Query returns schema.QueryResolver implementation.
|
||||
func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
// Report returns schema.ReportResolver implementation.
|
||||
func (r *Resolver) Report() schema.ReportResolver { return &reportResolver{r} }
|
||||
|
||||
// TrustCenter returns schema.TrustCenterResolver implementation.
|
||||
func (r *Resolver) TrustCenter() schema.TrustCenterResolver { return &trustCenterResolver{r} }
|
||||
|
||||
type auditResolver struct{ *Resolver }
|
||||
type documentResolver struct{ *Resolver }
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type organizationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
type reportResolver struct{ *Resolver }
|
||||
type trustCenterResolver struct{ *Resolver }
|
||||
Reference in New Issue
Block a user