• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pusher / pusher-http-go / 12285846667

11 Dec 2024 10:17PM UTC coverage: 87.082%. Remained the same
12285846667

Pull #93

github

web-flow
Bump golang.org/x/crypto

Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20200709230013-948cd5f35899 to 0.31.0.
- [Commits](https://github.com/golang/crypto/commits/v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #93: Bump golang.org/x/crypto from 0.0.0-20200709230013-948cd5f35899 to 0.31.0

573 of 658 relevant lines covered (87.08%)

1.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.73
/client.go
1
package pusher
2

3
import (
4
        "encoding/base64"
5
        "encoding/json"
6
        "errors"
7
        "fmt"
8
        "net/http"
9
        "net/url"
10
        "os"
11
        "regexp"
12
        "strings"
13
        "time"
14
)
15

16
var pusherPathRegex = regexp.MustCompile("^/apps/([0-9]+)$")
17
var maxTriggerableChannels = 100
18

19
const (
20
        libraryVersion = "5.1.1"
21
        libraryName    = "pusher-http-go"
22
)
23

24
/*
25
Client to the HTTP API of Pusher.
26

27
There easiest way to configure the library is by creating a `Pusher` instance:
28

29
        client := pusher.Client{
30
                AppID: "your_app_id",
31
                Key: "your_app_key",
32
                Secret: "your_app_secret",
33
        }
34

35
To ensure requests occur over HTTPS, set the `Secure` property of a
36
`pusher.Client` to `true`.
37

38
        client.Secure = true // false by default
39

40
If you wish to set a time-limit for each HTTP request, set the `Timeout`
41
property to an instance of `time.Duration`, for example:
42

43
        client.Timeout = time.Second * 3 // 5 seconds by default
44

45
Changing the `pusher.Client`'s `Host` property will make sure requests are sent
46
to your specified host.
47

48
        client.Host = "foo.bar.com" // by default this is "api.pusherapp.com".
49
*/
50
type Client struct {
51
        AppID                        string
52
        Key                          string
53
        Secret                       string
54
        Host                         string // host or host:port pair
55
        Secure                       bool   // true for HTTPS
56
        Cluster                      string
57
        HTTPClient                   *http.Client
58
        EncryptionMasterKey          string  // deprecated
59
        EncryptionMasterKeyBase64    string  // for E2E
60
        OverrideMaxMessagePayloadKB  int     // set the agreed Pusher message limit increase
61
        validatedEncryptionMasterKey *[]byte // parsed key for use
62
}
63

64
/*
65
ClientFromURL allows client instantiation from a specially-crafted Pusher URL.
66

67
        c := pusher.ClientFromURL("http://key:secret@api.pusherapp.com/apps/app_id")
68
*/
69
func ClientFromURL(serverURL string) (*Client, error) {
2✔
70
        url2, err := url.Parse(serverURL)
2✔
71
        if err != nil {
2✔
72
                return nil, err
×
73
        }
×
74

75
        c := Client{
2✔
76
                Host: url2.Host,
2✔
77
        }
2✔
78

2✔
79
        matches := pusherPathRegex.FindStringSubmatch(url2.Path)
2✔
80
        if len(matches) == 0 {
4✔
81
                return nil, errors.New("No app ID found")
2✔
82
        }
2✔
83
        c.AppID = matches[1]
2✔
84

2✔
85
        if url2.User == nil {
2✔
86
                return nil, errors.New("Missing <key>:<secret>")
×
87
        }
×
88
        c.Key = url2.User.Username()
2✔
89
        var isSet bool
2✔
90
        c.Secret, isSet = url2.User.Password()
2✔
91
        if !isSet {
2✔
92
                return nil, errors.New("Missing <secret>")
×
93
        }
×
94

95
        if url2.Scheme == "https" {
4✔
96
                c.Secure = true
2✔
97
        }
2✔
98

99
        return &c, nil
2✔
100
}
101

102
/*
103
ClientFromEnv allows instantiation of a client from an environment variable.
104
This is particularly relevant if you are using Pusher as a Heroku add-on,
105
which stores credentials in a `"PUSHER_URL"` environment variable. For example:
106

107
        client := pusher.ClientFromEnv("PUSHER_URL")
108
*/
109
func ClientFromEnv(key string) (*Client, error) {
2✔
110
        url := os.Getenv(key)
2✔
111
        return ClientFromURL(url)
2✔
112
}
2✔
113

114
/*
115
Returns the underlying HTTP client.
116
Useful to set custom properties to it.
117
*/
118
func (c *Client) requestClient() *http.Client {
2✔
119
        if c.HTTPClient == nil {
4✔
120
                c.HTTPClient = &http.Client{Timeout: time.Second * 5}
2✔
121
        }
2✔
122

123
        return c.HTTPClient
2✔
124
}
125

126
func (c *Client) request(method, url string, body []byte) ([]byte, error) {
2✔
127
        return request(c.requestClient(), method, url, body)
2✔
128
}
2✔
129

130
/*
131
Trigger triggers an event to the Pusher API.
132
It is possible to trigger an event on one or more channels. Channel names can
133
contain only characters which are alphanumeric, `_` or `-`` and have
134
to be at most 200 characters long. Event name can be at most 200 characters long too.
135

136
Pass in the channel's name, the event's name, and a data payload. The data payload must
137
be marshallable into JSON.
138

139
        data := map[string]string{"hello": "world"}
140
        client.Trigger("greeting_channel", "say_hello", data)
141
*/
142
func (c *Client) Trigger(channel string, eventName string, data interface{}) error {
2✔
143
        _, err := c.validateChannelsAndTrigger([]string{channel}, eventName, data, TriggerParams{})
2✔
144
        return err
2✔
145
}
2✔
146

147
/*
148
ChannelsParams are any parameters than can be sent with a
149
TriggerWithParams or TriggerMultiWithParams requests.
150
*/
151
type TriggerParams struct {
152
        // SocketID excludes a recipient whose connection has the `socket_id`
153
        // specified here. You can read more here:
154
        // http://pusher.com/docs/duplicates.
155
        SocketID *string
156
        // Info is comma-separated vales of `"user_count"`, for
157
        // presence-channels, and `"subscription_count"`, for all-channels.
158
        // Note that the subscription count is not allowed by default. Please
159
        // contact us at http://support.pusher.com if you wish to enable this.
160
        // Pass in `nil` if you do not wish to specify any query attributes.
161
        // This is part of an [experimental feature](https://pusher.com/docs/lab#experimental-program).
162
        Info *string
163
}
164

165
func (params TriggerParams) toMap() map[string]string {
2✔
166
        m := make(map[string]string)
2✔
167
        if params.SocketID != nil {
4✔
168
                m["socket_id"] = *params.SocketID
2✔
169
        }
2✔
170
        if params.Info != nil {
4✔
171
                m["info"] = *params.Info
2✔
172
        }
2✔
173
        return m
2✔
174
}
175

176
/*
177
TriggerWithParams is the same as `client.Trigger`, except it allows additional
178
parameters to be passed in. See:
179
https://pusher.com/docs/channels/library_auth_reference/rest-api#request
180
for a complete list.
181

182
        data := map[string]string{"hello": "world"}
183
        socketID := "1234.12"
184
        attributes := "user_count"
185
        params := pusher.TriggerParams{SocketID: &socketID, Info: &attributes}
186
        channels, err := client.Trigger("greeting_channel", "say_hello", data, params)
187

188
        //channels=> &{Channels:map[presence-chatroom:{UserCount:4} presence-notifications:{UserCount:31}]}
189
*/
190
func (c *Client) TriggerWithParams(
191
        channel string,
192
        eventName string,
193
        data interface{},
194
        params TriggerParams,
195
) (*TriggerChannelsList, error) {
2✔
196
        return c.validateChannelsAndTrigger([]string{channel}, eventName, data, params)
2✔
197
}
2✔
198

199
/*
200
TriggerMulti is the same as `client.Trigger`, except one passes in a slice of
201
`channels` as the first parameter. The maximum length of channels is 100.
202

203
        client.TriggerMulti([]string{"a_channel", "another_channel"}, "event", data)
204
*/
205
func (c *Client) TriggerMulti(channels []string, eventName string, data interface{}) error {
2✔
206
        _, err := c.validateChannelsAndTrigger(channels, eventName, data, TriggerParams{})
2✔
207
        return err
2✔
208
}
2✔
209

210
/*
211
TriggerMultiWithParams is the same as `client.TriggerMulti`, except it
212
allows additional parameters to be specified in the same way as
213
`client.TriggerWithParams`.
214
*/
215
func (c *Client) TriggerMultiWithParams(
216
        channels []string,
217
        eventName string,
218
        data interface{},
219
        params TriggerParams,
220
) (*TriggerChannelsList, error) {
2✔
221
        return c.validateChannelsAndTrigger(channels, eventName, data, params)
2✔
222
}
2✔
223

224
/*
225
TriggerExclusive triggers an event excluding a recipient whose connection has
226
the `socket_id` you specify here from receiving the event.
227
You can read more here: http://pusher.com/docs/duplicates.
228

229
        client.TriggerExclusive("a_channel", "event", data, "123.12")
230

231
Deprecated: use TriggerWithParams instead.
232
*/
233
func (c *Client) TriggerExclusive(channel string, eventName string, data interface{}, socketID string) error {
2✔
234
        params := TriggerParams{SocketID: &socketID}
2✔
235
        _, err := c.validateChannelsAndTrigger([]string{channel}, eventName, data, params)
2✔
236
        return err
2✔
237
}
2✔
238

239
/*
240
TriggerMultiExclusive triggers an event to multiple channels excluding a
241
recipient whose connection has the `socket_id` you specify here from receiving
242
the event on any of the channels.
243

244
        client.TriggerMultiExclusive([]string{"a_channel", "another_channel"}, "event", data, "123.12")
245

246
Deprecated: use TriggerMultiWithParams instead.
247
*/
248
func (c *Client) TriggerMultiExclusive(channels []string, eventName string, data interface{}, socketID string) error {
×
249
        params := TriggerParams{SocketID: &socketID}
×
250
        _, err := c.validateChannelsAndTrigger(channels, eventName, data, params)
×
251
        return err
×
252
}
×
253

254
/*
255
SendToUser triggers an event to a specific user.
256
Pass in the user id, the event's name, and a data payload. The data payload must
257
be marshallable into JSON.
258

259
        data := map[string]string{"hello": "world"}
260
        client.SendToUser("user123", "say_hello", data)
261
*/
262
func (c *Client) SendToUser(userId string, eventName string, data interface{}) error {
2✔
263
        if !validUserId(userId) {
4✔
264
                return fmt.Errorf("User id '%s' is invalid", userId)
2✔
265
        }
2✔
266
        _, err := c.trigger([]string{"#server-to-user-" + userId}, eventName, data, TriggerParams{})
2✔
267
        return err
2✔
268
}
269

270
func (c *Client) validateChannelsAndTrigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) {
2✔
271
        if len(channels) > maxTriggerableChannels {
4✔
272
                return nil, fmt.Errorf("You cannot trigger on more than %d channels at once", maxTriggerableChannels)
2✔
273
        }
2✔
274
        if !channelsAreValid(channels) {
4✔
275
                return nil, errors.New("At least one of your channels' names are invalid")
2✔
276
        }
2✔
277
        return c.trigger(channels, eventName, data, params)
2✔
278
}
279

280
func (c *Client) trigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) {
2✔
281
        hasEncryptedChannel := false
2✔
282
        for _, channel := range channels {
4✔
283
                if isEncryptedChannel(channel) {
4✔
284
                        hasEncryptedChannel = true
2✔
285
                }
2✔
286
        }
287
        if hasEncryptedChannel && len(channels) > 1 {
4✔
288
                // For rationale, see limitations of end-to-end encryption in the README
2✔
289
                return nil, errors.New("You cannot trigger to multiple channels when using encrypted channels")
2✔
290
        }
2✔
291
        masterKey, keyErr := c.encryptionMasterKey()
2✔
292
        if hasEncryptedChannel && keyErr != nil {
4✔
293
                return nil, keyErr
2✔
294
        }
2✔
295

296
        if err := validateSocketID(params.SocketID); err != nil {
4✔
297
                return nil, err
2✔
298
        }
2✔
299

300
        payload, err := encodeTriggerBody(channels, eventName, data, params.toMap(), masterKey, c.OverrideMaxMessagePayloadKB)
2✔
301
        if err != nil {
4✔
302
                return nil, err
2✔
303
        }
2✔
304
        path := fmt.Sprintf("/apps/%s/events", c.AppID)
2✔
305
        triggerURL, err := createRequestURL("POST", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, payload, nil, c.Cluster)
2✔
306
        if err != nil {
2✔
307
                return nil, err
×
308
        }
×
309
        response, err := c.request("POST", triggerURL, payload)
2✔
310
        if err != nil {
4✔
311
                return nil, err
2✔
312
        }
2✔
313

314
        return unmarshalledTriggerChannelsList(response)
2✔
315
}
316

317
/*
318
Event stores all the data for one Event that can be triggered.
319
*/
320
type Event struct {
321
        Channel  string
322
        Name     string
323
        Data     interface{}
324
        SocketID *string
325
        // Info is part of an [experimental feature](https://pusher.com/docs/lab#experimental-program).
326
        Info *string
327
}
328

329
/*
330
TriggerBatch triggers multiple events on multiple channels in a single call:
331

332
        info := "subscription_count"
333
        socketID := "1234.12"
334
        client.TriggerBatch([]pusher.Event{
335
                { Channel: "donut-1", Name: "ev1", Data: "d1", SocketID: socketID, Info: &info },
336
                { Channel: "private-encrypted-secretdonut", Name: "ev2", Data: "d2", SocketID: socketID, Info: &info },
337
        })
338
*/
339
func (c *Client) TriggerBatch(batch []Event) (*TriggerBatchChannelsList, error) {
2✔
340
        hasEncryptedChannel := false
2✔
341
        // validate every channel name and every sockedID (if present) in batch
2✔
342
        for _, event := range batch {
4✔
343
                if !validChannel(event.Channel) {
2✔
344
                        return nil, fmt.Errorf("The channel named %s has a non-valid name", event.Channel)
×
345
                }
×
346
                if err := validateSocketID(event.SocketID); err != nil {
2✔
347
                        return nil, err
×
348
                }
×
349
                if isEncryptedChannel(event.Channel) {
4✔
350
                        hasEncryptedChannel = true
2✔
351
                }
2✔
352
        }
353
        masterKey, keyErr := c.encryptionMasterKey()
2✔
354
        if hasEncryptedChannel && keyErr != nil {
4✔
355
                return nil, keyErr
2✔
356
        }
2✔
357

358
        payload, err := encodeTriggerBatchBody(batch, masterKey, c.OverrideMaxMessagePayloadKB)
2✔
359
        if err != nil {
4✔
360
                return nil, err
2✔
361
        }
2✔
362
        path := fmt.Sprintf("/apps/%s/batch_events", c.AppID)
2✔
363
        triggerURL, err := createRequestURL("POST", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, payload, nil, c.Cluster)
2✔
364
        if err != nil {
2✔
365
                return nil, err
×
366
        }
×
367
        response, err := c.request("POST", triggerURL, payload)
2✔
368
        if err != nil {
4✔
369
                return nil, err
2✔
370
        }
2✔
371

372
        return unmarshalledTriggerBatchChannelsList(response)
2✔
373
}
374

375
/*
376
ChannelsParams are any parameters than can be sent with a Channels request.
377
*/
378
type ChannelsParams struct {
379
        // FilterByPrefix will filter the returned channels.
380
        FilterByPrefix *string
381
        // Info should be specified with a value of "user_count" to get number
382
        // of users subscribed to a presence-channel. Pass in `nil` if you do
383
        // not wish to specify any query attributes.
384
        Info *string
385
}
386

387
func (params ChannelsParams) toMap() map[string]string {
2✔
388
        m := make(map[string]string)
2✔
389
        if params.FilterByPrefix != nil {
2✔
390
                m["filter_by_prefix"] = *params.FilterByPrefix
×
391
        }
×
392
        if params.Info != nil {
2✔
393
                m["info"] = *params.Info
×
394
        }
×
395
        return m
2✔
396
}
397

398
/*
399
Channels returns a list of all the channels in an application.
400

401
        prefixFilter := "presence-"
402
        attributes := "user_count"
403
        params := pusher.ChannelsParams{FilterByPrefix: &prefixFilter, Info: &attributes}
404
        channels, err := client.Channels(params)
405

406
        //channels=> &{Channels:map[presence-chatroom:{UserCount:4} presence-notifications:{UserCount:31}  ]}
407
*/
408
func (c *Client) Channels(params ChannelsParams) (*ChannelsList, error) {
2✔
409
        path := fmt.Sprintf("/apps/%s/channels", c.AppID)
2✔
410
        u, err := createRequestURL("GET", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, nil, params.toMap(), c.Cluster)
2✔
411
        if err != nil {
2✔
412
                return nil, err
×
413
        }
×
414
        response, err := c.request("GET", u, nil)
2✔
415
        if err != nil {
2✔
416
                return nil, err
×
417
        }
×
418
        return unmarshalledChannelsList(response)
2✔
419
}
420

421
/*
422
ChannelParams are any parameters than can be sent with a Channel request.
423
*/
424
type ChannelParams struct {
425
        // Info is comma-separated vales of `"user_count"`, for
426
        // presence-channels, and `"subscription_count"`, for all-channels.
427
        // Note that the subscription count is not allowed by default. Please
428
        // contact us at http://support.pusher.com if you wish to enable this.
429
        // Pass in `nil` if you do not wish to specify any query attributes.
430
        Info *string
431
}
432

433
func (params ChannelParams) toMap() map[string]string {
2✔
434
        m := make(map[string]string)
2✔
435
        if params.Info != nil {
4✔
436
                m["info"] = *params.Info
2✔
437
        }
2✔
438
        return m
2✔
439
}
440

441
/*
442
Channel allows you to get the state of a single channel.
443

444
        attributes := "user_count,subscription_count"
445
        params := pusher.ChannelParams{Info: &attributes}
446
        channel, err := client.Channel("presence-chatroom", params)
447

448
        //channel=> &{Name:presence-chatroom Occupied:true UserCount:42 SubscriptionCount:42}
449
*/
450
func (c *Client) Channel(name string, params ChannelParams) (*Channel, error) {
2✔
451
        path := fmt.Sprintf("/apps/%s/channels/%s", c.AppID, name)
2✔
452
        u, err := createRequestURL("GET", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, nil, params.toMap(), c.Cluster)
2✔
453
        if err != nil {
2✔
454
                return nil, err
×
455
        }
×
456
        response, err := c.request("GET", u, nil)
2✔
457
        if err != nil {
4✔
458
                return nil, err
2✔
459
        }
2✔
460
        return unmarshalledChannel(response, name)
2✔
461
}
462

463
/*
464
GetChannelUsers returns a list of users in a presence-channel by passing to this
465
method the channel name.
466

467
        users, err := client.GetChannelUsers("presence-chatroom")
468

469
        //users=> &{List:[{ID:13} {ID:90}]}
470
*/
471
func (c *Client) GetChannelUsers(name string) (*Users, error) {
2✔
472
        path := fmt.Sprintf("/apps/%s/channels/%s/users", c.AppID, name)
2✔
473
        u, err := createRequestURL("GET", c.Host, path, c.Key, c.Secret, authTimestamp(), c.Secure, nil, nil, c.Cluster)
2✔
474
        if err != nil {
2✔
475
                return nil, err
×
476
        }
×
477
        response, err := c.request("GET", u, nil)
2✔
478
        if err != nil {
2✔
479
                return nil, err
×
480
        }
×
481
        return unmarshalledChannelUsers(response)
2✔
482
}
483

484
/*
485
AuthenticateUser allows you to authenticate a user's connection.
486
It returns an authentication signature to send back to the client
487
and authenticate them. In order to identify a user, this method acceps a map containing
488
arbitrary user data. It must contain at least an id field with the user's id as a string.
489

490
For more information see our docs: http://pusher.com/docs/authenticating_users.
491

492
This is an example of authenticating a user, using the built-in
493
Golang HTTP library to start a server.
494

495
In order to authenticate a client, one must read the response into type `[]byte`
496
and pass it in. This will return a signature in the form of a `[]byte` for you
497
to send back to the client.
498

499
        func pusherUserAuth(res http.ResponseWriter, req *http.Request) {
500

501
                params, _ := ioutil.ReadAll(req.Body)
502
                userData := map[string]interface{} { "id": "1234", "twitter": "jamiepatel" }
503
                response, err := client.AuthenticateUser(params, userData)
504
                if err != nil {
505
                        panic(err)
506
                }
507

508
                fmt.Fprintf(res, string(response))
509
        }
510

511
        func main() {
512
                http.HandleFunc("/pusher/user-auth", pusherUserAuth)
513
                http.ListenAndServe(":5000", nil)
514
        }
515
*/
516
func (c *Client) AuthenticateUser(params []byte, userData map[string]interface{}) (response []byte, err error) {
2✔
517
        socketID, err := parseUserAuthenticationRequestParams(params)
2✔
518
        if err != nil {
4✔
519
                return
2✔
520
        }
2✔
521

522
        if err = validateSocketID(&socketID); err != nil {
2✔
523
                return
×
524
        }
×
525

526
        if err = validateUserData(userData); err != nil {
4✔
527
                return
2✔
528
        }
2✔
529

530
        var jsonUserData string
2✔
531
        if jsonUserData, err = jsonMarshalToString(userData); err != nil {
2✔
532
                return
×
533
        }
×
534
        stringToSign := strings.Join([]string{socketID, "user", jsonUserData}, "::")
2✔
535

2✔
536
        _response := createAuthMap(c.Key, c.Secret, stringToSign, "")
2✔
537
        _response["user_data"] = jsonUserData
2✔
538

2✔
539
        response, err = json.Marshal(_response)
2✔
540
        return
2✔
541
}
542

543
/*
544
AuthorizePrivateChannel allows you to authorize a users subscription to a
545
private channel. It returns an authorization signature to send back to the client
546
and authorize them.
547

548
For more information see our docs: http://pusher.com/docs/authorizing_users.
549

550
This is an example of authorizing a private-channel, using the built-in
551
Golang HTTP library to start a server.
552

553
In order to authorize a client, one must read the response into type `[]byte`
554
and pass it in. This will return a signature in the form of a `[]byte` for you
555
to send back to the client.
556

557
        func pusherAuth(res http.ResponseWriter, req *http.Request) {
558

559
                params, _ := ioutil.ReadAll(req.Body)
560
                response, err := client.AuthorizePrivateChannel(params)
561
                if err != nil {
562
                        panic(err)
563
                }
564

565
                fmt.Fprintf(res, string(response))
566
        }
567

568
        func main() {
569
                http.HandleFunc("/pusher/auth", pusherAuth)
570
                http.ListenAndServe(":5000", nil)
571
        }
572
*/
573
func (c *Client) AuthorizePrivateChannel(params []byte) (response []byte, err error) {
2✔
574
        return c.authorizeChannel(params, nil)
2✔
575
}
2✔
576

577
/*
578
AuthenticatePrivateChannel allows you to authorize a users subscription to a
579
private channel. It returns an authorization signature to send back to the client
580
and authorize them.
581

582
Deprecated: use AuthorizePrivateChannel instead.
583
*/
584
func (c *Client) AuthenticatePrivateChannel(params []byte) (response []byte, err error) {
2✔
585
        return c.authorizeChannel(params, nil)
2✔
586
}
2✔
587

588
/*
589
AuthorizePresenceChannel allows you to authorize a users subscription to a
590
presence channel. It returns an authorization signature to send back to the client
591
and authorize them. In order to identify a user, clients are sent a user_id and,
592
optionally, custom data.
593

594
In this library, one does this by passing a `pusher.MemberData` instance.
595

596
        params, _ := ioutil.ReadAll(req.Body)
597

598
        presenceData := pusher.MemberData{
599
                UserID: "1",
600
                UserInfo: map[string]string{
601
                        "twitter": "jamiepatel",
602
                },
603
        }
604

605
        response, err := client.AuthorizePresenceChannel(params, presenceData)
606
        if err != nil {
607
                panic(err)
608
        }
609

610
        fmt.Fprintf(res, response)
611
*/
612
func (c *Client) AuthorizePresenceChannel(params []byte, member MemberData) (response []byte, err error) {
×
613
        return c.authorizeChannel(params, &member)
×
614
}
×
615

616
/*
617
AuthenticatePresenceChannel allows you to authorize a users subscription to a
618
presence channel. It returns an authorization signature to send back to the client
619
and authorize them. In order to identify a user, clients are sent a user_id and,
620
optionally, custom data.
621

622
Deprecated: use AuthorizePresenceChannel instead.
623
*/
624
func (c *Client) AuthenticatePresenceChannel(params []byte, member MemberData) (response []byte, err error) {
2✔
625
        return c.authorizeChannel(params, &member)
2✔
626
}
2✔
627

628
func (c *Client) authorizeChannel(params []byte, member *MemberData) (response []byte, err error) {
2✔
629
        channelName, socketID, err := parseChannelAuthorizationRequestParams(params)
2✔
630
        if err != nil {
4✔
631
                return
2✔
632
        }
2✔
633

634
        if err = validateSocketID(&socketID); err != nil {
4✔
635
                return
2✔
636
        }
2✔
637

638
        stringToSign := strings.Join([]string{socketID, channelName}, ":")
2✔
639

2✔
640
        var jsonUserData string
2✔
641

2✔
642
        if member != nil {
4✔
643
                var _jsonUserData []byte
2✔
644
                _jsonUserData, err = json.Marshal(member)
2✔
645
                if err != nil {
2✔
646
                        return
×
647
                }
×
648

649
                jsonUserData = string(_jsonUserData)
2✔
650
                stringToSign = strings.Join([]string{stringToSign, jsonUserData}, ":")
2✔
651
        }
652

653
        var _response map[string]string
2✔
654

2✔
655
        if isEncryptedChannel(channelName) {
4✔
656
                masterKey, err := c.encryptionMasterKey()
2✔
657
                if err != nil {
4✔
658
                        return nil, err
2✔
659
                }
2✔
660
                sharedSecret := generateSharedSecret(channelName, masterKey)
×
661
                sharedSecretB64 := base64.StdEncoding.EncodeToString(sharedSecret[:])
×
662
                _response = createAuthMap(c.Key, c.Secret, stringToSign, sharedSecretB64)
×
663
        } else {
2✔
664
                _response = createAuthMap(c.Key, c.Secret, stringToSign, "")
2✔
665
        }
2✔
666

667
        if member != nil {
4✔
668
                _response["channel_data"] = jsonUserData
2✔
669
        }
2✔
670

671
        response, err = json.Marshal(_response)
2✔
672
        return
2✔
673
}
674

675
/*
676
Webhook allows you to check that a Webhook you receive is indeed from Pusher, by
677
checking the token and authentication signature in the header of the request. On
678
your dashboard at http://app.pusher.com, you can set up webhooks to POST a
679
payload to your server after certain events. Such events include channels being
680
occupied or vacated, members being added or removed in presence-channels, or
681
after client-originated events. For more information see
682
https://pusher.com/docs/webhooks.
683

684
If the webhook is valid, a `*pusher.Webhook* will be returned, and the `err`
685
value will be nil. If it is invalid, the first return value will be nil, and an
686
error will be passed.
687

688
        func pusherWebhook(res http.ResponseWriter, req *http.Request) {
689

690
                body, _ := ioutil.ReadAll(req.Body)
691
                webhook, err := client.Webhook(req.Header, body)
692
                if err != nil {
693
                        fmt.Println("Webhook is invalid :(")
694
                } else {
695
                        fmt.Printf("%+v\n", webhook.Events)
696
                }
697

698
        }
699
*/
700
func (c *Client) Webhook(header http.Header, body []byte) (*Webhook, error) {
2✔
701
        for _, token := range header["X-Pusher-Key"] {
4✔
702
                if token == c.Key && checkSignature(header.Get("X-Pusher-Signature"), c.Secret, body) {
4✔
703
                        unmarshalledWebhooks, err := unmarshalledWebhook(body)
2✔
704
                        if err != nil {
2✔
705
                                return nil, err
×
706
                        }
×
707

708
                        hasEncryptedChannel := false
2✔
709
                        for _, event := range unmarshalledWebhooks.Events {
2✔
710
                                if isEncryptedChannel(event.Channel) {
×
711
                                        hasEncryptedChannel = true
×
712
                                }
×
713
                        }
714
                        masterKey, keyErr := c.encryptionMasterKey()
2✔
715
                        if hasEncryptedChannel && keyErr != nil {
2✔
716
                                return nil, keyErr
×
717
                        }
×
718

719
                        return decryptEvents(*unmarshalledWebhooks, masterKey)
2✔
720
                }
721
        }
722
        return nil, errors.New("Invalid webhook")
2✔
723
}
724

725
func (c *Client) encryptionMasterKey() ([]byte, error) {
2✔
726
        if c.validatedEncryptionMasterKey != nil {
2✔
727
                return *(c.validatedEncryptionMasterKey), nil
×
728
        }
×
729

730
        if c.EncryptionMasterKey != "" && c.EncryptionMasterKeyBase64 != "" {
4✔
731
                return nil, errors.New("Do not specify both EncryptionMasterKey and EncryptionMasterKeyBase64. EncryptionMasterKey is deprecated, specify only EncryptionMasterKeyBase64")
2✔
732
        }
2✔
733

734
        if c.EncryptionMasterKey != "" {
4✔
735
                if len(c.EncryptionMasterKey) != 32 {
4✔
736
                        return nil, errors.New("EncryptionMasterKey must be 32 bytes. It is also deprecated, use EncryptionMasterKeyBase64")
2✔
737
                }
2✔
738

739
                keyBytes := []byte(c.EncryptionMasterKey)
×
740
                c.validatedEncryptionMasterKey = &keyBytes
×
741
                return keyBytes, nil
×
742
        }
743

744
        if c.EncryptionMasterKeyBase64 != "" {
4✔
745
                keyBytes, err := base64.StdEncoding.DecodeString(c.EncryptionMasterKeyBase64)
2✔
746
                if err != nil {
4✔
747
                        return nil, errors.New("EncryptionMasterKeyBase64 must be valid base64")
2✔
748
                }
2✔
749
                if len(keyBytes) != 32 {
4✔
750
                        return nil, errors.New("EncryptionMasterKeyBase64 must encode 32 bytes")
2✔
751
                }
2✔
752

753
                c.validatedEncryptionMasterKey = &keyBytes
2✔
754
                return keyBytes, nil
2✔
755
        }
756

757
        return nil, errors.New("No master encryption key supplied")
2✔
758
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc