Files
probo/pkg/server/api/complianceportal/v1/mux.go
Bryan Frimin 43ce3a7c53 Harden compliance portal auth and TLS
Align console references and OAuth branding with the
compliance-page model, and fix certificate cache eviction,
portal OAuth handlers, and magic-link edge cases left after
the trust-center rename.

Signed-off-by: Bryan Frimin <bryan@probo.com>
2026-07-21 15:44:22 +02:00

159 lines
4.6 KiB
Go

// 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 complianceportal_v1
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"go.gearno.de/kit/log"
"go.gearno.de/x/ref"
"go.probo.inc/probo/pkg/baseurl"
visitor "go.probo.inc/probo/pkg/complianceportal/visitor"
"go.probo.inc/probo/pkg/esign"
"go.probo.inc/probo/pkg/filemanager"
"go.probo.inc/probo/pkg/iam"
"go.probo.inc/probo/pkg/mailman"
"go.probo.inc/probo/pkg/resourcealias"
"go.probo.inc/probo/pkg/securecookie"
"go.probo.inc/probo/pkg/server"
"go.probo.inc/probo/pkg/server/api/authn"
"go.probo.inc/probo/pkg/server/api/complianceportal"
"go.probo.inc/probo/pkg/server/gqlutils"
)
type MuxConfig struct {
BaseURL *baseurl.BaseURL
ExtraHeaderFields map[string]string
Logger *log.Logger
IAM *iam.Service
Visitor *visitor.Service
ResourceAlias *resourcealias.Service
File *filemanager.Service
ESign *esign.Service
Mailman *mailman.Service
Cookie securecookie.Config
TokenSecret string
GraphQLLimits gqlutils.Limits
}
func NewMux(cfg MuxConfig) (http.Handler, error) {
webServer, err := NewServer(compliancePageHeadData())
if err != nil {
return nil, err
}
r := chi.NewRouter()
r.Use(complianceportal.NewSNIMiddleware(cfg.Visitor))
r.Use(server.NewSecurityHeadersMiddleware(cfg.ExtraHeaderFields))
markdownHandler := complianceportal.NewHandler(cfg.Visitor)
r.Get("/llms.txt", markdownHandler.HandleLLMsTxt)
r.Get("/robots.txt", markdownHandler.HandleRobotsTxt)
r.Get("/sitemap.xml", markdownHandler.HandleSitemap)
allowedHost := func(ctx context.Context, host string) bool {
_, err := cfg.Visitor.GetPortalByDomainName(ctx, host)
return err == nil
}
oauthInitiateHandler := NewOAuthInitiateHandler(
cfg.BaseURL,
cfg.Visitor,
allowedHost,
cfg.Logger,
)
oauthCallbackHandler := NewOAuthCallbackHandler(
cfg.IAM,
cfg.Visitor,
cfg.Cookie,
allowedHost,
cfg.Logger,
)
graphqlHandler := NewGraphQLHandler(
cfg.IAM,
cfg.Visitor,
cfg.ResourceAlias,
cfg.File,
cfg.ESign,
cfg.Mailman,
cfg.Logger,
cfg.BaseURL,
cfg.Cookie,
cfg.TokenSecret,
cfg.GraphQLLimits,
)
r.Group(
func(r chi.Router) {
r.Use(complianceportal.NewCompliancePagePresenceMiddleware())
r.Method(http.MethodGet, complianceportal.CIMDMetadataPath, NewOAuthClientMetadataHandler(cfg.Visitor))
r.Method(http.MethodGet, complianceportal.BrandLogoPath, NewBrandLogoHandler(cfg.Logger, cfg.File))
r.Method(http.MethodGet, complianceportal.BrandDarkLogoPath, NewBrandDarkLogoHandler(cfg.Logger, cfg.File))
r.Method(http.MethodGet, complianceportal.OAuthInitiatePath, oauthInitiateHandler)
r.Method(http.MethodGet, complianceportal.OAuthCallbackPath, oauthCallbackHandler)
r.Group(
func(r chi.Router) {
r.Use(authn.NewSessionMiddleware(cfg.IAM, cfg.Cookie))
r.Use(complianceportal.NewSessionHostMiddleware(cfg.Cookie))
r.Use(complianceportal.NewMemberProvisioningMiddleware(cfg.Visitor, cfg.Logger))
r.Handle(complianceportal.GraphQLPath, graphqlHandler)
},
)
r.Handle("/*", webServer)
},
)
return r, nil
}
func compliancePageHeadData() HeadDataFunc {
return func(r *http.Request) HeadData {
tc := complianceportal.CompliancePageFromContext(r.Context())
if tc == nil {
return HeadData{Title: "Compliance Page"}
}
compliancePageBaseURL := complianceportal.CompliancePageBaseURLFromContext(r.Context())
description := tc.Title + " Compliance Page"
if tc.Description != nil && *tc.Description != "" {
description = *tc.Description
}
headData := HeadData{
Title: tc.Title,
Description: description,
OGURL: ref.UnrefOrZero(compliancePageBaseURL),
}
if tc.LogoFileID != nil && compliancePageBaseURL != nil {
faviconURL, err := visitor.BrandLogoURL(*compliancePageBaseURL)
if err == nil {
headData.FaviconURL = faviconURL
}
}
return headData
}
}