~bigbes/core-go

ref: e36951dabc29afa20b3c1bcc06365b2b8e322104 core-go/webhooks/context.go -rw-r--r-- 3.7 KiB
e36951da — Conrad Hoffmann Update ProtonMail/go-crypto to v1.3.0 1 year, 1 month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package webhooks

import (
	"context"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"

	"github.com/99designs/gqlgen/complexity"
	"github.com/99designs/gqlgen/graphql"
	"github.com/99designs/gqlgen/graphql/executor"
	"github.com/google/uuid"

	"git.sr.ht/~sircmpwn/core-go/auth"
	"git.sr.ht/~sircmpwn/core-go/server"
)

type contextKey struct {
	name string
}

var payloadContextKey = &contextKey{"webhookPayloadContext"}

type WebhookContext struct {
	Name         string
	Event        string
	User         *auth.AuthContext
	Payload      interface{}
	PayloadUUID  uuid.UUID
	Subscription *WebhookSubscription
}

// Prepares an context for a specific webhook delivery.
func Context(ctx context.Context, payload interface{}) context.Context {
	return context.WithValue(ctx, payloadContextKey, payload)
}

// Returns the active payload for a webhook context.
func Payload(ctx context.Context) (interface{}, error) {
	payload := ctx.Value(payloadContextKey)
	if payload == nil {
		return nil, errors.New("Cannot use this resolver without an active webhook context")
	}
	return payload, nil
}

// Executes the GraphQL query prepared stored in the WebhookContext. Handles
// the configuration of a secondary authentication and GraphQL context.
func (webhook *WebhookContext) Exec(ctx context.Context,
	schema graphql.ExecutableSchema) ([]byte, error) {
	var (
		err       error
		tokenHash [64]byte
	)
	sub := webhook.Subscription

	switch sub.AuthMethod {
	case auth.AUTH_OAUTH2:
		tslice, err := hex.DecodeString(*sub.TokenHash)
		if err != nil {
			panic(err)
		}
		if sub.Expires == nil {
			panic(fmt.Errorf("OAuth 2 token has no expiry?"))
		}

		copy(tokenHash[:], tslice)
		ctx, err = auth.WebhookAuth(ctx, webhook.User,
			tokenHash, *sub.Grants, sub.ClientID, sub.Expires)
		if err != nil {
			// TODO: This codepath can occur when the token has
			// expired, and we may want to communicate this to the
			// user.
			return nil, err
		}
	case auth.AUTH_INTERNAL:
		ctx, err = auth.WebhookAuth(ctx, webhook.User,
			tokenHash, "", nil, nil)
		if err != nil {
			panic(err)
		}
	default:
		panic(fmt.Errorf("Unsupported authentication context for webhook"))
	}

	exec := executor.New(schema)
	params := graphql.RawParams{
		Query: sub.Query,
		ReadTime: graphql.TraceTiming{
			Start: graphql.Now(),
			End:   graphql.Now(),
		},
	}
	ctx = graphql.StartOperationTrace(ctx)
	rc, errors := exec.CreateOperationContext(ctx, &params)
	if errors != nil {
		panic(errors)
	}
	rc.RecoverFunc = server.EmailRecover

	op := rc.Doc.Operations.ForName(rc.OperationName)
	complexity := complexity.Calculate(schema, op, rc.Variables)
	srv := server.ForContext(ctx)
	if complexity > srv.MaxComplexity {
		// TODO: This doesn't bubble up to the user well
		return nil, fmt.Errorf("operation has complexity %d, which exceeds the maximum of %d",
			complexity, srv.MaxComplexity)
	}

	var resp graphql.ResponseHandler
	ctx = graphql.WithOperationContext(ctx, rc)
	resp, ctx = exec.DispatchOperation(ctx, rc)
	payload, err := json.Marshal(resp(ctx))
	if err != nil {
		panic(err)
	}
	return payload, nil
}

// Validates the given query against the provided schema and returns any errors
// should they be found, or nil if the query passes validation.
func Validate(schema graphql.ExecutableSchema, query string) error {
	// XXX: We would create less garbage if we ran the validator ourselves
	// instead of letting gqlgen do it for us via CreateOperationContext
	exec := executor.New(schema)
	params := graphql.RawParams{
		Query: query,
		ReadTime: graphql.TraceTiming{
			Start: graphql.Now(),
			End:   graphql.Now(),
		},
	}
	ctx := graphql.StartOperationTrace(context.TODO())
	_, errors := exec.CreateOperationContext(ctx, &params)
	if errors != nil {
		return fmt.Errorf("Error validating webhook query: %s", errors.Error())
	}
	return nil
}