~bigbes/core-go

ref: 07f694d2fb31e0278aa2c8fb28c7b28adb5aac2a core-go/webhooks/context.go -rw-r--r-- 3.4 KiB
07f694d2 — Drew DeVault webhooks: expand auth configuration 4 years 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
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) {
	sub := webhook.Subscription
	if sub.AuthMethod != auth.AUTH_OAUTH2 {
		panic("TODO")
	}
	tslice, err := hex.DecodeString(*sub.TokenHash)
	if err != nil {
		panic(err)
	}

	var tokenHash [64]byte
	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
	}

	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
}