From 1af887e4d1cd4905121d948e5cb4237156bc2671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 4 Mar 2026 20:25:58 +0400 Subject: [PATCH] Respond with unauthenticated on compliance page missing membership MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- pkg/iam/auth_service.go | 9 +++++ pkg/server/api/trust/v1/graphql_handler.go | 2 +- pkg/server/api/trust/v1/nda_directive.go | 24 ++----------- pkg/server/api/trust/v1/schema.graphql | 4 +-- pkg/server/api/trust/v1/schema/schema.go | 8 ++--- pkg/server/api/trust/v1/v1_resolver.go | 41 ++++++++++++++++++++-- 6 files changed, 57 insertions(+), 31 deletions(-) diff --git a/pkg/iam/auth_service.go b/pkg/iam/auth_service.go index 6ac8c551d..d87855f24 100644 --- a/pkg/iam/auth_service.go +++ b/pkg/iam/auth_service.go @@ -673,6 +673,15 @@ func (s AuthService) SendMagicLink(ctx context.Context, req *SendMagicLinkReques ) } +func (s AuthService) GetMagincLinkEmail(ctx context.Context, tokenString string) (mail.Addr, error) { + payload, err := statelesstoken.ValidateToken[MagicLinkData](s.tokenSecret, TokenTypeMagicLink, tokenString) + if err != nil { + return mail.Nil, NewInvalidTokenError() + } + + return payload.Data.Email, nil +} + func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString string) (*coredata.Identity, *coredata.Session, *string, error) { var ( now = time.Now() diff --git a/pkg/server/api/trust/v1/graphql_handler.go b/pkg/server/api/trust/v1/graphql_handler.go index 31aa599a7..2040f8c11 100644 --- a/pkg/server/api/trust/v1/graphql_handler.go +++ b/pkg/server/api/trust/v1/graphql_handler.go @@ -40,7 +40,7 @@ func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *e sessionCookie: authn.NewCookie(&cookieConfig), }, Directives: schema.DirectiveRoot{ - Nda: newNDADirectiveFunc(logger, trustSvc, esignSvc), + Nda: newNDADirective(logger, esignSvc), Session: session.Directive, }, } diff --git a/pkg/server/api/trust/v1/nda_directive.go b/pkg/server/api/trust/v1/nda_directive.go index 1ac25e077..daa2b0aa8 100644 --- a/pkg/server/api/trust/v1/nda_directive.go +++ b/pkg/server/api/trust/v1/nda_directive.go @@ -16,7 +16,6 @@ package trust_v1 import ( "context" - "errors" "github.com/99designs/gqlgen/graphql" "go.gearno.de/kit/log" @@ -25,12 +24,10 @@ import ( "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/compliancepage" "go.probo.inc/probo/pkg/server/gqlutils" - "go.probo.inc/probo/pkg/trust" ) -func newNDADirectiveFunc( +func newNDADirective( logger *log.Logger, - trustSvc *trust.Service, esignSvc *esign.Service, ) func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { return func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { @@ -41,24 +38,7 @@ func newNDADirectiveFunc( membership := compliancepage.ComplianceMembershipFromContext(ctx) if membership == nil { - // Connected user should always have a membership - logger.ErrorCtx(ctx, "cannot get membership from context") - return nil, gqlutils.Internal(ctx) - } - - compliancePage := compliancepage.CompliancePageFromContext(ctx) - if compliancePage == nil { - logger.ErrorCtx(ctx, "cannot get compliance page from context") - return nil, gqlutils.Internal(ctx) - } - - if _, err := trustSvc.GetNDAFile(ctx, compliancePage.ID); err != nil { - if errors.Is(err, trust.ErrNDAFileNotFound) { - return next(ctx) - } - - logger.ErrorCtx(ctx, "cannot get NDA file", log.Error(err)) - return nil, gqlutils.Internal(ctx) + return nil, gqlutils.Unauthenticatedf(ctx, "authentication needed") } if membership.ElectronicSignatureID == nil { diff --git a/pkg/server/api/trust/v1/schema.graphql b/pkg/server/api/trust/v1/schema.graphql index 47c10260e..bcc8fed06 100644 --- a/pkg/server/api/trust/v1/schema.graphql +++ b/pkg/server/api/trust/v1/schema.graphql @@ -824,9 +824,9 @@ type Query { type Mutation { sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload - @session(required: NONE) + @session(required: OPTIONAL) verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload - @session(required: NONE) + @session(required: OPTIONAL) requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda diff --git a/pkg/server/api/trust/v1/schema/schema.go b/pkg/server/api/trust/v1/schema/schema.go index d86638b49..6ec92d8c1 100644 --- a/pkg/server/api/trust/v1/schema/schema.go +++ b/pkg/server/api/trust/v1/schema/schema.go @@ -2246,9 +2246,9 @@ type Query { type Mutation { sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload - @session(required: NONE) + @session(required: OPTIONAL) verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload - @session(required: NONE) + @session(required: OPTIONAL) requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda @@ -4278,7 +4278,7 @@ func (ec *executionContext) _Mutation_sendMagicLink(ctx context.Context, field g directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") if err != nil { var zeroVal *types.SendMagicLinkPayload return zeroVal, err @@ -4341,7 +4341,7 @@ func (ec *executionContext) _Mutation_verifyMagicLink(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, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") if err != nil { var zeroVal *types.VerifyMagicLinkPayload return zeroVal, err diff --git a/pkg/server/api/trust/v1/v1_resolver.go b/pkg/server/api/trust/v1/v1_resolver.go index aa5f5db29..3c9bcb6b8 100644 --- a/pkg/server/api/trust/v1/v1_resolver.go +++ b/pkg/server/api/trust/v1/v1_resolver.go @@ -210,17 +210,54 @@ func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMa // VerifyMagicLink is the resolver for the verifyMagicLink field. func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) { - identity, session, continueURL, err := r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + session := authn.SessionFromContext(ctx) + identity := authn.IdentityFromContext(ctx) + + email, err := r.iam.AuthService.GetMagincLinkEmail(ctx, input.Token) if err != nil { var errInvalidToken *iam.ErrInvalidToken if errors.As(err, &errInvalidToken) { return nil, gqlutils.Invalid(ctx, err) } - r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + r.logger.ErrorCtx(ctx, "cannot get magic link email", log.Error(err)) return nil, gqlutils.Internal(ctx) } + var continueURL *string + + switch { + case session == nil: + var err error + identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + if err != nil { + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + case identity.EmailAddress != email: + if err := r.iam.SessionService.CloseSession(ctx, session.ID); err != nil { + r.logger.ErrorCtx(ctx, "cannot close session", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + var err error + identity, session, continueURL, err = r.iam.AuthService.OpenSessionWithMagicLink(ctx, input.Token) + if err != nil { + var errInvalidToken *iam.ErrInvalidToken + if errors.As(err, &errInvalidToken) { + return nil, gqlutils.Invalid(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot open session with magic link", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + } + trustCenter := compliancepage.CompliancePageFromContext(ctx) trustService := r.TrustService(ctx, trustCenter.ID.TenantID())