Add role management

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-11-07 09:20:38 +01:00
parent 696adb7d79
commit 21c4b7cd9d
143 changed files with 5976 additions and 2094 deletions

View File

@@ -17,11 +17,11 @@ package api
import (
"errors"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
"github.com/vektah/gqlparser/v2/ast"
"go.gearno.de/kit/httpserver"
"go.gearno.de/kit/log"
"go.probo.inc/probo/pkg/auth"
@@ -92,6 +92,11 @@ var (
ErrMissingAuthzService = errors.New("server configuration requires a valid authz.Service instance")
)
// GetConsoleSchema returns the GraphQL schema for the console API
func GetConsoleSchema() *ast.Schema {
return console_v1.GetSchema()
}
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()

View File

@@ -17,6 +17,10 @@ resolver:
autobind: []
call_argument_directives_with_null: true
directives:
mustBeAuthorized:
skip_runtime: false
models:
ID:
model:

View File

@@ -31,6 +31,7 @@ import (
"github.com/99designs/gqlgen/graphql/handler/transport"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/go-chi/chi/v5"
"github.com/vektah/gqlparser/v2/ast"
"github.com/vektah/gqlparser/v2/gqlerror"
"go.gearno.de/crypto/uuid"
"go.gearno.de/kit/httpserver"
@@ -64,6 +65,7 @@ type (
samlSvc *auth.SAMLService
authCfg AuthConfig
customDomainCname string
schema *ast.Schema
}
ctxKey struct{ name string }
@@ -307,21 +309,32 @@ func NewMux(
return r
}
// GetSchema returns the parsed GraphQL schema for the console API
// This is used by other services like authz to extract permissions from @mustBeAuthorized directives
func GetSchema() *ast.Schema {
execSchema := schema.NewExecutableSchema(schema.Config{})
return execSchema.Schema()
}
func graphqlHandler(logger *log.Logger, proboSvc *probo.Service, authSvc *auth.Service, authzSvc *authz.Service, samlSvc *auth.SAMLService, authCfg AuthConfig, customDomainCname string) http.HandlerFunc {
var mb int64 = 1 << 20
es := schema.NewExecutableSchema(
schema.Config{
Resolvers: &Resolver{
proboSvc: proboSvc,
authSvc: authSvc,
authzSvc: authzSvc,
samlSvc: samlSvc,
authCfg: authCfg,
customDomainCname: customDomainCname,
},
// Parse the schema first to make it available to resolvers
execSchema := schema.NewExecutableSchema(schema.Config{})
cfg := schema.Config{
Resolvers: &Resolver{
proboSvc: proboSvc,
authSvc: authSvc,
authzSvc: authzSvc,
samlSvc: samlSvc,
authCfg: authCfg,
customDomainCname: customDomainCname,
schema: execSchema.Schema(),
},
)
}
es := schema.NewExecutableSchema(cfg)
srv := handler.New(es)
srv.AddTransport(transport.POST{})
srv.AddTransport(
@@ -504,3 +517,14 @@ func validateTenantAccess(ctx context.Context, tenantID gid.TenantID) {
panic(&authz.TenantAccessError{Message: "tenant not found"})
}
}
func (r *Resolver) MustBeAuthorized(ctx context.Context, entityID gid.GID, action authz.Action) {
user := UserFromContext(ctx)
apiKey := UserAPIKeyFromContext(ctx)
authzSvc := r.AuthzService(ctx, entityID.TenantID())
err := authzSvc.Authorize(ctx, user, apiKey, entityID, action)
if err != nil {
panic(err)
}
}

View File

@@ -33,6 +33,14 @@ type PageInfo {
endCursor: CursorKey
}
# Roles
enum Role {
OWNER
ADMIN
VIEWER
FULL
}
# Enums
enum OrderDirection
@goModel(model: "go.probo.inc/probo/pkg/page.OrderDirection") {
@@ -83,13 +91,17 @@ enum InvitationStatus
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired")
}
enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") {
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleOwner")
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleAdmin")
MEMBER @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleMember")
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.RoleViewer")
enum MembershipRole @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipRole") {
OWNER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleOwner")
ADMIN @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleAdmin")
VIEWER @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipRoleViewer")
}
enum APIRole @goModel(model: "go.probo.inc/probo/pkg/coredata.APIRole") {
FULL @goEnum(value: "go.probo.inc/probo/pkg/coredata.APIRoleFull")
}
enum DocumentStatus
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") {
DRAFT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft")
@@ -1662,7 +1674,7 @@ type Membership implements Node {
id: ID!
userID: ID!
organizationID: ID!
role: Role!
role: MembershipRole!
fullName: String!
emailAddress: String!
authMethod: UserAuthMethod! @goField(forceResolver: true)
@@ -1674,7 +1686,7 @@ type Invitation implements Node {
id: ID!
email: String!
fullName: String!
role: Role!
role: MembershipRole!
status: InvitationStatus!
expiresAt: Datetime!
acceptedAt: Datetime
@@ -1731,7 +1743,7 @@ type Vendor implements Node {
): VendorComplianceReportConnection! @goField(forceResolver: true)
businessAssociateAgreement: VendorBusinessAssociateAgreement
@goField(forceResolver: true)
@goField(forceResolver: true)
dataPrivacyAgreement: VendorDataPrivacyAgreement @goField(forceResolver: true)
contacts(
@@ -1855,7 +1867,9 @@ type Framework implements Node {
updatedAt: Datetime!
}
type Control implements Node {
type Control implements Node @goModel(
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.Control"
) {
id: ID!
sectionTitle: String!
name: String!
@@ -2349,7 +2363,7 @@ type TrustCenterReferenceEdge {
node: TrustCenterReference!
}
type TrustCenterFile implements Node {
type TrustCenterFile {
id: ID!
name: String!
category: String!
@@ -2723,8 +2737,7 @@ type Mutation {
# Organization mutations
createOrganization(
input: CreateOrganizationInput!
): CreateOrganizationPayload!
updateOrganization(
): CreateOrganizationPayload! updateOrganization(
input: UpdateOrganizationInput!
): UpdateOrganizationPayload!
updateOrganizationContext(
@@ -2732,218 +2745,145 @@ type Mutation {
): UpdateOrganizationContextPayload!
deleteOrganizationHorizontalLogo(
input: DeleteOrganizationHorizontalLogoInput!
): DeleteOrganizationHorizontalLogoPayload!
deleteOrganization(
): DeleteOrganizationHorizontalLogoPayload! deleteOrganization(
input: DeleteOrganizationInput!
): DeleteOrganizationPayload!
updateTrustCenter(input: UpdateTrustCenterInput!): UpdateTrustCenterPayload!
uploadTrustCenterNDA(
input: UploadTrustCenterNDAInput!
): UploadTrustCenterNDAPayload!
deleteTrustCenterNDA(
input: DeleteTrustCenterNDAInput!
): DeleteTrustCenterNDAPayload!
# Trust Center Access CRUD mutations
createTrustCenterAccess(
input: CreateTrustCenterAccessInput!
): CreateTrustCenterAccessPayload!
updateTrustCenterAccess(
input: UpdateTrustCenterAccessInput!
): UpdateTrustCenterAccessPayload!
deleteTrustCenterAccess(
input: DeleteTrustCenterAccessInput!
): DeleteTrustCenterAccessPayload!
# Trust Center Reference mutations
createTrustCenterReference(
input: CreateTrustCenterReferenceInput!
): CreateTrustCenterReferencePayload!
updateTrustCenterReference(
input: UpdateTrustCenterReferenceInput!
): UpdateTrustCenterReferencePayload!
deleteTrustCenterReference(
input: DeleteTrustCenterReferenceInput!
): DeleteTrustCenterReferencePayload!
# Trust Center File mutations
createTrustCenterFile(
input: CreateTrustCenterFileInput!
): CreateTrustCenterFilePayload!
updateTrustCenterFile(
input: UpdateTrustCenterFileInput!
): UpdateTrustCenterFilePayload!
getTrustCenterFile(
input: GetTrustCenterFileInput!
): GetTrustCenterFilePayload!
deleteTrustCenterFile(
input: DeleteTrustCenterFileInput!
): DeleteTrustCenterFilePayload!
# User mutations
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
inviteUser(input: InviteUserInput!): InviteUserPayload!
acceptInvitation(input: AcceptInvitationInput!): AcceptInvitationPayload!
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload!
removeMember(input: RemoveMemberInput!): RemoveMemberPayload!
deleteInvitation(input: DeleteInvitationInput!): DeleteInvitationPayload! removeMember(input: RemoveMemberInput!): RemoveMemberPayload!
updateMembership(input: UpdateMembershipInput!): UpdateMembershipPayload!
# People mutations
createPeople(input: CreatePeopleInput!): CreatePeoplePayload!
updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload!
deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
createPeople(input: CreatePeopleInput!): CreatePeoplePayload! updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload! deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
# Vendor mutations
createVendor(input: CreateVendorInput!): CreateVendorPayload!
updateVendor(input: UpdateVendorInput!): UpdateVendorPayload!
deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
createVendor(input: CreateVendorInput!): CreateVendorPayload! updateVendor(input: UpdateVendorInput!): UpdateVendorPayload! deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
# Vendor Contact mutations
createVendorContact(
input: CreateVendorContactInput!
): CreateVendorContactPayload!
updateVendorContact(
): CreateVendorContactPayload! updateVendorContact(
input: UpdateVendorContactInput!
): UpdateVendorContactPayload!
deleteVendorContact(
): UpdateVendorContactPayload! deleteVendorContact(
input: DeleteVendorContactInput!
): DeleteVendorContactPayload!
# Vendor Service mutations
createVendorService(
input: CreateVendorServiceInput!
): CreateVendorServicePayload!
updateVendorService(
): CreateVendorServicePayload! updateVendorService(
input: UpdateVendorServiceInput!
): UpdateVendorServicePayload!
deleteVendorService(
): UpdateVendorServicePayload! deleteVendorService(
input: DeleteVendorServiceInput!
): DeleteVendorServicePayload!
# Framework mutations
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload!
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload!
deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload!
generateFrameworkStateOfApplicability(
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload! updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload! deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload! generateFrameworkStateOfApplicability(
input: GenerateFrameworkStateOfApplicabilityInput!
): GenerateFrameworkStateOfApplicabilityPayload!
exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload!
): GenerateFrameworkStateOfApplicabilityPayload! exportFramework(input: ExportFrameworkInput!): ExportFrameworkPayload!
# Control mutations
createControl(input: CreateControlInput!): CreateControlPayload!
updateControl(input: UpdateControlInput!): UpdateControlPayload!
deleteControl(input: DeleteControlInput!): DeleteControlPayload!
createControl(input: CreateControlInput!): CreateControlPayload! updateControl(input: UpdateControlInput!): UpdateControlPayload! deleteControl(input: DeleteControlInput!): DeleteControlPayload!
# Measure mutations
createMeasure(input: CreateMeasureInput!): CreateMeasurePayload!
updateMeasure(input: UpdateMeasureInput!): UpdateMeasurePayload!
importMeasure(input: ImportMeasureInput!): ImportMeasurePayload!
deleteMeasure(input: DeleteMeasureInput!): DeleteMeasurePayload!
createMeasure(input: CreateMeasureInput!): CreateMeasurePayload! updateMeasure(input: UpdateMeasureInput!): UpdateMeasurePayload! importMeasure(input: ImportMeasureInput!): ImportMeasurePayload! deleteMeasure(input: DeleteMeasureInput!): DeleteMeasurePayload!
# Control mutations
createControlMeasureMapping(
input: CreateControlMeasureMappingInput!
): CreateControlMeasureMappingPayload!
createControlDocumentMapping(
): CreateControlMeasureMappingPayload! createControlDocumentMapping(
input: CreateControlDocumentMappingInput!
): CreateControlDocumentMappingPayload!
deleteControlMeasureMapping(
): CreateControlDocumentMappingPayload! deleteControlMeasureMapping(
input: DeleteControlMeasureMappingInput!
): DeleteControlMeasureMappingPayload!
deleteControlDocumentMapping(
): DeleteControlMeasureMappingPayload! deleteControlDocumentMapping(
input: DeleteControlDocumentMappingInput!
): DeleteControlDocumentMappingPayload!
createControlAuditMapping(
): DeleteControlDocumentMappingPayload! createControlAuditMapping(
input: CreateControlAuditMappingInput!
): CreateControlAuditMappingPayload!
deleteControlAuditMapping(
): CreateControlAuditMappingPayload! deleteControlAuditMapping(
input: DeleteControlAuditMappingInput!
): DeleteControlAuditMappingPayload!
createControlSnapshotMapping(
): DeleteControlAuditMappingPayload! createControlSnapshotMapping(
input: CreateControlSnapshotMappingInput!
): CreateControlSnapshotMappingPayload!
deleteControlSnapshotMapping(
): CreateControlSnapshotMappingPayload! deleteControlSnapshotMapping(
input: DeleteControlSnapshotMappingInput!
): DeleteControlSnapshotMappingPayload!
# Task mutations
createTask(input: CreateTaskInput!): CreateTaskPayload!
updateTask(input: UpdateTaskInput!): UpdateTaskPayload!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
assignTask(input: AssignTaskInput!): AssignTaskPayload!
unassignTask(input: UnassignTaskInput!): UnassignTaskPayload!
createTask(input: CreateTaskInput!): CreateTaskPayload! updateTask(input: UpdateTaskInput!): UpdateTaskPayload! deleteTask(input: DeleteTaskInput!): DeleteTaskPayload! assignTask(input: AssignTaskInput!): AssignTaskPayload! unassignTask(input: UnassignTaskInput!): UnassignTaskPayload!
# Risk mutations
createRisk(input: CreateRiskInput!): CreateRiskPayload!
updateRisk(input: UpdateRiskInput!): UpdateRiskPayload!
deleteRisk(input: DeleteRiskInput!): DeleteRiskPayload!
createRiskMeasureMapping(
createRisk(input: CreateRiskInput!): CreateRiskPayload! updateRisk(input: UpdateRiskInput!): UpdateRiskPayload! deleteRisk(input: DeleteRiskInput!): DeleteRiskPayload! createRiskMeasureMapping(
input: CreateRiskMeasureMappingInput!
): CreateRiskMeasureMappingPayload!
deleteRiskMeasureMapping(
): CreateRiskMeasureMappingPayload! deleteRiskMeasureMapping(
input: DeleteRiskMeasureMappingInput!
): DeleteRiskMeasureMappingPayload!
createRiskDocumentMapping(
input: CreateRiskDocumentMappingInput!
): CreateRiskDocumentMappingPayload!
deleteRiskDocumentMapping(
): CreateRiskDocumentMappingPayload! deleteRiskDocumentMapping(
input: DeleteRiskDocumentMappingInput!
): DeleteRiskDocumentMappingPayload!
createRiskObligationMapping(
input: CreateRiskObligationMappingInput!
): CreateRiskObligationMappingPayload!
deleteRiskObligationMapping(
): CreateRiskObligationMappingPayload! deleteRiskObligationMapping(
input: DeleteRiskObligationMappingInput!
): DeleteRiskObligationMappingPayload!
# Evidence mutations
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
uploadMeasureEvidence(
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload! uploadMeasureEvidence(
input: UploadMeasureEvidenceInput!
): UploadMeasureEvidencePayload!
# Vendor Compliance Report mutations
uploadVendorComplianceReport(
input: UploadVendorComplianceReportInput!
): UploadVendorComplianceReportPayload!
deleteVendorComplianceReport(
): UploadVendorComplianceReportPayload! deleteVendorComplianceReport(
input: DeleteVendorComplianceReportInput!
): DeleteVendorComplianceReportPayload!
# Vendor Business Associate Agreement mutations
uploadVendorBusinessAssociateAgreement(
input: UploadVendorBusinessAssociateAgreementInput!
): UploadVendorBusinessAssociateAgreementPayload!
updateVendorBusinessAssociateAgreement(
): UploadVendorBusinessAssociateAgreementPayload! updateVendorBusinessAssociateAgreement(
input: UpdateVendorBusinessAssociateAgreementInput!
): UpdateVendorBusinessAssociateAgreementPayload!
deleteVendorBusinessAssociateAgreement(
): UpdateVendorBusinessAssociateAgreementPayload! deleteVendorBusinessAssociateAgreement(
input: DeleteVendorBusinessAssociateAgreementInput!
): DeleteVendorBusinessAssociateAgreementPayload!
# Vendor Data Privacy Agreement mutations
uploadVendorDataPrivacyAgreement(
input: UploadVendorDataPrivacyAgreementInput!
): UploadVendorDataPrivacyAgreementPayload!
updateVendorDataPrivacyAgreement(
): UploadVendorDataPrivacyAgreementPayload! updateVendorDataPrivacyAgreement(
input: UpdateVendorDataPrivacyAgreementInput!
): UpdateVendorDataPrivacyAgreementPayload!
deleteVendorDataPrivacyAgreement(
): UpdateVendorDataPrivacyAgreementPayload! deleteVendorDataPrivacyAgreement(
input: DeleteVendorDataPrivacyAgreementInput!
): DeleteVendorDataPrivacyAgreementPayload!
# Document mutations
createDocument(input: CreateDocumentInput!): CreateDocumentPayload!
updateDocument(input: UpdateDocumentInput!): UpdateDocumentPayload!
@@ -2954,134 +2894,85 @@ type Mutation {
deleteMeeting(input: DeleteMeetingInput!): DeleteMeetingPayload!
publishDocumentVersion(
input: PublishDocumentVersionInput!
): PublishDocumentVersionPayload!
bulkPublishDocumentVersions(
): PublishDocumentVersionPayload! bulkPublishDocumentVersions(
input: BulkPublishDocumentVersionsInput!
): BulkPublishDocumentVersionsPayload!
bulkDeleteDocuments(
): BulkPublishDocumentVersionsPayload! bulkDeleteDocuments(
input: BulkDeleteDocumentsInput!
): BulkDeleteDocumentsPayload!
bulkExportDocuments(
): BulkDeleteDocumentsPayload! bulkExportDocuments(
input: BulkExportDocumentsInput!
): BulkExportDocumentsPayload!
generateDocumentChangelog(
): BulkExportDocumentsPayload! generateDocumentChangelog(
input: GenerateDocumentChangelogInput!
): GenerateDocumentChangelogPayload!
createDraftDocumentVersion(
): GenerateDocumentChangelogPayload! createDraftDocumentVersion(
input: CreateDraftDocumentVersionInput!
): CreateDraftDocumentVersionPayload!
deleteDraftDocumentVersion(
): CreateDraftDocumentVersionPayload! deleteDraftDocumentVersion(
input: DeleteDraftDocumentVersionInput!
): DeleteDraftDocumentVersionPayload!
updateDocumentVersion(
): DeleteDraftDocumentVersionPayload! updateDocumentVersion(
input: UpdateDocumentVersionInput!
): UpdateDocumentVersionPayload!
requestSignature(input: RequestSignatureInput!): RequestSignaturePayload!
bulkRequestSignatures(
): UpdateDocumentVersionPayload! requestSignature(input: RequestSignatureInput!): RequestSignaturePayload! bulkRequestSignatures(
input: BulkRequestSignaturesInput!
): BulkRequestSignaturesPayload!
sendSigningNotifications(
): BulkRequestSignaturesPayload! sendSigningNotifications(
input: SendSigningNotificationsInput!
): SendSigningNotificationsPayload!
cancelSignatureRequest(
): SendSigningNotificationsPayload! cancelSignatureRequest(
input: CancelSignatureRequestInput!
): CancelSignatureRequestPayload!
exportDocumentVersionPDF(
): CancelSignatureRequestPayload! exportDocumentVersionPDF(
input: ExportDocumentVersionPDFInput!
): ExportDocumentVersionPDFPayload!
createVendorRiskAssessment(
input: CreateVendorRiskAssessmentInput!
): CreateVendorRiskAssessmentPayload!
assessVendor(input: AssessVendorInput!): AssessVendorPayload!
createAsset(input: CreateAssetInput!): CreateAssetPayload!
updateAsset(input: UpdateAssetInput!): UpdateAssetPayload!
deleteAsset(input: DeleteAssetInput!): DeleteAssetPayload!
createDatum(input: CreateDatumInput!): CreateDatumPayload!
updateDatum(input: UpdateDatumInput!): UpdateDatumPayload!
deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload!
createAudit(input: CreateAuditInput!): CreateAuditPayload!
updateAudit(input: UpdateAuditInput!): UpdateAuditPayload!
deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload!
uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload!
deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload!
createAsset(input: CreateAssetInput!): CreateAssetPayload! updateAsset(input: UpdateAssetInput!): UpdateAssetPayload! deleteAsset(input: DeleteAssetInput!): DeleteAssetPayload!
createDatum(input: CreateDatumInput!): CreateDatumPayload! updateDatum(input: UpdateDatumInput!): UpdateDatumPayload! deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload!
createAudit(input: CreateAuditInput!): CreateAuditPayload! updateAudit(input: UpdateAuditInput!): UpdateAuditPayload! deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload! uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload! deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload!
# Nonconformity mutations
createNonconformity(
input: CreateNonconformityInput!
): CreateNonconformityPayload!
updateNonconformity(
): CreateNonconformityPayload! updateNonconformity(
input: UpdateNonconformityInput!
): UpdateNonconformityPayload!
deleteNonconformity(
): UpdateNonconformityPayload! deleteNonconformity(
input: DeleteNonconformityInput!
): DeleteNonconformityPayload!
# Obligation mutations
createObligation(input: CreateObligationInput!): CreateObligationPayload!
updateObligation(input: UpdateObligationInput!): UpdateObligationPayload!
deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload!
createObligation(input: CreateObligationInput!): CreateObligationPayload! updateObligation(input: UpdateObligationInput!): UpdateObligationPayload! deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload!
# Continual Improvement mutations
createContinualImprovement(
input: CreateContinualImprovementInput!
): CreateContinualImprovementPayload!
updateContinualImprovement(
): CreateContinualImprovementPayload! updateContinualImprovement(
input: UpdateContinualImprovementInput!
): UpdateContinualImprovementPayload!
deleteContinualImprovement(
): UpdateContinualImprovementPayload! deleteContinualImprovement(
input: DeleteContinualImprovementInput!
): DeleteContinualImprovementPayload!
# Processing Activity mutations
createProcessingActivity(
input: CreateProcessingActivityInput!
): CreateProcessingActivityPayload!
updateProcessingActivity(
): CreateProcessingActivityPayload! updateProcessingActivity(
input: UpdateProcessingActivityInput!
): UpdateProcessingActivityPayload!
deleteProcessingActivity(
): UpdateProcessingActivityPayload! deleteProcessingActivity(
input: DeleteProcessingActivityInput!
): DeleteProcessingActivityPayload!
# Snapshot mutations
createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload!
deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload!
createSnapshot(input: CreateSnapshotInput!): CreateSnapshotPayload! deleteSnapshot(input: DeleteSnapshotInput!): DeleteSnapshotPayload!
# Custom Domain mutations
createCustomDomain(
input: CreateCustomDomainInput!
): CreateCustomDomainPayload!
deleteCustomDomain(
): CreateCustomDomainPayload! deleteCustomDomain(
input: DeleteCustomDomainInput!
): DeleteCustomDomainPayload!
# SAML Configuration mutations (OWNER/ADMIN only)
# Step 1: Initiate domain verification (creates SAML config with unverified domain)
initiateDomainVerification(
input: InitiateDomainVerificationInput!
): InitiateDomainVerificationPayload!
# Step 2: Verify domain ownership via DNS TXT record
verifyDomain(input: VerifyDomainInput!): VerifyDomainPayload!
# Step 3: Configure SAML (only allowed after domain is verified)
createSAMLConfiguration(
input: CreateSAMLConfigurationInput!
): CreateSAMLConfigurationPayload!
updateSAMLConfiguration(
): CreateSAMLConfigurationPayload! updateSAMLConfiguration(
input: UpdateSAMLConfigurationInput!
): UpdateSAMLConfigurationPayload!
deleteSAMLConfiguration(
): UpdateSAMLConfigurationPayload! deleteSAMLConfiguration(
input: DeleteSAMLConfigurationInput!
): DeleteSAMLConfigurationPayload!
enableSAML(input: EnableSAMLInput!): EnableSAMLPayload!
disableSAML(input: DisableSAMLInput!): DisableSAMLPayload!
}
): DeleteSAMLConfigurationPayload! enableSAML(input: EnableSAMLInput!): EnableSAMLPayload! disableSAML(input: DisableSAMLInput!): DisableSAMLPayload!}
# Input Types
input GenerateFrameworkStateOfApplicabilityInput {
@@ -3629,6 +3520,7 @@ input InviteUserInput {
organizationId: ID!
email: String!
fullName: String!
role: MembershipRole!
createPeople: Boolean!
}
@@ -3645,6 +3537,12 @@ input RemoveMemberInput {
memberId: ID!
}
input UpdateMembershipInput {
organizationId: ID!
memberId: ID!
role: MembershipRole!
}
input CreateControlInput {
frameworkId: ID!
sectionTitle: String!
@@ -4214,6 +4112,10 @@ type RemoveMemberPayload {
deletedMemberId: ID!
}
type UpdateMembershipPayload {
membership: Membership!
}
input VendorRiskAssessmentOrder {
field: VendorRiskAssessmentOrderField!
direction: OrderDirection!
@@ -4264,7 +4166,7 @@ type DeleteMeasurePayload {
deletedMeasureId: ID!
}
type DocumentVersion implements Node {
type DocumentVersion implements Node @goModel(model: "go.probo.inc/probo/pkg/server/api/console/v1/types.DocumentVersion") {
id: ID!
document: Document! @goField(forceResolver: true)
status: DocumentStatus!
@@ -4549,7 +4451,7 @@ type DeleteAssetPayload {
deletedAssetId: ID!
}
type Datum implements Node {
type Datum implements Node @goModel(model: "go.probo.inc/probo/pkg/server/api/console/v1/types.Datum") {
id: ID!
snapshotId: ID
name: String!

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,33 @@
// 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 (
"time"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type Control struct {
ID gid.GID `json:"id"`
OrganizationID gid.GID `json:"-"`
SectionTitle string `json:"sectionTitle"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Status coredata.ControlStatus `json:"status"`
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
Framework *Framework `json:"framework"`
Measures *MeasureConnection `json:"measures"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Snapshots *SnapshotConnection `json:"snapshots"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Control) IsNode() {}
func (c Control) GetID() gid.GID { return c.ID }
type (
ControlOrderBy OrderBy[coredata.ControlOrderField]
@@ -36,42 +44,42 @@ type (
func NewControlConnection(
p *page.Page[*coredata.Control, coredata.ControlOrderField],
parentType any,
resolver any,
parentID gid.GID,
filters *coredata.ControlFilter,
filter *coredata.ControlFilter,
) *ControlConnection {
var edges = make([]*ControlEdge, len(p.Data))
for i := range edges {
edges[i] = NewControlEdge(p.Data[i], p.Cursor.OrderBy.Field)
edges := make([]*ControlEdge, len(p.Data))
for i, control := range p.Data {
edges[i] = NewControlEdge(control, p.Cursor.OrderBy.Field)
}
return &ControlConnection{
Edges: edges,
PageInfo: *NewPageInfo(p),
Resolver: parentType,
Resolver: resolver,
ParentID: parentID,
Filters: filters,
Filters: filter,
}
}
func NewControlEdge(c *coredata.Control, orderBy coredata.ControlOrderField) *ControlEdge {
return &ControlEdge{
Cursor: c.CursorKey(orderBy),
Node: NewControl(c),
}
}
func NewControl(c *coredata.Control) *Control {
func NewControl(control *coredata.Control) *Control {
return &Control{
ID: c.ID,
SectionTitle: c.SectionTitle,
Name: c.Name,
Description: c.Description,
Status: c.Status,
ExclusionJustification: c.ExclusionJustification,
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
ID: control.ID,
OrganizationID: control.OrganizationID,
SectionTitle: control.SectionTitle,
Name: control.Name,
Description: control.Description,
Status: control.Status,
ExclusionJustification: control.ExclusionJustification,
CreatedAt: control.CreatedAt,
UpdatedAt: control.UpdatedAt,
}
}
func NewControlEdge(control *coredata.Control, orderField coredata.ControlOrderField) *ControlEdge {
return &ControlEdge{
Node: NewControl(control),
Cursor: control.CursorKey(orderField),
}
}

View File

@@ -15,11 +15,29 @@
package types
import (
"time"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type Datum struct {
ID gid.GID `json:"id"`
OrganizationID gid.GID `json:"-"`
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
Name string `json:"name"`
DataClassification coredata.DataClassification `json:"dataClassification"`
Owner *People `json:"owner"`
Vendors *VendorConnection `json:"vendors"`
Organization *Organization `json:"organization"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Datum) IsNode() {}
func (this Datum) GetID() gid.GID { return this.ID }
type (
DatumOrderBy OrderBy[coredata.DatumOrderField]
@@ -58,12 +76,12 @@ func NewDataConnection(
func NewDatum(d *coredata.Datum) *Datum {
return &Datum{
ID: d.ID,
OrganizationID: d.OrganizationID,
Name: d.Name,
SnapshotID: d.SnapshotID,
DataClassification: d.DataClassification,
CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt,
Organization: &Organization{ID: d.OrganizationID},
}
}

View File

@@ -15,14 +15,40 @@
package types
import (
"time"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
type (
DocumentVersion struct {
ID gid.GID `json:"id"`
OrganizationID gid.GID `json:"-"`
Document *Document `json:"document"`
Status coredata.DocumentStatus `json:"status"`
Version int `json:"version"`
Content string `json:"content"`
Changelog string `json:"changelog"`
Title string `json:"title"`
Classification coredata.DocumentClassification `json:"classification"`
Owner *People `json:"owner"`
Signatures *DocumentVersionSignatureConnection `json:"signatures"`
PublishedAt *time.Time `json:"publishedAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
DocumentVersionOrderBy OrderBy[coredata.DocumentVersionOrderField]
)
func (DocumentVersion) IsNode() {}
func (d DocumentVersion) GetID() gid.GID {
return d.ID
}
func NewDocumentVersionConnection(page *page.Page[*coredata.DocumentVersion, coredata.DocumentVersionOrderField]) *DocumentVersionConnection {
edges := make([]*DocumentVersionEdge, len(page.Data))
for i, documentVersion := range page.Data {
@@ -55,6 +81,7 @@ func NewDocumentVersionEdge(documentVersion *coredata.DocumentVersion, orderBy c
func NewDocumentVersion(documentVersion *coredata.DocumentVersion) *DocumentVersion {
return &DocumentVersion{
ID: documentVersion.ID,
OrganizationID: documentVersion.OrganizationID,
Version: documentVersion.VersionNumber,
Title: documentVersion.Title,
Content: documentVersion.Content,

View File

@@ -59,6 +59,7 @@ func NewProcessingActivity(par *coredata.ProcessingActivity) *ProcessingActivity
return &ProcessingActivity{
ID: par.ID,
SnapshotID: par.SnapshotID,
SourceID: par.SourceID,
Name: par.Name,
Purpose: par.Purpose,
DataSubjectCategory: par.DataSubjectCategory,

View File

@@ -30,6 +30,7 @@ func NewTrustCenterAccess(tca *coredata.TrustCenterAccess) *TrustCenterAccess {
HasAcceptedNonDisclosureAgreement: tca.HasAcceptedNonDisclosureAgreement,
CreatedAt: tca.CreatedAt,
UpdatedAt: tca.UpdatedAt,
LastTokenExpiresAt: tca.LastTokenExpiresAt,
}
}

View File

@@ -36,6 +36,7 @@ type (
TrustCenterDocumentAccess struct {
ID gid.GID `json:"id"`
OrganizationID gid.GID `json:"-"`
Active bool `json:"active"`
Requested bool `json:"requested"`
CreatedAt time.Time `json:"createdAt"`
@@ -55,6 +56,7 @@ type (
func NewTrustCenterDocumentAccess(tcda *coredata.TrustCenterDocumentAccess) *TrustCenterDocumentAccess {
return &TrustCenterDocumentAccess{
ID: tcda.ID,
OrganizationID: tcda.OrganizationID,
Active: tcda.Active,
Requested: tcda.Requested,
CreatedAt: tcda.CreatedAt,

View File

@@ -3,6 +3,10 @@
package types
import (
"bytes"
"fmt"
"io"
"strconv"
"time"
"github.com/99designs/gqlgen/graphql"
@@ -174,25 +178,6 @@ type ContinualImprovementFilter struct {
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
}
type Control struct {
ID gid.GID `json:"id"`
SectionTitle string `json:"sectionTitle"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Status coredata.ControlStatus `json:"status"`
ExclusionJustification *string `json:"exclusionJustification,omitempty"`
Framework *Framework `json:"framework"`
Measures *MeasureConnection `json:"measures"`
Documents *DocumentConnection `json:"documents"`
Audits *AuditConnection `json:"audits"`
Snapshots *SnapshotConnection `json:"snapshots"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Control) IsNode() {}
func (this Control) GetID() gid.GID { return this.ID }
type ControlEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Control `json:"node"`
@@ -685,21 +670,6 @@ type DNSRecordInstruction struct {
Purpose string `json:"purpose"`
}
type Datum struct {
ID gid.GID `json:"id"`
SnapshotID *gid.GID `json:"snapshotId,omitempty"`
Name string `json:"name"`
DataClassification coredata.DataClassification `json:"dataClassification"`
Owner *People `json:"owner"`
Vendors *VendorConnection `json:"vendors"`
Organization *Organization `json:"organization"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Datum) IsNode() {}
func (this Datum) GetID() gid.GID { return this.ID }
type DatumEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *Datum `json:"node"`
@@ -1087,25 +1057,6 @@ type DocumentFilter struct {
Query *string `json:"query,omitempty"`
}
type DocumentVersion struct {
ID gid.GID `json:"id"`
Document *Document `json:"document"`
Status coredata.DocumentStatus `json:"status"`
Version int `json:"version"`
Content string `json:"content"`
Changelog string `json:"changelog"`
Title string `json:"title"`
Classification coredata.DocumentClassification `json:"classification"`
Owner *People `json:"owner"`
Signatures *DocumentVersionSignatureConnection `json:"signatures"`
PublishedAt *time.Time `json:"publishedAt,omitempty"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (DocumentVersion) IsNode() {}
func (this DocumentVersion) GetID() gid.GID { return this.ID }
type DocumentVersionConnection struct {
Edges []*DocumentVersionEdge `json:"edges"`
PageInfo *PageInfo `json:"pageInfo"`
@@ -1297,7 +1248,7 @@ type Invitation struct {
ID gid.GID `json:"id"`
Email string `json:"email"`
FullName string `json:"fullName"`
Role coredata.Role `json:"role"`
Role coredata.MembershipRole `json:"role"`
Status coredata.InvitationStatus `json:"status"`
ExpiresAt time.Time `json:"expiresAt"`
AcceptedAt *time.Time `json:"acceptedAt,omitempty"`
@@ -1323,10 +1274,11 @@ type InvitationOrder struct {
}
type InviteUserInput struct {
OrganizationID gid.GID `json:"organizationId"`
Email string `json:"email"`
FullName string `json:"fullName"`
CreatePeople bool `json:"createPeople"`
OrganizationID gid.GID `json:"organizationId"`
Email string `json:"email"`
FullName string `json:"fullName"`
Role coredata.MembershipRole `json:"role"`
CreatePeople bool `json:"createPeople"`
}
type InviteUserPayload struct {
@@ -1383,7 +1335,7 @@ type Membership struct {
ID gid.GID `json:"id"`
UserID gid.GID `json:"userID"`
OrganizationID gid.GID `json:"organizationID"`
Role coredata.Role `json:"role"`
Role coredata.MembershipRole `json:"role"`
FullName string `json:"fullName"`
EmailAddress string `json:"emailAddress"`
AuthMethod coredata.UserAuthMethod `json:"authMethod"`
@@ -1855,9 +1807,6 @@ type TrustCenterFile struct {
Organization *Organization `json:"organization"`
}
func (TrustCenterFile) IsNode() {}
func (this TrustCenterFile) GetID() gid.GID { return this.ID }
type TrustCenterFileEdge struct {
Cursor page.CursorKey `json:"cursor"`
Node *TrustCenterFile `json:"node"`
@@ -2014,6 +1963,16 @@ type UpdateMeetingPayload struct {
Meeting *Meeting `json:"meeting"`
}
type UpdateMembershipInput struct {
OrganizationID gid.GID `json:"organizationId"`
MemberID gid.GID `json:"memberId"`
Role coredata.MembershipRole `json:"role"`
}
type UpdateMembershipPayload struct {
Membership *Membership `json:"membership"`
}
type UpdateNonconformityInput struct {
ID gid.GID `json:"id"`
ReferenceID *string `json:"referenceId,omitempty"`
@@ -2551,3 +2510,62 @@ type Viewer struct {
User *User `json:"user"`
Organizations *OrganizationConnection `json:"organizations"`
}
type Role string
const (
RoleOwner Role = "OWNER"
RoleAdmin Role = "ADMIN"
RoleViewer Role = "VIEWER"
RoleFull Role = "FULL"
)
var AllRole = []Role{
RoleOwner,
RoleAdmin,
RoleViewer,
RoleFull,
}
func (e Role) IsValid() bool {
switch e {
case RoleOwner, RoleAdmin, RoleViewer, RoleFull:
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
}

File diff suppressed because it is too large Load Diff