Skip to content

Commit

Permalink
graphql: add register middleware based on schema
Browse files Browse the repository at this point in the history
  • Loading branch information
agungdwiprasetyo committed Oct 29, 2020
1 parent 5f6709b commit 523c7ca
Show file tree
Hide file tree
Showing 16 changed files with 116 additions and 48 deletions.
28 changes: 16 additions & 12 deletions candishared/context.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package candishared

import (
"context"
)
import "context"

// ContextKey represent Key of all context
type ContextKey string

const (
// ContextKeyHTTPHeader context key
ContextKeyHTTPHeader ContextKey = "httpHeader"

// ContextKeyTaskQueueRetry context key
ContextKeyTaskQueueRetry ContextKey = "taskQueueRetry"

// ContextKeyTokenClaim context key
ContextKeyTokenClaim ContextKey = "tokenClaim"
)

// SetToContext will set context with specific key
func SetToContext(ctx context.Context, key ContextKey, value interface{}) context.Context {
return context.WithValue(ctx, key, value)
Expand All @@ -17,12 +26,7 @@ func GetValueFromContext(ctx context.Context, key ContextKey) interface{} {
return ctx.Value(key)
}

type contextKeyStruct struct{}

var (
// HTTPHeaderContextKey context key
HTTPHeaderContextKey = contextKeyStruct{}

// TaskQueueRetryContextKey context key
TaskQueueRetryContextKey = contextKeyStruct{}
)
// ParseTokenClaimFromContext parse token claim from given context
func ParseTokenClaimFromContext(ctx context.Context) *TokenClaim {
return GetValueFromContext(ctx, ContextKeyTokenClaim).(*TokenClaim)
}
7 changes: 1 addition & 6 deletions candishared/token_claim_payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,5 @@ import "github.com/dgrijalva/jwt-go"
// TokenClaim for token claim data
type TokenClaim struct {
jwt.StandardClaims
DeviceID string `json:"did"`
User struct {
ID string `json:"id"`
Username string `json:"username"`
} `json:"user"`
Alg string `json:"-"`
Additional map[string]interface{}
}
1 change: 1 addition & 0 deletions cmd/candi/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ func main() {
{TargetDir: "usecase/", IsDir: true, Childs: []FileStructure{
{FromTemplate: true, DataSource: srvConfig, Source: templateUsecaseUOW, FileName: "usecase.go"},
}},
{FromTemplate: true, DataSource: srvConfig, Source: templateSharedTokenValidator, FileName: "token_validator.go"},
}},
}...)
baseDirectoryFile.Childs = []FileStructure{
Expand Down
3 changes: 2 additions & 1 deletion cmd/candi/template_configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package configs
import (
"context"
"{{.GoModName}}/pkg/shared"
{{ if not (or .SQLDeps .MongoDeps) }}// {{ end }}"{{.GoModName}}/pkg/shared/repository"
{{ if not (or .SQLDeps .MongoDeps) }}// {{ end }}"{{.PackageName}}/candihelper"
Expand Down Expand Up @@ -39,7 +40,7 @@ func LoadConfigs(baseCfg *config.Config) (deps dependency.Dependency) {
// inject all service dependencies
// See all option in dependency package
deps = dependency.InitDependency(
dependency.SetMiddleware(middleware.NewMiddleware(nil)),
dependency.SetMiddleware(middleware.NewMiddleware(&shared.DefaultTokenValidator{})),
dependency.SetValidator(validator.NewValidator()),
{{if not .KafkaDeps}}// {{end}}dependency.SetBroker(KafkaDeps),
{{if not .RedisDeps}}// {{end}}dependency.SetRedisPool(redisDeps),
Expand Down
5 changes: 5 additions & 0 deletions cmd/candi/template_delivery_graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func NewGraphQLHandler(mw interfaces.Middleware, uc usecase.{{clean (upper .Modu
return h
}
// RegisterMiddleware register resolver based on schema in "api/graphql/*" path
func (h *GraphQLHandler) RegisterMiddleware(group *types.GraphQLMiddlewareGroup) {
group.Add("{{clean (upper .ModuleName)}}QueryModule.hello", h.mw.GraphQLBearerAuth)
}
// Query method
func (h *GraphQLHandler) Query() interface{} {
return &queryResolver{root: h}
Expand Down
22 changes: 22 additions & 0 deletions cmd/candi/template_shared.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

const (
templateSharedTokenValidator = `// {{.Header}}
package shared
import (
"context"
"{{.PackageName}}/candishared"
)
// DefaultTokenValidator for token validator
type DefaultTokenValidator struct {
}
// ValidateToken implement TokenValidator
func (v *DefaultTokenValidator) ValidateToken(ctx context.Context, token string) (*candishared.TokenClaim, error) {
return &candishared.TokenClaim{}, nil
}
`
)
2 changes: 1 addition & 1 deletion cmd/candi/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const (
packageName = "pkg.agungdwiprasetyo.com/candi"
initService = "initservice"
addModule = "addmodule"
version = "v0.0.11"
version = "v0.0.12"

restHandler = "restHandler"
grpcHandler = "grpcHandler"
Expand Down
8 changes: 5 additions & 3 deletions codebase/app/graphql_server/graphql_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import (
"pkg.agungdwiprasetyo.com/candi/candihelper"
"pkg.agungdwiprasetyo.com/candi/candishared"
"pkg.agungdwiprasetyo.com/candi/codebase/factory"
"pkg.agungdwiprasetyo.com/candi/codebase/factory/types"
"pkg.agungdwiprasetyo.com/candi/config"
"pkg.agungdwiprasetyo.com/candi/logger"
"pkg.agungdwiprasetyo.com/candi/tracer"

graphql "github.com/golangid/graphql-go"
"github.com/graph-gophers/graphql-transport-ws/graphqlws"
Expand Down Expand Up @@ -85,10 +85,12 @@ func NewHandler(service factory.ServiceFactory) Handler {
queryResolverValues := make(map[string]interface{})
mutationResolverValues := make(map[string]interface{})
subscriptionResolverValues := make(map[string]interface{})
middlewareResolvers := make(types.GraphQLMiddlewareGroup)
var queryResolverFields, mutationResolverFields, subscriptionResolverFields []reflect.StructField
for _, m := range service.GetModules() {
if resolverModule := m.GraphQLHandler(); resolverModule != nil {
rootName := string(m.Name())
resolverModule.RegisterMiddleware(&middlewareResolvers)
query, mutation, subscription := resolverModule.Query(), resolverModule.Mutation(), resolverModule.Subscription()

appendStructField(rootName, query, &queryResolverFields)
Expand All @@ -109,7 +111,7 @@ func NewHandler(service factory.ServiceFactory) Handler {
schemaOpts := []graphql.SchemaOpt{
graphql.UseStringDescriptions(),
graphql.UseFieldResolvers(),
graphql.Tracer(&tracer.GraphQLTracer{}),
graphql.Tracer(newGraphQLTracer(middlewareResolvers)),
graphql.Logger(&panicLogger{}),
}
if config.BaseEnv().IsProduction {
Expand Down Expand Up @@ -154,7 +156,7 @@ func (s *handlerImpl) ServeGraphQL() http.HandlerFunc {
}
req.Header.Set("X-Real-IP", ip)

ctx := context.WithValue(req.Context(), candishared.HTTPHeaderContextKey, req.Header)
ctx := context.WithValue(req.Context(), candishared.ContextKeyHTTPHeader, req.Header)
response := s.schema.Exec(ctx, params.Query, params.OperationName, params.Variables)
responseJSON, err := json.Marshal(response)
if err != nil {
Expand Down
40 changes: 26 additions & 14 deletions tracer/graphql_tracer.go → codebase/app/graphql_server/tracer.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package tracer
package graphqlserver

import (
"context"
Expand All @@ -7,27 +7,40 @@ import (
"fmt"
"net/http"
"os"
"strings"
"time"

gqlerrors "github.com/golangid/graphql-go/errors"
"github.com/golangid/graphql-go/introspection"
"github.com/golangid/graphql-go/trace"
"pkg.agungdwiprasetyo.com/candi/candihelper"
"pkg.agungdwiprasetyo.com/candi/candishared"
"pkg.agungdwiprasetyo.com/candi/config"
"pkg.agungdwiprasetyo.com/candi/codebase/factory/types"
"pkg.agungdwiprasetyo.com/candi/logger"
"pkg.agungdwiprasetyo.com/candi/tracer"
)

const schemaRootInstropectionField = "__schema"

var gqlTypeNotShowLog = map[string]bool{
"Query": true, "Mutation": true, "Subscription": true, "__Type": true, "__Schema": true,
}

// GraphQLTracer struct
type GraphQLTracer struct{}
// graphQLTracer struct
type graphQLTracer struct {
midd types.GraphQLMiddlewareGroup
}

// newGraphQLTracer constructor
func newGraphQLTracer(midd types.GraphQLMiddlewareGroup) *graphQLTracer {
return &graphQLTracer{
midd: midd,
}
}

// TraceQuery method
func (GraphQLTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, trace.TraceQueryFinishFunc) {
trace := StartTrace(ctx, "GraphQL-Root")
func (t *graphQLTracer) TraceQuery(ctx context.Context, queryString string, operationName string, variables map[string]interface{}, varTypes map[string]*introspection.Type) (context.Context, trace.TraceQueryFinishFunc) {
trace := tracer.StartTrace(ctx, strings.TrimSuffix(fmt.Sprintf("GraphQL-Root:%s", operationName), ":"))

tags := trace.Tags()
tags["graphql.query"] = queryString
Expand All @@ -36,13 +49,13 @@ func (GraphQLTracer) TraceQuery(ctx context.Context, queryString string, operati
tags["graphql.variables"] = variables
}

if headers, ok := ctx.Value(candishared.HTTPHeaderContextKey).(http.Header); ok {
if headers, ok := ctx.Value(candishared.ContextKeyHTTPHeader).(http.Header); ok {
tags["http.header"] = headers
}

return trace.Context(), func(data []byte, errs []*gqlerrors.QueryError) {
defer trace.Finish()
logger.LogGreen("graphql " + GetTraceURL(trace.Context()))
logger.LogGreen("graphql " + tracer.GetTraceURL(trace.Context()))
tags["response.data"] = string(data)

if len(errs) > 0 {
Expand All @@ -57,15 +70,14 @@ func (GraphQLTracer) TraceQuery(ctx context.Context, queryString string, operati
}

// TraceField method
func (GraphQLTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, trace.TraceFieldFinishFunc) {
func (t *graphQLTracer) TraceField(ctx context.Context, label, typeName, fieldName string, trivial bool, args map[string]interface{}) (context.Context, trace.TraceFieldFinishFunc) {
start := time.Now()
if middFunc, ok := t.midd[fmt.Sprintf("%s.%s", typeName, fieldName)]; ok {
ctx = middFunc(ctx)
}
return ctx, func(data []byte, err *gqlerrors.QueryError) {
if !config.BaseEnv().DebugMode {
return
}

end := time.Now()
if !trivial && !gqlTypeNotShowLog[typeName] {
if !trivial && !gqlTypeNotShowLog[typeName] && fieldName != schemaRootInstropectionField {
statusColor := candihelper.Green
status := " OK "
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions codebase/factory/types/graphql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package types

import "context"

// GraphQLMiddlewareFunc type
type GraphQLMiddlewareFunc func(context.Context) context.Context

// GraphQLMiddlewareGroup type
type GraphQLMiddlewareGroup map[string]GraphQLMiddlewareFunc

// Add register resolver to middleware
func (mw GraphQLMiddlewareGroup) Add(schemaResolverName string, middlewareFunc GraphQLMiddlewareFunc) {
mw[schemaResolverName] = middlewareFunc
}
2 changes: 1 addition & 1 deletion codebase/interfaces/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ type GRPCHandler interface {

// GraphQLHandler delivery factory for graphql resolver handler
type GraphQLHandler interface {
// waiting https://github.com/graph-gophers/graphql-go/issues/145 if include subscription in schema
Query() interface{}
Mutation() interface{}
Subscription() interface{}
RegisterMiddleware(group *types.GraphQLMiddlewareGroup)
}

// WorkerHandler delivery factory for all worker handler
Expand Down
4 changes: 2 additions & 2 deletions codebase/interfaces/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ type GRPCMiddleware interface {

// GraphQLMiddleware interface, common middleware for graphql handler, as directive in graphql schema
type GraphQLMiddleware interface {
GraphQLBasicAuth(ctx context.Context)
GraphQLBearerAuth(ctx context.Context) *candishared.TokenClaim
GraphQLBasicAuth(ctx context.Context) context.Context
GraphQLBearerAuth(ctx context.Context) context.Context
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/go-stack/stack v1.8.0 // indirect
github.com/gojektech/heimdall v5.0.2+incompatible
github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 // indirect
github.com/golangid/graphql-go v0.0.4
github.com/golangid/graphql-go v0.0.5
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/uuid v1.1.1
github.com/gorilla/websocket v1.4.2 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangid/graphql-go v0.0.4 h1:5woE0o2oa6NaJu+PEi5t+PVU10oz4Ls8eaO+q7TvGKY=
github.com/golangid/graphql-go v0.0.4/go.mod h1:FaEQ9PwKpunEBXoMJQt1yXzKSu7cYTmE1XX6H+CwzwE=
github.com/golangid/graphql-go v0.0.5 h1:t3sM2rBfTPxzy3n954e+HWsdSRLEEdYvnRX/FRsIb2c=
github.com/golangid/graphql-go v0.0.5/go.mod h1:FaEQ9PwKpunEBXoMJQt1yXzKSu7cYTmE1XX6H+CwzwE=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
Expand Down
9 changes: 7 additions & 2 deletions middleware/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"pkg.agungdwiprasetyo.com/candi/candishared"
"pkg.agungdwiprasetyo.com/candi/tracer"
"pkg.agungdwiprasetyo.com/candi/wrapper"

"github.com/labstack/echo"
Expand Down Expand Up @@ -80,8 +81,11 @@ func (m *Middleware) HTTPBasicAuth(showAlert bool) echo.MiddlewareFunc {
}

// GraphQLBasicAuth for graphql resolver
func (m *Middleware) GraphQLBasicAuth(ctx context.Context) {
headers := ctx.Value(candishared.HTTPHeaderContextKey).(http.Header)
func (m *Middleware) GraphQLBasicAuth(ctx context.Context) context.Context {
trace := tracer.StartTrace(ctx, "Middleware:GraphQLBasicAuth")
defer trace.Finish()

headers := ctx.Value(candishared.ContextKeyHTTPHeader).(http.Header)
authorization := headers.Get(echo.HeaderAuthorization)

key, err := extractAuthType(Basic, authorization)
Expand All @@ -104,6 +108,7 @@ func (m *Middleware) GraphQLBasicAuth(ctx context.Context) {
},
})
}
return trace.Context()
}

// GRPCBasicAuth method
Expand Down
13 changes: 10 additions & 3 deletions middleware/bearer.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"google.golang.org/grpc/metadata"
"pkg.agungdwiprasetyo.com/candi/candihelper"
"pkg.agungdwiprasetyo.com/candi/candishared"
"pkg.agungdwiprasetyo.com/candi/tracer"
"pkg.agungdwiprasetyo.com/candi/wrapper"

"github.com/labstack/echo"
Expand Down Expand Up @@ -53,8 +54,12 @@ func (m *Middleware) HTTPBearerAuth() echo.MiddlewareFunc {
}

// GraphQLBearerAuth for graphql resolver
func (m *Middleware) GraphQLBearerAuth(ctx context.Context) *candishared.TokenClaim {
headers := ctx.Value(candishared.HTTPHeaderContextKey).(http.Header)
func (m *Middleware) GraphQLBearerAuth(ctx context.Context) context.Context {
trace := tracer.StartTrace(ctx, "Middleware:GraphQLBearerAuth")
defer trace.Finish()
tags := trace.Tags()

headers := ctx.Value(candishared.ContextKeyHTTPHeader).(http.Header)
authorization := headers.Get(echo.HeaderAuthorization)

tokenValue, err := extractAuthType(Bearer, authorization)
Expand All @@ -67,6 +72,7 @@ func (m *Middleware) GraphQLBearerAuth(ctx context.Context) *candishared.TokenCl
},
})
}
tags["token"] = tokenValue

tokenClaim, err := m.Bearer(ctx, tokenValue)
if err != nil {
Expand All @@ -79,7 +85,8 @@ func (m *Middleware) GraphQLBearerAuth(ctx context.Context) *candishared.TokenCl
})
}

return tokenClaim
tags["token_claim"] = tokenClaim
return candishared.SetToContext(trace.Context(), candishared.ContextKeyTokenClaim, tokenClaim)
}

// GRPCBearerAuth method
Expand Down

0 comments on commit 523c7ca

Please sign in to comment.