From 10eed9a6a31cab0f6f8d131cf1b0fdae64724bed Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Tue, 13 Jan 2026 14:10:24 +0100 Subject: [PATCH] Extract session directive in a dedicated package Signed-off-by: Bryan Frimin --- pkg/server/api/connect/v1/gqlgen.yaml | 4 +- pkg/server/api/connect/v1/graphql_handler.go | 31 +---- pkg/server/api/connect/v1/schema/schema.go | 93 +++++++-------- pkg/server/api/connect/v1/types/types.go | 57 --------- .../directives/session/schema.graphql | 38 ++++++ .../gqlutils/directives/session/session.go | 108 ++++++++++++++++++ 6 files changed, 198 insertions(+), 133 deletions(-) create mode 100644 pkg/server/gqlutils/directives/session/schema.graphql create mode 100644 pkg/server/gqlutils/directives/session/session.go diff --git a/pkg/server/api/connect/v1/gqlgen.yaml b/pkg/server/api/connect/v1/gqlgen.yaml index eacc879c6..b42e084e4 100644 --- a/pkg/server/api/connect/v1/gqlgen.yaml +++ b/pkg/server/api/connect/v1/gqlgen.yaml @@ -1,4 +1,4 @@ -schema: ["schema.graphql"] +schema: ["schema.graphql", "../../gqlutils/directives/session/schema.graphql"] exec: filename: "schema/schema.go" @@ -20,6 +20,8 @@ call_argument_directives_with_null: true directives: mustBeAuthorized: skip_runtime: false + session: + skip_runtime: false models: ID: diff --git a/pkg/server/api/connect/v1/graphql_handler.go b/pkg/server/api/connect/v1/graphql_handler.go index 46845fba5..b7f7a9421 100644 --- a/pkg/server/api/connect/v1/graphql_handler.go +++ b/pkg/server/api/connect/v1/graphql_handler.go @@ -15,10 +15,8 @@ package connect_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" @@ -26,35 +24,10 @@ import ( "go.probo.inc/probo/pkg/server/api/authn" "go.probo.inc/probo/pkg/server/api/authz" "go.probo.inc/probo/pkg/server/api/connect/v1/schema" - "go.probo.inc/probo/pkg/server/api/connect/v1/types" "go.probo.inc/probo/pkg/server/gqlutils" + "go.probo.inc/probo/pkg/server/gqlutils/directives/session" ) -func SessionDirective(ctx context.Context, obj any, next graphql.Resolver, required types.SessionRequirement) (any, error) { - session := authn.SessionFromContext(ctx) - apiKey := authn.APIKeyFromContext(ctx) - - switch required { - case types.SessionRequirementOptional: - case types.SessionRequirementPresent: - if session == nil && apiKey == nil { - return nil, gqlutils.Unauthenticatedf( - ctx, - "authentication is required to access this resouce", - ) - } - case types.SessionRequirementNone: - if session != nil && apiKey != nil { - return nil, gqlutils.Invalidf( - ctx, - "authentication not allowed for this resource/action", - ) - } - } - - return next(ctx) -} - func NewGraphQLHandler(svc *iam.Service, logger *log.Logger, baseURL *baseurl.BaseURL, cookieConfig securecookie.Config) http.Handler { config := schema.Config{ Resolvers: &Resolver{ @@ -65,7 +38,7 @@ func NewGraphQLHandler(svc *iam.Service, logger *log.Logger, baseURL *baseurl.Ba sessionCookie: authn.NewCookie(&cookieConfig), }, Directives: schema.DirectiveRoot{ - Session: SessionDirective, + Session: session.Directive, }, } diff --git a/pkg/server/api/connect/v1/schema/schema.go b/pkg/server/api/connect/v1/schema/schema.go index 785de3d9d..e2bfc33c1 100644 --- a/pkg/server/api/connect/v1/schema/schema.go +++ b/pkg/server/api/connect/v1/schema/schema.go @@ -21,6 +21,7 @@ import ( "go.probo.inc/probo/pkg/mail" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/server/api/connect/v1/types" + "go.probo.inc/probo/pkg/server/gqlutils/directives/session" "go.probo.inc/probo/pkg/server/gqlutils/types/cursor" gid1 "go.probo.inc/probo/pkg/server/gqlutils/types/gid" mail1 "go.probo.inc/probo/pkg/server/gqlutils/types/mail" @@ -67,7 +68,7 @@ type ResolverRoot interface { } type DirectiveRoot struct { - Session func(ctx context.Context, obj any, next graphql.Resolver, required types.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 { @@ -3160,7 +3161,7 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) func (ec *executionContext) dir_session_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} - arg0, err := graphql.ProcessArgField(ctx, rawArgs, "required", ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement) + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "required", ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement) if err != nil { return nil, err } @@ -4939,7 +4940,7 @@ func (ec *executionContext) _Identity_permission(ctx context.Context, field grap directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -5293,7 +5294,7 @@ func (ec *executionContext) _Invitation_permission(ctx context.Context, field gr directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -5919,7 +5920,7 @@ func (ec *executionContext) _Membership_permission(ctx context.Context, field gr directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -6277,7 +6278,7 @@ func (ec *executionContext) _MembershipProfile_permission(ctx context.Context, f directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -6336,7 +6337,7 @@ func (ec *executionContext) _Mutation_signIn(ctx context.Context, field graphql. directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "OPTIONAL") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") if err != nil { var zeroVal *types.SignInPayload return zeroVal, err @@ -6401,7 +6402,7 @@ func (ec *executionContext) _Mutation_signUp(ctx context.Context, field graphql. directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE") if err != nil { var zeroVal *types.SignUpPayload return zeroVal, err @@ -6463,7 +6464,7 @@ func (ec *executionContext) _Mutation_signOut(ctx context.Context, field graphql directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.SignOutPayload return zeroVal, err @@ -6515,7 +6516,7 @@ func (ec *executionContext) _Mutation_signUpFromInvitation(ctx context.Context, directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE") if err != nil { var zeroVal *types.SignUpFromInvitationPayload return zeroVal, err @@ -6578,7 +6579,7 @@ func (ec *executionContext) _Mutation_forgotPassword(ctx context.Context, field directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE") if err != nil { var zeroVal *types.ForgotPasswordPayload return zeroVal, err @@ -6641,7 +6642,7 @@ func (ec *executionContext) _Mutation_resetPassword(ctx context.Context, field g directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE") if err != nil { var zeroVal *types.ResetPasswordPayload return zeroVal, err @@ -6704,7 +6705,7 @@ func (ec *executionContext) _Mutation_verifyEmail(ctx context.Context, field gra directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "OPTIONAL") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "OPTIONAL") if err != nil { var zeroVal *types.VerifyEmailPayload return zeroVal, err @@ -6767,7 +6768,7 @@ func (ec *executionContext) _Mutation_changePassword(ctx context.Context, field directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.ChangePasswordPayload return zeroVal, err @@ -6830,7 +6831,7 @@ func (ec *executionContext) _Mutation_changeEmail(ctx context.Context, field gra directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.ChangeEmailPayload return zeroVal, err @@ -6893,7 +6894,7 @@ func (ec *executionContext) _Mutation_assumeOrganizationSession(ctx context.Cont directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.AssumeOrganizationSessionPayload return zeroVal, err @@ -6956,7 +6957,7 @@ func (ec *executionContext) _Mutation_revokeSession(ctx context.Context, field g directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.RevokeSessionPayload return zeroVal, err @@ -7018,7 +7019,7 @@ func (ec *executionContext) _Mutation_revokeAllSessions(ctx context.Context, fie directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.RevokeAllSessionsPayload return zeroVal, err @@ -7070,7 +7071,7 @@ func (ec *executionContext) _Mutation_createPersonalAPIKey(ctx context.Context, directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.CreatePersonalAPIKeyPayload return zeroVal, err @@ -7135,7 +7136,7 @@ func (ec *executionContext) _Mutation_revokePersonalAPIKey(ctx context.Context, directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.RevokePersonalAPIKeyPayload return zeroVal, err @@ -7198,7 +7199,7 @@ func (ec *executionContext) _Mutation_createOrganization(ctx context.Context, fi directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.CreateOrganizationPayload return zeroVal, err @@ -7263,7 +7264,7 @@ func (ec *executionContext) _Mutation_updateOrganization(ctx context.Context, fi directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.UpdateOrganizationPayload return zeroVal, err @@ -7326,7 +7327,7 @@ func (ec *executionContext) _Mutation_deleteOrganization(ctx context.Context, fi directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.DeleteOrganizationPayload return zeroVal, err @@ -7389,7 +7390,7 @@ func (ec *executionContext) _Mutation_deleteOrganizationHorizontalLogo(ctx conte directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.DeleteOrganizationHorizontalLogoPayload return zeroVal, err @@ -7452,7 +7453,7 @@ func (ec *executionContext) _Mutation_inviteMember(ctx context.Context, field gr directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.InviteMemberPayload return zeroVal, err @@ -7515,7 +7516,7 @@ func (ec *executionContext) _Mutation_deleteInvitation(ctx context.Context, fiel directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.DeleteInvitationPayload return zeroVal, err @@ -7623,7 +7624,7 @@ func (ec *executionContext) _Mutation_removeMember(ctx context.Context, field gr directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.RemoveMemberPayload return zeroVal, err @@ -7686,7 +7687,7 @@ func (ec *executionContext) _Mutation_acceptInvitation(ctx context.Context, fiel directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.AcceptInvitationPayload return zeroVal, err @@ -7751,7 +7752,7 @@ func (ec *executionContext) _Mutation_createSAMLConfiguration(ctx context.Contex directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.CreateSAMLConfigurationPayload return zeroVal, err @@ -7814,7 +7815,7 @@ func (ec *executionContext) _Mutation_updateSAMLConfiguration(ctx context.Contex directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.UpdateSAMLConfigurationPayload return zeroVal, err @@ -7877,7 +7878,7 @@ func (ec *executionContext) _Mutation_deleteSAMLConfiguration(ctx context.Contex directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.DeleteSAMLConfigurationPayload return zeroVal, err @@ -7940,7 +7941,7 @@ func (ec *executionContext) _Mutation_createSCIMConfiguration(ctx context.Contex directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.CreateSCIMConfigurationPayload return zeroVal, err @@ -8005,7 +8006,7 @@ func (ec *executionContext) _Mutation_deleteSCIMConfiguration(ctx context.Contex directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.DeleteSCIMConfigurationPayload return zeroVal, err @@ -8068,7 +8069,7 @@ func (ec *executionContext) _Mutation_regenerateSCIMToken(ctx context.Context, f directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.RegenerateSCIMTokenPayload return zeroVal, err @@ -8666,7 +8667,7 @@ func (ec *executionContext) _Organization_permission(ctx context.Context, field directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -9142,7 +9143,7 @@ func (ec *executionContext) _PersonalAPIKey_permission(ctx context.Context, fiel directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -9378,7 +9379,7 @@ func (ec *executionContext) _Query_node(ctx context.Context, field graphql.Colle directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal types.Node return zeroVal, err @@ -9436,7 +9437,7 @@ func (ec *executionContext) _Query_viewer(ctx context.Context, field graphql.Col directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal *types.Identity return zeroVal, err @@ -9508,7 +9509,7 @@ func (ec *executionContext) _Query_ssoLoginURL(ctx context.Context, field graphq directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "NONE") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "NONE") if err != nil { var zeroVal *string return zeroVal, err @@ -10455,7 +10456,7 @@ func (ec *executionContext) _SAMLConfiguration_permission(ctx context.Context, f directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -10933,7 +10934,7 @@ func (ec *executionContext) _SCIMConfiguration_permission(ctx context.Context, f directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -11304,7 +11305,7 @@ func (ec *executionContext) _SCIMEvent_permission(ctx context.Context, field gra directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -11775,7 +11776,7 @@ func (ec *executionContext) _Session_permission(ctx context.Context, field graph directive0 := next directive1 := func(ctx context.Context) (any, error) { - required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx, "PRESENT") + required, err := ec.unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx, "PRESENT") if err != nil { var zeroVal bool return zeroVal, err @@ -20614,13 +20615,13 @@ var ( } ) -func (ec *executionContext) unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx context.Context, v any) (types.SessionRequirement, error) { - var res types.SessionRequirement +func (ec *executionContext) unmarshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx context.Context, v any) (session.SessionRequirement, error) { + var res session.SessionRequirement err := res.UnmarshalGQL(v) return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconnectᚋv1ᚋtypesᚐSessionRequirement(ctx context.Context, sel ast.SelectionSet, v types.SessionRequirement) graphql.Marshaler { +func (ec *executionContext) marshalNSessionRequirement2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋgqlutilsᚋdirectivesᚋsessionᚐSessionRequirement(ctx context.Context, sel ast.SelectionSet, v session.SessionRequirement) graphql.Marshaler { return v } diff --git a/pkg/server/api/connect/v1/types/types.go b/pkg/server/api/connect/v1/types/types.go index 8fe176857..639e42279 100644 --- a/pkg/server/api/connect/v1/types/types.go +++ b/pkg/server/api/connect/v1/types/types.go @@ -593,60 +593,3 @@ func (e ReauthenticationReason) MarshalJSON() ([]byte, error) { e.MarshalGQL(&buf) return buf.Bytes(), nil } - -type SessionRequirement string - -const ( - SessionRequirementPresent SessionRequirement = "PRESENT" - SessionRequirementNone SessionRequirement = "NONE" - SessionRequirementOptional SessionRequirement = "OPTIONAL" -) - -var AllSessionRequirement = []SessionRequirement{ - SessionRequirementPresent, - SessionRequirementNone, - SessionRequirementOptional, -} - -func (e SessionRequirement) IsValid() bool { - switch e { - case SessionRequirementPresent, SessionRequirementNone, SessionRequirementOptional: - return true - } - return false -} - -func (e SessionRequirement) String() string { - return string(e) -} - -func (e *SessionRequirement) UnmarshalGQL(v any) error { - str, ok := v.(string) - if !ok { - return fmt.Errorf("enums must be strings") - } - - *e = SessionRequirement(str) - if !e.IsValid() { - return fmt.Errorf("%s is not a valid SessionRequirement", str) - } - return nil -} - -func (e SessionRequirement) MarshalGQL(w io.Writer) { - fmt.Fprint(w, strconv.Quote(e.String())) -} - -func (e *SessionRequirement) UnmarshalJSON(b []byte) error { - s, err := strconv.Unquote(string(b)) - if err != nil { - return err - } - return e.UnmarshalGQL(s) -} - -func (e SessionRequirement) MarshalJSON() ([]byte, error) { - var buf bytes.Buffer - e.MarshalGQL(&buf) - return buf.Bytes(), nil -} diff --git a/pkg/server/gqlutils/directives/session/schema.graphql b/pkg/server/gqlutils/directives/session/schema.graphql new file mode 100644 index 000000000..b61fff24c --- /dev/null +++ b/pkg/server/gqlutils/directives/session/schema.graphql @@ -0,0 +1,38 @@ +# Session directive for GraphQL APIs +# Include this schema in your gqlgen configuration to enable session-based access control. +# +# Usage in your schema.graphql: +# type Query { +# viewer: User @session(required: PRESENT) +# publicData: Data @session(required: OPTIONAL) +# signup(input: SignUpInput!): SignUpPayload @session(required: NONE) +# } + +directive @session(required: SessionRequirement!) on FIELD_DEFINITION + +enum SessionRequirement + @goModel( + model: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirement" + ) { + """ + Requires an authenticated session or API key. + """ + PRESENT + @goEnum( + value: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirementPresent" + ) + """ + Forbids authenticated access (e.g., for login/signup endpoints). + """ + NONE + @goEnum( + value: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirementNone" + ) + """ + Allows both authenticated and unauthenticated access. + """ + OPTIONAL + @goEnum( + value: "go.probo.inc/probo/pkg/server/gqlutils/directives/session.SessionRequirementOptional" + ) +} diff --git a/pkg/server/gqlutils/directives/session/session.go b/pkg/server/gqlutils/directives/session/session.go new file mode 100644 index 000000000..074d9c212 --- /dev/null +++ b/pkg/server/gqlutils/directives/session/session.go @@ -0,0 +1,108 @@ +// 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 session + +import ( + "bytes" + "context" + "fmt" + "io" + "strconv" + + "github.com/99designs/gqlgen/graphql" + "go.probo.inc/probo/pkg/server/api/authn" + "go.probo.inc/probo/pkg/server/gqlutils" +) + +type SessionRequirement string + +const ( + SessionRequirementPresent SessionRequirement = "PRESENT" + SessionRequirementNone SessionRequirement = "NONE" + SessionRequirementOptional SessionRequirement = "OPTIONAL" +) + +var AllSessionRequirement = []SessionRequirement{ + SessionRequirementPresent, + SessionRequirementNone, + SessionRequirementOptional, +} + +func (e SessionRequirement) IsValid() bool { + switch e { + case SessionRequirementPresent, SessionRequirementNone, SessionRequirementOptional: + return true + } + return false +} + +func (e SessionRequirement) String() string { + return string(e) +} + +func (e *SessionRequirement) UnmarshalGQL(v any) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = SessionRequirement(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid SessionRequirement", str) + } + return nil +} + +func (e SessionRequirement) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +func (e *SessionRequirement) UnmarshalJSON(b []byte) error { + s, err := strconv.Unquote(string(b)) + if err != nil { + return err + } + return e.UnmarshalGQL(s) +} + +func (e SessionRequirement) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + e.MarshalGQL(&buf) + return buf.Bytes(), nil +} + +func Directive(ctx context.Context, obj any, next graphql.Resolver, required SessionRequirement) (any, error) { + identity := authn.IdentityFromContext(ctx) + + switch required { + case SessionRequirementOptional: + case SessionRequirementPresent: + if identity == nil { + return nil, gqlutils.Unauthenticatedf( + ctx, + "authentication is required to access this resouce", + ) + } + case SessionRequirementNone: + if identity != nil { + return nil, gqlutils.Invalidf( + ctx, + "authentication not allowed for this resource/action", + ) + } + } + + return next(ctx) +}