@@ -3,6 +3,7 @@ package graphqlws
3
3
import (
4
4
"encoding/json"
5
5
"errors"
6
+ "fmt"
6
7
"time"
7
8
8
9
"github.com/google/uuid"
@@ -30,6 +31,12 @@ const (
30
31
writeTimeout = 10 * time .Second
31
32
)
32
33
34
+ // InitMessagePayload defines the parameters of a connection
35
+ // init message.
36
+ type InitMessagePayload struct {
37
+ AuthToken string `json:"authToken"`
38
+ }
39
+
33
40
// StartMessagePayload defines the parameters of an operation that
34
41
// a client requests to be started.
35
42
type StartMessagePayload struct {
@@ -59,6 +66,10 @@ func (msg OperationMessage) String() string {
59
66
return "<invalid>"
60
67
}
61
68
69
+ // UserFromAuthTokenFunc is a function that resolves an auth token
70
+ // into a user (or returns an error if that isn't possible).
71
+ type UserFromAuthTokenFunc func (token string ) (interface {}, error )
72
+
62
73
// ConnectionEventHandlers define the event handlers for a connection.
63
74
// Event handlers allow other system components to react to events such
64
75
// as the connection closing or an operation being started or stopped.
@@ -81,12 +92,22 @@ type ConnectionEventHandlers struct {
81
92
StopOperation func (Connection , string )
82
93
}
83
94
95
+ // ConnectionConfig defines the configuration parameters of a
96
+ // GraphQL WebSocket connection.
97
+ type ConnectionConfig struct {
98
+ UserFromAuthToken UserFromAuthTokenFunc
99
+ EventHandlers ConnectionEventHandlers
100
+ }
101
+
84
102
// Connection is an interface to represent GraphQL WebSocket connections.
85
103
// Each connection is associated with an ID that is unique to the server.
86
104
type Connection interface {
87
105
// ID returns the unique ID of the connection.
88
106
ID () string
89
107
108
+ // User returns the user associated with the connection (or nil).
109
+ User () interface {}
110
+
90
111
// SendData sends results of executing an operation (typically a
91
112
// subscription) to the client.
92
113
SendData (string , * DataMessagePayload )
@@ -100,11 +121,12 @@ type Connection interface {
100
121
*/
101
122
102
123
type connection struct {
103
- id string
104
- ws * websocket.Conn
105
- eventHandlers * ConnectionEventHandlers
106
- logger * log.Entry
107
- outgoing chan OperationMessage
124
+ id string
125
+ ws * websocket.Conn
126
+ config ConnectionConfig
127
+ logger * log.Entry
128
+ outgoing chan OperationMessage
129
+ user interface {}
108
130
}
109
131
110
132
func operationMessageForType (messageType string ) OperationMessage {
@@ -116,11 +138,11 @@ func operationMessageForType(messageType string) OperationMessage {
116
138
// NewConnection establishes a GraphQL WebSocket connection. It implements
117
139
// the GraphQL WebSocket protocol by managing its internal state and handling
118
140
// the client-server communication.
119
- func NewConnection (ws * websocket.Conn , eventHandlers * ConnectionEventHandlers ) Connection {
141
+ func NewConnection (ws * websocket.Conn , config ConnectionConfig ) Connection {
120
142
conn := new (connection )
121
143
conn .id = uuid .New ().String ()
122
144
conn .ws = ws
123
- conn .eventHandlers = eventHandlers
145
+ conn .config = config
124
146
conn .logger = NewLogger ("connection/" + conn .id )
125
147
126
148
conn .outgoing = make (chan OperationMessage )
@@ -137,6 +159,10 @@ func (conn *connection) ID() string {
137
159
return conn .id
138
160
}
139
161
162
+ func (conn * connection ) User () interface {} {
163
+ return conn .user
164
+ }
165
+
140
166
func (conn * connection ) SendData (opID string , data * DataMessagePayload ) {
141
167
msg := operationMessageForType (gqlData )
142
168
msg .ID = opID
@@ -162,8 +188,8 @@ func (conn *connection) close() {
162
188
close (conn .outgoing )
163
189
164
190
// Notify event handlers
165
- if conn .eventHandlers != nil {
166
- conn .eventHandlers .Close (conn )
191
+ if conn .config . EventHandlers . Close != nil {
192
+ conn .config . EventHandlers .Close (conn )
167
193
}
168
194
169
195
conn .logger .Info ("Closed connection" )
@@ -238,16 +264,31 @@ func (conn *connection) readLoop() {
238
264
239
265
// When the GraphQL WS connection is initiated, send an ACK back
240
266
case gqlConnectionInit :
241
- conn .outgoing <- operationMessageForType (gqlConnectionAck )
267
+ data := InitMessagePayload {}
268
+ if err := json .Unmarshal (rawPayload , & data ); err != nil {
269
+ conn .SendError (errors .New ("Invalid GQL_CONNECTION_INIT payload" ))
270
+ } else {
271
+ if conn .config .UserFromAuthToken != nil {
272
+ user , err := conn .config .UserFromAuthToken (data .AuthToken )
273
+ if err != nil {
274
+ conn .SendError (fmt .Errorf ("Failed to authenticate user: %v" , err ))
275
+ } else {
276
+ conn .user = user
277
+ conn .outgoing <- operationMessageForType (gqlConnectionAck )
278
+ }
279
+ } else {
280
+ conn .outgoing <- operationMessageForType (gqlConnectionAck )
281
+ }
282
+ }
242
283
243
284
// Let event handlers deal with starting operations
244
285
case gqlStart :
245
- if conn .eventHandlers != nil {
286
+ if conn .config . EventHandlers . StartOperation != nil {
246
287
data := StartMessagePayload {}
247
288
if err := json .Unmarshal (rawPayload , & data ); err != nil {
248
289
conn .SendError (errors .New ("Invalid GQL_START payload" ))
249
290
} else {
250
- errs := conn .eventHandlers .StartOperation (conn , msg .ID , & data )
291
+ errs := conn .config . EventHandlers .StartOperation (conn , msg .ID , & data )
251
292
if errs != nil {
252
293
conn .sendOperationErrors (msg .ID , errs )
253
294
}
@@ -256,8 +297,8 @@ func (conn *connection) readLoop() {
256
297
257
298
// Let event handlers deal with stopping operations
258
299
case gqlStop :
259
- if conn .eventHandlers != nil {
260
- conn .eventHandlers .StopOperation (conn , msg .ID )
300
+ if conn .config . EventHandlers . StopOperation != nil {
301
+ conn .config . EventHandlers .StopOperation (conn , msg .ID )
261
302
}
262
303
263
304
// When the GraphQL WS connection is terminated by the client,
0 commit comments