Respond with unauthenticated on compliance page missing membership

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-03-04 20:25:58 +04:00
parent 3bf6e67729
commit 1af887e4d1
6 changed files with 57 additions and 31 deletions

View File

@@ -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) { func (s AuthService) OpenSessionWithMagicLink(ctx context.Context, tokenString string) (*coredata.Identity, *coredata.Session, *string, error) {
var ( var (
now = time.Now() now = time.Now()

View File

@@ -40,7 +40,7 @@ func NewGraphQLHandler(iamSvc *iam.Service, trustSvc *trust.Service, esignSvc *e
sessionCookie: authn.NewCookie(&cookieConfig), sessionCookie: authn.NewCookie(&cookieConfig),
}, },
Directives: schema.DirectiveRoot{ Directives: schema.DirectiveRoot{
Nda: newNDADirectiveFunc(logger, trustSvc, esignSvc), Nda: newNDADirective(logger, esignSvc),
Session: session.Directive, Session: session.Directive,
}, },
} }

View File

@@ -16,7 +16,6 @@ package trust_v1
import ( import (
"context" "context"
"errors"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
"go.gearno.de/kit/log" "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/authn"
"go.probo.inc/probo/pkg/server/api/compliancepage" "go.probo.inc/probo/pkg/server/api/compliancepage"
"go.probo.inc/probo/pkg/server/gqlutils" "go.probo.inc/probo/pkg/server/gqlutils"
"go.probo.inc/probo/pkg/trust"
) )
func newNDADirectiveFunc( func newNDADirective(
logger *log.Logger, logger *log.Logger,
trustSvc *trust.Service,
esignSvc *esign.Service, esignSvc *esign.Service,
) func(ctx context.Context, obj any, next graphql.Resolver) (any, error) { ) func(ctx context.Context, obj any, next graphql.Resolver) (any, error) {
return 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) membership := compliancepage.ComplianceMembershipFromContext(ctx)
if membership == nil { if membership == nil {
// Connected user should always have a membership return nil, gqlutils.Unauthenticatedf(ctx, "authentication needed")
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)
} }
if membership.ElectronicSignatureID == nil { if membership.ElectronicSignatureID == nil {

View File

@@ -824,9 +824,9 @@ type Query {
type Mutation { type Mutation {
sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload
@session(required: NONE) @session(required: OPTIONAL)
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
@session(required: NONE) @session(required: OPTIONAL)
requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda

View File

@@ -2246,9 +2246,9 @@ type Query {
type Mutation { type Mutation {
sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload sendMagicLink(input: SendMagicLinkInput!): SendMagicLinkPayload
@session(required: NONE) @session(required: OPTIONAL)
verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload verifyMagicLink(input: VerifyMagicLinkInput!): VerifyMagicLinkPayload
@session(required: NONE) @session(required: OPTIONAL)
requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda requestAllAccesses: RequestAccessesPayload! @session(required: PRESENT) @nda
@@ -4278,7 +4278,7 @@ func (ec *executionContext) _Mutation_sendMagicLink(ctx context.Context, field g
directive0 := next directive0 := next
directive1 := func(ctx context.Context) (any, error) { 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 { if err != nil {
var zeroVal *types.SendMagicLinkPayload var zeroVal *types.SendMagicLinkPayload
return zeroVal, err return zeroVal, err
@@ -4341,7 +4341,7 @@ func (ec *executionContext) _Mutation_verifyMagicLink(ctx context.Context, field
directive0 := next directive0 := next
directive1 := func(ctx context.Context) (any, error) { 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 { if err != nil {
var zeroVal *types.VerifyMagicLinkPayload var zeroVal *types.VerifyMagicLinkPayload
return zeroVal, err return zeroVal, err

View File

@@ -210,17 +210,54 @@ func (r *mutationResolver) SendMagicLink(ctx context.Context, input types.SendMa
// VerifyMagicLink is the resolver for the verifyMagicLink field. // VerifyMagicLink is the resolver for the verifyMagicLink field.
func (r *mutationResolver) VerifyMagicLink(ctx context.Context, input types.VerifyMagicLinkInput) (*types.VerifyMagicLinkPayload, error) { 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 { if err != nil {
var errInvalidToken *iam.ErrInvalidToken var errInvalidToken *iam.ErrInvalidToken
if errors.As(err, &errInvalidToken) { if errors.As(err, &errInvalidToken) {
return nil, gqlutils.Invalid(ctx, err) 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) 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) trustCenter := compliancepage.CompliancePageFromContext(ctx)
trustService := r.TrustService(ctx, trustCenter.ID.TenantID()) trustService := r.TrustService(ctx, trustCenter.ID.TenantID())