Files
probo/pkg/complianceportal/visitor/compliance_page_service.go
Bryan Frimin 7e0d187dcf 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>
2026-07-21 15:44:20 +02:00

714 lines
16 KiB
Go

// Copyright (c) 2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package visitor
import (
"context"
_ "embed"
"fmt"
"io"
"strings"
"text/template"
"go.gearno.de/x/ref"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/page"
)
//go:embed compliance.md.tmpl
var complianceTmplContent string
//go:embed sitemap.xml.tmpl
var sitemapTmplContent string
//go:embed robots.txt.tmpl
var robotsTmplContent string
var complianceTmpl = template.Must(
template.New("compliance").
Funcs(template.FuncMap{
"cell": func(s string) string {
s = strings.ReplaceAll(s, `|`, `\|`)
s = strings.ReplaceAll(s, "\n", " ")
s = strings.ReplaceAll(s, "\r", "")
return s
},
}).
Parse(complianceTmplContent),
)
var sitemapTmpl = template.Must(
template.New("sitemap").Parse(sitemapTmplContent),
)
var robotsTmpl = template.Must(
template.New("robots").Parse(robotsTmplContent),
)
type (
compliancePageData struct {
OrgName string
Description string
Details []compliancePageDetail
Frameworks []compliancePageFramework
Documents []compliancePageDocument
Audits []compliancePageAudit
ThirdParties []compliancePageThirdParty
References []compliancePageReference
CustomLinks []compliancePageCustomLink
}
compliancePageDetail struct {
Label string
Value string
}
compliancePageFramework struct {
Name string
Description string
}
compliancePageDocument struct {
Title string
Type string
}
compliancePageAudit struct {
Name string
Framework string
ValidFrom string
ValidUntil string
}
compliancePageThirdParty struct {
Name string
Category string
Countries string
Website string
}
compliancePageReference struct {
Name string
Description string
Website string
}
compliancePageCustomLink struct {
Name string
URL string
}
)
func (s *Service) RenderCompliancePageMarkdown(
ctx context.Context,
w io.Writer,
compliancePageID gid.GID,
scope coredata.Scoper,
) error {
org, err := s.GetPortalOrganization(ctx, compliancePageID)
if err != nil {
return fmt.Errorf("cannot load organization for compliance page: %w", err)
}
compliancePage, err := s.GetPortalByID(ctx, compliancePageID)
if err != nil {
return fmt.Errorf("cannot load compliance page: %w", err)
}
data := &compliancePageData{
OrgName: org.Name,
}
if compliancePage.Description != nil && *compliancePage.Description != "" {
data.Description = *compliancePage.Description
}
if compliancePage.WebsiteURL != nil && *compliancePage.WebsiteURL != "" {
data.Details = append(data.Details, compliancePageDetail{Label: "Website", Value: *compliancePage.WebsiteURL})
}
if compliancePage.Email != nil && *compliancePage.Email != "" {
data.Details = append(data.Details, compliancePageDetail{Label: "Email", Value: *compliancePage.Email})
}
if compliancePage.HeadquarterAddress != nil && *compliancePage.HeadquarterAddress != "" {
data.Details = append(data.Details, compliancePageDetail{Label: "Headquarters", Value: *compliancePage.HeadquarterAddress})
}
data.Frameworks, err = s.fetchComplianceFrameworks(ctx, scope, compliancePageID)
if err != nil {
return fmt.Errorf("cannot fetch compliance frameworks: %w", err)
}
data.Documents, err = s.fetchDocuments(ctx, scope, org.ID)
if err != nil {
return fmt.Errorf("cannot fetch documents: %w", err)
}
data.Audits, err = s.fetchAudits(ctx, scope, org.ID)
if err != nil {
return fmt.Errorf("cannot fetch audits: %w", err)
}
data.ThirdParties, err = s.fetchThirdParties(ctx, scope, org.ID)
if err != nil {
return fmt.Errorf("cannot fetch thirdParties: %w", err)
}
data.References, err = s.fetchReferences(ctx, scope, compliancePageID)
if err != nil {
return fmt.Errorf("cannot fetch references: %w", err)
}
data.CustomLinks, err = s.fetchCustomLinks(ctx, scope, compliancePageID)
if err != nil {
return fmt.Errorf("cannot fetch external links: %w", err)
}
if err := complianceTmpl.Execute(w, data); err != nil {
return fmt.Errorf("cannot render compliance page markdown: %w", err)
}
return nil
}
type (
sitemapData struct {
BaseURL string
Documents []string
}
robotsData struct {
Indexable bool
BaseURL string
}
)
func (s *Service) RenderSitemap(
ctx context.Context,
w io.Writer,
compliancePageID gid.GID,
scope coredata.Scoper,
baseURL string,
) error {
org, err := s.GetPortalOrganization(ctx, compliancePageID)
if err != nil {
return fmt.Errorf("cannot load organization for sitemap: %w", err)
}
data := &sitemapData{
BaseURL: baseURL,
}
data.Documents, err = s.fetchDocumentIDs(ctx, scope, org.ID)
if err != nil {
return fmt.Errorf("cannot fetch document IDs for sitemap: %w", err)
}
if err := sitemapTmpl.Execute(w, data); err != nil {
return fmt.Errorf("cannot render sitemap: %w", err)
}
return nil
}
func (s *Service) RenderRobotsTxt(
ctx context.Context,
w io.Writer,
searchEngineIndexing coredata.SearchEngineIndexing,
baseURL string,
) error {
data := &robotsData{
Indexable: searchEngineIndexing == coredata.SearchEngineIndexingIndexable,
BaseURL: baseURL,
}
if err := robotsTmpl.Execute(w, data); err != nil {
return fmt.Errorf("cannot render robots.txt: %w", err)
}
return nil
}
func (s *Service) fetchDocumentIDs(
ctx context.Context,
scope coredata.Scoper,
orgID gid.GID,
) ([]string, error) {
seen := make(map[gid.GID]struct{})
var resourceIDs []gid.GID
appendResourceID := func(id gid.GID) {
if _, ok := seen[id]; ok {
return
}
seen[id] = struct{}{}
resourceIDs = append(resourceIDs, id)
}
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.DocumentOrderField]{
Field: coredata.DocumentOrderFieldTitle,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListDocumentsForOrganizationID(ctx, scope, orgID, cursor, nil)
if err != nil {
return nil, fmt.Errorf("cannot list documents: %w", err)
}
for _, doc := range result.Data {
if doc.TrustCenterVisibility == coredata.TrustCenterVisibilityNone {
continue
}
appendResourceID(doc.ID)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.DocumentOrderFieldTitle)
cursorKey = &ck
}
cursorKey = nil
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.TrustCenterFileOrderField]{
Field: coredata.TrustCenterFileOrderFieldCreatedAt,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListPortalFilesForOrganizationID(
ctx,
scope,
orgID,
cursor,
coredata.NewTrustCenterFileFilter(),
)
if err != nil {
return nil, fmt.Errorf("cannot list compliance page files: %w", err)
}
for _, file := range result.Data {
if file.TrustCenterVisibility == coredata.TrustCenterVisibilityNone {
continue
}
appendResourceID(file.ID)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.TrustCenterFileOrderFieldCreatedAt)
cursorKey = &ck
}
cursorKey = nil
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.AuditOrderField]{
Field: coredata.AuditOrderFieldCreatedAt,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListAuditsForOrganizationID(ctx, scope, orgID, cursor, nil)
if err != nil {
return nil, fmt.Errorf("cannot list audits: %w", err)
}
for _, audit := range result.Data {
if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityNone {
continue
}
if audit.ReportFileID == nil {
continue
}
appendResourceID(*audit.ReportFileID)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.AuditOrderFieldCreatedAt)
cursorKey = &ck
}
aliases, err := s.resourceAlias.LoadByResourceIDs(ctx, scope, resourceIDs)
if err != nil {
return nil, fmt.Errorf("cannot load resource aliases: %w", err)
}
paths := make([]string, 0, len(resourceIDs))
for _, resourceID := range resourceIDs {
if alias, ok := aliases[resourceID]; ok {
paths = append(paths, alias)
continue
}
paths = append(paths, resourceID.String())
}
return paths, nil
}
func (s *Service) fetchComplianceFrameworks(
ctx context.Context,
scope coredata.Scoper,
compliancePageID gid.GID,
) ([]compliancePageFramework, error) {
var frameworks []compliancePageFramework
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.ComplianceFrameworkOrderField]{
Field: coredata.ComplianceFrameworkOrderFieldRank,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListComplianceFrameworksByPortalID(ctx, scope, compliancePageID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list compliance frameworks: %w", err)
}
for _, cf := range result.Data {
if cf.Visibility != coredata.ComplianceFrameworkVisibilityPublic {
continue
}
fw, err := s.GetFramework(ctx, scope, cf.FrameworkID)
if err != nil {
return nil, fmt.Errorf("cannot get framework %s: %w", cf.FrameworkID, err)
}
fi := compliancePageFramework{Name: fw.Name}
if fw.Description != nil {
fi.Description = *fw.Description
}
frameworks = append(frameworks, fi)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.ComplianceFrameworkOrderFieldRank)
cursorKey = &ck
}
return frameworks, nil
}
func (s *Service) fetchDocuments(
ctx context.Context,
scope coredata.Scoper,
orgID gid.GID,
) ([]compliancePageDocument, error) {
var docs []compliancePageDocument
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.DocumentOrderField]{
Field: coredata.DocumentOrderFieldTitle,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListDocumentsForOrganizationID(ctx, scope, orgID, cursor, nil)
if err != nil {
return nil, fmt.Errorf("cannot list documents: %w", err)
}
for _, doc := range result.Data {
if doc.TrustCenterVisibility == coredata.TrustCenterVisibilityNone {
continue
}
docs = append(
docs,
compliancePageDocument{
Title: doc.Title,
Type: doc.DocumentType.String(),
},
)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.DocumentOrderFieldTitle)
cursorKey = &ck
}
return docs, nil
}
func (s *Service) fetchAudits(
ctx context.Context,
scope coredata.Scoper,
orgID gid.GID,
) ([]compliancePageAudit, error) {
var audits []compliancePageAudit
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.AuditOrderField]{
Field: coredata.AuditOrderFieldCreatedAt,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListAuditsForOrganizationID(ctx, scope, orgID, cursor, nil)
if err != nil {
return nil, fmt.Errorf("cannot list audits: %w", err)
}
for _, audit := range result.Data {
if audit.TrustCenterVisibility == coredata.TrustCenterVisibilityNone {
continue
}
frameworkName := ""
fw, err := s.GetFramework(ctx, scope, audit.FrameworkID)
if err == nil {
frameworkName = fw.Name
}
ai := compliancePageAudit{
Name: ref.UnrefOrZero(audit.Name),
Framework: frameworkName,
}
if audit.ValidFrom != nil {
ai.ValidFrom = audit.ValidFrom.Format("2006-01-02")
}
if audit.ValidUntil != nil {
ai.ValidUntil = audit.ValidUntil.Format("2006-01-02")
}
audits = append(audits, ai)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.AuditOrderFieldCreatedAt)
cursorKey = &ck
}
return audits, nil
}
func (s *Service) fetchThirdParties(
ctx context.Context,
scope coredata.Scoper,
orgID gid.GID,
) ([]compliancePageThirdParty, error) {
var thirdParties []compliancePageThirdParty
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.ThirdPartyOrderField]{
Field: coredata.ThirdPartyOrderFieldName,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListThirdPartiesForOrganizationID(ctx, scope, orgID, cursor, nil)
if err != nil {
return nil, fmt.Errorf("cannot list thirdParties: %w", err)
}
for _, v := range result.Data {
var countries []string
for _, c := range v.Countries {
countries = append(countries, c.String())
}
thirdParties = append(
thirdParties,
compliancePageThirdParty{
Name: v.Name,
Category: v.Category.String(),
Countries: strings.Join(countries, ", "),
Website: ref.UnrefOrZero(v.WebsiteURL),
},
)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.ThirdPartyOrderFieldName)
cursorKey = &ck
}
return thirdParties, nil
}
func (s *Service) fetchReferences(
ctx context.Context,
scope coredata.Scoper,
compliancePageID gid.GID,
) ([]compliancePageReference, error) {
var refs []compliancePageReference
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.TrustCenterReferenceOrderField]{
Field: coredata.TrustCenterReferenceOrderFieldRank,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListPortalReferencesForPortalID(ctx, scope, compliancePageID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list references: %w", err)
}
for _, r := range result.Data {
ri := compliancePageReference{
Name: r.Name,
Website: r.WebsiteURL,
}
if r.Description != nil {
ri.Description = *r.Description
}
refs = append(refs, ri)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.TrustCenterReferenceOrderFieldRank)
cursorKey = &ck
}
return refs, nil
}
func (s *Service) fetchCustomLinks(
ctx context.Context,
scope coredata.Scoper,
compliancePageID gid.GID,
) ([]compliancePageCustomLink, error) {
var links []compliancePageCustomLink
var cursorKey *page.CursorKey
for {
cursor := page.NewCursor(
page.MaxCursorSize,
cursorKey,
page.Head,
page.OrderBy[coredata.ComplianceCustomLinkOrderField]{
Field: coredata.ComplianceCustomLinkOrderFieldRank,
Direction: page.OrderDirectionAsc,
},
)
result, err := s.ListCustomLinksForPortalID(ctx, scope, compliancePageID, cursor)
if err != nil {
return nil, fmt.Errorf("cannot list custom links: %w", err)
}
for _, l := range result.Data {
links = append(
links,
compliancePageCustomLink{
Name: l.Name,
URL: l.URL,
},
)
}
if !result.Info.HasNext {
break
}
last := result.Data[len(result.Data)-1]
ck := last.CursorKey(coredata.ComplianceCustomLinkOrderFieldRank)
cursorKey = &ck
}
return links, nil
}