graphql

package
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2025 License: MIT Imports: 16 Imported by: 0

README

GraphQL Package

This package provides utilities for working with GraphQL in Go services, including server configuration, error handling, and role-based access control (RBAC).

Features

  • GraphQL server configuration with sensible defaults
  • Error handling with proper context and logging
  • Role-based access control (RBAC) with the @isAuthorized directive
  • Metrics and tracing for GraphQL operations
  • Timeout and cancellation handling

Role-Based Access Control (RBAC)

The package provides a generic implementation of the @isAuthorized directive for GraphQL, which can be used to restrict access to GraphQL operations based on user roles.

Setup
  1. Define a Role enum in your GraphQL schema:
enum Role {
  ADMIN
  EDITOR
  VIEWER
  # Add more roles as needed
}

enum Scope {
  READ
  WRITE
  DELETE
  CREATE
}

enum Resource {
  FAMILY
  PARENT
  CHILD
  ITEM
}

directive @isAuthorized(
  allowedRoles: [Role!]!, 
  requiredScopes: [Scope!] = [READ], 
  resource: Resource = FAMILY
) on FIELD_DEFINITION
  1. Apply the directive to your queries and mutations:
type Query {
  getItem(id: ID!): Item @isAuthorized(
    allowedRoles: [ADMIN, EDITOR, VIEWER], 
    requiredScopes: [READ], 
    resource: ITEM
  )
}

type Mutation {
  createItem(input: ItemInput!): Item! @isAuthorized(
    allowedRoles: [ADMIN, EDITOR], 
    requiredScopes: [CREATE], 
    resource: ITEM
  )
}
  1. Register the directive in your GraphQL server:
schema := generated.NewExecutableSchema(generated.Config{
    Resolvers: resolverInstance,
    Directives: generated.DirectiveRoot{
        IsAuthorized: func(ctx context.Context, obj interface{}, next graphql.Resolver, allowedRoles []string, requiredScopes []string, resource string) (interface{}, error) {
            return graphql.IsAuthorizedDirective(ctx, obj, next, allowedRoles, requiredScopes, resource, logger)
        },
    },
})
JWT Authentication

The RBAC implementation works with the JWT authentication middleware from the servicelib/auth package. The middleware extracts the user's roles, scopes, and resources from the JWT token and adds them to the request context, which is then used by the @isAuthorized directive to check if the user has the required roles, scopes, and access to the specified resource.

To set up JWT authentication:

  1. Configure the auth service in your dependency injection container:
authConfig := auth.DefaultConfig()
authConfig.JWT.SecretKey = cfg.Auth.JWT.SecretKey
authConfig.JWT.Issuer = cfg.Auth.JWT.Issuer
authConfig.JWT.TokenDuration = cfg.Auth.JWT.TokenDuration
authConfig.Middleware.SkipPaths = []string{"/health", "/metrics", "/playground"}

authService, err := auth.New(ctx, authConfig, logger)
if err != nil {
    return nil, fmt.Errorf("failed to initialize auth service: %w", err)
}
  1. Apply the auth middleware to your HTTP handler:
handler = authService.Middleware()(handler)
Helper Functions

The package provides several helper functions for working with RBAC:

  • IsAuthorizedDirective: The implementation of the @isAuthorized directive that checks roles, scopes, and resources
  • CheckAuthorization: A helper function for checking authorization in resolvers with roles, scopes, and resources
  • ConvertRolesToStrings: A generic helper function for converting enum types to strings
  • IsAuthorizedWithScopes: A function that checks if a user has the required roles, scopes, and access to a resource
  • HasScope: A function that checks if a user has a specific scope
  • HasResource: A function that checks if a user has access to a specific resource

Example Usage

// In your resolver
func (r *Resolver) CreateItem(ctx context.Context, input model.ItemInput) (*model.Item, error) {
    // Check authorization manually if needed
    if err := graphql.CheckAuthorization(ctx, []string{"ADMIN", "EDITOR"}, []string{"CREATE"}, "ITEM", "CreateItem", r.logger); err != nil {
        return nil, err
    }

    // Proceed with the operation
    // ...
}

Generating JWT Tokens

To test the RBAC implementation, you can use the genjwt tool to generate JWT tokens with different roles, scopes, and resources:

// Generate admin token with all scopes for all resources
adminScopes := []string{"READ", "WRITE", "DELETE", "CREATE"}
adminResources := []string{"FAMILY", "PARENT", "CHILD"}
adminToken, err := authInstance.GenerateToken(ctx, "admin", []string{"ADMIN"}, adminScopes, adminResources)

// Generate editor token with all scopes for all resources
editorScopes := []string{"READ", "WRITE", "DELETE", "CREATE"}
editorResources := []string{"FAMILY", "PARENT", "CHILD"}
editorToken, err := authInstance.GenerateToken(ctx, "editor", []string{"EDITOR"}, editorScopes, editorResources)

// Generate viewer token with only READ scope for all resources
viewerScopes := []string{"READ"}
viewerResources := []string{"FAMILY", "PARENT", "CHILD"}
viewerToken, err := authInstance.GenerateToken(ctx, "viewer", []string{"VIEWER"}, viewerScopes, viewerResources)

Metrics

The package provides metrics for authorization checks:

  • authorization_check_duration_seconds: Duration of authorization checks in seconds
  • authorization_failures_total: Total number of authorization failures

These metrics are automatically registered with Prometheus and can be used to monitor the performance and security of your GraphQL API.

Documentation

Overview

Package graphql provides utilities for working with GraphQL.

Package graphql provides utilities for working with GraphQL.

Package graphql provides utilities for working with GraphQL.

Index

Constants

This section is empty.

Variables

View Source
var (
	// AuthorizationCheckDuration measures the duration of authorization checks
	AuthorizationCheckDuration = prometheus.NewHistogram(prometheus.HistogramOpts{
		Name:    "authorization_check_duration_seconds",
		Help:    "Duration of authorization checks in seconds",
		Buckets: prometheus.DefBuckets,
	})

	// AuthorizationFailures counts the number of failed authorization attempts
	AuthorizationFailures = prometheus.NewCounter(prometheus.CounterOpts{
		Name: "authorization_failures_total",
		Help: "Total number of authorization failures",
	})
)

Functions

func CheckAuthorization

func CheckAuthorization(ctx context.Context, allowedRoles []string, requiredScopes []string, resource string, operation string, logger *logging.ContextLogger) error

CheckAuthorization is a helper function to check if the user is authorized to perform an operation It can be used in resolvers to perform custom authorization checks

Parameters:

  • ctx: The context of the request
  • allowedRoles: The roles that are allowed to perform the operation
  • requiredScopes: The scopes that are required to perform the operation (optional)
  • resource: The resource being accessed (optional)
  • operation: The name of the operation being performed
  • logger: A context logger for logging (optional, can be nil)

Returns:

  • An error if not authorized, nil if authorized

func ConvertRolesToStrings

func ConvertRolesToStrings[T fmt.Stringer](roles []T) []string

ConvertRolesToStrings converts a slice of enum roles to a slice of strings This is useful when working with generated enum types for roles

Parameters:

  • roles: A slice of role enums that have a String() method

Returns:

  • A slice of strings representing the roles

func HandleError

func HandleError(ctx context.Context, err error, operation string, logger *logging.ContextLogger) error

HandleError processes an error and returns an appropriate GraphQL error. It logs the error and converts it to a GraphQL error with appropriate extensions. Parameters:

  • ctx: The context containing trace information
  • err: The error to handle
  • operation: The name of the operation that caused the error
  • logger: The logger to use for logging the error

Returns:

  • error: A GraphQL error with appropriate extensions

func IsAuthorizedDirective

func IsAuthorizedDirective(ctx context.Context, obj interface{}, next graphql.Resolver, allowedRoles []string, requiredScopes []string, resource string, logger *logging.ContextLogger) (interface{}, error)

IsAuthorizedDirective implements the @isAuthorized directive for GraphQL It checks if the user has any of the allowed roles and all of the required scopes for the specified resource

Parameters:

  • ctx: The context of the request
  • obj: The object being resolved
  • next: The next resolver in the chain
  • allowedRoles: The roles that are allowed to access this field
  • requiredScopes: The scopes that are required to access this field (optional)
  • resource: The resource being accessed (optional)
  • logger: A context logger for logging (optional, can be nil)

Returns:

  • The result of the next resolver if authorized
  • An error if not authorized

func NewServer

func NewServer(schema graphql.ExecutableSchema, logger *logging.ContextLogger, cfg ServerConfig) *handler.Server

NewServer creates a new GraphQL server with the given schema and configuration

Types

type ServerConfig

type ServerConfig struct {
	MaxQueryDepth      int
	MaxQueryComplexity int
	RequestTimeout     time.Duration
}

ServerConfig contains configuration for the GraphQL server

func NewDefaultServerConfig

func NewDefaultServerConfig() ServerConfig

NewDefaultServerConfig creates a new server configuration with default values

Directories

Path Synopsis
Package mocks is a generated GoMock package.
Package mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL