From 31157ff2e3c4c52e6c26846f4ca546257e2cff5d Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Wed, 15 Jul 2026 10:59:08 +0200 Subject: [PATCH] Move trust GraphQL API under complianceportal v1 Relocate the public trust center GraphQL surface, OAuth handlers, and SPA serving into the compliance portal API package and remove the legacy trust v1 server tree. Signed-off-by: Bryan Frimin --- pkg/server/api/complianceportal/oauth.go | 35 ++++ pkg/server/api/complianceportal/session.go | 61 ++++++ .../api/complianceportal/session_test.go | 135 ++++++++++++++ .../v1/auth_resolvers.go | 20 +- .../v1/base_resolvers.go | 6 +- .../v1/file_loader.go | 4 +- .../v1/gqlgen.yaml | 2 +- .../v1/graphql/auth.graphql | 0 .../v1/graphql/base.graphql | 0 .../v1/graphql/file.graphql | 0 .../v1/graphql/mailing_list.graphql | 5 +- .../v1/graphql/nda.graphql | 0 .../v1/graphql/rights_request.graphql | 0 .../v1/graphql/trust_center.graphql | 14 +- .../v1/graphql_handler.go | 4 +- .../v1/mailing_list_resolvers.go | 4 +- pkg/server/api/complianceportal/v1/mux.go | 163 +++++++++++++++++ .../v1/nda_directive.go | 2 +- .../v1/nda_resolvers.go | 24 ++- .../v1/oauth_callback_handler.go | 173 ++++++++++++++++++ .../v1/oauth_client_metadata_handler.go | 50 +++++ .../v1/oauth_initiate_handler.go | 116 ++++++++++++ .../api/complianceportal/v1/resolver.go | 56 ++++++ .../v1/resource_alias_resolvers.go | 2 +- .../v1/rights_request_resolvers.go | 12 +- .../v1/schema/.gitignore | 0 .../v1/schema/doc.go | 0 .../complianceportal/v1/spa.go} | 3 +- .../v1/trust_center_resolvers.go | 20 +- .../v1/types/audit.go | 0 .../v1/types/audit_report.go | 0 .../v1/types/compliance_custom_link.go | 0 .../v1/types/compliance_framework.go | 18 +- .../v1/types/compliance_portal_commitment.go | 0 .../v1/types/cursorkey.go | 0 .../v1/types/document.go | 0 .../v1/types/electronic_signature.go | 0 .../v1/types/file.go | 0 .../v1/types/framework.go | 0 .../v1/types/mailing_list_subscriber.go | 16 -- .../v1/types/mailing_list_update.go | 0 .../v1/types/pageinfo.go | 0 .../v1/types/rights_request.go | 0 .../v1/types/third_party.go | 0 .../v1/types/trust_center.go | 0 .../v1/types/trust_center_file.go | 0 .../v1/types/trust_center_reference.go | 0 pkg/server/api/trust/v1/resolver.go | 141 -------------- 48 files changed, 862 insertions(+), 224 deletions(-) create mode 100644 pkg/server/api/complianceportal/oauth.go create mode 100644 pkg/server/api/complianceportal/session.go create mode 100644 pkg/server/api/complianceportal/session_test.go rename pkg/server/api/{trust => complianceportal}/v1/auth_resolvers.go (92%) rename pkg/server/api/{trust => complianceportal}/v1/base_resolvers.go (98%) rename pkg/server/api/{trust => complianceportal}/v1/file_loader.go (94%) rename pkg/server/api/{trust => complianceportal}/v1/gqlgen.yaml (96%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/auth.graphql (100%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/base.graphql (100%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/file.graphql (100%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/mailing_list.graphql (89%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/nda.graphql (100%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/rights_request.graphql (100%) rename pkg/server/api/{trust => complianceportal}/v1/graphql/trust_center.graphql (96%) rename pkg/server/api/{trust => complianceportal}/v1/graphql_handler.go (96%) rename pkg/server/api/{trust => complianceportal}/v1/mailing_list_resolvers.go (96%) create mode 100644 pkg/server/api/complianceportal/v1/mux.go rename pkg/server/api/{trust => complianceportal}/v1/nda_directive.go (99%) rename pkg/server/api/{trust => complianceportal}/v1/nda_resolvers.go (86%) create mode 100644 pkg/server/api/complianceportal/v1/oauth_callback_handler.go create mode 100644 pkg/server/api/complianceportal/v1/oauth_client_metadata_handler.go create mode 100644 pkg/server/api/complianceportal/v1/oauth_initiate_handler.go create mode 100644 pkg/server/api/complianceportal/v1/resolver.go rename pkg/server/api/{trust => complianceportal}/v1/resource_alias_resolvers.go (98%) rename pkg/server/api/{trust => complianceportal}/v1/rights_request_resolvers.go (83%) rename pkg/server/api/{trust => complianceportal}/v1/schema/.gitignore (100%) rename pkg/server/api/{trust => complianceportal}/v1/schema/doc.go (100%) rename pkg/server/{trust/trust.go => api/complianceportal/v1/spa.go} (96%) rename pkg/server/api/{trust => complianceportal}/v1/trust_center_resolvers.go (98%) rename pkg/server/api/{trust => complianceportal}/v1/types/audit.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/audit_report.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/compliance_custom_link.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/compliance_framework.go (81%) rename pkg/server/api/{trust => complianceportal}/v1/types/compliance_portal_commitment.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/cursorkey.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/document.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/electronic_signature.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/file.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/framework.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/mailing_list_subscriber.go (70%) rename pkg/server/api/{trust => complianceportal}/v1/types/mailing_list_update.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/pageinfo.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/rights_request.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/third_party.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/trust_center.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/trust_center_file.go (100%) rename pkg/server/api/{trust => complianceportal}/v1/types/trust_center_reference.go (100%) delete mode 100644 pkg/server/api/trust/v1/resolver.go diff --git a/pkg/server/api/complianceportal/oauth.go b/pkg/server/api/complianceportal/oauth.go new file mode 100644 index 000000000..241060dfe --- /dev/null +++ b/pkg/server/api/complianceportal/oauth.go @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 + +import ( + portal "go.probo.inc/probo/pkg/complianceportal" +) + +const ( + VisitorOAuthScope = portal.VisitorOAuthScope + GraphQLPath = "/graphql" + CIMDMetadataPath = portal.CIMDMetadataPath + OAuthInitiatePath = "/initiate" + OAuthCallbackPath = portal.OAuthCallbackPath +) + +func CIMDClientIDURL(portalBaseURL string) (string, error) { + return portal.CIMDClientIDURL(portalBaseURL) +} + +func OAuthCallbackURL(portalBaseURL string) (string, error) { + return portal.OAuthCallbackURL(portalBaseURL) +} diff --git a/pkg/server/api/complianceportal/session.go b/pkg/server/api/complianceportal/session.go new file mode 100644 index 000000000..ca044a01f --- /dev/null +++ b/pkg/server/api/complianceportal/session.go @@ -0,0 +1,61 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 + +import ( + "net/http" + + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/securecookie" + "go.probo.inc/probo/pkg/server/api/authn" +) + +func TrustedRequestHost(r *http.Request) (string, bool) { + if r.TLS != nil && r.TLS.ServerName != "" { + return coredata.NormalizeBoundHost(r.TLS.ServerName), true + } + + if r.Host == "" { + return "", false + } + + return coredata.NormalizeBoundHost(r.Host), true +} + +func NewSessionHostMiddleware(cookieConfig securecookie.Config) 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() + session := authn.SessionFromContext(ctx) + if session == nil { + next.ServeHTTP(w, r) + return + } + + host, ok := TrustedRequestHost(r) + if ok && session.Data.MatchesBoundHost(host) { + next.ServeHTTP(w, r) + return + } + + securecookie.Clear(w, cookieConfig) + ctx = authn.ContextWithSession(ctx, nil) + ctx = authn.ContextWithIdentity(ctx, nil) + next.ServeHTTP(w, r.WithContext(ctx)) + }, + ) + } +} diff --git a/pkg/server/api/complianceportal/session_test.go b/pkg/server/api/complianceportal/session_test.go new file mode 100644 index 000000000..57569f3c0 --- /dev/null +++ b/pkg/server/api/complianceportal/session_test.go @@ -0,0 +1,135 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 + +import ( + "crypto/tls" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/securecookie" + "go.probo.inc/probo/pkg/server/api/authn" +) + +func TestTrustedRequestHost_PrefersTLSServerName(t *testing.T) { + t.Parallel() + + req := httptest.NewRequest(http.MethodGet, "https://evil.example.com/graphql", nil) + req.Host = "evil.example.com" + req.TLS = &tls.ConnectionState{ServerName: "portal.example.com"} + + host, ok := TrustedRequestHost(req) + + require.True(t, ok) + assert.Equal(t, "portal.example.com", host) +} + +func TestSessionHostMiddleware_RejectsMismatchedHost(t *testing.T) { + t.Parallel() + + var authenticated bool + + handler := NewSessionHostMiddleware(securecookie.Config{Name: "ssid"})( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authenticated = authn.IdentityFromContext(r.Context()) != nil + w.WriteHeader(http.StatusOK) + }), + ) + + identity := &coredata.Identity{ID: gid.New(gid.NilTenant, coredata.IdentityEntityType)} + session := &coredata.Session{ + ID: identity.ID, + Data: coredata.SessionDataForHost("portal-a.example.com"), + } + + req := httptest.NewRequest(http.MethodGet, "/graphql", nil) + req.Host = "portal-b.example.com" + req.TLS = &tls.ConnectionState{ServerName: "portal-b.example.com"} + req = req.WithContext(authn.ContextWithIdentity(req.Context(), identity)) + req = req.WithContext(authn.ContextWithSession(req.Context(), session)) + + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + assert.False(t, authenticated) +} + +func TestSessionHostMiddleware_AllowsMatchingTLSHost(t *testing.T) { + t.Parallel() + + var authenticated bool + + handler := NewSessionHostMiddleware(securecookie.Config{Name: "ssid"})( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authenticated = authn.IdentityFromContext(r.Context()) != nil + w.WriteHeader(http.StatusOK) + }), + ) + + identity := &coredata.Identity{ID: gid.New(gid.NilTenant, coredata.IdentityEntityType)} + session := &coredata.Session{ + ID: identity.ID, + Data: coredata.SessionDataForHost("portal.example.com"), + } + + req := httptest.NewRequest(http.MethodGet, "/graphql", nil) + req.Host = "evil.example.com" + req.TLS = &tls.ConnectionState{ServerName: "portal.example.com"} + req = req.WithContext(authn.ContextWithIdentity(req.Context(), identity)) + req = req.WithContext(authn.ContextWithSession(req.Context(), session)) + + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + assert.True(t, authenticated) +} + +func TestSessionHostMiddleware_RejectsSpoofedHostHeader(t *testing.T) { + t.Parallel() + + var authenticated bool + + handler := NewSessionHostMiddleware(securecookie.Config{Name: "ssid"})( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + authenticated = authn.IdentityFromContext(r.Context()) != nil + w.WriteHeader(http.StatusOK) + }), + ) + + identity := &coredata.Identity{ID: gid.New(gid.NilTenant, coredata.IdentityEntityType)} + session := &coredata.Session{ + ID: identity.ID, + Data: coredata.SessionDataForHost("portal.example.com"), + } + + req := httptest.NewRequest(http.MethodGet, "/graphql", nil) + req.Host = "portal.example.com" + req.TLS = &tls.ConnectionState{ServerName: "other.example.com"} + req = req.WithContext(authn.ContextWithIdentity(req.Context(), identity)) + req = req.WithContext(authn.ContextWithSession(req.Context(), session)) + + rec := httptest.NewRecorder() + handler.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + assert.False(t, authenticated) +} diff --git a/pkg/server/api/trust/v1/auth_resolvers.go b/pkg/server/api/complianceportal/v1/auth_resolvers.go similarity index 92% rename from pkg/server/api/trust/v1/auth_resolvers.go rename to pkg/server/api/complianceportal/v1/auth_resolvers.go index 581f9c0d0..f34a7723d 100644 --- a/pkg/server/api/trust/v1/auth_resolvers.go +++ b/pkg/server/api/complianceportal/v1/auth_resolvers.go @@ -1,4 +1,4 @@ -package trust_v1 +package complianceportal_v1 // This file will be automatically regenerated based on the schema, any resolver // implementations @@ -16,7 +16,7 @@ import ( "go.probo.inc/probo/pkg/saferedirect" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/complianceportal" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" ) @@ -123,6 +123,22 @@ func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.Veri } } + req := gqlutils.HTTPRequestFromContext(ctx) + if req == nil { + return nil, gqlutils.Internal(ctx) + } + + host, ok := complianceportal.TrustedRequestHost(req) + if !ok { + return nil, gqlutils.Internal(ctx) + } + + session.Data = coredata.SessionDataForHost(host) + if err := r.iam.SessionService.UpdateSessionData(ctx, session.ID, session.Data); err != nil { + r.logger.ErrorCtx(ctx, "cannot bind session to host", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + trustCenter := complianceportal.CompliancePageFromContext(ctx) if _, err := r.trust.ProvisionPortalMember(ctx, trustCenter.ID, identity.ID); err != nil { diff --git a/pkg/server/api/trust/v1/base_resolvers.go b/pkg/server/api/complianceportal/v1/base_resolvers.go similarity index 98% rename from pkg/server/api/trust/v1/base_resolvers.go rename to pkg/server/api/complianceportal/v1/base_resolvers.go index 59e1fa08d..67025ee4c 100644 --- a/pkg/server/api/trust/v1/base_resolvers.go +++ b/pkg/server/api/complianceportal/v1/base_resolvers.go @@ -1,4 +1,4 @@ -package trust_v1 +package complianceportal_v1 // This file will be automatically regenerated based on the schema, any resolver // implementations @@ -17,8 +17,8 @@ import ( "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/complianceportal" - "go.probo.inc/probo/pkg/server/api/trust/v1/schema" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/schema" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" ) diff --git a/pkg/server/api/trust/v1/file_loader.go b/pkg/server/api/complianceportal/v1/file_loader.go similarity index 94% rename from pkg/server/api/trust/v1/file_loader.go rename to pkg/server/api/complianceportal/v1/file_loader.go index dd6fc561c..5e51711d3 100644 --- a/pkg/server/api/trust/v1/file_loader.go +++ b/pkg/server/api/complianceportal/v1/file_loader.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package trust_v1 +package complianceportal_v1 import ( "context" @@ -27,7 +27,7 @@ import ( "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" ) diff --git a/pkg/server/api/trust/v1/gqlgen.yaml b/pkg/server/api/complianceportal/v1/gqlgen.yaml similarity index 96% rename from pkg/server/api/trust/v1/gqlgen.yaml rename to pkg/server/api/complianceportal/v1/gqlgen.yaml index ee5cbe567..51747273f 100644 --- a/pkg/server/api/trust/v1/gqlgen.yaml +++ b/pkg/server/api/complianceportal/v1/gqlgen.yaml @@ -14,7 +14,7 @@ model: resolver: layout: "follow-schema" dir: "." - package: "trust_v1" + package: "complianceportal_v1" filename_template: "{name}_resolvers.go" autobind: [] diff --git a/pkg/server/api/trust/v1/graphql/auth.graphql b/pkg/server/api/complianceportal/v1/graphql/auth.graphql similarity index 100% rename from pkg/server/api/trust/v1/graphql/auth.graphql rename to pkg/server/api/complianceportal/v1/graphql/auth.graphql diff --git a/pkg/server/api/trust/v1/graphql/base.graphql b/pkg/server/api/complianceportal/v1/graphql/base.graphql similarity index 100% rename from pkg/server/api/trust/v1/graphql/base.graphql rename to pkg/server/api/complianceportal/v1/graphql/base.graphql diff --git a/pkg/server/api/trust/v1/graphql/file.graphql b/pkg/server/api/complianceportal/v1/graphql/file.graphql similarity index 100% rename from pkg/server/api/trust/v1/graphql/file.graphql rename to pkg/server/api/complianceportal/v1/graphql/file.graphql diff --git a/pkg/server/api/trust/v1/graphql/mailing_list.graphql b/pkg/server/api/complianceportal/v1/graphql/mailing_list.graphql similarity index 89% rename from pkg/server/api/trust/v1/graphql/mailing_list.graphql rename to pkg/server/api/complianceportal/v1/graphql/mailing_list.graphql index 061291f86..87e08a704 100644 --- a/pkg/server/api/trust/v1/graphql/mailing_list.graphql +++ b/pkg/server/api/complianceportal/v1/graphql/mailing_list.graphql @@ -29,10 +29,7 @@ enum MailingListSubscriberStatus ) } -type MailingListSubscriber implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.MailingListSubscriber" - ) { +type MailingListSubscriber implements Node { id: ID! fullName: String! email: EmailAddr! diff --git a/pkg/server/api/trust/v1/graphql/nda.graphql b/pkg/server/api/complianceportal/v1/graphql/nda.graphql similarity index 100% rename from pkg/server/api/trust/v1/graphql/nda.graphql rename to pkg/server/api/complianceportal/v1/graphql/nda.graphql diff --git a/pkg/server/api/trust/v1/graphql/rights_request.graphql b/pkg/server/api/complianceportal/v1/graphql/rights_request.graphql similarity index 100% rename from pkg/server/api/trust/v1/graphql/rights_request.graphql rename to pkg/server/api/complianceportal/v1/graphql/rights_request.graphql diff --git a/pkg/server/api/trust/v1/graphql/trust_center.graphql b/pkg/server/api/complianceportal/v1/graphql/trust_center.graphql similarity index 96% rename from pkg/server/api/trust/v1/graphql/trust_center.graphql rename to pkg/server/api/complianceportal/v1/graphql/trust_center.graphql index 018d52c9f..4eaf6d98d 100644 --- a/pkg/server/api/trust/v1/graphql/trust_center.graphql +++ b/pkg/server/api/complianceportal/v1/graphql/trust_center.graphql @@ -172,26 +172,20 @@ type AuditEdge @nda { node: Audit! } -type ComplianceFramework implements Node - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFramework" - ) { +type ComplianceFramework implements Node { id: ID! framework: Framework! @goField(forceResolver: true) } type ComplianceFrameworkConnection @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFrameworkConnection" + model: "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types.ComplianceFrameworkConnection" ) { edges: [ComplianceFrameworkEdge!]! pageInfo: PageInfo! } -type ComplianceFrameworkEdge - @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.ComplianceFrameworkEdge" - ) { +type ComplianceFrameworkEdge { cursor: CursorKey! node: ComplianceFramework! } @@ -285,7 +279,7 @@ input SubprocessorFilter { type SubprocessorConnection @goModel( - model: "go.probo.inc/probo/pkg/server/api/trust/v1/types.SubprocessorConnection" + model: "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types.SubprocessorConnection" ) @nda { edges: [SubprocessorEdge!]! pageInfo: PageInfo! diff --git a/pkg/server/api/trust/v1/graphql_handler.go b/pkg/server/api/complianceportal/v1/graphql_handler.go similarity index 96% rename from pkg/server/api/trust/v1/graphql_handler.go rename to pkg/server/api/complianceportal/v1/graphql_handler.go index 697fb5abb..f326a0c76 100644 --- a/pkg/server/api/trust/v1/graphql_handler.go +++ b/pkg/server/api/complianceportal/v1/graphql_handler.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package trust_v1 +package complianceportal_v1 import ( "net/http" @@ -33,7 +33,7 @@ import ( "go.probo.inc/probo/pkg/resourcealias" "go.probo.inc/probo/pkg/securecookie" "go.probo.inc/probo/pkg/server/api/authn" - "go.probo.inc/probo/pkg/server/api/trust/v1/schema" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/schema" "go.probo.inc/probo/pkg/server/gqlutils" "go.probo.inc/probo/pkg/server/gqlutils/directives/authentication" "go.probo.inc/probo/pkg/server/gqlutils/directives/session" diff --git a/pkg/server/api/trust/v1/mailing_list_resolvers.go b/pkg/server/api/complianceportal/v1/mailing_list_resolvers.go similarity index 96% rename from pkg/server/api/trust/v1/mailing_list_resolvers.go rename to pkg/server/api/complianceportal/v1/mailing_list_resolvers.go index 5990848bf..ab7922278 100644 --- a/pkg/server/api/trust/v1/mailing_list_resolvers.go +++ b/pkg/server/api/complianceportal/v1/mailing_list_resolvers.go @@ -1,4 +1,4 @@ -package trust_v1 +package complianceportal_v1 // This file will be automatically regenerated based on the schema, any resolver // implementations @@ -13,7 +13,7 @@ import ( "go.probo.inc/probo/pkg/mailman" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/complianceportal" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" "go.probo.inc/probo/pkg/validator" ) diff --git a/pkg/server/api/complianceportal/v1/mux.go b/pkg/server/api/complianceportal/v1/mux.go new file mode 100644 index 000000000..ff0afb21b --- /dev/null +++ b/pkg/server/api/complianceportal/v1/mux.go @@ -0,0 +1,163 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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" + "errors" + "net/http" + + "github.com/go-chi/chi/v5" + "go.gearno.de/kit/httpserver" + "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(cfg.BaseURL)) + 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()) + 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) + r.NotFound(handleCustomDomain404) + }, + ) + + return r, nil +} + +func handleCustomDomain404(w http.ResponseWriter, r *http.Request) { + httpserver.RenderError(w, http.StatusNotFound, errors.New("not found")) +} + +func compliancePageHeadData(baseURL *baseurl.BaseURL) 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 { + faviconURL, err := baseURL.WithPath("/api/files/v1/public/" + tc.LogoFileID.String()).String() + if err == nil { + headData.FaviconURL = faviconURL + } + } + + return headData + } +} diff --git a/pkg/server/api/trust/v1/nda_directive.go b/pkg/server/api/complianceportal/v1/nda_directive.go similarity index 99% rename from pkg/server/api/trust/v1/nda_directive.go rename to pkg/server/api/complianceportal/v1/nda_directive.go index 377bb533f..4315eda8d 100644 --- a/pkg/server/api/trust/v1/nda_directive.go +++ b/pkg/server/api/complianceportal/v1/nda_directive.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package trust_v1 +package complianceportal_v1 import ( "context" diff --git a/pkg/server/api/trust/v1/nda_resolvers.go b/pkg/server/api/complianceportal/v1/nda_resolvers.go similarity index 86% rename from pkg/server/api/trust/v1/nda_resolvers.go rename to pkg/server/api/complianceportal/v1/nda_resolvers.go index a40698f9d..f2f97888e 100644 --- a/pkg/server/api/trust/v1/nda_resolvers.go +++ b/pkg/server/api/complianceportal/v1/nda_resolvers.go @@ -1,4 +1,4 @@ -package trust_v1 +package complianceportal_v1 // This file will be automatically regenerated based on the schema, any resolver // implementations @@ -15,16 +15,17 @@ import ( "go.probo.inc/probo/pkg/esign" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/complianceportal" - "go.probo.inc/probo/pkg/server/api/trust/v1/schema" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/schema" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" ) // AcceptElectronicSignature is the resolver for the acceptElectronicSignature field. func (r *mutationResolver) AcceptElectronicSignature(ctx context.Context, input types.AcceptElectronicSignatureInput) (*types.AcceptElectronicSignaturePayload, error) { var ( - identity = authn.IdentityFromContext(ctx) - httpReq = gqlutils.HTTPRequestFromContext(ctx) + identity = authn.IdentityFromContext(ctx) + httpReq = gqlutils.HTTPRequestFromContext(ctx) + trustCenter = complianceportal.CompliancePageFromContext(ctx) ) signerIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) @@ -32,8 +33,11 @@ func (r *mutationResolver) AcceptElectronicSignature(ctx context.Context, input signerIP = httpReq.RemoteAddr } + scope := coredata.NewScopeFromObjectID(trustCenter.ID) + signature, err := r.esign.AcceptSignature( ctx, + scope, &esign.AcceptSignatureRequest{ SignatureID: input.SignatureID, SignerFullName: identity.FullName, @@ -55,8 +59,9 @@ func (r *mutationResolver) AcceptElectronicSignature(ctx context.Context, input // RecordSigningEvent is the resolver for the recordSigningEvent field. func (r *mutationResolver) RecordSigningEvent(ctx context.Context, input types.RecordSigningEventInput) (*types.RecordSigningEventPayload, error) { var ( - identity = authn.IdentityFromContext(ctx) - httpReq = gqlutils.HTTPRequestFromContext(ctx) + identity = authn.IdentityFromContext(ctx) + httpReq = gqlutils.HTTPRequestFromContext(ctx) + trustCenter = complianceportal.CompliancePageFromContext(ctx) ) actorIP, _, _ := net.SplitHostPort(httpReq.RemoteAddr) @@ -64,8 +69,11 @@ func (r *mutationResolver) RecordSigningEvent(ctx context.Context, input types.R actorIP = httpReq.RemoteAddr } + scope := coredata.NewScopeFromObjectID(trustCenter.ID) + if err := r.esign.RecordEvent( ctx, + scope, &esign.RecordEventRequest{ SignatureID: input.SignatureID, EventType: input.EventType, @@ -132,7 +140,7 @@ func (r *nonDisclosureAgreementResolver) ViewerSignature(ctx context.Context, ob return nil, nil } - sig, err := r.esign.GetSignatureByID(ctx, *access.ElectronicSignatureID) + sig, err := r.esign.GetSignatureByID(ctx, scope, *access.ElectronicSignatureID) if err != nil { return nil, nil } diff --git a/pkg/server/api/complianceportal/v1/oauth_callback_handler.go b/pkg/server/api/complianceportal/v1/oauth_callback_handler.go new file mode 100644 index 000000000..994eecf59 --- /dev/null +++ b/pkg/server/api/complianceportal/v1/oauth_callback_handler.go @@ -0,0 +1,173 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 ( + "errors" + "net/http" + + "go.gearno.de/kit/httpserver" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/complianceportal/visitor" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/iam" + "go.probo.inc/probo/pkg/iam/oauth2" + "go.probo.inc/probo/pkg/saferedirect" + "go.probo.inc/probo/pkg/securecookie" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/api/complianceportal" +) + +var ( + errNotFound = errors.New("not found") + errInvalidContinueURL = errors.New("invalid continue URL") + errInternal = errors.New("internal server error") + errInvalidOAuthRequest = errors.New("invalid oauth request") +) + +type OAuthCallbackHandler struct { + iam *iam.Service + visitor *visitor.Service + sessionCookie *authn.Cookie + safeRedirect *saferedirect.SafeRedirect + logger *log.Logger +} + +func NewOAuthCallbackHandler( + iamSvc *iam.Service, + visitorSvc *visitor.Service, + cookieConfig securecookie.Config, + allowedHost saferedirect.AllowedHostFunc, + logger *log.Logger, +) *OAuthCallbackHandler { + return &OAuthCallbackHandler{ + iam: iamSvc, + visitor: visitorSvc, + sessionCookie: authn.NewCookie(&cookieConfig), + safeRedirect: saferedirect.New(allowedHost), + logger: logger, + } +} + +func (h *OAuthCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + if oauthErr := r.URL.Query().Get("error"); oauthErr != "" { + h.logger.WarnCtx( + ctx, + "oauth callback returned error", + log.String("error", oauthErr), + log.String("error_description", r.URL.Query().Get("error_description")), + ) + httpserver.RenderError(w, http.StatusBadRequest, errInvalidOAuthRequest) + + return + } + + code := r.URL.Query().Get("code") + stateToken := r.URL.Query().Get("state") + if code == "" || stateToken == "" { + httpserver.RenderError(w, http.StatusBadRequest, errInvalidOAuthRequest) + return + } + + state, err := h.visitor.ConsumeOAuthState(ctx, stateToken) + if err != nil { + h.logger.WarnCtx(ctx, "invalid oauth state", log.Error(err)) + httpserver.RenderError(w, http.StatusBadRequest, errInvalidOAuthRequest) + + return + } + + portal := complianceportal.CompliancePageFromContext(ctx) + portalBaseURL := complianceportal.CompliancePageBaseURLFromContext(ctx) + if portal == nil || portalBaseURL == nil { + httpserver.RenderError(w, http.StatusNotFound, errNotFound) + return + } + + clientID, err := complianceportal.CIMDClientIDURL(*portalBaseURL) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot build cimd client_id", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + redirectURI, err := complianceportal.OAuthCallbackURL(*portalBaseURL) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot build oauth redirect_uri", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + tokenResult, err := h.iam.OAuth2ServerService.ExchangeAuthorizationCode( + ctx, + clientID, + code, + redirectURI, + state.CodeVerifier, + ) + if err != nil { + h.logger.WarnCtx(ctx, "cannot exchange authorization code", log.Error(err)) + httpserver.RenderError(w, http.StatusBadRequest, errInvalidOAuthRequest) + + return + } + + identityID, err := oauth2.ParseIDTokenIdentity(tokenResult.IDToken, state.Nonce) + if err != nil { + h.logger.WarnCtx(ctx, "cannot validate id token", log.Error(err)) + httpserver.RenderError(w, http.StatusBadRequest, errInvalidOAuthRequest) + + return + } + + host, ok := complianceportal.TrustedRequestHost(r) + if !ok { + httpserver.RenderError(w, http.StatusBadRequest, errInvalidOAuthRequest) + return + } + + session, err := h.iam.AuthService.OpenRootSession( + ctx, + identityID, + coredata.AuthMethodOIDC, + coredata.SessionDataForHost(host), + ) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot open session", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + if _, err := h.visitor.ProvisionPortalMember(ctx, portal.ID, identityID); err != nil { + h.logger.ErrorCtx(ctx, "cannot provision portal member", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + h.sessionCookie.Set(w, session) + + continueURL := state.ContinueURL + if continueURL == "" { + continueURL = "/" + } + + h.safeRedirect.Redirect(w, r, continueURL, "/", http.StatusFound) +} diff --git a/pkg/server/api/complianceportal/v1/oauth_client_metadata_handler.go b/pkg/server/api/complianceportal/v1/oauth_client_metadata_handler.go new file mode 100644 index 000000000..a97953880 --- /dev/null +++ b/pkg/server/api/complianceportal/v1/oauth_client_metadata_handler.go @@ -0,0 +1,50 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 ( + "encoding/json" + "net/http" + + "go.gearno.de/kit/httpserver" + portal "go.probo.inc/probo/pkg/complianceportal" + "go.probo.inc/probo/pkg/server/api/complianceportal" +) + +type oauthClientMetadataHandler struct{} + +func NewOAuthClientMetadataHandler() http.Handler { + return &oauthClientMetadataHandler{} +} + +func (h *oauthClientMetadataHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + compliancePage := complianceportal.CompliancePageFromContext(r.Context()) + baseURL := complianceportal.CompliancePageBaseURLFromContext(r.Context()) + + if compliancePage == nil || baseURL == nil { + httpserver.RenderError(w, http.StatusNotFound, errNotFound) + return + } + + doc, err := portal.BuildClientMetadataDocument(compliancePage, *baseURL) + if err != nil { + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Cache-Control", "public, max-age=300") + _ = json.NewEncoder(w).Encode(doc) +} diff --git a/pkg/server/api/complianceportal/v1/oauth_initiate_handler.go b/pkg/server/api/complianceportal/v1/oauth_initiate_handler.go new file mode 100644 index 000000000..7bd37e3ec --- /dev/null +++ b/pkg/server/api/complianceportal/v1/oauth_initiate_handler.go @@ -0,0 +1,116 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 ( + "net/http" + + "go.gearno.de/kit/httpclient" + "go.gearno.de/kit/httpserver" + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/baseurl" + "go.probo.inc/probo/pkg/complianceportal/visitor" + "go.probo.inc/probo/pkg/iam/oauth2" + "go.probo.inc/probo/pkg/saferedirect" + "go.probo.inc/probo/pkg/server/api/complianceportal" +) + +type OAuthInitiateHandler struct { + proboBaseURL *baseurl.BaseURL + visitor *visitor.Service + safeRedirect *saferedirect.SafeRedirect + httpClient *http.Client + logger *log.Logger +} + +func NewOAuthInitiateHandler( + proboBaseURL *baseurl.BaseURL, + visitorSvc *visitor.Service, + allowedHost saferedirect.AllowedHostFunc, + logger *log.Logger, +) *OAuthInitiateHandler { + return &OAuthInitiateHandler{ + proboBaseURL: proboBaseURL, + visitor: visitorSvc, + safeRedirect: saferedirect.New(allowedHost), + httpClient: httpclient.DefaultClient( + httpclient.WithLogger(logger), + httpclient.WithSSRFProtection(), + ), + logger: logger, + } +} + +func (h *OAuthInitiateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + portalBaseURL := complianceportal.CompliancePageBaseURLFromContext(ctx) + if portalBaseURL == nil { + httpserver.RenderError(w, http.StatusNotFound, errNotFound) + return + } + + continueURL := r.URL.Query().Get("continue") + if continueURL == "" { + continueURL = "/overview" + } + + safeContinue, ok := h.safeRedirect.Validate(ctx, continueURL) + if !ok { + httpserver.RenderError(w, http.StatusBadRequest, errInvalidContinueURL) + return + } + + clientID, err := complianceportal.CIMDClientIDURL(*portalBaseURL) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot build cimd client_id", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + redirectURI, err := complianceportal.OAuthCallbackURL(*portalBaseURL) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot build oauth redirect_uri", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + metadata, err := oauth2.FetchServerMetadata(ctx, h.httpClient, h.proboBaseURL.String()) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot fetch discovery metadata", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + authorizeURL, err := h.visitor.InitiateOAuthAuthorizeURL( + ctx, + metadata.AuthorizationEndpoint.String(), + clientID, + redirectURI, + []string{complianceportal.VisitorOAuthScope}, + safeContinue, + ) + if err != nil { + h.logger.ErrorCtx(ctx, "cannot initiate oauth authorize", log.Error(err)) + httpserver.RenderError(w, http.StatusInternalServerError, errInternal) + + return + } + + http.Redirect(w, r, authorizeURL, http.StatusFound) +} diff --git a/pkg/server/api/complianceportal/v1/resolver.go b/pkg/server/api/complianceportal/v1/resolver.go new file mode 100644 index 000000000..01270a6b3 --- /dev/null +++ b/pkg/server/api/complianceportal/v1/resolver.go @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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. + +//go:generate go run github.com/99designs/gqlgen generate + +package complianceportal_v1 + +import ( + "time" + + "go.gearno.de/kit/log" + "go.probo.inc/probo/pkg/baseurl" + trust "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/server/api/authn" +) + +type ( + TrustAuthConfig struct { + CookieName string + CookieDomain string + CookieDuration time.Duration + TokenDuration time.Duration + ReportURLDuration time.Duration + Scope string + TokenType string + CookieSecure bool + } + + Resolver struct { + trust *trust.Service + resourceAlias *resourcealias.Service + fileManager *filemanager.Service + esign *esign.Service + mailman *mailman.Service + logger *log.Logger + iam *iam.Service + sessionCookie *authn.Cookie + baseURL *baseurl.BaseURL + } +) diff --git a/pkg/server/api/trust/v1/resource_alias_resolvers.go b/pkg/server/api/complianceportal/v1/resource_alias_resolvers.go similarity index 98% rename from pkg/server/api/trust/v1/resource_alias_resolvers.go rename to pkg/server/api/complianceportal/v1/resource_alias_resolvers.go index a873b6e38..4a4f79f7f 100644 --- a/pkg/server/api/trust/v1/resource_alias_resolvers.go +++ b/pkg/server/api/complianceportal/v1/resource_alias_resolvers.go @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -package trust_v1 +package complianceportal_v1 import ( "context" diff --git a/pkg/server/api/trust/v1/rights_request_resolvers.go b/pkg/server/api/complianceportal/v1/rights_request_resolvers.go similarity index 83% rename from pkg/server/api/trust/v1/rights_request_resolvers.go rename to pkg/server/api/complianceportal/v1/rights_request_resolvers.go index 201edb003..d0712d2c0 100644 --- a/pkg/server/api/trust/v1/rights_request_resolvers.go +++ b/pkg/server/api/complianceportal/v1/rights_request_resolvers.go @@ -1,4 +1,4 @@ -package trust_v1 +package complianceportal_v1 // This file will be automatically regenerated based on the schema, any resolver // implementations @@ -9,12 +9,12 @@ import ( "context" "go.gearno.de/kit/log" + trust "go.probo.inc/probo/pkg/complianceportal/visitor" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/server/api/authn" - "go.probo.inc/probo/pkg/server/api/compliancepage" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" ) // CreateRightsRequest is the resolver for the createRightsRequest field. @@ -26,10 +26,10 @@ func (r *mutationResolver) CreateRightsRequest(ctx context.Context, input types. return nil, gqlutils.Unauthenticatedf(ctx, "a verified email is required to submit a request") } - compliancePage := compliancepage.CompliancePageFromContext(ctx) + compliancePage := complianceportal.CompliancePageFromContext(ctx) scope := coredata.NewScopeFromObjectID(compliancePage.OrganizationID) - rightsRequest, err := r.trust.RightsRequests.Create( + rightsRequest, err := r.trust.CreateRightsRequest( ctx, scope, &trust.CreateRightsRequest{ diff --git a/pkg/server/api/trust/v1/schema/.gitignore b/pkg/server/api/complianceportal/v1/schema/.gitignore similarity index 100% rename from pkg/server/api/trust/v1/schema/.gitignore rename to pkg/server/api/complianceportal/v1/schema/.gitignore diff --git a/pkg/server/api/trust/v1/schema/doc.go b/pkg/server/api/complianceportal/v1/schema/doc.go similarity index 100% rename from pkg/server/api/trust/v1/schema/doc.go rename to pkg/server/api/complianceportal/v1/schema/doc.go diff --git a/pkg/server/trust/trust.go b/pkg/server/api/complianceportal/v1/spa.go similarity index 96% rename from pkg/server/trust/trust.go rename to pkg/server/api/complianceportal/v1/spa.go index d6d8e73f9..e3a90f17d 100644 --- a/pkg/server/trust/trust.go +++ b/pkg/server/api/complianceportal/v1/spa.go @@ -18,8 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -// Package trust provides functionality for serving the trust center SPA frontend. -package trust +package complianceportal_v1 import ( "fmt" diff --git a/pkg/server/api/trust/v1/trust_center_resolvers.go b/pkg/server/api/complianceportal/v1/trust_center_resolvers.go similarity index 98% rename from pkg/server/api/trust/v1/trust_center_resolvers.go rename to pkg/server/api/complianceportal/v1/trust_center_resolvers.go index 8e5ad5b5b..a98c82438 100644 --- a/pkg/server/api/trust/v1/trust_center_resolvers.go +++ b/pkg/server/api/complianceportal/v1/trust_center_resolvers.go @@ -1,4 +1,4 @@ -package trust_v1 +package complianceportal_v1 // This file will be automatically regenerated based on the schema, any resolver // implementations @@ -18,8 +18,8 @@ import ( "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/complianceportal" - "go.probo.inc/probo/pkg/server/api/trust/v1/schema" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/schema" + "go.probo.inc/probo/pkg/server/api/complianceportal/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" ) @@ -165,7 +165,13 @@ func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types. scope := coredata.NewScopeFromObjectID(obj.ID) trustService := r.trust - framework, err := trustService.GetFramework(ctx, scope, obj.FrameworkID) + complianceFramework, err := trustService.GetComplianceFramework(ctx, scope, obj.ID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load compliance framework", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + framework, err := trustService.GetFramework(ctx, scope, complianceFramework.FrameworkID) if err != nil { r.logger.ErrorCtx(ctx, "cannot load framework", log.Error(err)) return nil, gqlutils.Internal(ctx) @@ -827,9 +833,10 @@ func (r *trustCenterResolver) Subprocessors(ctx context.Context, obj *types.Trus // SubprocessorCategories is the resolver for the subprocessorCategories field. func (r *trustCenterResolver) SubprocessorCategories(ctx context.Context, obj *types.TrustCenter) ([]coredata.ThirdPartyCategory, error) { + trustCenter := complianceportal.CompliancePageFromContext(ctx) scope := coredata.NewScopeFromObjectID(obj.ID) - categories, err := r.trust.ListDistinctTrustCenterCategoriesForOrganizationID(ctx, scope, obj.Organization.ID) + categories, err := r.trust.ListDistinctTrustCenterCategoriesForOrganizationID(ctx, scope, trustCenter.OrganizationID) if err != nil { r.logger.ErrorCtx(ctx, "cannot list subprocessor categories", log.Error(err)) return nil, gqlutils.Internal(ctx) @@ -840,9 +847,10 @@ func (r *trustCenterResolver) SubprocessorCategories(ctx context.Context, obj *t // SubprocessorCountries is the resolver for the subprocessorCountries field. func (r *trustCenterResolver) SubprocessorCountries(ctx context.Context, obj *types.TrustCenter) ([]coredata.CountryCode, error) { + trustCenter := complianceportal.CompliancePageFromContext(ctx) scope := coredata.NewScopeFromObjectID(obj.ID) - countries, err := r.trust.ListDistinctTrustCenterCountriesForOrganizationID(ctx, scope, obj.Organization.ID) + countries, err := r.trust.ListDistinctTrustCenterCountriesForOrganizationID(ctx, scope, trustCenter.OrganizationID) if err != nil { r.logger.ErrorCtx(ctx, "cannot list subprocessor countries", log.Error(err)) return nil, gqlutils.Internal(ctx) diff --git a/pkg/server/api/trust/v1/types/audit.go b/pkg/server/api/complianceportal/v1/types/audit.go similarity index 100% rename from pkg/server/api/trust/v1/types/audit.go rename to pkg/server/api/complianceportal/v1/types/audit.go diff --git a/pkg/server/api/trust/v1/types/audit_report.go b/pkg/server/api/complianceportal/v1/types/audit_report.go similarity index 100% rename from pkg/server/api/trust/v1/types/audit_report.go rename to pkg/server/api/complianceportal/v1/types/audit_report.go diff --git a/pkg/server/api/trust/v1/types/compliance_custom_link.go b/pkg/server/api/complianceportal/v1/types/compliance_custom_link.go similarity index 100% rename from pkg/server/api/trust/v1/types/compliance_custom_link.go rename to pkg/server/api/complianceportal/v1/types/compliance_custom_link.go diff --git a/pkg/server/api/trust/v1/types/compliance_framework.go b/pkg/server/api/complianceportal/v1/types/compliance_framework.go similarity index 81% rename from pkg/server/api/trust/v1/types/compliance_framework.go rename to pkg/server/api/complianceportal/v1/types/compliance_framework.go index 96d121fbf..be6e1a990 100644 --- a/pkg/server/api/trust/v1/types/compliance_framework.go +++ b/pkg/server/api/complianceportal/v1/types/compliance_framework.go @@ -22,33 +22,17 @@ package types import ( "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" ) -type ComplianceFramework struct { - ID gid.GID `json:"id"` - Framework *Framework `json:"framework"` - FrameworkID gid.GID `json:"-"` -} - -func (ComplianceFramework) IsNode() {} -func (cf ComplianceFramework) GetID() gid.GID { return cf.ID } - type ComplianceFrameworkConnection struct { Edges []*ComplianceFrameworkEdge `json:"edges"` PageInfo *PageInfo `json:"pageInfo"` } -type ComplianceFrameworkEdge struct { - Cursor page.CursorKey `json:"cursor"` - Node *ComplianceFramework `json:"node"` -} - func NewComplianceFramework(cf *coredata.ComplianceFramework) *ComplianceFramework { return &ComplianceFramework{ - ID: cf.ID, - FrameworkID: cf.FrameworkID, + ID: cf.ID, } } diff --git a/pkg/server/api/trust/v1/types/compliance_portal_commitment.go b/pkg/server/api/complianceportal/v1/types/compliance_portal_commitment.go similarity index 100% rename from pkg/server/api/trust/v1/types/compliance_portal_commitment.go rename to pkg/server/api/complianceportal/v1/types/compliance_portal_commitment.go diff --git a/pkg/server/api/trust/v1/types/cursorkey.go b/pkg/server/api/complianceportal/v1/types/cursorkey.go similarity index 100% rename from pkg/server/api/trust/v1/types/cursorkey.go rename to pkg/server/api/complianceportal/v1/types/cursorkey.go diff --git a/pkg/server/api/trust/v1/types/document.go b/pkg/server/api/complianceportal/v1/types/document.go similarity index 100% rename from pkg/server/api/trust/v1/types/document.go rename to pkg/server/api/complianceportal/v1/types/document.go diff --git a/pkg/server/api/trust/v1/types/electronic_signature.go b/pkg/server/api/complianceportal/v1/types/electronic_signature.go similarity index 100% rename from pkg/server/api/trust/v1/types/electronic_signature.go rename to pkg/server/api/complianceportal/v1/types/electronic_signature.go diff --git a/pkg/server/api/trust/v1/types/file.go b/pkg/server/api/complianceportal/v1/types/file.go similarity index 100% rename from pkg/server/api/trust/v1/types/file.go rename to pkg/server/api/complianceportal/v1/types/file.go diff --git a/pkg/server/api/trust/v1/types/framework.go b/pkg/server/api/complianceportal/v1/types/framework.go similarity index 100% rename from pkg/server/api/trust/v1/types/framework.go rename to pkg/server/api/complianceportal/v1/types/framework.go diff --git a/pkg/server/api/trust/v1/types/mailing_list_subscriber.go b/pkg/server/api/complianceportal/v1/types/mailing_list_subscriber.go similarity index 70% rename from pkg/server/api/trust/v1/types/mailing_list_subscriber.go rename to pkg/server/api/complianceportal/v1/types/mailing_list_subscriber.go index 9e6929806..ee00d4474 100644 --- a/pkg/server/api/trust/v1/types/mailing_list_subscriber.go +++ b/pkg/server/api/complianceportal/v1/types/mailing_list_subscriber.go @@ -21,25 +21,9 @@ package types import ( - "time" - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/mail" ) -type MailingListSubscriber struct { - ID gid.GID `json:"id"` - FullName string `json:"fullName"` - Email mail.Addr `json:"email"` - Status coredata.MailingListSubscriberStatus `json:"status"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -func (MailingListSubscriber) IsNode() {} -func (m MailingListSubscriber) GetID() gid.GID { return m.ID } - func NewMailingListSubscriber(s *coredata.MailingListSubscriber) *MailingListSubscriber { return &MailingListSubscriber{ ID: s.ID, diff --git a/pkg/server/api/trust/v1/types/mailing_list_update.go b/pkg/server/api/complianceportal/v1/types/mailing_list_update.go similarity index 100% rename from pkg/server/api/trust/v1/types/mailing_list_update.go rename to pkg/server/api/complianceportal/v1/types/mailing_list_update.go diff --git a/pkg/server/api/trust/v1/types/pageinfo.go b/pkg/server/api/complianceportal/v1/types/pageinfo.go similarity index 100% rename from pkg/server/api/trust/v1/types/pageinfo.go rename to pkg/server/api/complianceportal/v1/types/pageinfo.go diff --git a/pkg/server/api/trust/v1/types/rights_request.go b/pkg/server/api/complianceportal/v1/types/rights_request.go similarity index 100% rename from pkg/server/api/trust/v1/types/rights_request.go rename to pkg/server/api/complianceportal/v1/types/rights_request.go diff --git a/pkg/server/api/trust/v1/types/third_party.go b/pkg/server/api/complianceportal/v1/types/third_party.go similarity index 100% rename from pkg/server/api/trust/v1/types/third_party.go rename to pkg/server/api/complianceportal/v1/types/third_party.go diff --git a/pkg/server/api/trust/v1/types/trust_center.go b/pkg/server/api/complianceportal/v1/types/trust_center.go similarity index 100% rename from pkg/server/api/trust/v1/types/trust_center.go rename to pkg/server/api/complianceportal/v1/types/trust_center.go diff --git a/pkg/server/api/trust/v1/types/trust_center_file.go b/pkg/server/api/complianceportal/v1/types/trust_center_file.go similarity index 100% rename from pkg/server/api/trust/v1/types/trust_center_file.go rename to pkg/server/api/complianceportal/v1/types/trust_center_file.go diff --git a/pkg/server/api/trust/v1/types/trust_center_reference.go b/pkg/server/api/complianceportal/v1/types/trust_center_reference.go similarity index 100% rename from pkg/server/api/trust/v1/types/trust_center_reference.go rename to pkg/server/api/complianceportal/v1/types/trust_center_reference.go diff --git a/pkg/server/api/trust/v1/resolver.go b/pkg/server/api/trust/v1/resolver.go deleted file mode 100644 index 5548c5355..000000000 --- a/pkg/server/api/trust/v1/resolver.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2025-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. - -//go:generate go tool github.com/99designs/gqlgen generate - -// Copyright (c) 2025 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 trust_v1 - -import ( - "context" - "net/http" - "time" - - "github.com/go-chi/chi/v5" - "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/baseurl" - trust "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/api/authn" - "go.probo.inc/probo/pkg/server/api/complianceportal" - "go.probo.inc/probo/pkg/server/gqlutils" -) - -type ( - TrustAuthConfig struct { - CookieName string - CookieDomain string - CookieDuration time.Duration - TokenDuration time.Duration - ReportURLDuration time.Duration - Scope string - TokenType string - CookieSecure bool - } - - Resolver struct { - trust *trust.Service - resourceAlias *resourcealias.Service - fileManager *filemanager.Service - esign *esign.Service - mailman *mailman.Service - logger *log.Logger - iam *iam.Service - sessionCookie *authn.Cookie - baseURL *baseurl.BaseURL - } -) - -func NewMux( - logger *log.Logger, - iamSvc *iam.Service, - trustSvc *trust.Service, - resourceAliasSvc *resourcealias.Service, - fileManagerSvc *filemanager.Service, - esignSvc *esign.Service, - mailmanSvc *mailman.Service, - cookieConfig securecookie.Config, - tokenSecret string, - baseURL *baseurl.BaseURL, - graphqlLimits gqlutils.Limits, -) *chi.Mux { - r := chi.NewMux() - - r.Use(complianceportal.NewCompliancePagePresenceMiddleware()) - - sessionTransferHandler := NewSessionTransferHandler( - iamSvc, - cookieConfig, - func(ctx context.Context, host string) bool { - _, err := trustSvc.GetPortalByDomainName(ctx, host) - return err == nil - }, - logger, - ) - r.Method(http.MethodGet, "/session-transfer", sessionTransferHandler) - - graphqlHandler := NewGraphQLHandler( - iamSvc, - trustSvc, - resourceAliasSvc, - fileManagerSvc, - esignSvc, - mailmanSvc, - logger, - baseURL, - cookieConfig, - tokenSecret, - graphqlLimits, - ) - - r.Group( - func(r chi.Router) { - r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) - r.Use(complianceportal.NewMemberProvisioningMiddleware(trustSvc, logger)) - r.Handle("/graphql", graphqlHandler) - }, - ) - - return r -}