diff --git a/pkg/server/api/compliancepage/id_middleware.go b/pkg/server/api/compliancepage/id_middleware.go deleted file mode 100644 index 17e7d972c..000000000 --- a/pkg/server/api/compliancepage/id_middleware.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// 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 compliancepage - -import ( - "context" - "errors" - "net/http" - - "github.com/99designs/gqlgen/graphql" - "github.com/go-chi/chi/v5" - "github.com/vektah/gqlparser/v2/gqlerror" - "go.gearno.de/kit/httpserver" - "go.probo.inc/probo/pkg/baseurl" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" -) - -func NewIDMiddleware(trustSvc *trust.Service, baseURL string) func(next http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - // TODO: remove slug support - value := chi.URLParam(r, "slugOrId") - - if id, err := gid.ParseGID(value); err == nil { - compliancePage, err := trustSvc.Get(ctx, id) - if err != nil { - if errors.Is(err, trust.ErrPageNotFound) { - next.ServeHTTP(w, r) - return - } - - httpserver.RenderJSON( - w, - http.StatusInternalServerError, - &graphql.Response{ - Errors: gqlerror.List{ - gqlutils.Internal(ctx), - }, - }, - ) - - return - } - - baseURL := baseurl.MustParse(baseURL).AppendPath("/trust/" + id.String()).MustString() - ctx = context.WithValue(ctx, compliancePageBaseURLKey, &baseURL) - r = r.WithContext(ctx) - - if !compliancePage.Active { - next.ServeHTTP(w, r) - return - } - - ctx = context.WithValue(ctx, compliancePageKey, compliancePage) - next.ServeHTTP(w, r.WithContext(ctx)) - - return - } - - compliancePage, err := trustSvc.GetBySlug(ctx, value) - if err != nil { - if errors.Is(err, trust.ErrPageNotFound) { - next.ServeHTTP(w, r) - return - } - - httpserver.RenderJSON( - w, - http.StatusInternalServerError, - &graphql.Response{ - Errors: gqlerror.List{ - gqlutils.Internal(ctx), - }, - }, - ) - - return - } - - baseURL := baseurl.MustParse(baseURL).AppendPath("/trust/" + value).MustString() - ctx = context.WithValue(ctx, compliancePageBaseURLKey, &baseURL) - r = r.WithContext(ctx) - - if compliancePage.Active { - ctx = context.WithValue(ctx, compliancePageKey, compliancePage) - next.ServeHTTP(w, r.WithContext(ctx)) - - return - } - - next.ServeHTTP(w, r) - }, - ) - } -} diff --git a/pkg/server/api/compliancepage/compliance_page_presence_middleware.go b/pkg/server/api/complianceportal/compliance_page_presence_middleware.go similarity index 98% rename from pkg/server/api/compliancepage/compliance_page_presence_middleware.go rename to pkg/server/api/complianceportal/compliance_page_presence_middleware.go index dc627b2e2..4ae078bae 100644 --- a/pkg/server/api/compliancepage/compliance_page_presence_middleware.go +++ b/pkg/server/api/complianceportal/compliance_page_presence_middleware.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package compliancepage +package complianceportal import ( "net/http" diff --git a/pkg/server/api/compliancepage/context.go b/pkg/server/api/complianceportal/context.go similarity index 98% rename from pkg/server/api/compliancepage/context.go rename to pkg/server/api/complianceportal/context.go index 21c36fe7f..98eef7cc8 100644 --- a/pkg/server/api/compliancepage/context.go +++ b/pkg/server/api/complianceportal/context.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package compliancepage +package complianceportal import ( "context" diff --git a/pkg/server/api/compliancepage/markdown_handler.go b/pkg/server/api/complianceportal/markdown_handler.go similarity index 97% rename from pkg/server/api/compliancepage/markdown_handler.go rename to pkg/server/api/complianceportal/markdown_handler.go index de8b48409..d23177dac 100644 --- a/pkg/server/api/compliancepage/markdown_handler.go +++ b/pkg/server/api/complianceportal/markdown_handler.go @@ -18,13 +18,13 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package compliancepage +package complianceportal import ( "net/http" + trust "go.probo.inc/probo/pkg/complianceportal/visitor" "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/trust" ) type Handler struct { diff --git a/pkg/server/api/compliancepage/member_provisioning_middleware.go b/pkg/server/api/complianceportal/member_provisioning_middleware.go similarity index 92% rename from pkg/server/api/compliancepage/member_provisioning_middleware.go rename to pkg/server/api/complianceportal/member_provisioning_middleware.go index 21e5fb9ef..f7644943f 100644 --- a/pkg/server/api/compliancepage/member_provisioning_middleware.go +++ b/pkg/server/api/complianceportal/member_provisioning_middleware.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package compliancepage +package complianceportal import ( "net/http" @@ -27,9 +27,9 @@ import ( "github.com/vektah/gqlparser/v2/gqlerror" "go.gearno.de/kit/httpserver" "go.gearno.de/kit/log" + trust "go.probo.inc/probo/pkg/complianceportal/visitor" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" ) func NewMemberProvisioningMiddleware(trustSvc *trust.Service, logger *log.Logger) func(next http.Handler) http.Handler { @@ -46,7 +46,7 @@ func NewMemberProvisioningMiddleware(trustSvc *trust.Service, logger *log.Logger compliancePage := CompliancePageFromContext(r.Context()) - if _, err := trustSvc.ProvisionMember(ctx, compliancePage.ID, identity.ID); err != nil { + if _, err := trustSvc.ProvisionPortalMember(ctx, compliancePage.ID, identity.ID); err != nil { logger.ErrorCtx(ctx, "cannot provision member", log.Error(err)) httpserver.RenderJSON( w, diff --git a/pkg/server/api/compliancepage/sni_middleware.go b/pkg/server/api/complianceportal/sni_middleware.go similarity index 67% rename from pkg/server/api/compliancepage/sni_middleware.go rename to pkg/server/api/complianceportal/sni_middleware.go index 252fd12a9..8fd5e66b7 100644 --- a/pkg/server/api/compliancepage/sni_middleware.go +++ b/pkg/server/api/complianceportal/sni_middleware.go @@ -18,19 +18,20 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package compliancepage +package complianceportal import ( "context" "errors" "net/http" "net/url" + "strings" "github.com/99designs/gqlgen/graphql" "github.com/vektah/gqlparser/v2/gqlerror" "go.gearno.de/kit/httpserver" + trust "go.probo.inc/probo/pkg/complianceportal/visitor" "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" ) func NewSNIMiddleware(trustSvc *trust.Service) func(next http.Handler) http.Handler { @@ -43,7 +44,7 @@ func NewSNIMiddleware(trustSvc *trust.Service) func(next http.Handler) http.Hand return } - compliancePage, err := trustSvc.GetByDomainName(ctx, r.TLS.ServerName) + compliancePage, err := trustSvc.GetPortalByDomainName(ctx, r.TLS.ServerName) if err != nil { if errors.Is(err, trust.ErrPageNotFound) { next.ServeHTTP(w, r) @@ -63,16 +64,50 @@ func NewSNIMiddleware(trustSvc *trust.Service) func(next http.Handler) http.Hand return } + // Redirect secondary domains to the canonical host so a compliance + // page is only ever served under a single origin. ACME HTTP-01 + // challenges are handled upstream and never reach this middleware. + if !strings.HasPrefix(r.URL.Path, "/.well-known/") { + canonicalHost, err := trustSvc.GetPortalEffectiveCanonicalHost(ctx, compliancePage.ID) + if err != nil { + httpserver.RenderJSON( + w, + http.StatusInternalServerError, + &graphql.Response{ + Errors: gqlerror.List{ + gqlutils.Internal(ctx), + }, + }, + ) + + return + } + + if canonicalHost != "" && canonicalHost != r.Host { + target := &url.URL{ + Scheme: "https", + Host: canonicalHost, + Path: r.URL.Path, + RawQuery: r.URL.RawQuery, + } + + http.Redirect(w, r, target.String(), http.StatusMovedPermanently) + + return + } + } + baseURL := &url.URL{ Host: r.Host, Path: r.URL.Path, Scheme: "https", } + baseURLString := baseURL.String() ctx = context.WithValue( ctx, compliancePageBaseURLKey, - new(baseURL.String()), + &baseURLString, ) r = r.WithContext(ctx)