diff --git a/CHANGELOG.md b/CHANGELOG.md index 51224bf..12c5076 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 5.1.0 + +* [ADDED] SendToUser method +* [ADDED] AuthenticateUser method +* [ADDED] AuthorizePrivateChannel method +* [ADDED] AuthorizePresenceChannel method +* [CHANGED] AuthenticatePrivateChannel method deprecated +* [CHANGED] AuthenticatePresenceChannel method deprecated + ## 5.0.0 / 2021-02-19 * Breaking change: `TriggerBatch` now returns `(*TriggerBatchChannelsList, error)` instead of `error` diff --git a/README.md b/README.md index 1cb6ec1..1ea8f18 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The Golang library for interacting with the Pusher Channels HTTP API. -This package lets you trigger events to your client and query the state of your Pusher channels. When used with a server, you can validate Pusher Channels webhooks and authenticate `private-` or `presence-` channels. +This package lets you trigger events to your client and query the state of your Pusher channels. When used with a server, you can validate Pusher Channels webhooks and authorize `private-` or `presence-` channels. Register for free at and use the application credentials within your app as shown below. @@ -21,7 +21,8 @@ Register for free at and use the application crede - [Google App Engine](#google-app-engine) - [Usage](#usage) - [Triggering events](#triggering-events) - - [Authenticating Channels](#authenticating-channels) + - [Authenticating Users](#authenticating-users) + - [Authorizing Channels](#authorizing-channels) - [Application state](#application-state) - [Webhook validation](#webhook-validation) - [Feature Support](#feature-support) @@ -146,7 +147,7 @@ pusherClient.Cluster = "eu" // in this case requests will be made to api-eu.push This library supports end to end encryption of your private channels. This means that only you and your connected clients will be able to read your messages. Pusher cannot decrypt them. You can enable this feature by following these steps: -1. You should first set up Private channels. This involves [creating an authentication endpoint on your server](https://pusher.com/docs/authenticating_users). +1. You should first set up Private channels. This involves [creating an authorization endpoint on your server](https://pusher.com/docs/authorizing_users). 2. Next, generate a 32 byte master encryption key, base64 encode it and store it securely. @@ -371,19 +372,72 @@ for i, attributes := range response.Batch { // channel: presence-b-channel, name: event, user_count: 4 ``` -### Authenticating Channels +#### Send to user -Application security is very important so Pusher Channels provides a mechanism for authenticating a user’s access to a channel at the point of subscription. +##### `func (c *Client) SendToUser` -This can be used both to restrict access to private channels, and in the case of presence channels notify subscribers of who else is also subscribed via presence events. +| Argument |Description | +| :-: | :-: | +| userId `string` | The id of the user who should receive the event. | +| event `string` | The name of the event you wish to trigger. | +| data `interface{}` | The payload you wish to send. Must be marshallable into JSON. | + +###### Example + +```go +data := map[string]string{"hello": "world"} +pusherClient.SendToUser("user123", "say_hello", data) +``` + +### Authenticating Users -This library provides a mechanism for generating an authentication signature to send back to the client and authorize them. +Pusher Channels provides a mechanism for authenticating users. This can be used to send messages to specific users based on user id and to terminate misbehaving user connections, for example. For more information see our [docs](http://pusher.com/docs/authenticating_users). +#### `func (c *Client) AuthenticateUser` + +| Argument | Description | +| :-: | :-: | +| params `[]byte` | The request body sent by the client | + +| Return Value | Description | +| :-: | :-: | +| response `[]byte` | The response to send back to the client, carrying an authentication signature | +| err `error` | Any errors generated | + +###### Example + +```go +func pusherUserAuth(res http.ResponseWriter, req *http.Request) { + params, _ := ioutil.ReadAll(req.Body) + response, err := pusherClient.AuthenticateUser(params) + if err != nil { + panic(err) + } + + fmt.Fprintf(res, string(response)) +} + +func main() { + http.HandleFunc("/pusher/user-auth", pusherUserAuth) + http.ListenAndServe(":5000", nil) +} +``` + +### Authorizing Channels + +Application security is very important so Pusher Channels provides a mechanism for authorizing a user’s access to a channel at the point of subscription. + +This can be used both to restrict access to private channels, and in the case of presence channels notify subscribers of who else is also subscribed via presence events. + +This library provides a mechanism for generating an authorization signature to send back to the client and authorize them. + +For more information see our [docs](http://pusher.com/docs/authorizing_users). + #### Private channels -##### `func (c *Client) AuthenticatePrivateChannel` +##### `func (c *Client) AuthorizePrivateChannel` | Argument | Description | | :-: | :-: | @@ -391,7 +445,7 @@ For more information see our [docs](http://pusher.com/docs/authenticating_users) | Return Value | Description | | :-: | :-: | -| response `[]byte` | The response to send back to the client, carrying an authentication signature | +| response `[]byte` | The response to send back to the client, carrying an authorization signature | | err `error` | Any errors generated | ###### Example @@ -399,7 +453,7 @@ For more information see our [docs](http://pusher.com/docs/authenticating_users) ```go func pusherAuth(res http.ResponseWriter, req *http.Request) { params, _ := ioutil.ReadAll(req.Body) - response, err := pusherClient.AuthenticatePrivateChannel(params) + response, err := pusherClient.AuthorizePrivateChannel(params) if err != nil { panic(err) } @@ -431,7 +485,7 @@ func pusherJsonpAuth(res http.ResponseWriter, req *http.Request) { params = []byte(q.Encode()) } - response, err := pusherClient.AuthenticatePrivateChannel(params) + response, err := pusherClient.AuthorizePrivateChannel(params) if err != nil { panic(err) } @@ -446,11 +500,11 @@ func main() { } ``` -#### Authenticating presence channels +#### Authorizing presence channels Using presence channels is similar to private channels, but in order to identify a user, clients are sent a user_id and, optionally, custom data. -##### `func (c *Client) AuthenticatePresenceChannel` +##### `func (c *Client) AuthorizePresenceChannel` | Argument | Description | | :-: | :-: | @@ -480,7 +534,7 @@ presenceData := pusher.MemberData{ }, } -response, err := pusherClient.AuthenticatePresenceChannel(params, presenceData) +response, err := pusherClient.AuthorizePresenceChannel(params, presenceData) if err != nil { panic(err) @@ -692,8 +746,10 @@ Trigger event on multiple channels | *✔* Trigger events in batches | *✔* Excluding recipients from events | *✔* Fetching info on trigger | *✔* -Authenticating private channels | *✔* -Authenticating presence channels | *✔* +Send to user | *✔* +Authenticating users | *✔* +Authorizing private channels | *✔* +Authorizing presence channels | *✔* Get the list of channels in an application | *✔* Get the state of a single channel | *✔* Get a list of users in a presence channel | *✔* diff --git a/client.go b/client.go index 13d0e89..ebf1942 100644 --- a/client.go +++ b/client.go @@ -17,7 +17,7 @@ var pusherPathRegex = regexp.MustCompile("^/apps/([0-9]+)$") var maxTriggerableChannels = 100 const ( - libraryVersion = "5.0.0" + libraryVersion = "5.1.0" libraryName = "pusher-http-go" ) @@ -142,7 +142,7 @@ be marshallable into JSON. */ func (c *Client) Trigger(channel string, eventName string, data interface{}) error { - _, err := c.trigger([]string{channel}, eventName, data, TriggerParams{}) + _, err := c.validateChannelsAndTrigger([]string{channel}, eventName, data, TriggerParams{}) return err } @@ -195,7 +195,7 @@ func (c *Client) TriggerWithParams( data interface{}, params TriggerParams, ) (*TriggerChannelsList, error) { - return c.trigger([]string{channel}, eventName, data, params) + return c.validateChannelsAndTrigger([]string{channel}, eventName, data, params) } /* @@ -204,7 +204,7 @@ TriggerMulti is the same as `client.Trigger`, except one passes in a slice of client.TriggerMulti([]string{"a_channel", "another_channel"}, "event", data) */ func (c *Client) TriggerMulti(channels []string, eventName string, data interface{}) error { - _, err := c.trigger(channels, eventName, data, TriggerParams{}) + _, err := c.validateChannelsAndTrigger(channels, eventName, data, TriggerParams{}) return err } @@ -219,7 +219,7 @@ func (c *Client) TriggerMultiWithParams( data interface{}, params TriggerParams, ) (*TriggerChannelsList, error) { - return c.trigger(channels, eventName, data, params) + return c.validateChannelsAndTrigger(channels, eventName, data, params) } /* @@ -232,7 +232,7 @@ Deprecated: use TriggerWithParams instead. */ func (c *Client) TriggerExclusive(channel string, eventName string, data interface{}, socketID string) error { params := TriggerParams{SocketID: &socketID} - _, err := c.trigger([]string{channel}, eventName, data, params) + _, err := c.validateChannelsAndTrigger([]string{channel}, eventName, data, params) return err } @@ -246,18 +246,37 @@ Deprecated: use TriggerMultiWithParams instead. */ func (c *Client) TriggerMultiExclusive(channels []string, eventName string, data interface{}, socketID string) error { params := TriggerParams{SocketID: &socketID} - _, err := c.trigger(channels, eventName, data, params) + _, err := c.validateChannelsAndTrigger(channels, eventName, data, params) return err } -func (c *Client) trigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { +/* +SendToUser triggers an event to a specific user. +Pass in the user id, the event's name, and a data payload. The data payload must +be marshallable into JSON. + + data := map[string]string{"hello": "world"} + client.SendToUser("user123", "say_hello", data) +*/ +func (c *Client) SendToUser(userId string, eventName string, data interface{}) error { + if !validUserId(userId) { + return fmt.Errorf("User id '%s' is invalid", userId) + } + _, err := c.trigger([]string{"#server-to-user-" + userId}, eventName, data, TriggerParams{}) + return err +} + +func (c *Client) validateChannelsAndTrigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { if len(channels) > maxTriggerableChannels { return nil, fmt.Errorf("You cannot trigger on more than %d channels at once", maxTriggerableChannels) } if !channelsAreValid(channels) { return nil, errors.New("At least one of your channels' names are invalid") } + return c.trigger(channels, eventName, data, params) +} +func (c *Client) trigger(channels []string, eventName string, data interface{}, params TriggerParams) (*TriggerChannelsList, error) { hasEncryptedChannel := false for _, channel := range channels { if isEncryptedChannel(channel) { @@ -272,6 +291,7 @@ func (c *Client) trigger(channels []string, eventName string, data interface{}, if hasEncryptedChannel && keyErr != nil { return nil, keyErr } + if err := validateSocketID(params.SocketID); err != nil { return nil, err } @@ -462,13 +482,71 @@ func (c *Client) GetChannelUsers(name string) (*Users, error) { } /* -AuthenticatePrivateChannel allows you to authenticate a users subscription to a -private channel. It returns authentication signature to send back to the client -and authorize them. +AuthenticateUser allows you to authenticate a user s subscription to a +private channel. It returns an authentication signature to send back to the client +and authenticate them. In order to identify a user, this method acceps a map containing +arbitrary user data. It must contain at least and id field with the user's id as a string. For more information see our docs: http://pusher.com/docs/authenticating_users. -This is an example of authenticating a private-channel, using the built-in +This is an example of authenticating a user, using the built-in +Golang HTTP library to start a server. + +In order to authenticate a client, one must read the response into type `[]byte` +and pass it in. This will return a signature in the form of a `[]byte` for you +to send back to the client. + + func pusherUserAuth(res http.ResponseWriter, req *http.Request) { + + params, _ := ioutil.ReadAll(req.Body) + response, err := client.AuthenticateUser(params) + if err != nil { + panic(err) + } + + fmt.Fprintf(res, string(response)) + } + + func main() { + http.HandleFunc("/pusher/user-auth", pusherUserAuth) + http.ListenAndServe(":5000", nil) + } +*/ +func (c *Client) AuthenticateUser(params []byte, userData map[string]interface{}) (response []byte, err error) { + socketID, err := parseUserAuthenticationRequestParams(params) + if err != nil { + return + } + + if err = validateSocketID(&socketID); err != nil { + return + } + + if err = validateUserData(userData); err != nil { + return + } + + var jsonUserData string + if jsonUserData, err = jsonMarshalToString(userData); err != nil { + return + } + stringToSign := strings.Join([]string{socketID, "user", jsonUserData}, "::") + + _response := createAuthMap(c.Key, c.Secret, stringToSign, "") + _response["user_data"] = jsonUserData + + response, err = json.Marshal(_response) + return +} + +/* +AuthorizePrivateChannel allows you to authorize a users subscription to a +private channel. It returns an authorization signature to send back to the client +and authorize them. + +For more information see our docs: http://pusher.com/docs/authorizing_users. + +This is an example of authorizing a private-channel, using the built-in Golang HTTP library to start a server. In order to authorize a client, one must read the response into type `[]byte` @@ -478,7 +556,7 @@ to send back to the client. func pusherAuth(res http.ResponseWriter, req *http.Request) { params, _ := ioutil.ReadAll(req.Body) - response, err := client.AuthenticatePrivateChannel(params) + response, err := client.AuthorizePrivateChannel(params) if err != nil { panic(err) } @@ -491,13 +569,24 @@ to send back to the client. http.ListenAndServe(":5000", nil) } */ +func (c *Client) AuthorizePrivateChannel(params []byte) (response []byte, err error) { + return c.authorizeChannel(params, nil) +} + +/* +AuthenticatePrivateChannel allows you to authorize a users subscription to a +private channel. It returns an authorization signature to send back to the client +and authorize them. + +Deprecated: use AuthorizePrivateChannel instead. +*/ func (c *Client) AuthenticatePrivateChannel(params []byte) (response []byte, err error) { - return c.authenticateChannel(params, nil) + return c.authorizeChannel(params, nil) } /* -AuthenticatePresenceChannel allows you to authenticate a users subscription to a -presence channel. It returns authentication signature to send back to the client +AuthorizePresenceChannel allows you to authorize a users subscription to a +presence channel. It returns an authorization signature to send back to the client and authorize them. In order to identify a user, clients are sent a user_id and, optionally, custom data. @@ -512,19 +601,31 @@ In this library, one does this by passing a `pusher.MemberData` instance. }, } - response, err := client.AuthenticatePresenceChannel(params, presenceData) + response, err := client.AuthorizePresenceChannel(params, presenceData) if err != nil { panic(err) } fmt.Fprintf(res, response) */ +func (c *Client) AuthorizePresenceChannel(params []byte, member MemberData) (response []byte, err error) { + return c.authorizeChannel(params, &member) +} + +/* +AuthenticatePresenceChannel allows you to authorize a users subscription to a +presence channel. It returns an authorization signature to send back to the client +and authorize them. In order to identify a user, clients are sent a user_id and, +optionally, custom data. + +Deprecated: use AuthorizePresenceChannel instead. +*/ func (c *Client) AuthenticatePresenceChannel(params []byte, member MemberData) (response []byte, err error) { - return c.authenticateChannel(params, &member) + return c.authorizeChannel(params, &member) } -func (c *Client) authenticateChannel(params []byte, member *MemberData) (response []byte, err error) { - channelName, socketID, err := parseAuthRequestParams(params) +func (c *Client) authorizeChannel(params []byte, member *MemberData) (response []byte, err error) { + channelName, socketID, err := parseChannelAuthorizationRequestParams(params) if err != nil { return } diff --git a/client_test.go b/client_test.go index e169b1a..53c04e6 100644 --- a/client_test.go +++ b/client_test.go @@ -15,6 +15,45 @@ import ( "gopkg.in/stretchr/testify.v1/assert" ) +func TestSendToUserSuccessCase(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(200) + fmt.Fprintf(res, "{}") + assert.Equal(t, "POST", req.Method) + + expectedBody := map[string]interface{}{"name": "test", "channels": []interface{}{"#server-to-user-123456"}, "data": "yolo"} + bodyDecoder := json.NewDecoder(req.Body) + var actualBody map[string]interface{} + err := bodyDecoder.Decode(&actualBody) + assert.NoError(t, err) + assert.Equal(t, expectedBody, actualBody) + + assert.Equal(t, "application/json", req.Header["Content-Type"][0]) + lib := fmt.Sprintf("%s %s", libraryName, libraryVersion) + assert.Equal(t, lib, req.Header["X-Pusher-Library"][0]) + assert.NoError(t, err) + })) + defer server.Close() + + u, _ := url.Parse(server.URL) + client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} + err := client.SendToUser("123456", "test", "yolo") + assert.NoError(t, err) +} + +func TestSendToUserRejected(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + t.Fatal("No request should reach the API") + })) + defer server.Close() + + u, _ := url.Parse(server.URL) + client := Client{AppID: "id", Key: "key", Secret: "secret", Host: u.Host} + err := client.SendToUser("", "test", "yolo") + assert.Error(t, err) + assert.Contains(t, err.Error(), "User id '' is invalid") +} + func TestTriggerSuccessCase(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(200) @@ -546,7 +585,44 @@ func TestTriggerInvalidMasterKey(t *testing.T) { assert.Contains(t, err.Error(), "valid base64") } -func TestAuthenticateInvalidMasterKey(t *testing.T) { +func TestAuthenticateUser(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + t.Fatal("No HTTP request should have been made") + })) + defer server.Close() + u, _ := url.Parse(server.URL) + + client := Client{ + AppID: "appid", + Key: "key", + Secret: "secret", + Host: u.Host, + } + + var params []byte + var userData map[string]interface{} + + params = []byte("socket_id=12345.12345") + userData = map[string]interface{} {} + _, err := client.AuthenticateUser(params, userData) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Missing id in user data") + + params = []byte("not_socket_id=12345.12345") + userData = map[string]interface{} { "id": "1234" } + _, err = client.AuthenticateUser(params, userData) + assert.Error(t, err) + assert.Contains(t, err.Error(), "socket_id not found") + + params = []byte("socket_id=12345.12345") + userData = map[string]interface{} { "id": "1234" } + var response []byte + response, err = client.AuthenticateUser(params, userData) + assert.NoError(t, err) + assert.Equal(t, string(response), "{\"auth\":\"key:e4c63b82c1e1d0955901f6a29ca51b244155bafda93968bc5664010f5ba54a41\",\"user_data\":\"{\\\"id\\\":\\\"1234\\\"}\"}") +} + +func TestAuthorizeInvalidMasterKey(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { t.Fatal("No HTTP request should have been made") })) @@ -563,7 +639,7 @@ func TestAuthenticateInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKey: "this is 31 bytes 12345678901234", } - _, err := client.AuthenticatePrivateChannel(params) + _, err := client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -575,7 +651,7 @@ func TestAuthenticateInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKey: "this is 33 bytes 1234567890123456", } - _, err = client.AuthenticatePrivateChannel(params) + _, err = client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -588,7 +664,7 @@ func TestAuthenticateInvalidMasterKey(t *testing.T) { EncryptionMasterKey: "this is 32 bytes 123456789012345", EncryptionMasterKeyBase64: "dGhpcyBpcyAzMiBieXRlcyAxMjM0NTY3ODkwMTIzNDU=", } - _, err = client.AuthenticatePrivateChannel(params) + _, err = client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "both") @@ -600,7 +676,7 @@ func TestAuthenticateInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKeyBase64: "dGhpcyBpcyAzMSBieXRlcyAxMjM0NTY3ODkwMTIzNA==", } - _, err = client.AuthenticatePrivateChannel(params) + _, err = client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -612,7 +688,7 @@ func TestAuthenticateInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKeyBase64: "dGhpcyBpcyAzMiBieXRlcyAxMjM0NTY3ODkwMTIzNDU2", } - _, err = client.AuthenticatePrivateChannel(params) + _, err = client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "32 bytes") @@ -624,7 +700,7 @@ func TestAuthenticateInvalidMasterKey(t *testing.T) { Host: u.Host, EncryptionMasterKeyBase64: "dGhp!yBpcyAzMiBieXRlcy#xMjM0NTY3ODkwMTIzNDU=", } - _, err = client.AuthenticatePrivateChannel(params) + _, err = client.AuthorizePrivateChannel(params) assert.Error(t, err) assert.Contains(t, err.Error(), "valid base64") } diff --git a/util.go b/util.go index b536744..17adb5e 100644 --- a/util.go +++ b/util.go @@ -2,6 +2,8 @@ package pusher import ( "errors" + "encoding/json" + "fmt" "net/url" "regexp" "strconv" @@ -13,24 +15,49 @@ var channelValidationRegex = regexp.MustCompile("^[-a-zA-Z0-9_=@,.;]+$") var socketIDValidationRegex = regexp.MustCompile(`\A\d+\.\d+\z`) var maxChannelNameSize = 200 +func jsonMarshalToString(data interface{}) (result string, err error) { + var _result []byte + _result, err = json.Marshal(data) + if err != nil { + return + } + return string(_result), err +} + func authTimestamp() string { return strconv.FormatInt(time.Now().Unix(), 10) } -func parseAuthRequestParams(_params []byte) (channelName string, socketID string, err error) { +func parseUserAuthenticationRequestParams(_params []byte) (socketID string, err error) { + params, err := url.ParseQuery(string(_params)) + if err != nil { + return + } + if _, ok := params["socket_id"]; !ok { + return "", errors.New("socket_id not found") + } + return params["socket_id"][0], nil +} + +func parseChannelAuthorizationRequestParams(_params []byte) (channelName string, socketID string, err error) { params, err := url.ParseQuery(string(_params)) if err != nil { return } if _, ok := params["channel_name"]; !ok { - return "", "", errors.New("Channel param not found") + return "", "", errors.New("channel_name not found") } if _, ok := params["socket_id"]; !ok { - return "", "", errors.New("Socket_id not found") + return "", "", errors.New("socket_id not found") } return params["channel_name"][0], params["socket_id"][0], nil } +func validUserId(userId string) bool { + length := len(userId) + return length > 0 && length < maxChannelNameSize +} + func validChannel(channel string) bool { if len(channel) > maxChannelNameSize || !channelValidationRegex.MatchString(channel) { return false @@ -54,6 +81,22 @@ func isEncryptedChannel(channel string) bool { return false } +func validateUserData(userData map[string]interface{}) (err error) { + _id, ok := userData["id"] + if !ok || _id == nil { + return errors.New("Missing id in user data") + } + var id string + id, ok = _id.(string) + if !ok { + return errors.New("id field in user data is not a string") + } + if !validUserId(id) { + return fmt.Errorf("Invalid id in user data: '%s'", id) + } + return +} + func validateSocketID(socketID *string) (err error) { if (socketID == nil) || socketIDValidationRegex.MatchString(*socketID) { return diff --git a/util_test.go b/util_test.go index f8d8766..6b588c7 100644 --- a/util_test.go +++ b/util_test.go @@ -6,22 +6,77 @@ import ( "gopkg.in/stretchr/testify.v1/assert" ) -func TestParseAuthRequestParamsNoSock(t *testing.T) { +func TestParseUserAuthenticationRequestParamsNoSock(t *testing.T) { + params := "abc=hello" + _, result := parseUserAuthenticationRequestParams([]byte(params)) + assert.Error(t, result) + assert.EqualError(t, result, "socket_id not found") +} + +func TestInvalidUserAuthenticationParams(t *testing.T) { + params := "%$@£$${}$£%|$^%$^|" + _, result := parseUserAuthenticationRequestParams([]byte(params)) + assert.Error(t, result) +} + +func TestUserAuthenticationParamsSuccess(t *testing.T) { + params := "socket_id=123" + socket_id, result := parseUserAuthenticationRequestParams([]byte(params)) + assert.Equal(t, socket_id, "123") + assert.NoError(t, result) +} + +func TestParseChannelAuthorizationRequestParamsNoSock(t *testing.T) { params := "channel_name=hello" - _, _, result := parseAuthRequestParams([]byte(params)) + _, _, result := parseChannelAuthorizationRequestParams([]byte(params)) assert.Error(t, result) - assert.EqualError(t, result, "Socket_id not found") + assert.EqualError(t, result, "socket_id not found") } -func TestParseAuthRequestParamsNoChan(t *testing.T) { +func TestParseChannelAuthorizationRequestParamsNoChan(t *testing.T) { params := "socket_id=45.3" - _, _, result := parseAuthRequestParams([]byte(params)) + _, _, result := parseChannelAuthorizationRequestParams([]byte(params)) assert.Error(t, result) - assert.EqualError(t, result, "Channel param not found") + assert.EqualError(t, result, "channel_name not found") } -func TestInvalidAuthParams(t *testing.T) { +func TestInvalidChannelAuthorizationParams(t *testing.T) { params := "%$@£$${}$£%|$^%$^|" - _, _, result := parseAuthRequestParams([]byte(params)) + _, _, result := parseChannelAuthorizationRequestParams([]byte(params)) assert.Error(t, result) } + +func TestValidateUserDataSuccess(t *testing.T) { + m := map[string]interface{}{ + "id": "12345", + "email": "test@test.com", + } + err := validateUserData(m) + assert.NoError(t, err) +} + +func TestValidateUserDataNoId(t *testing.T) { + m := map[string]interface{}{ + "email": "test@test.com", + } + err := validateUserData(m) + assert.EqualError(t, err, "Missing id in user data") +} + +func TestValidateUserDataIdIsNotString(t *testing.T) { + m := map[string]interface{}{ + "id": 123, + "email": "test@test.com", + } + err := validateUserData(m) + assert.EqualError(t, err, "id field in user data is not a string") +} + +func TestValidateUserDataInvalidId(t *testing.T) { + m := map[string]interface{}{ + "id": "", + "email": "test@test.com", + } + err := validateUserData(m) + assert.EqualError(t, err, "Invalid id in user data: ''") +}