Flatten compliance portal package layout
Remove the root complianceportal package and the resolver facade that existed only to break an IAM import cycle. Admin policies, domain URL helpers, and actions live under management; visitor OAuth metadata, brand URLs, and public read paths live under visitor. Drop the duplicate trust API magic-link mutations now that Connect handles portal auth, and stop IAM from owning compliance page email branding. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -83,7 +83,7 @@ func (utcar *UpdateAccessRequest) Validate() error {
|
||||
func (s *Service) ListAccesses(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
cursor *page.Cursor[coredata.TrustCenterAccessOrderField],
|
||||
) (*page.Page[*coredata.TrustCenterAccess, coredata.TrustCenterAccessOrderField], error) {
|
||||
var accesses coredata.TrustCenterAccesses
|
||||
@@ -91,7 +91,7 @@ func (s *Service) ListAccesses(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
return accesses.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor)
|
||||
return accesses.LoadByTrustCenterID(ctx, conn, scope, compliancePageID, cursor)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -244,7 +244,7 @@ func (s *Service) UpdateAccess(
|
||||
access = &coredata.TrustCenterAccess{}
|
||||
|
||||
if err := access.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page access: %w", err)
|
||||
}
|
||||
|
||||
var tcdas coredata.TrustCenterDocumentAccesses
|
||||
@@ -310,11 +310,11 @@ func (s *Service) UpdateAccess(
|
||||
|
||||
trustCenterFiles := &coredata.TrustCenterFiles{}
|
||||
if err := trustCenterFiles.LoadByIDs(ctx, tx, scope, trustCenterFileIDs); err != nil {
|
||||
return fmt.Errorf("cannot load trust center files: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page files: %w", err)
|
||||
}
|
||||
|
||||
if err := tcdas.MergeTrustCenterFileAccesses(ctx, tx, scope, access.OrganizationID, access.ID, fileData); err != nil {
|
||||
return fmt.Errorf("cannot merge trust center file accesses: %w", err)
|
||||
return fmt.Errorf("cannot merge compliance page file accesses: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,11 +358,11 @@ func (s *Service) DeleteAccess(
|
||||
access := &coredata.TrustCenterAccess{}
|
||||
|
||||
if err := access.LoadByID(ctx, tx, scope, trustCenterAccessID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page access: %w", err)
|
||||
}
|
||||
|
||||
if err := access.Delete(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot delete trust center access: %w", err)
|
||||
return fmt.Errorf("cannot delete compliance page access: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -387,7 +387,7 @@ func (s *Service) sendAccessEmail(
|
||||
access.UpdatedAt = now
|
||||
|
||||
if err := access.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center access with expiration: %w", err)
|
||||
return fmt.Errorf("cannot update compliance page access with expiration: %w", err)
|
||||
}
|
||||
|
||||
profile := &coredata.MembershipProfile{}
|
||||
@@ -410,7 +410,7 @@ func (s *Service) sendAccessEmail(
|
||||
|
||||
subject, textBody, htmlBody, err := emailPresenter.RenderTrustCenterAccess(ctx, organization.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot render trust center access email: %w", err)
|
||||
return fmt.Errorf("cannot render compliance page access email: %w", err)
|
||||
}
|
||||
|
||||
accessEmail := coredata.NewEmail(
|
||||
|
||||
81
pkg/complianceportal/management/actions.go
Normal file
81
pkg/complianceportal/management/actions.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@probo.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 management
|
||||
|
||||
const (
|
||||
// Custom domain actions.
|
||||
ActionCustomDomainGet = "compliance-portal:custom-domain:get"
|
||||
ActionCustomDomainCreate = "compliance-portal:custom-domain:create"
|
||||
ActionCustomDomainDelete = "compliance-portal:custom-domain:delete"
|
||||
|
||||
// Compliance portal actions.
|
||||
ActionCompliancePortalGet = "compliance-portal:portal:get"
|
||||
ActionCompliancePortalUpdate = "compliance-portal:portal:update"
|
||||
ActionCompliancePortalGetNda = "compliance-portal:portal:get-nda"
|
||||
ActionCompliancePortalNonDisclosureAgreementUpload = "compliance-portal:portal:upload-nda"
|
||||
ActionCompliancePortalNonDisclosureAgreementDelete = "compliance-portal:portal:delete-nda"
|
||||
|
||||
// Compliance portal access actions.
|
||||
ActionCompliancePortalAccessGet = "compliance-portal:portal-access:get"
|
||||
ActionCompliancePortalAccessList = "compliance-portal:portal-access:list"
|
||||
ActionCompliancePortalAccessCreate = "compliance-portal:portal-access:create"
|
||||
ActionCompliancePortalAccessUpdate = "compliance-portal:portal-access:update"
|
||||
ActionCompliancePortalAccessDelete = "compliance-portal:portal-access:delete"
|
||||
|
||||
// Compliance portal reference actions.
|
||||
ActionCompliancePortalReferenceList = "compliance-portal:portal-reference:list"
|
||||
ActionCompliancePortalReferenceGetLogoUrl = "compliance-portal:portal-reference:get-logo-url"
|
||||
ActionCompliancePortalReferenceCreate = "compliance-portal:portal-reference:create"
|
||||
ActionCompliancePortalReferenceUpdate = "compliance-portal:portal-reference:update"
|
||||
ActionCompliancePortalReferenceDelete = "compliance-portal:portal-reference:delete"
|
||||
|
||||
// Compliance portal file actions.
|
||||
ActionCompliancePortalFileGet = "compliance-portal:portal-file:get"
|
||||
ActionCompliancePortalFileList = "compliance-portal:portal-file:list"
|
||||
ActionCompliancePortalFileGetFileUrl = "compliance-portal:portal-file:get-file-url"
|
||||
ActionCompliancePortalFileUpdate = "compliance-portal:portal-file:update"
|
||||
ActionCompliancePortalFileDelete = "compliance-portal:portal-file:delete"
|
||||
ActionCompliancePortalFileCreate = "compliance-portal:portal-file:create"
|
||||
|
||||
// Compliance portal document access actions.
|
||||
ActionCompliancePortalDocumentAccessList = "compliance-portal:portal-document-access:list"
|
||||
|
||||
// MailingListUpdate actions.
|
||||
ActionMailingListUpdateList = "compliance-portal:mailing-list-update:list"
|
||||
ActionMailingListUpdateCreate = "compliance-portal:mailing-list-update:create"
|
||||
ActionMailingListUpdateUpdate = "compliance-portal:mailing-list-update:update"
|
||||
ActionMailingListUpdateSend = "compliance-portal:mailing-list-update:send"
|
||||
ActionMailingListUpdateDelete = "compliance-portal:mailing-list-update:delete"
|
||||
|
||||
// MailingList actions.
|
||||
ActionMailingListUpdate = "compliance-portal:mailing-list:update"
|
||||
|
||||
// MailingListSubscriber actions.
|
||||
ActionMailingListSubscriberList = "compliance-portal:mailing-list-subscriber:list"
|
||||
ActionMailingListSubscriberCreate = "compliance-portal:mailing-list-subscriber:create"
|
||||
ActionMailingListSubscriberDelete = "compliance-portal:mailing-list-subscriber:delete"
|
||||
|
||||
// ComplianceFramework actions.
|
||||
ActionComplianceFrameworkList = "compliance-portal:compliance-framework:list"
|
||||
ActionComplianceFrameworkCreate = "compliance-portal:compliance-framework:create"
|
||||
ActionComplianceFrameworkDelete = "compliance-portal:compliance-framework:delete"
|
||||
ActionComplianceFrameworkUpdateRank = "compliance-portal:compliance-framework:update-rank"
|
||||
|
||||
// ComplianceCustomLink actions.
|
||||
ActionComplianceCustomLinkList = "compliance-portal:compliance-custom-link:list"
|
||||
ActionComplianceCustomLinkCreate = "compliance-portal:compliance-custom-link:create"
|
||||
ActionComplianceCustomLinkUpdate = "compliance-portal:compliance-custom-link:update"
|
||||
ActionComplianceCustomLinkDelete = "compliance-portal:compliance-custom-link:delete"
|
||||
)
|
||||
@@ -78,7 +78,7 @@ func (r *DeleteCustomLinkRequest) Validate() error {
|
||||
func (s *Service) ListCustomLinks(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
cursor *page.Cursor[coredata.ComplianceCustomLinkOrderField],
|
||||
) (*page.Page[*coredata.ComplianceCustomLink, coredata.ComplianceCustomLinkOrderField], error) {
|
||||
var items coredata.ComplianceCustomLinks
|
||||
@@ -86,7 +86,7 @@ func (s *Service) ListCustomLinks(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
if err := items.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor); err != nil {
|
||||
if err := items.LoadByTrustCenterID(ctx, conn, scope, compliancePageID, cursor); err != nil {
|
||||
return fmt.Errorf("cannot load custom links: %w", err)
|
||||
}
|
||||
|
||||
@@ -117,14 +117,14 @@ func (s *Service) CreateCustomLink(
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, tx pg.Tx) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
item = &coredata.ComplianceCustomLink{
|
||||
ID: id,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
OrganizationID: compliancePage.OrganizationID,
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Name: req.Name,
|
||||
URL: req.URL,
|
||||
|
||||
133
pkg/complianceportal/management/domain.go
Normal file
133
pkg/complianceportal/management/domain.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@probo.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 management
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
)
|
||||
|
||||
func (s *Service) EffectiveDomainForCompliancePage(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope coredata.Scoper,
|
||||
compliancePage *coredata.TrustCenter,
|
||||
) (*coredata.CustomDomain, error) {
|
||||
byID, active, err := loadDomains(ctx, conn, scope, compliancePage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if compliancePage.CustomDomainID != nil {
|
||||
if d := byID[*compliancePage.CustomDomainID]; d != nil && active[d.ID] {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
if compliancePage.DefaultDomainID != nil {
|
||||
if d := byID[*compliancePage.DefaultDomainID]; d != nil && active[d.ID] {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *Service) PublicURLForCompliancePage(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope coredata.Scoper,
|
||||
compliancePage *coredata.TrustCenter,
|
||||
) (string, error) {
|
||||
byID, active, err := loadDomains(ctx, conn, scope, compliancePage)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var host string
|
||||
|
||||
switch {
|
||||
case compliancePage.CustomDomainID != nil && byID[*compliancePage.CustomDomainID] != nil && active[*compliancePage.CustomDomainID]:
|
||||
host = byID[*compliancePage.CustomDomainID].Domain
|
||||
case compliancePage.DefaultDomainID != nil && byID[*compliancePage.DefaultDomainID] != nil:
|
||||
host = byID[*compliancePage.DefaultDomainID].Domain
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
host = compliancePage.Slug + "." + s.baseDomain
|
||||
}
|
||||
|
||||
return "https://" + host, nil
|
||||
}
|
||||
|
||||
func loadDomains(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope coredata.Scoper,
|
||||
compliancePage *coredata.TrustCenter,
|
||||
) (map[gid.GID]*coredata.CustomDomain, map[gid.GID]bool, error) {
|
||||
var ids []gid.GID
|
||||
if compliancePage.CustomDomainID != nil {
|
||||
ids = append(ids, *compliancePage.CustomDomainID)
|
||||
}
|
||||
|
||||
if compliancePage.DefaultDomainID != nil {
|
||||
ids = append(ids, *compliancePage.DefaultDomainID)
|
||||
}
|
||||
|
||||
byID := make(map[gid.GID]*coredata.CustomDomain)
|
||||
active := make(map[gid.GID]bool)
|
||||
|
||||
if len(ids) == 0 {
|
||||
return byID, active, nil
|
||||
}
|
||||
|
||||
var domains coredata.CustomDomains
|
||||
if err := domains.LoadByIDs(ctx, conn, scope, ids); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot load custom domains: %w", err)
|
||||
}
|
||||
|
||||
var certificateIDs []gid.GID
|
||||
domainByCertificate := make(map[gid.GID]gid.GID)
|
||||
|
||||
for _, d := range domains {
|
||||
byID[d.ID] = d
|
||||
if d.CertificateID != nil {
|
||||
certificateIDs = append(certificateIDs, *d.CertificateID)
|
||||
domainByCertificate[*d.CertificateID] = d.ID
|
||||
}
|
||||
}
|
||||
|
||||
if len(certificateIDs) == 0 {
|
||||
return byID, active, nil
|
||||
}
|
||||
|
||||
var certificates coredata.Certificates
|
||||
if err := certificates.LoadByIDs(ctx, conn, scope, certificateIDs); err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot load certificates: %w", err)
|
||||
}
|
||||
|
||||
for _, c := range certificates {
|
||||
if domainID, ok := domainByCertificate[c.ID]; ok {
|
||||
active[domainID] = c.Status == coredata.CertificateStatusActive
|
||||
}
|
||||
}
|
||||
|
||||
return byID, active, nil
|
||||
}
|
||||
@@ -21,27 +21,13 @@ import (
|
||||
"time"
|
||||
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/complianceportal"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/validator"
|
||||
)
|
||||
|
||||
// The compliance portal service owns the relationship between a compliance
|
||||
// page (trust center) and its domains. A page has two slots stored on the
|
||||
// trust center row: a default {slug}.probopage.com domain provided by Probo and
|
||||
// an optional custom domain. It provisions each domain's TLS certificate
|
||||
// through the generic certmanager service within the trust center's transaction
|
||||
// so slot changes stay atomic with the page.
|
||||
|
||||
// ErrCustomDomainSlotTaken is returned when a compliance page already has a
|
||||
// custom domain and another one is added.
|
||||
var ErrCustomDomainSlotTaken = errors.New("compliance page already has a custom domain")
|
||||
|
||||
// AddCustomDomain provisions the compliance page's custom domain. It fails
|
||||
// when the page already has one. The default probopage subdomain, provisioned
|
||||
// at page creation, keeps serving as a fallback while the new certificate
|
||||
// provisions.
|
||||
func (s *Service) AddCustomDomain(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
@@ -60,12 +46,12 @@ func (s *Service) AddCustomDomain(
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, tx pg.Tx) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, tx, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.CustomDomainID != nil {
|
||||
if compliancePage.CustomDomainID != nil {
|
||||
return ErrCustomDomainSlotTaken
|
||||
}
|
||||
|
||||
@@ -76,7 +62,7 @@ func (s *Service) AddCustomDomain(
|
||||
|
||||
customDomain = coredata.NewCustomDomain(
|
||||
scope.GetTenantID(),
|
||||
trustCenter.OrganizationID,
|
||||
compliancePage.OrganizationID,
|
||||
domain,
|
||||
false,
|
||||
)
|
||||
@@ -86,11 +72,11 @@ func (s *Service) AddCustomDomain(
|
||||
return fmt.Errorf("cannot insert custom domain: %w", err)
|
||||
}
|
||||
|
||||
trustCenter.CustomDomainID = &customDomain.ID
|
||||
trustCenter.UpdatedAt = time.Now()
|
||||
compliancePage.CustomDomainID = &customDomain.ID
|
||||
compliancePage.UpdatedAt = time.Now()
|
||||
|
||||
if err := trustCenter.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center: %w", err)
|
||||
if err := compliancePage.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update compliance page: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -103,9 +89,6 @@ func (s *Service) AddCustomDomain(
|
||||
return customDomain, nil
|
||||
}
|
||||
|
||||
// RemoveCustomDomain clears the compliance page's custom domain and deletes
|
||||
// the underlying domain together with its certificate. The default domain
|
||||
// cannot be removed.
|
||||
func (s *Service) RemoveCustomDomain(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
@@ -120,24 +103,24 @@ func (s *Service) RemoveCustomDomain(
|
||||
}
|
||||
|
||||
if domain.Managed {
|
||||
return complianceportal.ErrCustomDomainManaged
|
||||
return ErrCustomDomainManaged
|
||||
}
|
||||
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
err := trustCenter.LoadByDomainID(ctx, tx, customDomainID)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
err := compliancePage.LoadByDomainID(ctx, tx, customDomainID)
|
||||
switch {
|
||||
case err == nil:
|
||||
if trustCenter.CustomDomainID != nil && *trustCenter.CustomDomainID == customDomainID {
|
||||
trustCenter.CustomDomainID = nil
|
||||
trustCenter.UpdatedAt = time.Now()
|
||||
if compliancePage.CustomDomainID != nil && *compliancePage.CustomDomainID == customDomainID {
|
||||
compliancePage.CustomDomainID = nil
|
||||
compliancePage.UpdatedAt = time.Now()
|
||||
|
||||
if err := trustCenter.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center: %w", err)
|
||||
if err := compliancePage.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update compliance page: %w", err)
|
||||
}
|
||||
}
|
||||
case errors.Is(err, coredata.ErrResourceNotFound):
|
||||
default:
|
||||
return fmt.Errorf("cannot load trust center by domain id: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page by domain id: %w", err)
|
||||
}
|
||||
|
||||
if err := domain.Delete(ctx, tx, scope); err != nil {
|
||||
@@ -155,78 +138,37 @@ func (s *Service) RemoveCustomDomain(
|
||||
)
|
||||
}
|
||||
|
||||
// GetCertificate returns the certificate backing a custom domain, or nil when
|
||||
// the domain has no certificate yet.
|
||||
func (s *Service) GetCertificate(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
domain *coredata.CustomDomain,
|
||||
) (*coredata.Certificate, error) {
|
||||
if domain == nil || domain.CertificateID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return s.certManager.Get(ctx, scope, *domain.CertificateID)
|
||||
}
|
||||
|
||||
// GetDefaultDomain returns the compliance page's default probopage subdomain,
|
||||
// or nil when it has not been provisioned yet.
|
||||
func (s *Service) GetDefaultDomain(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
compliancePageID gid.GID,
|
||||
) (*coredata.CustomDomain, error) {
|
||||
return s.domainSlot(ctx, scope, compliancePageID, func(tc *coredata.TrustCenter) *gid.GID {
|
||||
return tc.DefaultDomainID
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// GetCustomDomain returns the compliance page's custom domain, or nil when
|
||||
// none is configured.
|
||||
func (s *Service) GetCustomDomain(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
compliancePageID gid.GID,
|
||||
) (*coredata.CustomDomain, error) {
|
||||
return s.domainSlot(ctx, scope, compliancePageID, func(tc *coredata.TrustCenter) *gid.GID {
|
||||
return tc.CustomDomainID
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *Service) domainSlot(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
compliancePageID gid.GID,
|
||||
slot func(*coredata.TrustCenter) *gid.GID,
|
||||
) (*coredata.CustomDomain, error) {
|
||||
var domain *coredata.CustomDomain
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
domainID := slot(trustCenter)
|
||||
if domainID == nil {
|
||||
if compliancePage.DefaultDomainID == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
loaded := &coredata.CustomDomain{}
|
||||
if err := loaded.LoadByID(ctx, conn, scope, *domainID); err != nil {
|
||||
domain = &coredata.CustomDomain{}
|
||||
if err := domain.LoadByID(ctx, conn, scope, *compliancePage.DefaultDomainID); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
domain = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load custom domain: %w", err)
|
||||
}
|
||||
|
||||
domain = loaded
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
@@ -237,31 +179,34 @@ func (s *Service) domainSlot(
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// EffectiveDomain returns the domain a compliance page is served under: the
|
||||
// custom domain when it has an active certificate, otherwise the default
|
||||
// subdomain when its certificate is active. It returns nil when no serving
|
||||
// domain is available yet.
|
||||
func (s *Service) EffectiveDomain(
|
||||
func (s *Service) GetCustomDomain(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
compliancePageID gid.GID,
|
||||
) (*coredata.CustomDomain, error) {
|
||||
var effective *coredata.CustomDomain
|
||||
var domain *coredata.CustomDomain
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
d, err := complianceportal.EffectiveDomainForTrustCenter(ctx, conn, scope, trustCenter)
|
||||
if err != nil {
|
||||
return err
|
||||
if compliancePage.CustomDomainID == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
effective = d
|
||||
domain = &coredata.CustomDomain{}
|
||||
if err := domain.LoadByID(ctx, conn, scope, *compliancePage.CustomDomainID); err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
domain = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot load custom domain: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -270,26 +215,7 @@ func (s *Service) EffectiveDomain(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return effective, nil
|
||||
}
|
||||
|
||||
// EffectiveCanonicalHost returns the host a compliance page should be served
|
||||
// under, or an empty string when no serving host is available yet.
|
||||
func (s *Service) EffectiveCanonicalHost(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
compliancePageID gid.GID,
|
||||
) (string, error) {
|
||||
domain, err := s.EffectiveDomain(ctx, scope, compliancePageID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if domain == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return domain.Domain, nil
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// PublicURL returns the canonical public URL of a compliance page on its
|
||||
@@ -304,18 +230,17 @@ func (s *Service) PublicURL(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
url, err := complianceportal.PublicURLForTrustCenter(ctx, conn, scope, trustCenter, s.baseDomain)
|
||||
var err error
|
||||
publicURL, err = s.PublicURLForCompliancePage(ctx, conn, scope, compliancePage)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("cannot resolve public url: %w", err)
|
||||
}
|
||||
|
||||
publicURL = url
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
31
pkg/complianceportal/management/errors.go
Normal file
31
pkg/complianceportal/management/errors.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@probo.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 management
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
// ErrCustomDomainNotActive is returned when a domain is set as primary
|
||||
// while its SSL certificate is not yet active.
|
||||
ErrCustomDomainNotActive = errors.New("custom domain SSL certificate is not active")
|
||||
|
||||
// ErrCustomDomainManaged is returned when an operation is attempted on the
|
||||
// managed probopage subdomain that is only allowed on customer domains.
|
||||
ErrCustomDomainManaged = errors.New("managed custom domain cannot be modified")
|
||||
|
||||
// ErrCustomDomainNotFound is returned when no custom domain exists for the
|
||||
// requested resource.
|
||||
ErrCustomDomainNotFound = errors.New("custom domain not found")
|
||||
)
|
||||
@@ -92,7 +92,7 @@ func (s *Service) ListFilesForOrganizationID(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
if err := files.LoadByOrganizationID(ctx, conn, scope, organizationID, cursor, filter); err != nil {
|
||||
return fmt.Errorf("cannot load trust center files: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page files: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -119,7 +119,7 @@ func (s *Service) CountFilesForOrganizationID(
|
||||
|
||||
count, err = (&coredata.TrustCenterFiles{}).CountByOrganizationID(ctx, conn, scope, organizationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count trust center files: %w", err)
|
||||
return fmt.Errorf("cannot count compliance page files: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -144,7 +144,7 @@ func (s *Service) GetFile(
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
file = &coredata.TrustCenterFile{}
|
||||
if err := file.LoadByID(ctx, conn, scope, id); err != nil {
|
||||
return fmt.Errorf("cannot load trust center file: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -210,7 +210,7 @@ func (s *Service) CreateFile(
|
||||
}
|
||||
|
||||
if err := file.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert trust center file: %w", err)
|
||||
return fmt.Errorf("cannot insert compliance page file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -243,7 +243,7 @@ func (s *Service) UpdateFile(
|
||||
file = &coredata.TrustCenterFile{}
|
||||
|
||||
if err := file.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center file: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page file: %w", err)
|
||||
}
|
||||
|
||||
if req.Name != nil {
|
||||
@@ -261,7 +261,7 @@ func (s *Service) UpdateFile(
|
||||
file.UpdatedAt = now
|
||||
|
||||
if err := file.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center file: %w", err)
|
||||
return fmt.Errorf("cannot update compliance page file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -285,11 +285,11 @@ func (s *Service) DeleteFile(
|
||||
file := &coredata.TrustCenterFile{}
|
||||
|
||||
if err := file.LoadByID(ctx, tx, scope, trustCenterFileID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center file: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page file: %w", err)
|
||||
}
|
||||
|
||||
if err := file.Delete(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot delete trust center file: %w", err)
|
||||
return fmt.Errorf("cannot delete compliance page file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -311,7 +311,7 @@ func (s *Service) GenerateFileURL(
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
file := &coredata.TrustCenterFile{}
|
||||
if err := file.LoadByID(ctx, conn, scope, trustCenterFileID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center file: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page file: %w", err)
|
||||
}
|
||||
|
||||
storedFile = &coredata.File{}
|
||||
@@ -405,9 +405,9 @@ func (s *Service) uploadFile(
|
||||
ContentType: new(contentType),
|
||||
CacheControl: new("private, max-age=3600"),
|
||||
Metadata: map[string]string{
|
||||
"type": "trust-center-file",
|
||||
"trust-center-file-id": trustCenterFileID.String(),
|
||||
"organization-id": organizationID.String(),
|
||||
"type": "compliance-page-file",
|
||||
"compliance-page-file-id": trustCenterFileID.String(),
|
||||
"organization-id": organizationID.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -76,7 +76,7 @@ func (r *DeleteFrameworkRequest) Validate() error {
|
||||
func (s *Service) ListFrameworksWithHidden(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
cursor *page.Cursor[coredata.ComplianceFrameworkOrderField],
|
||||
) (*page.Page[*coredata.ComplianceFramework, coredata.ComplianceFrameworkOrderField], error) {
|
||||
var cfs coredata.ComplianceFrameworks
|
||||
@@ -84,7 +84,7 @@ func (s *Service) ListFrameworksWithHidden(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
if err := cfs.LoadWithHiddenByTrustCenterID(ctx, conn, scope, trustCenterID, cursor); err != nil {
|
||||
if err := cfs.LoadWithHiddenByTrustCenterID(ctx, conn, scope, compliancePageID, cursor); err != nil {
|
||||
return fmt.Errorf("cannot load frameworks with hidden: %w", err)
|
||||
}
|
||||
|
||||
@@ -116,9 +116,9 @@ func (s *Service) CreateFramework(
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, tx pg.Tx) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
framework := &coredata.Framework{}
|
||||
@@ -128,7 +128,7 @@ func (s *Service) CreateFramework(
|
||||
|
||||
cf = &coredata.ComplianceFramework{
|
||||
ID: cfID,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
OrganizationID: compliancePage.OrganizationID,
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
FrameworkID: req.FrameworkID,
|
||||
CreatedAt: now,
|
||||
|
||||
93
pkg/complianceportal/management/oauth2_scopes.go
Normal file
93
pkg/complianceportal/management/oauth2_scopes.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.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 management
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
)
|
||||
|
||||
// The compliance-page scope string values are part of the external OAuth2
|
||||
// contract and are kept stable even though the feature is named "compliance
|
||||
// portal" on the Go side.
|
||||
const (
|
||||
ScopeV1CompliancePortalRead coredata.OAuth2Scope = "v1:compliance-page:read"
|
||||
ScopeV1CompliancePortal coredata.OAuth2Scope = "v1:compliance-page"
|
||||
)
|
||||
|
||||
// OAuth2ScopeMappings maps the compliance portal OAuth2 scopes to the actions
|
||||
// they grant.
|
||||
var OAuth2ScopeMappings = map[coredata.OAuth2Scope][]string{
|
||||
ScopeV1CompliancePortalRead: {
|
||||
ActionCompliancePortalGet,
|
||||
ActionCompliancePortalGetNda,
|
||||
ActionCompliancePortalAccessGet,
|
||||
ActionCompliancePortalAccessList,
|
||||
ActionCompliancePortalFileGet,
|
||||
ActionCompliancePortalFileList,
|
||||
ActionCompliancePortalFileGetFileUrl,
|
||||
ActionCompliancePortalReferenceList,
|
||||
ActionCompliancePortalReferenceGetLogoUrl,
|
||||
ActionCompliancePortalDocumentAccessList,
|
||||
ActionMailingListUpdateList,
|
||||
ActionMailingListSubscriberList,
|
||||
ActionComplianceFrameworkList,
|
||||
ActionComplianceCustomLinkList,
|
||||
ActionCustomDomainGet,
|
||||
},
|
||||
ScopeV1CompliancePortal: {
|
||||
ActionCompliancePortalGet,
|
||||
ActionCompliancePortalGetNda,
|
||||
ActionCompliancePortalAccessGet,
|
||||
ActionCompliancePortalAccessList,
|
||||
ActionCompliancePortalFileGet,
|
||||
ActionCompliancePortalFileList,
|
||||
ActionCompliancePortalFileGetFileUrl,
|
||||
ActionCompliancePortalReferenceList,
|
||||
ActionCompliancePortalReferenceGetLogoUrl,
|
||||
ActionCompliancePortalDocumentAccessList,
|
||||
ActionMailingListUpdateList,
|
||||
ActionMailingListSubscriberList,
|
||||
ActionComplianceFrameworkList,
|
||||
ActionComplianceCustomLinkList,
|
||||
ActionCustomDomainGet,
|
||||
ActionCompliancePortalUpdate,
|
||||
ActionCompliancePortalNonDisclosureAgreementUpload,
|
||||
ActionCompliancePortalNonDisclosureAgreementDelete,
|
||||
ActionCompliancePortalAccessCreate,
|
||||
ActionCompliancePortalAccessUpdate,
|
||||
ActionCompliancePortalAccessDelete,
|
||||
ActionCompliancePortalFileUpdate,
|
||||
ActionCompliancePortalFileDelete,
|
||||
ActionCompliancePortalFileCreate,
|
||||
ActionCompliancePortalReferenceCreate,
|
||||
ActionCompliancePortalReferenceUpdate,
|
||||
ActionCompliancePortalReferenceDelete,
|
||||
ActionMailingListUpdateCreate,
|
||||
ActionMailingListUpdateUpdate,
|
||||
ActionMailingListUpdateSend,
|
||||
ActionMailingListUpdateDelete,
|
||||
ActionMailingListUpdate,
|
||||
ActionMailingListSubscriberCreate,
|
||||
ActionMailingListSubscriberDelete,
|
||||
ActionComplianceFrameworkCreate,
|
||||
ActionComplianceFrameworkDelete,
|
||||
ActionComplianceFrameworkUpdateRank,
|
||||
ActionComplianceCustomLinkCreate,
|
||||
ActionComplianceCustomLinkUpdate,
|
||||
ActionComplianceCustomLinkDelete,
|
||||
ActionCustomDomainCreate,
|
||||
ActionCustomDomainDelete,
|
||||
},
|
||||
}
|
||||
65
pkg/complianceportal/management/policies.go
Normal file
65
pkg/complianceportal/management/policies.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@probo.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 management
|
||||
|
||||
import (
|
||||
"go.probo.inc/probo/pkg/iam"
|
||||
"go.probo.inc/probo/pkg/iam/policy"
|
||||
)
|
||||
|
||||
var organizationCondition = policy.Equals("principal.organization_id", "resource.organization_id")
|
||||
|
||||
// FullAccessPolicy grants organization owners and admins complete access to
|
||||
// every compliance portal capability, including custom domains, portal
|
||||
// configuration, access grants, files, references, frameworks, external URLs
|
||||
// and mailing lists.
|
||||
//
|
||||
// The managed probopage subdomain is a system-owned resource and can never be
|
||||
// deleted, so an explicit deny (which takes precedence over any allow) blocks
|
||||
// deletion of managed domains for every role.
|
||||
var FullAccessPolicy = policy.NewPolicy(
|
||||
"compliance-portal:full-access",
|
||||
"Compliance Portal Full Access",
|
||||
policy.Allow("compliance-portal:*").
|
||||
WithSID("compliance-portal-full-access").
|
||||
When(organizationCondition),
|
||||
policy.Deny(ActionCustomDomainDelete).
|
||||
WithSID("custom-domain-managed-no-delete").
|
||||
When(policy.Equals("resource.managed", "true")),
|
||||
).WithDescription("Full compliance portal access for organization owners and admins")
|
||||
|
||||
// ViewerPolicy grants organization viewers read-only access to the compliance
|
||||
// portal.
|
||||
var ViewerPolicy = policy.NewPolicy(
|
||||
"compliance-portal:viewer",
|
||||
"Compliance Portal Viewer",
|
||||
policy.Allow(
|
||||
ActionCustomDomainGet,
|
||||
ActionCompliancePortalGet,
|
||||
ActionCompliancePortalAccessGet, ActionCompliancePortalAccessList,
|
||||
ActionCompliancePortalDocumentAccessList,
|
||||
ActionCompliancePortalFileGet, ActionCompliancePortalFileList, ActionCompliancePortalFileGetFileUrl,
|
||||
ActionCompliancePortalReferenceList, ActionCompliancePortalReferenceGetLogoUrl,
|
||||
ActionComplianceFrameworkList,
|
||||
).WithSID("compliance-portal-read-access").When(organizationCondition),
|
||||
).WithDescription("Read-only compliance portal access for organization viewers")
|
||||
|
||||
// PolicySet returns the PolicySet for the compliance portal service.
|
||||
func PolicySet() *iam.PolicySet {
|
||||
return iam.NewPolicySet().
|
||||
AddRolePolicy("OWNER", FullAccessPolicy).
|
||||
AddRolePolicy("ADMIN", FullAccessPolicy).
|
||||
AddRolePolicy("VIEWER", ViewerPolicy)
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import (
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/packages/emails"
|
||||
"go.probo.inc/probo/pkg/complianceportal"
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/filevalidation"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
@@ -134,26 +133,26 @@ func (req *UpdateBrandRequest) Validate() error {
|
||||
func (s *Service) Get(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
) (*coredata.TrustCenter, error) {
|
||||
var trustCenter *coredata.TrustCenter
|
||||
var compliancePage *coredata.TrustCenter
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
trustCenter = &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, trustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage = &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load trust center: %w", err)
|
||||
return nil, fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
return trustCenter, nil
|
||||
return compliancePage, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetByOrganizationID(
|
||||
@@ -161,14 +160,14 @@ func (s *Service) GetByOrganizationID(
|
||||
scope coredata.Scoper,
|
||||
organizationID gid.GID,
|
||||
) (*coredata.TrustCenter, error) {
|
||||
var trustCenter *coredata.TrustCenter
|
||||
var compliancePage *coredata.TrustCenter
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
trustCenter = &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByOrganizationID(ctx, conn, scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage = &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByOrganizationID(ctx, conn, scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -178,7 +177,7 @@ func (s *Service) GetByOrganizationID(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return trustCenter, nil
|
||||
return compliancePage, nil
|
||||
}
|
||||
|
||||
func (s *Service) Update(
|
||||
@@ -191,40 +190,40 @@ func (s *Service) Update(
|
||||
}
|
||||
|
||||
var (
|
||||
trustCenter *coredata.TrustCenter
|
||||
file *coredata.File
|
||||
compliancePage *coredata.TrustCenter
|
||||
file *coredata.File
|
||||
)
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
trustCenter = &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage = &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
if req.Active != nil {
|
||||
trustCenter.Active = *req.Active
|
||||
compliancePage.Active = *req.Active
|
||||
}
|
||||
|
||||
if req.Slug != nil {
|
||||
trustCenter.Slug = *req.Slug
|
||||
compliancePage.Slug = *req.Slug
|
||||
}
|
||||
|
||||
if req.SearchEngineIndexing != nil {
|
||||
trustCenter.SearchEngineIndexing = *req.SearchEngineIndexing
|
||||
compliancePage.SearchEngineIndexing = *req.SearchEngineIndexing
|
||||
}
|
||||
|
||||
if req.Title != nil {
|
||||
trustCenter.Title = *req.Title
|
||||
compliancePage.Title = *req.Title
|
||||
}
|
||||
|
||||
if req.Description != nil {
|
||||
trustCenter.Description = *req.Description
|
||||
compliancePage.Description = *req.Description
|
||||
}
|
||||
|
||||
if req.WebsiteURL != nil {
|
||||
trustCenter.WebsiteURL = *req.WebsiteURL
|
||||
compliancePage.WebsiteURL = *req.WebsiteURL
|
||||
}
|
||||
|
||||
if req.Email != nil {
|
||||
@@ -234,22 +233,22 @@ func (s *Service) Update(
|
||||
}
|
||||
}
|
||||
|
||||
trustCenter.Email = *req.Email
|
||||
compliancePage.Email = *req.Email
|
||||
}
|
||||
|
||||
if req.HeadquarterAddress != nil {
|
||||
trustCenter.HeadquarterAddress = *req.HeadquarterAddress
|
||||
compliancePage.HeadquarterAddress = *req.HeadquarterAddress
|
||||
}
|
||||
|
||||
trustCenter.UpdatedAt = time.Now()
|
||||
compliancePage.UpdatedAt = time.Now()
|
||||
|
||||
if err := trustCenter.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center: %w", err)
|
||||
if err := compliancePage.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update compliance page: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.NonDisclosureAgreementFileID != nil {
|
||||
if compliancePage.NonDisclosureAgreementFileID != nil {
|
||||
file = &coredata.File{}
|
||||
if err := file.LoadByID(ctx, conn, scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
|
||||
if err := file.LoadByID(ctx, conn, scope, *compliancePage.NonDisclosureAgreementFileID); err != nil {
|
||||
return fmt.Errorf("cannot load file: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -261,7 +260,7 @@ func (s *Service) Update(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return trustCenter, file, nil
|
||||
return compliancePage, file, nil
|
||||
}
|
||||
|
||||
func (s *Service) UploadNDA(
|
||||
@@ -274,20 +273,20 @@ func (s *Service) UploadNDA(
|
||||
}
|
||||
|
||||
var (
|
||||
trustCenter *coredata.TrustCenter
|
||||
file *coredata.File
|
||||
compliancePage *coredata.TrustCenter
|
||||
file *coredata.File
|
||||
)
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
trustCenter = &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage = &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.OrganizationID == gid.Nil {
|
||||
return fmt.Errorf("trust center %s has no organization", req.TrustCenterID)
|
||||
if compliancePage.OrganizationID == gid.Nil {
|
||||
return fmt.Errorf("compliance page %s has no organization", req.TrustCenterID)
|
||||
}
|
||||
|
||||
objectKey, err := uuid.NewV7()
|
||||
@@ -305,7 +304,7 @@ func (s *Service) UploadNDA(
|
||||
|
||||
file = &coredata.File{
|
||||
ID: fileID,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
OrganizationID: compliancePage.OrganizationID,
|
||||
BucketName: s.bucket,
|
||||
MimeType: mimeType,
|
||||
FileName: req.FileName,
|
||||
@@ -320,9 +319,9 @@ func (s *Service) UploadNDA(
|
||||
file,
|
||||
req.File,
|
||||
map[string]string{
|
||||
"type": "trust-center-nda",
|
||||
"trust-center-id": req.TrustCenterID.String(),
|
||||
"organization-id": trustCenter.OrganizationID.String(),
|
||||
"type": "compliance-page-nda",
|
||||
"compliance-page-id": req.TrustCenterID.String(),
|
||||
"organization-id": compliancePage.OrganizationID.String(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -335,11 +334,11 @@ func (s *Service) UploadNDA(
|
||||
return fmt.Errorf("cannot insert file: %w", err)
|
||||
}
|
||||
|
||||
trustCenter.NonDisclosureAgreementFileID = &fileID
|
||||
trustCenter.UpdatedAt = now
|
||||
compliancePage.NonDisclosureAgreementFileID = &fileID
|
||||
compliancePage.UpdatedAt = now
|
||||
|
||||
if err := trustCenter.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center: %w", err)
|
||||
if err := compliancePage.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update compliance page: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -349,29 +348,29 @@ func (s *Service) UploadNDA(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return trustCenter, file, nil
|
||||
return compliancePage, file, nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteNDA(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
) (*coredata.TrustCenter, *coredata.File, error) {
|
||||
var trustCenter *coredata.TrustCenter
|
||||
var compliancePage *coredata.TrustCenter
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
trustCenter = &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, trustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage = &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
trustCenter.NonDisclosureAgreementFileID = nil
|
||||
trustCenter.UpdatedAt = time.Now()
|
||||
compliancePage.NonDisclosureAgreementFileID = nil
|
||||
compliancePage.UpdatedAt = time.Now()
|
||||
|
||||
if err := trustCenter.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center: %w", err)
|
||||
if err := compliancePage.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update compliance page: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -381,7 +380,7 @@ func (s *Service) DeleteNDA(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return trustCenter, nil, nil
|
||||
return compliancePage, nil, nil
|
||||
}
|
||||
|
||||
func (s *Service) UpdateBrand(
|
||||
@@ -394,55 +393,55 @@ func (s *Service) UpdateBrand(
|
||||
}
|
||||
|
||||
var (
|
||||
trustCenter *coredata.TrustCenter
|
||||
ndaFile *coredata.File
|
||||
compliancePage *coredata.TrustCenter
|
||||
ndaFile *coredata.File
|
||||
)
|
||||
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
trustCenter = &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage = &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if req.LogoFile != nil {
|
||||
if *req.LogoFile == nil {
|
||||
trustCenter.LogoFileID = nil
|
||||
compliancePage.LogoFileID = nil
|
||||
} else {
|
||||
file, err := s.uploadBrandFile(ctx, scope, conn, *req.LogoFile, "trust-center-logo", trustCenter)
|
||||
file, err := s.uploadBrandFile(ctx, scope, conn, *req.LogoFile, "compliance-page-logo", compliancePage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload logo file: %w", err)
|
||||
}
|
||||
|
||||
trustCenter.LogoFileID = &file.ID
|
||||
compliancePage.LogoFileID = &file.ID
|
||||
}
|
||||
}
|
||||
|
||||
if req.DarkLogoFile != nil {
|
||||
if *req.DarkLogoFile == nil {
|
||||
trustCenter.DarkLogoFileID = nil
|
||||
compliancePage.DarkLogoFileID = nil
|
||||
} else {
|
||||
file, err := s.uploadBrandFile(ctx, scope, conn, *req.DarkLogoFile, "trust-center-dark-logo", trustCenter)
|
||||
file, err := s.uploadBrandFile(ctx, scope, conn, *req.DarkLogoFile, "compliance-page-dark-logo", compliancePage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload dark logo file: %w", err)
|
||||
}
|
||||
|
||||
trustCenter.DarkLogoFileID = &file.ID
|
||||
compliancePage.DarkLogoFileID = &file.ID
|
||||
}
|
||||
}
|
||||
|
||||
trustCenter.UpdatedAt = now
|
||||
compliancePage.UpdatedAt = now
|
||||
|
||||
if err := trustCenter.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center: %w", err)
|
||||
if err := compliancePage.Update(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot update compliance page: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.NonDisclosureAgreementFileID != nil {
|
||||
if compliancePage.NonDisclosureAgreementFileID != nil {
|
||||
ndaFile = &coredata.File{}
|
||||
if err := ndaFile.LoadByID(ctx, conn, scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
|
||||
if err := ndaFile.LoadByID(ctx, conn, scope, *compliancePage.NonDisclosureAgreementFileID); err != nil {
|
||||
return fmt.Errorf("cannot load nda file: %w", err)
|
||||
}
|
||||
}
|
||||
@@ -454,7 +453,7 @@ func (s *Service) UpdateBrand(
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return trustCenter, ndaFile, nil
|
||||
return compliancePage, ndaFile, nil
|
||||
}
|
||||
|
||||
func (s *Service) uploadBrandFile(
|
||||
@@ -463,7 +462,7 @@ func (s *Service) uploadBrandFile(
|
||||
conn pg.Tx,
|
||||
fileUpload *FileUpload,
|
||||
fileType string,
|
||||
trustCenter *coredata.TrustCenter,
|
||||
compliancePage *coredata.TrustCenter,
|
||||
) (*coredata.File, error) {
|
||||
objectKey, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
@@ -482,9 +481,9 @@ func (s *Service) uploadBrandFile(
|
||||
ContentType: &mimeType,
|
||||
CacheControl: new("max-age=3600, public"),
|
||||
Metadata: map[string]string{
|
||||
"type": fileType,
|
||||
"trust-center-id": trustCenter.ID.String(),
|
||||
"organization-id": trustCenter.OrganizationID.String(),
|
||||
"type": fileType,
|
||||
"compliance-page-id": compliancePage.ID.String(),
|
||||
"organization-id": compliancePage.OrganizationID.String(),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -504,7 +503,7 @@ func (s *Service) uploadBrandFile(
|
||||
|
||||
file := &coredata.File{
|
||||
ID: fileID,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
OrganizationID: compliancePage.OrganizationID,
|
||||
BucketName: s.bucket,
|
||||
MimeType: mimeType,
|
||||
FileName: fileUpload.Filename,
|
||||
@@ -525,26 +524,26 @@ func (s *Service) uploadBrandFile(
|
||||
func (s *Service) GenerateNDAFileURL(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
expiresIn time.Duration,
|
||||
) (*string, error) {
|
||||
var file *coredata.File
|
||||
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, trustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.NonDisclosureAgreementFileID == nil {
|
||||
if compliancePage.NonDisclosureAgreementFileID == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
file = &coredata.File{}
|
||||
if err := file.LoadByID(ctx, conn, scope, *trustCenter.NonDisclosureAgreementFileID); err != nil {
|
||||
if err := file.LoadByID(ctx, conn, scope, *compliancePage.NonDisclosureAgreementFileID); err != nil {
|
||||
return fmt.Errorf("cannot load file: %w", err)
|
||||
}
|
||||
|
||||
@@ -555,7 +554,7 @@ func (s *Service) GenerateNDAFileURL(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if trustCenter.NonDisclosureAgreementFileID == nil {
|
||||
if compliancePage.NonDisclosureAgreementFileID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -691,13 +690,7 @@ func (s *Service) EmailPresenterConfig(
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
publicURL, err := complianceportal.PublicURLForTrustCenter(
|
||||
ctx,
|
||||
conn,
|
||||
scope,
|
||||
compliancePage,
|
||||
s.baseDomain,
|
||||
)
|
||||
publicURL, err := s.PublicURLForCompliancePage(ctx, conn, scope, compliancePage)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot resolve compliance page URL: %w", err)
|
||||
}
|
||||
@@ -736,24 +729,24 @@ func (s *Service) EmailPresenterConfig(
|
||||
func (s *Service) GetMailingList(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
) (*coredata.MailingList, error) {
|
||||
var mailingList *coredata.MailingList
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, conn, scope, trustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
if trustCenter.MailingListID == nil {
|
||||
if compliancePage.MailingListID == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mailingList = &coredata.MailingList{}
|
||||
if err := mailingList.LoadByID(ctx, conn, scope, *trustCenter.MailingListID); err != nil {
|
||||
if err := mailingList.LoadByID(ctx, conn, scope, *compliancePage.MailingListID); err != nil {
|
||||
return fmt.Errorf("cannot load mailing list: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ func (utcrr *UpdateReferenceRequest) Validate() error {
|
||||
func (s *Service) ListReferences(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
cursor *page.Cursor[coredata.TrustCenterReferenceOrderField],
|
||||
) (*page.Page[*coredata.TrustCenterReference, coredata.TrustCenterReferenceOrderField], error) {
|
||||
var references coredata.TrustCenterReferences
|
||||
@@ -90,9 +90,9 @@ func (s *Service) ListReferences(
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
err := references.LoadByTrustCenterID(ctx, conn, scope, trustCenterID, cursor)
|
||||
err := references.LoadByTrustCenterID(ctx, conn, scope, compliancePageID, cursor)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load trust center references: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page references: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -108,7 +108,7 @@ func (s *Service) ListReferences(
|
||||
func (s *Service) CountReferences(
|
||||
ctx context.Context,
|
||||
scope coredata.Scoper,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
@@ -117,9 +117,9 @@ func (s *Service) CountReferences(
|
||||
func(ctx context.Context, conn pg.Querier) (err error) {
|
||||
references := coredata.TrustCenterReferences{}
|
||||
|
||||
count, err = references.CountByTrustCenterID(ctx, conn, scope, trustCenterID)
|
||||
count, err = references.CountByTrustCenterID(ctx, conn, scope, compliancePageID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count trust center references: %w", err)
|
||||
return fmt.Errorf("cannot count compliance page references: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -144,7 +144,7 @@ func (s *Service) GetReference(
|
||||
func(ctx context.Context, conn pg.Querier) error {
|
||||
err := reference.LoadByID(ctx, conn, scope, referenceID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load trust center reference: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -177,9 +177,9 @@ func (s *Service) CreateReference(
|
||||
err := s.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, tx pg.Tx) error {
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, tx, scope, req.TrustCenterID); err != nil {
|
||||
return fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
fileID, s3Key, err := s.uploadReferenceLogoFile(ctx, scope, tx, req.LogoFile, referenceID, req.TrustCenterID, now)
|
||||
@@ -191,7 +191,7 @@ func (s *Service) CreateReference(
|
||||
|
||||
reference = &coredata.TrustCenterReference{
|
||||
ID: referenceID,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
OrganizationID: compliancePage.OrganizationID,
|
||||
TrustCenterID: req.TrustCenterID,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
@@ -202,7 +202,7 @@ func (s *Service) CreateReference(
|
||||
}
|
||||
|
||||
if err := reference.Insert(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert trust center reference: %w", err)
|
||||
return fmt.Errorf("cannot insert compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -239,7 +239,7 @@ func (s *Service) UpdateReference(
|
||||
reference = &coredata.TrustCenterReference{}
|
||||
|
||||
if err := reference.LoadByID(ctx, tx, scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center reference: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
if req.LogoFile != nil {
|
||||
@@ -278,7 +278,7 @@ func (s *Service) UpdateReference(
|
||||
}
|
||||
|
||||
if err := reference.Update(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot update trust center reference: %w", err)
|
||||
return fmt.Errorf("cannot update compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -303,11 +303,11 @@ func (s *Service) DeleteReference(
|
||||
reference := &coredata.TrustCenterReference{}
|
||||
|
||||
if err := reference.LoadByID(ctx, tx, scope, trustCenterReferenceID); err != nil {
|
||||
return fmt.Errorf("cannot load trust center reference: %w", err)
|
||||
return fmt.Errorf("cannot load compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
if err := reference.Delete(ctx, tx, scope); err != nil {
|
||||
return fmt.Errorf("cannot delete trust center reference: %w", err)
|
||||
return fmt.Errorf("cannot delete compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -331,7 +331,7 @@ func (s *Service) GenerateReferenceLogoURL(
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot load trust center reference: %w", err)
|
||||
return "", fmt.Errorf("cannot load compliance page reference: %w", err)
|
||||
}
|
||||
|
||||
file, err := s.fileManager.GetPublicFile(ctx, reference.LogoFileID)
|
||||
@@ -348,7 +348,7 @@ func (s *Service) uploadReferenceLogoFile(
|
||||
tx pg.Tx,
|
||||
file File,
|
||||
referenceID gid.GID,
|
||||
trustCenterID gid.GID,
|
||||
compliancePageID gid.GID,
|
||||
now time.Time,
|
||||
) (gid.GID, string, error) {
|
||||
fileID := gid.New(scope.GetTenantID(), coredata.FileEntityType)
|
||||
@@ -358,9 +358,9 @@ func (s *Service) uploadReferenceLogoFile(
|
||||
return gid.GID{}, "", fmt.Errorf("cannot generate object key: %w", err)
|
||||
}
|
||||
|
||||
trustCenter := &coredata.TrustCenter{}
|
||||
if err := trustCenter.LoadByID(ctx, tx, scope, trustCenterID); err != nil {
|
||||
return gid.GID{}, "", fmt.Errorf("cannot load trust center: %w", err)
|
||||
compliancePage := &coredata.TrustCenter{}
|
||||
if err := compliancePage.LoadByID(ctx, tx, scope, compliancePageID); err != nil {
|
||||
return gid.GID{}, "", fmt.Errorf("cannot load compliance page: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -418,9 +418,9 @@ func (s *Service) uploadReferenceLogoFile(
|
||||
ContentType: new(contentType),
|
||||
CacheControl: new("max-age=3600, public"),
|
||||
Metadata: map[string]string{
|
||||
"type": "trust-center-reference-logo",
|
||||
"trust-center-reference-id": referenceID.String(),
|
||||
"organization-id": trustCenter.OrganizationID.String(),
|
||||
"type": "compliance-page-reference-logo",
|
||||
"compliance-page-reference-id": referenceID.String(),
|
||||
"organization-id": compliancePage.OrganizationID.String(),
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -430,7 +430,7 @@ func (s *Service) uploadReferenceLogoFile(
|
||||
|
||||
fileRecord := &coredata.File{
|
||||
ID: fileID,
|
||||
OrganizationID: trustCenter.OrganizationID,
|
||||
OrganizationID: compliancePage.OrganizationID,
|
||||
BucketName: s.bucket,
|
||||
MimeType: contentType,
|
||||
FileName: filename,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// Package management holds the scoped, admin-facing compliance portal services
|
||||
// (trust center CRUD, domains, frameworks, external URLs, references, files and
|
||||
// (compliance page CRUD, domains, frameworks, external URLs, references, files and
|
||||
// accesses). It is the write side of the compliance portal feature.
|
||||
package management
|
||||
|
||||
@@ -37,7 +37,7 @@ const (
|
||||
|
||||
type (
|
||||
// Service is the admin-facing compliance portal service. It exposes the
|
||||
// scoped CRUD operations for the trust center and its related resources as
|
||||
// scoped CRUD operations for the compliance page and its related resources as
|
||||
// methods on a single type.
|
||||
Service struct {
|
||||
pg *pg.Client
|
||||
|
||||
Reference in New Issue
Block a user