Update trust center handler

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-10-08 20:33:31 +02:00
parent 5c1531bd2b
commit 65b7d90d61
9 changed files with 418 additions and 85 deletions

View File

@@ -98,11 +98,8 @@ type (
Snapshots *SnapshotService Snapshots *SnapshotService
ContinualImprovements *ContinualImprovementService ContinualImprovements *ContinualImprovementService
ProcessingActivities *ProcessingActivityService ProcessingActivities *ProcessingActivityService
<<<<<<< HEAD
Files *FileService Files *FileService
=======
CustomDomains *CustomDomainService CustomDomains *CustomDomainService
>>>>>>> 087e86b5 (Add service impl)
} }
) )
@@ -213,7 +210,7 @@ func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
acmeService: s.acmeService, acmeService: s.acmeService,
logger: s.logger.Named("custom_domains"), logger: s.logger.Named("custom_domains"),
} }
return tenantService return tenantService
} }
@@ -359,3 +356,29 @@ func (s *Service) LoadOrganizationByDomain(ctx context.Context, domain string) (
return organizationID, err return organizationID, err
} }
type TrustCenterInfo struct {
ID gid.GID
OrganizationID gid.GID
}
func (s *Service) LoadTrustCenterBySlug(ctx context.Context, slug string) (*TrustCenterInfo, error) {
var info TrustCenterInfo
err := s.pg.WithConn(
ctx,
func(conn pg.Conn) error {
var trustCenter coredata.TrustCenter
if err := trustCenter.LoadBySlug(ctx, conn, slug); err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
info.ID = trustCenter.ID
info.OrganizationID = trustCenter.OrganizationID
return nil
},
)
return &info, err
}

View File

@@ -392,7 +392,7 @@ func (impl *Implm) Run(
defer stopTrustCenterServer() defer stopTrustCenterServer()
wg.Go( wg.Go(
func() { func() {
if err := impl.runTrustCenterServer(trustCenterServerCtx, l, r, tp, pgClient, serverHandler, acmeService); err != nil { if err := impl.runTrustCenterServer(trustCenterServerCtx, l, r, tp, pgClient, serverHandler.TrustCenterHandler(), acmeService); err != nil {
cancel(fmt.Errorf("trust center server crashed: %w", err)) cancel(fmt.Errorf("trust center server crashed: %w", err))
} }
}, },

View File

@@ -53,20 +53,21 @@ type (
} }
Config struct { Config struct {
AllowedOrigins []string AllowedOrigins []string
Probo *probo.Service Probo *probo.Service
Usrmgr *usrmgr.Service Usrmgr *usrmgr.Service
Trust *trust.Service Trust *trust.Service
Auth ConsoleAuthConfig Auth ConsoleAuthConfig
TrustAuth TrustAuthConfig TrustAuth TrustAuthConfig
ConnectorRegistry *connector.ConnectorRegistry ConnectorRegistry *connector.ConnectorRegistry
SafeRedirect *saferedirect.SafeRedirect SafeRedirect *saferedirect.SafeRedirect
CustomDomainCname string CustomDomainCname string
Logger *log.Logger Logger *log.Logger
} }
Server struct { Server struct {
cfg Config cfg Config
trustAPIHandler http.Handler
} }
) )
@@ -108,11 +109,39 @@ func NewServer(cfg Config) (*Server, error) {
return nil, ErrMissingUsrmgrService return nil, ErrMissingUsrmgrService
} }
// Create trust API handler once
trustAPIHandler := trust_v1.NewMux(
cfg.Logger.Named("trust.v1"),
cfg.Usrmgr,
cfg.Trust,
console_v1.AuthConfig{
CookieName: cfg.Auth.CookieName,
CookieDomain: cfg.Auth.CookieDomain,
SessionDuration: cfg.Auth.SessionDuration,
CookieSecret: cfg.Auth.CookieSecret,
},
trust_v1.TrustAuthConfig{
CookieName: cfg.TrustAuth.CookieName,
CookieDomain: cfg.TrustAuth.CookieDomain,
CookieDuration: cfg.TrustAuth.CookieDuration,
TokenDuration: cfg.TrustAuth.TokenDuration,
ReportURLDuration: cfg.TrustAuth.ReportURLDuration,
TokenSecret: cfg.TrustAuth.TokenSecret,
Scope: cfg.TrustAuth.Scope,
TokenType: cfg.TrustAuth.TokenType,
},
)
return &Server{ return &Server{
cfg: cfg, cfg: cfg,
trustAPIHandler: trustAPIHandler,
}, nil }, nil
} }
func (s *Server) TrustAPIHandler() http.Handler {
return s.trustAPIHandler
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
corsOpts := cors.Options{ corsOpts := cors.Options{
AllowedOrigins: s.cfg.AllowedOrigins, AllowedOrigins: s.cfg.AllowedOrigins,
@@ -160,30 +189,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
) )
// Mount the trust API with authentication // Mount the trust API with authentication
router.Mount( router.Mount("/trust/v1", s.trustAPIHandler)
"/trust/v1",
trust_v1.NewMux(
s.cfg.Logger.Named("trust.v1"),
s.cfg.Usrmgr,
s.cfg.Trust,
console_v1.AuthConfig{
CookieName: s.cfg.Auth.CookieName,
CookieDomain: s.cfg.Auth.CookieDomain,
SessionDuration: s.cfg.Auth.SessionDuration,
CookieSecret: s.cfg.Auth.CookieSecret,
},
trust_v1.TrustAuthConfig{
CookieName: s.cfg.TrustAuth.CookieName,
CookieDomain: s.cfg.TrustAuth.CookieDomain,
CookieDuration: s.cfg.TrustAuth.CookieDuration,
TokenDuration: s.cfg.TrustAuth.TokenDuration,
ReportURLDuration: s.cfg.TrustAuth.ReportURLDuration,
TokenSecret: s.cfg.TrustAuth.TokenSecret,
Scope: s.cfg.TrustAuth.Scope,
TokenType: s.cfg.TrustAuth.TokenType,
},
),
)
router.ServeHTTP(w, r) router.ServeHTTP(w, r)
} }

View File

@@ -71,7 +71,6 @@ type DocumentEdge {
node: Document! node: Document!
} }
type Framework implements Node { type Framework implements Node {
id: ID! id: ID!
name: String! name: String!
@@ -572,20 +571,18 @@ type AcceptNonDisclosureAgreementPayload {
type Query { type Query {
node(id: ID!): Node! node(id: ID!): Node!
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE) trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
currentTrustCenter: TrustCenter @mustBeAuthenticated(role: NONE)
} }
type Mutation { type Mutation {
requestAllAccesses( requestAllAccesses(input: RequestAllAccessesInput!): RequestAccessesPayload!
input: RequestAllAccessesInput! @mustBeAuthenticated(role: NONE)
): RequestAccessesPayload! @mustBeAuthenticated(role: NONE)
exportDocumentPDF( exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload!
input: ExportDocumentPDFInput! @mustBeAuthenticated(role: NONE)
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: NONE)
exportReportPDF( exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload!
input: ExportReportPDFInput! @mustBeAuthenticated(role: NONE)
): ExportReportPDFPayload! @mustBeAuthenticated(role: NONE)
acceptNonDisclosureAgreement( acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput! input: AcceptNonDisclosureAgreementInput!

View File

@@ -137,8 +137,9 @@ type ComplexityRoot struct {
} }
Query struct { Query struct {
Node func(childComplexity int, id gid.GID) int CurrentTrustCenter func(childComplexity int) int
TrustCenterBySlug func(childComplexity int, slug string) int Node func(childComplexity int, id gid.GID) int
TrustCenterBySlug func(childComplexity int, slug string) int
} }
Report struct { Report struct {
@@ -235,6 +236,7 @@ type OrganizationResolver interface {
type QueryResolver interface { type QueryResolver interface {
Node(ctx context.Context, id gid.GID) (types.Node, error) Node(ctx context.Context, id gid.GID) (types.Node, error)
TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error) TrustCenterBySlug(ctx context.Context, slug string) (*types.TrustCenter, error)
CurrentTrustCenter(ctx context.Context) (*types.TrustCenter, error)
} }
type ReportResolver interface { type ReportResolver interface {
IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error)
@@ -569,6 +571,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.PageInfo.StartCursor(childComplexity), true return e.complexity.PageInfo.StartCursor(childComplexity), true
case "Query.currentTrustCenter":
if e.complexity.Query.CurrentTrustCenter == nil {
break
}
return e.complexity.Query.CurrentTrustCenter(childComplexity), true
case "Query.node": case "Query.node":
if e.complexity.Query.Node == nil { if e.complexity.Query.Node == nil {
break break
@@ -1084,7 +1093,6 @@ type DocumentEdge {
node: Document! node: Document!
} }
type Framework implements Node { type Framework implements Node {
id: ID! id: ID!
name: String! name: String!
@@ -1585,20 +1593,18 @@ type AcceptNonDisclosureAgreementPayload {
type Query { type Query {
node(id: ID!): Node! node(id: ID!): Node!
trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE) trustCenterBySlug(slug: String!): TrustCenter @mustBeAuthenticated(role: NONE)
currentTrustCenter: TrustCenter @mustBeAuthenticated(role: NONE)
} }
type Mutation { type Mutation {
requestAllAccesses( requestAllAccesses(input: RequestAllAccessesInput!): RequestAccessesPayload!
input: RequestAllAccessesInput! @mustBeAuthenticated(role: NONE)
): RequestAccessesPayload! @mustBeAuthenticated(role: NONE)
exportDocumentPDF( exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload!
input: ExportDocumentPDFInput! @mustBeAuthenticated(role: NONE)
): ExportDocumentPDFPayload! @mustBeAuthenticated(role: NONE)
exportReportPDF( exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload!
input: ExportReportPDFInput! @mustBeAuthenticated(role: NONE)
): ExportReportPDFPayload! @mustBeAuthenticated(role: NONE)
acceptNonDisclosureAgreement( acceptNonDisclosureAgreement(
input: AcceptNonDisclosureAgreementInput! input: AcceptNonDisclosureAgreementInput!
@@ -4391,6 +4397,100 @@ func (ec *executionContext) fieldContext_Query_trustCenterBySlug(ctx context.Con
return fc, nil return fc, nil
} }
func (ec *executionContext) _Query_currentTrustCenter(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query_currentTrustCenter(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
directive0 := func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().CurrentTrustCenter(rctx)
}
directive1 := func(ctx context.Context) (any, error) {
role, err := ec.unmarshalORole2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐRole(ctx, "NONE")
if err != nil {
var zeroVal *types.TrustCenter
return zeroVal, err
}
if ec.directives.MustBeAuthenticated == nil {
var zeroVal *types.TrustCenter
return zeroVal, errors.New("directive mustBeAuthenticated is not implemented")
}
return ec.directives.MustBeAuthenticated(ctx, nil, directive0, role)
}
tmp, err := directive1(rctx)
if err != nil {
return nil, graphql.ErrorOnPath(ctx, err)
}
if tmp == nil {
return nil, nil
}
if data, ok := tmp.(*types.TrustCenter); ok {
return data, nil
}
return nil, fmt.Errorf(`unexpected type %T from directive, should be *github.com/getprobo/probo/pkg/server/api/trust/v1/types.TrustCenter`, tmp)
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*types.TrustCenter)
fc.Result = res
return ec.marshalOTrustCenter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐTrustCenter(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Query_currentTrustCenter(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Query",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_TrustCenter_id(ctx, field)
case "active":
return ec.fieldContext_TrustCenter_active(ctx, field)
case "slug":
return ec.fieldContext_TrustCenter_slug(ctx, field)
case "ndaFileName":
return ec.fieldContext_TrustCenter_ndaFileName(ctx, field)
case "ndaFileUrl":
return ec.fieldContext_TrustCenter_ndaFileUrl(ctx, field)
case "organization":
return ec.fieldContext_TrustCenter_organization(ctx, field)
case "isUserAuthenticated":
return ec.fieldContext_TrustCenter_isUserAuthenticated(ctx, field)
case "hasAcceptedNonDisclosureAgreement":
return ec.fieldContext_TrustCenter_hasAcceptedNonDisclosureAgreement(ctx, field)
case "documents":
return ec.fieldContext_TrustCenter_documents(ctx, field)
case "audits":
return ec.fieldContext_TrustCenter_audits(ctx, field)
case "vendors":
return ec.fieldContext_TrustCenter_vendors(ctx, field)
case "references":
return ec.fieldContext_TrustCenter_references(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type TrustCenter", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query___type(ctx, field) fc, err := ec.fieldContext_Query___type(ctx, field)
if err != nil { if err != nil {
@@ -9559,6 +9659,25 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
} }
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "currentTrustCenter":
field := field
innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_currentTrustCenter(ctx, field)
return res
}
rrm := func(ctx context.Context) graphql.Marshaler {
return ec.OperationContext.RootResolverMiddleware(ctx,
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "__type": case "__type":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {

View File

@@ -21,6 +21,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/probo" "github.com/getprobo/probo/pkg/probo"
console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1" console_v1 "github.com/getprobo/probo/pkg/server/api/console/v1"
"github.com/getprobo/probo/pkg/server/session" "github.com/getprobo/probo/pkg/server/session"
@@ -29,6 +30,21 @@ import (
"go.gearno.de/kit/httpserver" "go.gearno.de/kit/httpserver"
) )
var (
CustomDomainTenantIDKey = &ctxKey{name: "custom_domain_tenant_id"}
CustomDomainOrganizationIDKey = &ctxKey{name: "custom_domain_organization_id"}
)
func GetCustomDomainTenantID(ctx context.Context) (gid.TenantID, bool) {
tenantID, ok := ctx.Value(CustomDomainTenantIDKey).(gid.TenantID)
return tenantID, ok
}
func GetCustomDomainOrganizationID(ctx context.Context) (gid.GID, bool) {
organizationID, ok := ctx.Value(CustomDomainOrganizationIDKey).(gid.GID)
return organizationID, ok
}
type ( type (
AuthTokenRequest struct { AuthTokenRequest struct {
Token string `json:"token"` Token string `json:"token"`

View File

@@ -547,6 +547,45 @@ func (r *queryResolver) TrustCenterBySlug(ctx context.Context, slug string) (*ty
return response, nil return response, nil
} }
// CurrentTrustCenter is the resolver for the currentTrustCenter field.
func (r *queryResolver) CurrentTrustCenter(ctx context.Context) (*types.TrustCenter, error) {
// Get organization and tenant from custom domain context
organizationID, ok := GetCustomDomainOrganizationID(ctx)
if !ok {
return nil, fmt.Errorf("organization not found for custom domain")
}
tenantID, ok := GetCustomDomainTenantID(ctx)
if !ok {
return nil, fmt.Errorf("tenant not found for custom domain")
}
publicTrustService := r.PublicTrustService(ctx, tenantID)
trustCenter, err := publicTrustService.TrustCenters.GetByOrganizationID(ctx, organizationID)
if err != nil {
return nil, fmt.Errorf("cannot load trust center: %w", err)
}
if !trustCenter.Active {
return nil, nil
}
trustCenter, file, err := publicTrustService.TrustCenters.Get(ctx, trustCenter.ID)
if err != nil {
panic(fmt.Errorf("cannot get trust center: %w", err))
}
org, err := publicTrustService.Organizations.Get(ctx, organizationID)
if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err))
}
response := types.NewTrustCenter(trustCenter, file)
response.Organization = types.NewOrganization(org)
return response, nil
}
// IsUserAuthorized is the resolver for the isUserAuthorized field. // IsUserAuthorized is the resolver for the isUserAuthorized field.
func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) { func (r *reportResolver) IsUserAuthorized(ctx context.Context, obj *types.Report) (bool, error) {
publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID()) publicTrustService := r.PublicTrustService(ctx, obj.ID.TenantID())

View File

@@ -16,14 +16,15 @@
package server package server
import ( import (
"context"
"net/http" "net/http"
"strings"
"github.com/getprobo/probo/pkg/agents" "github.com/getprobo/probo/pkg/agents"
"github.com/getprobo/probo/pkg/connector" "github.com/getprobo/probo/pkg/connector"
"github.com/getprobo/probo/pkg/probo" "github.com/getprobo/probo/pkg/probo"
"github.com/getprobo/probo/pkg/saferedirect" "github.com/getprobo/probo/pkg/saferedirect"
"github.com/getprobo/probo/pkg/server/api" "github.com/getprobo/probo/pkg/server/api"
trust_v1 "github.com/getprobo/probo/pkg/server/api/trust/v1"
"github.com/getprobo/probo/pkg/server/trust" "github.com/getprobo/probo/pkg/server/trust"
"github.com/getprobo/probo/pkg/server/web" "github.com/getprobo/probo/pkg/server/web"
trust_pkg "github.com/getprobo/probo/pkg/trust" trust_pkg "github.com/getprobo/probo/pkg/trust"
@@ -55,6 +56,8 @@ type Server struct {
trustServer *trust.Server trustServer *trust.Server
router *chi.Mux router *chi.Mux
extraHeaderFields map[string]string extraHeaderFields map[string]string
proboService *probo.Service
logger *log.Logger
} }
// NewServer creates a new server instance // NewServer creates a new server instance
@@ -98,6 +101,8 @@ func NewServer(cfg Config) (*Server, error) {
trustServer: trustServer, trustServer: trustServer,
router: router, router: router,
extraHeaderFields: cfg.ExtraHeaderFields, extraHeaderFields: cfg.ExtraHeaderFields,
proboService: cfg.Probo,
logger: cfg.Logger,
} }
// Set up routes // Set up routes
@@ -108,36 +113,139 @@ func NewServer(cfg Config) (*Server, error) {
// setupRoutes configures the routing for the server // setupRoutes configures the routing for the server
func (s *Server) setupRoutes() { func (s *Server) setupRoutes() {
// API routes under /api // API routes
s.router.Mount("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s.router.Mount("/api", s.apiServer)
// Strip the /api prefix from the path
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/api")
if r.URL.Path == "" {
r.URL.Path = "/"
}
s.apiServer.ServeHTTP(w, r)
}))
// Trust routes go to the trust SPA // Trust center routes by slug
s.router.Route("/trust", func(r chi.Router) { s.router.Route("/trust/{slug}", func(r chi.Router) {
r.Mount("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { r.Use(s.loadTrustCenterBySlug)
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/trust") r.Mount("/", s.trustCenterRouter())
if req.URL.Path == "" {
req.URL.Path = "/"
}
s.trustServer.ServeHTTP(w, req)
}))
}) })
// All other routes go to the console SPA frontend // Console SPA (catch-all)
s.router.Mount("/", s.webServer) s.router.Mount("/", s.webServer)
} }
// ServeHTTP implements the http.Handler interface // ServeHTTP implements the http.Handler interface
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.setExtraHeaders(w)
s.router.ServeHTTP(w, r)
}
// setExtraHeaders adds configured extra headers to the response
func (s *Server) setExtraHeaders(w http.ResponseWriter) {
for key, value := range s.extraHeaderFields { for key, value := range s.extraHeaderFields {
w.Header().Set(key, value) w.Header().Set(key, value)
} }
}
s.router.ServeHTTP(w, r)
// loadTrustCenterBySlug middleware loads trust center info from slug and adds to context
func (s *Server) loadTrustCenterBySlug(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
slug := chi.URLParam(r, "slug")
s.logger.InfoCtx(ctx, "loading trust center by slug",
log.String("slug", slug),
log.String("path", r.URL.Path),
)
trustCenter, err := s.proboService.LoadTrustCenterBySlug(ctx, slug)
if err != nil {
s.logger.WarnCtx(ctx, "trust center not found",
log.String("slug", slug),
log.Error(err),
)
http.Error(w, "Trust center not found", http.StatusNotFound)
return
}
s.logger.InfoCtx(ctx, "trust center loaded",
log.String("slug", slug),
log.String("trust_center_id", trustCenter.ID.String()),
log.String("organization_id", trustCenter.OrganizationID.String()),
)
ctx = s.addTrustCenterToContext(ctx, trustCenter.ID.TenantID(), trustCenter.OrganizationID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// loadTrustCenterByDomain middleware loads trust center info from custom domain and adds to context
func (s *Server) loadTrustCenterByDomain(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if r.TLS == nil || r.TLS.ServerName == "" {
next.ServeHTTP(w, r)
return
}
domain := r.TLS.ServerName
s.logger.InfoCtx(ctx, "loading organization by custom domain",
log.String("domain", domain),
log.String("path", r.URL.Path),
)
organizationID, err := s.proboService.LoadOrganizationByDomain(ctx, domain)
if err != nil {
s.logger.WarnCtx(ctx, "organization not found for domain",
log.String("domain", domain),
log.Error(err),
)
next.ServeHTTP(w, r)
return
}
s.logger.InfoCtx(ctx, "organization loaded",
log.String("domain", domain),
log.String("organization_id", organizationID.String()),
)
ctx = s.addTrustCenterToContext(ctx, organizationID.TenantID(), organizationID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// addTrustCenterToContext adds trust center identification to context
func (s *Server) addTrustCenterToContext(ctx context.Context, tenantID, organizationID interface{}) context.Context {
ctx = context.WithValue(ctx, trust_v1.CustomDomainTenantIDKey, tenantID)
ctx = context.WithValue(ctx, trust_v1.CustomDomainOrganizationIDKey, organizationID)
return ctx
}
// trustCenterRouter returns a router for trust center content (API + frontend)
func (s *Server) trustCenterRouter() chi.Router {
r := chi.NewRouter()
// Trust API routes
r.Mount("/api/trust/v1", s.apiServer.TrustAPIHandler())
// Trust center frontend (catch-all)
r.Handle("/*", s.trustServer)
return r
}
// TrustCenterHandler returns an HTTP handler for serving trust centers on custom domains
func (s *Server) TrustCenterHandler() http.Handler {
r := chi.NewRouter()
// Set security headers
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
s.setExtraHeaders(w)
next.ServeHTTP(w, r)
})
})
// Load organization by custom domain
r.Use(s.loadTrustCenterByDomain)
// Mount trust center content
r.Mount("/", s.trustCenterRouter())
return r
} }

View File

@@ -89,6 +89,31 @@ func (s TrustCenterService) Get(
return trustCenter, file, nil return trustCenter, file, nil
} }
func (s TrustCenterService) GetByOrganizationID(
ctx context.Context,
organizationID gid.GID,
) (*coredata.TrustCenter, error) {
trustCenter := &coredata.TrustCenter{}
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
err := trustCenter.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID)
if err != nil {
return fmt.Errorf("cannot load trust center: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return trustCenter, nil
}
func (s TrustCenterService) GenerateNDAFileURL( func (s TrustCenterService) GenerateNDAFileURL(
ctx context.Context, ctx context.Context,
trustCenterID gid.GID, trustCenterID gid.GID,