diff --git a/apps/console/src/routes.tsx b/apps/console/src/routes.tsx index 032605f68..6b94a32dd 100644 --- a/apps/console/src/routes.tsx +++ b/apps/console/src/routes.tsx @@ -131,7 +131,6 @@ const routes = [ }, { path: "/organizations/:organizationId/employee", - Fallback: () => "fallback employee...", Component: lazy( () => import("./pages/organizations/employee/EmployeeLayoutLoader"), ), diff --git a/pkg/server/api/compliancepage/membership_middleware.go b/pkg/server/api/compliancepage/membership_middleware.go deleted file mode 100644 index b3632d804..000000000 --- a/pkg/server/api/compliancepage/membership_middleware.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2025 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 compliancepage - -import ( - "context" - "errors" - "net/http" - - "github.com/99designs/gqlgen/graphql" - "github.com/vektah/gqlparser/v2/gqlerror" - "go.gearno.de/kit/httpserver" - "go.gearno.de/kit/log" - "go.probo.inc/probo/pkg/server/api/authn" - "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" -) - -func NewMembershipMiddleware(trustSvc *trust.Service, logger *log.Logger) 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() - - identity := authn.IdentityFromContext(r.Context()) - if identity == nil { - next.ServeHTTP(w, r) - return - } - compliancePage := CompliancePageFromContext(ctx) - - membership, err := trustSvc.GetMembershipByCompliancePageIDAndEmail(ctx, compliancePage.ID, identity.EmailAddress) - if err != nil { - if errors.Is(err, trust.ErrMembershipNotFound) { - next.ServeHTTP(w, r) - return - } - - logger.ErrorCtx(ctx, "cannot get membership by page id and email", log.Error(err)) - httpserver.RenderJSON( - w, - http.StatusInternalServerError, - &graphql.Response{ - Errors: gqlerror.List{ - gqlutils.Internal(ctx), - }, - }, - ) - return - } - - if membership.Active { - ctx = context.WithValue(ctx, complianceMembershipKey, membership) - next.ServeHTTP(w, r.WithContext(ctx)) - return - } - - next.ServeHTTP(w, r) - }, - ) - } -} diff --git a/pkg/server/api/trust/v1/graphql_handler.go b/pkg/server/api/trust/v1/graphql_handler.go index 40051245c..1305d5931 100644 --- a/pkg/server/api/trust/v1/graphql_handler.go +++ b/pkg/server/api/trust/v1/graphql_handler.go @@ -15,32 +15,19 @@ package trust_v1 import ( - "context" "net/http" - "github.com/99designs/gqlgen/graphql" "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/baseurl" "go.probo.inc/probo/pkg/iam" "go.probo.inc/probo/pkg/securecookie" "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/schema" "go.probo.inc/probo/pkg/server/gqlutils" "go.probo.inc/probo/pkg/server/gqlutils/directives/session" "go.probo.inc/probo/pkg/trust" ) -func MembersOnlyDirective(ctx context.Context, obj any, next graphql.Resolver) (any, error) { - membership := compliancepage.ComplianceMembershipFromContext(ctx) - - if membership == nil { - return nil, gqlutils.Forbiddenf(ctx, "insufficient permission to access this resource") - } - - return next(ctx) -} - func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, logger *log.Logger, baseURL *baseurl.BaseURL, cookieConfig securecookie.Config) http.Handler { config := schema.Config{ Resolvers: &Resolver{ @@ -51,8 +38,7 @@ func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, logger *log sessionCookie: authn.NewCookie(&cookieConfig), }, Directives: schema.DirectiveRoot{ - Session: session.Directive, - MembersOnly: MembersOnlyDirective, + Session: session.Directive, }, } diff --git a/pkg/server/api/trust/v1/resolver.go b/pkg/server/api/trust/v1/resolver.go index f6214eec2..81f4acebf 100644 --- a/pkg/server/api/trust/v1/resolver.go +++ b/pkg/server/api/trust/v1/resolver.go @@ -82,7 +82,6 @@ func NewMux( r.Use(compliancepage.NewCompliancePagePresenceMiddleware()) r.Use(authn.NewSessionMiddleware(iamSvc, cookieConfig)) - r.Use(compliancepage.NewMembershipMiddleware(trustSvc, logger)) graphqlHandler := NewGraphQLHandler(iamSvc, trustSvc, logger, baseURL, cookieConfig) diff --git a/pkg/server/api/trust/v1/schema.graphql b/pkg/server/api/trust/v1/schema.graphql index 4b7c99376..42f0cc6c3 100644 --- a/pkg/server/api/trust/v1/schema.graphql +++ b/pkg/server/api/trust/v1/schema.graphql @@ -12,13 +12,6 @@ directive @goModel( directive @goEnum(value: String) on ENUM_VALUE -directive @membersOnly on FIELD_DEFINITION - -enum Role { - NONE - USER -} - scalar CursorKey scalar Datetime scalar EmailAddr @@ -623,18 +616,18 @@ type Mutation { requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload! - @session(required: PRESENT) - @membersOnly + @session(required: OPTIONAL) exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload! - @session(required: PRESENT) - @membersOnly + @session(required: OPTIONAL) + + exportTrustCenterFile( + input: ExportTrustCenterFileInput! + ): ExportTrustCenterFilePayload! @session(required: OPTIONAL) acceptNonDisclosureAgreement( input: AcceptNonDisclosureAgreementInput! - ): AcceptNonDisclosureAgreementPayload - @session(required: PRESENT) - @membersOnly + ): AcceptNonDisclosureAgreementPayload @session(required: PRESENT) requestDocumentAccess( input: RequestDocumentAccessInput! @@ -647,8 +640,4 @@ type Mutation { requestTrustCenterFileAccess( input: RequestTrustCenterFileAccessInput! ): RequestAccessesPayload! @session(required: PRESENT) - - exportTrustCenterFile( - input: ExportTrustCenterFileInput! - ): ExportTrustCenterFilePayload! @session(required: PRESENT) @membersOnly } diff --git a/pkg/server/api/trust/v1/schema/schema.go b/pkg/server/api/trust/v1/schema/schema.go index 8d339f719..8ea2cea5a 100644 --- a/pkg/server/api/trust/v1/schema/schema.go +++ b/pkg/server/api/trust/v1/schema/schema.go @@ -60,8 +60,7 @@ type ResolverRoot interface { } type DirectiveRoot struct { - MembersOnly func(ctx context.Context, obj any, next graphql.Resolver) (res any, err error) - Session func(ctx context.Context, obj any, next graphql.Resolver, required session.SessionRequirement) (res any, err error) + Session func(ctx context.Context, obj any, next graphql.Resolver, required session.SessionRequirement) (res any, err error) } type ComplexityRoot struct { @@ -284,11 +283,11 @@ type MutationResolver interface { RequestAllAccesses(ctx context.Context) (*types.RequestAccessesPayload, error) ExportDocumentPDF(ctx context.Context, input types.ExportDocumentPDFInput) (*types.ExportDocumentPDFPayload, error) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) + ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) RequestDocumentAccess(ctx context.Context, input types.RequestDocumentAccessInput) (*types.RequestAccessesPayload, error) RequestReportAccess(ctx context.Context, input types.RequestReportAccessInput) (*types.RequestAccessesPayload, error) RequestTrustCenterFileAccess(ctx context.Context, input types.RequestTrustCenterFileAccessInput) (*types.RequestAccessesPayload, error) - ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) } type OrganizationResolver interface { LogoURL(ctx context.Context, obj *types.Organization) (*string, error) @@ -1216,13 +1215,6 @@ directive @goModel( directive @goEnum(value: String) on ENUM_VALUE -directive @membersOnly on FIELD_DEFINITION - -enum Role { - NONE - USER -} - scalar CursorKey scalar Datetime scalar EmailAddr @@ -1827,18 +1819,18 @@ type Mutation { requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) exportDocumentPDF(input: ExportDocumentPDFInput!): ExportDocumentPDFPayload! - @session(required: PRESENT) - @membersOnly + @session(required: OPTIONAL) exportReportPDF(input: ExportReportPDFInput!): ExportReportPDFPayload! - @session(required: PRESENT) - @membersOnly + @session(required: OPTIONAL) + + exportTrustCenterFile( + input: ExportTrustCenterFileInput! + ): ExportTrustCenterFilePayload! @session(required: OPTIONAL) acceptNonDisclosureAgreement( input: AcceptNonDisclosureAgreementInput! - ): AcceptNonDisclosureAgreementPayload - @session(required: PRESENT) - @membersOnly + ): AcceptNonDisclosureAgreementPayload @session(required: PRESENT) requestDocumentAccess( input: RequestDocumentAccessInput! @@ -1851,10 +1843,6 @@ type Mutation { requestTrustCenterFileAccess( input: RequestTrustCenterFileAccessInput! ): RequestAccessesPayload! @session(required: PRESENT) - - exportTrustCenterFile( - input: ExportTrustCenterFileInput! - ): ExportTrustCenterFilePayload! @session(required: PRESENT) @membersOnly } `, BuiltIn: false}, {Name: "../../../../gqlutils/directives/session/schema.graphql", Input: `# Session directive for GraphQL APIs @@ -3350,7 +3338,7 @@ func (ec *executionContext) _Mutation_exportDocumentPDF(ctx context.Context, fie directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") if err != nil { var zeroVal *types.ExportDocumentPDFPayload return zeroVal, err @@ -3361,15 +3349,8 @@ func (ec *executionContext) _Mutation_exportDocumentPDF(ctx context.Context, fie } return ec.directives.Session(ctx, nil, directive0, required) } - directive2 := func(ctx context.Context) (any, error) { - if ec.directives.MembersOnly == nil { - var zeroVal *types.ExportDocumentPDFPayload - return zeroVal, errors.New("directive membersOnly is not implemented") - } - return ec.directives.MembersOnly(ctx, nil, directive1) - } - next = directive2 + next = directive1 return next }, ec.marshalNExportDocumentPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportDocumentPDFPayload, @@ -3420,7 +3401,7 @@ func (ec *executionContext) _Mutation_exportReportPDF(ctx context.Context, field directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") if err != nil { var zeroVal *types.ExportReportPDFPayload return zeroVal, err @@ -3431,15 +3412,8 @@ func (ec *executionContext) _Mutation_exportReportPDF(ctx context.Context, field } return ec.directives.Session(ctx, nil, directive0, required) } - directive2 := func(ctx context.Context) (any, error) { - if ec.directives.MembersOnly == nil { - var zeroVal *types.ExportReportPDFPayload - return zeroVal, errors.New("directive membersOnly is not implemented") - } - return ec.directives.MembersOnly(ctx, nil, directive1) - } - next = directive2 + next = directive1 return next }, ec.marshalNExportReportPDFPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportReportPDFPayload, @@ -3476,6 +3450,69 @@ func (ec *executionContext) fieldContext_Mutation_exportReportPDF(ctx context.Co return fc, nil } +func (ec *executionContext) _Mutation_exportTrustCenterFile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_Mutation_exportTrustCenterFile, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.Mutation().ExportTrustCenterFile(ctx, fc.Args["input"].(types.ExportTrustCenterFileInput)) + }, + func(ctx context.Context, next graphql.Resolver) graphql.Resolver { + directive0 := next + + directive1 := func(ctx context.Context) (any, error) { + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") + if err != nil { + var zeroVal *types.ExportTrustCenterFilePayload + return zeroVal, err + } + if ec.directives.Session == nil { + var zeroVal *types.ExportTrustCenterFilePayload + return zeroVal, errors.New("directive session is not implemented") + } + return ec.directives.Session(ctx, nil, directive0, required) + } + + next = directive1 + return next + }, + ec.marshalNExportTrustCenterFilePayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportTrustCenterFilePayload, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_Mutation_exportTrustCenterFile(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Mutation", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "data": + return ec.fieldContext_ExportTrustCenterFilePayload_data(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ExportTrustCenterFilePayload", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Mutation_exportTrustCenterFile_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -3501,15 +3538,8 @@ func (ec *executionContext) _Mutation_acceptNonDisclosureAgreement(ctx context.C } return ec.directives.Session(ctx, nil, directive0, required) } - directive2 := func(ctx context.Context) (any, error) { - if ec.directives.MembersOnly == nil { - var zeroVal *types.AcceptNonDisclosureAgreementPayload - return zeroVal, errors.New("directive membersOnly is not implemented") - } - return ec.directives.MembersOnly(ctx, nil, directive1) - } - next = directive2 + next = directive1 return next }, ec.marshalOAcceptNonDisclosureAgreementPayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐAcceptNonDisclosureAgreementPayload, @@ -3735,76 +3765,6 @@ func (ec *executionContext) fieldContext_Mutation_requestTrustCenterFileAccess(c return fc, nil } -func (ec *executionContext) _Mutation_exportTrustCenterFile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - return graphql.ResolveField( - ctx, - ec.OperationContext, - field, - ec.fieldContext_Mutation_exportTrustCenterFile, - func(ctx context.Context) (any, error) { - fc := graphql.GetFieldContext(ctx) - return ec.resolvers.Mutation().ExportTrustCenterFile(ctx, fc.Args["input"].(types.ExportTrustCenterFileInput)) - }, - func(ctx context.Context, next graphql.Resolver) graphql.Resolver { - directive0 := next - - directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") - if err != nil { - var zeroVal *types.ExportTrustCenterFilePayload - return zeroVal, err - } - if ec.directives.Session == nil { - var zeroVal *types.ExportTrustCenterFilePayload - return zeroVal, errors.New("directive session is not implemented") - } - return ec.directives.Session(ctx, nil, directive0, required) - } - directive2 := func(ctx context.Context) (any, error) { - if ec.directives.MembersOnly == nil { - var zeroVal *types.ExportTrustCenterFilePayload - return zeroVal, errors.New("directive membersOnly is not implemented") - } - return ec.directives.MembersOnly(ctx, nil, directive1) - } - - next = directive2 - return next - }, - ec.marshalNExportTrustCenterFilePayload2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋtrustᚋv1ᚋtypesᚐExportTrustCenterFilePayload, - true, - true, - ) -} - -func (ec *executionContext) fieldContext_Mutation_exportTrustCenterFile(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "Mutation", - Field: field, - IsMethod: true, - IsResolver: true, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - switch field.Name { - case "data": - return ec.fieldContext_ExportTrustCenterFilePayload_data(ctx, field) - } - return nil, fmt.Errorf("no field named %q was found under type ExportTrustCenterFilePayload", field.Name) - }, - } - defer func() { - if r := recover(); r != nil { - err = ec.Recover(ctx, r) - ec.Error(ctx, err) - } - }() - ctx = graphql.WithFieldContext(ctx, fc) - if fc.Args, err = ec.field_Mutation_exportTrustCenterFile_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return fc, err - } - return fc, nil -} - func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -8680,6 +8640,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "exportTrustCenterFile": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_exportTrustCenterFile(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "acceptNonDisclosureAgreement": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_acceptNonDisclosureAgreement(ctx, field) @@ -8705,13 +8672,6 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } - case "exportTrustCenterFile": - out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { - return ec._Mutation_exportTrustCenterFile(ctx, field) - }) - if out.Values[i] == graphql.Null { - out.Invalids++ - } default: panic("unknown field " + strconv.Quote(field.Name)) } diff --git a/pkg/server/api/trust/v1/trustauth/auth.go b/pkg/server/api/trust/v1/trustauth/auth.go deleted file mode 100644 index 7cf382d2e..000000000 --- a/pkg/server/api/trust/v1/trustauth/auth.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) 2025 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 trustauth - -import ( - "context" - "fmt" - - "github.com/99designs/gqlgen/graphql" - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/mail" - "go.probo.inc/probo/pkg/server/api/trust/v1/types" -) - -type TokenAccessData struct { - TrustCenterID gid.GID - Email mail.Addr - TenantID gid.TenantID - Scope string -} - -type ContextAccessor interface { - IdentityFromContext(ctx context.Context) *coredata.Identity - TokenAccessFromContext(ctx context.Context) *TokenAccessData -} - -func ValidateTenantAccess(ctx context.Context, accessor ContextAccessor, userTenantContextKey interface{}, resourceTenantID gid.TenantID) error { - tokenAccess := accessor.TokenAccessFromContext(ctx) - if tokenAccess != nil { - if tokenAccess.TenantID != resourceTenantID { - return fmt.Errorf("access denied: token not authorized for this organization") - } - return nil - } - - identity := accessor.IdentityFromContext(ctx) - if identity != nil { - userTenants, ok := ctx.Value(userTenantContextKey).(*[]gid.TenantID) - if !ok || userTenants == nil { - return fmt.Errorf("access denied: no tenant information available") - } - - for _, tenantID := range *userTenants { - if tenantID == resourceTenantID { - return nil - } - } - return fmt.Errorf("access denied: not authorized for this organization") - } - - return fmt.Errorf("access denied: authentication required") -} - -func GetCurrentUserRole(ctx context.Context, accessor ContextAccessor) types.Role { - identity := accessor.IdentityFromContext(ctx) - tokenAccess := accessor.TokenAccessFromContext(ctx) - - if identity != nil || tokenAccess != nil { - return types.RoleUser - } - return types.RoleNone -} - -func MustBeAuthenticatedDirective(accessor ContextAccessor) func(ctx context.Context, obj any, next graphql.Resolver, role *types.Role) (any, error) { - return func(ctx context.Context, obj any, next graphql.Resolver, role *types.Role) (any, error) { - currentRole := GetCurrentUserRole(ctx, accessor) - - if role != nil && *role == types.RoleUser && currentRole == types.RoleNone { - return nil, fmt.Errorf("access denied: authentication required") - } - - return next(ctx) - } -} diff --git a/pkg/server/api/trust/v1/types/types.go b/pkg/server/api/trust/v1/types/types.go index d3843cf6f..97622aaa6 100644 --- a/pkg/server/api/trust/v1/types/types.go +++ b/pkg/server/api/trust/v1/types/types.go @@ -3,10 +3,6 @@ package types import ( - "bytes" - "fmt" - "io" - "strconv" "time" "go.probo.inc/probo/pkg/coredata" @@ -275,58 +271,3 @@ type VerifyMagicLinkInput struct { type VerifyMagicLinkPayload struct { Success bool `json:"success"` } - -type Role string - -const ( - RoleNone Role = "NONE" - RoleUser Role = "USER" -) - -var AllRole = []Role{ - RoleNone, - RoleUser, -} - -func (e Role) IsValid() bool { - switch e { - case RoleNone, RoleUser: - return true - } - return false -} - -func (e Role) String() string { - return string(e) -} - -func (e *Role) UnmarshalGQL(v any) error { - str, ok := v.(string) - if !ok { - return fmt.Errorf("enums must be strings") - } - - *e = Role(str) - if !e.IsValid() { - return fmt.Errorf("%s is not a valid Role", str) - } - return nil -} - -func (e Role) MarshalGQL(w io.Writer) { - fmt.Fprint(w, strconv.Quote(e.String())) -} - -func (e *Role) UnmarshalJSON(b []byte) error { - s, err := strconv.Unquote(string(b)) - if err != nil { - return err - } - return e.UnmarshalGQL(s) -} - -func (e Role) MarshalJSON() ([]byte, error) { - var buf bytes.Buffer - e.MarshalGQL(&buf) - return buf.Bytes(), nil -} diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index 0e40b99e6..de1c4f3cd 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -343,11 +343,6 @@ func (r *mutationResolver) ExportDocumentPDF(ctx context.Context, input types.Ex // ExportReportPDF is the resolver for the exportReportPDF field. func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.ExportReportPDFInput) (*types.ExportReportPDFPayload, error) { - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") - } - trustService := r.TrustService(ctx, input.ReportID.TenantID()) trustCenter := compliancepage.CompliancePageFromContext(ctx) @@ -370,6 +365,11 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo }, nil } + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") + } + ndaExists := true hasAcceptedNDA := false @@ -421,6 +421,83 @@ func (r *mutationResolver) ExportReportPDF(ctx context.Context, input types.Expo }, nil } +// ExportTrustCenterFile is the resolver for the exportTrustCenterFile field. +func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) { + trustCenter := compliancepage.CompliancePageFromContext(ctx) + trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) + + trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, input.TrustCenterFileID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { + fileData, err := trustService.TrustCenterFiles.ExportFileWithoutWatermark(ctx, input.TrustCenterFileID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportTrustCenterFilePayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(fileData)), + }, nil + } + + identity := authn.IdentityFromContext(ctx) + if identity == nil { + return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") + } + + ndaExists := true + hasAcceptedNDA := false + + if trustCenter.NonDisclosureAgreementFileID == nil { + ndaExists = false + } + + if ndaExists { + hasAcceptedNDA, err = trustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, + trustCenter.ID, + identity.EmailAddress, + ) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot check if user has accepted NDA", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + + fileAccess, err := trustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, + trustCenter.ID, + identity.EmailAddress, + input.TrustCenterFileID, + ) + if err != nil { + // FIXME check for not found and return without error in this case + // r.logger.ErrorCtx(ctx, "cannot check trust center file access", log.Error(err)) + // return false, gqlutils.Internal(ctx) + return nil, nil + } + + if fileAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { + return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this file") + } + + if ndaExists && !hasAcceptedNDA { + return nil, gqlutils.Forbiddenf(ctx, "user has not accepted NDA") + } + + fileData, err := trustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, identity.EmailAddress) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.ExportTrustCenterFilePayload{ + Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(fileData)), + }, nil +} + // AcceptNonDisclosureAgreement is the resolver for the acceptNonDisclosureAgreement field. func (r *mutationResolver) AcceptNonDisclosureAgreement(ctx context.Context, input types.AcceptNonDisclosureAgreementInput) (*types.AcceptNonDisclosureAgreementPayload, error) { identity := authn.IdentityFromContext(ctx) @@ -604,83 +681,6 @@ func (r *mutationResolver) RequestTrustCenterFileAccess(ctx context.Context, inp }, nil } -// ExportTrustCenterFile is the resolver for the exportTrustCenterFile field. -func (r *mutationResolver) ExportTrustCenterFile(ctx context.Context, input types.ExportTrustCenterFileInput) (*types.ExportTrustCenterFilePayload, error) { - identity := authn.IdentityFromContext(ctx) - if identity == nil { - return nil, gqlutils.Unauthenticatedf(ctx, "unauthenticated") - } - - trustCenter := compliancepage.CompliancePageFromContext(ctx) - trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) - - trustCenterFile, err := trustService.TrustCenterFiles.Get(ctx, input.TrustCenterFileID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot load trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - if trustCenterFile.TrustCenterVisibility == coredata.TrustCenterVisibilityPublic { - fileData, err := trustService.TrustCenterFiles.ExportFileWithoutWatermark(ctx, input.TrustCenterFileID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportTrustCenterFilePayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(fileData)), - }, nil - } - - ndaExists := true - hasAcceptedNDA := false - - if trustCenter.NonDisclosureAgreementFileID == nil { - ndaExists = false - } - - if ndaExists { - hasAcceptedNDA, err = trustService.TrustCenterAccesses.HasAcceptedNonDisclosureAgreement(ctx, - trustCenter.ID, - identity.EmailAddress, - ) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot check if user has accepted NDA", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - fileAccess, err := trustService.TrustCenterAccesses.LoadTrustCenterFileAccess(ctx, - trustCenter.ID, - identity.EmailAddress, - input.TrustCenterFileID, - ) - if err != nil { - // FIXME check for not found and return without error in this case - // r.logger.ErrorCtx(ctx, "cannot check trust center file access", log.Error(err)) - // return false, gqlutils.Internal(ctx) - return nil, nil - } - - if fileAccess.Status != coredata.TrustCenterDocumentAccessStatusGranted { - return nil, gqlutils.Forbiddenf(ctx, "access denied: no permission to access this file") - } - - if ndaExists && !hasAcceptedNDA { - return nil, gqlutils.Forbiddenf(ctx, "user has not accepted NDA") - } - - fileData, err := trustService.TrustCenterFiles.ExportFile(ctx, input.TrustCenterFileID, identity.EmailAddress) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot export trust center file", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.ExportTrustCenterFilePayload{ - Data: fmt.Sprintf("data:application/pdf;base64,%s", base64.StdEncoding.EncodeToString(fileData)), - }, nil -} - // LogoURL is the resolver for the logoUrl field. func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { trustService := r.TrustService(ctx, obj.ID.TenantID()) diff --git a/pkg/trust/service.go b/pkg/trust/service.go index e7679ec99..d389df7e5 100644 --- a/pkg/trust/service.go +++ b/pkg/trust/service.go @@ -29,7 +29,6 @@ import ( "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/iam" - "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/slack" ) @@ -269,29 +268,3 @@ func (s *Service) GetCustomDomainByOrganizationID(ctx context.Context, organizat return customDomain, err } - -func (s *Service) GetMembershipByCompliancePageIDAndEmail(ctx context.Context, compliancePageID gid.GID, email mail.Addr) (*coredata.TrustCenterAccess, error) { - membership := &coredata.TrustCenterAccess{} - - err := s.pg.WithConn( - ctx, - func(conn pg.Conn) error { - return membership.LoadByTrustCenterIDAndEmail( - ctx, - conn, - coredata.NewScopeFromObjectID(compliancePageID), - compliancePageID, - email, - ) - }, - ) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, ErrMembershipNotFound - } - - return nil, err - } - - return membership, nil -}