diff --git a/client.go b/client.go index d055e34..6d47d97 100644 --- a/client.go +++ b/client.go @@ -50,6 +50,7 @@ type UserInfo struct { } type Channel struct { + Client *Client Name string Occupied bool `json:"occupied"` UserCount int `json:"user_count",omitempty` @@ -61,6 +62,20 @@ func (c *Channel) String() string { return fmt.Sprintf(format, c.Name, c.Occupied, c.UserCount, c.SubscriptionCount) } +func (c *Channel) Authenticate(socketID string, data interface{}) (*AuthInfo, error) { + + userAuthentication := &UserAuthentication{c.Client.key, c.Client.secret} + + var authInfo *AuthInfo + authInfo, err := userAuthentication.Authenticate(c.Name, socketID, data) + + if err != nil { + return nil, fmt.Errorf("pusher: Channel Authenticate failed: %s", err) + } + + return authInfo, err +} + func NewClient(appid, key, secret string) *Client { return &Client{ appid: appid, @@ -129,6 +144,7 @@ func (c *Client) Channel(name string, queryParameters map[string]string) (*Chann return nil, fmt.Errorf("pusher: Channel failed: %s", err) } + channel.Client = c channel.Name = name return channel, nil diff --git a/client_test.go b/client_test.go index 0ecb857..7e341f1 100644 --- a/client_test.go +++ b/client_test.go @@ -103,3 +103,22 @@ func TestDefaultScheme(t *testing.T) { t.Errorf("Scheme not set correctly") } } + +func TestAuthenticate(t *testing.T) { + key := "278d425bdf160c739803" + secret := "7ad3773142a6692b25b8" + + client := NewClient("1", key, secret) + channel := &Channel{client, "private-foobar", false, 0, 0} + + authExpected := key + ":58df8b0c36d6982b82c3ecf6b4662e34fe8c25bba48f5369f135bf843651c3a4" + authInfo, _ := channel.Authenticate("1234.1234", nil) + + if authExpected != authInfo.Auth { + t.Errorf("Authenticate(): Expected %s, got %s", authExpected, authInfo.Auth) + } + + if "" != authInfo.ChannelData { + t.Errorf("Authenticate(): Expected %s, got %s", nil, authInfo.ChannelData) + } +} diff --git a/examples/channelAuthenticate.go b/examples/channelAuthenticate.go new file mode 100644 index 0000000..c962000 --- /dev/null +++ b/examples/channelAuthenticate.go @@ -0,0 +1,37 @@ +package main + +import ( + "../../pusher" + "fmt" + "time" +) + +func main() { + client := pusher.NewClient("4115", "23ed642e81512118260e", "cd72de5494540704dcf1") + + done := make(chan bool) + + go func() { + channel, err := client.Channel("common", nil) + if err != nil { + fmt.Printf("Error %s\n", err) + } else { + fmt.Println(channel) + } + + authInfo, errAuth := channel.Authenticate("1234.1234", nil) + if errAuth != nil { + fmt.Printf("Error %s\n", errAuth) + } else { + fmt.Println(authInfo) + } + done <- true + }() + + select { + case <-done: + fmt.Println("Done :-)") + case <-time.After(1 * time.Minute): + fmt.Println("Timeout :-(") + } +} diff --git a/userAuthentication.go b/userAuthentication.go new file mode 100644 index 0000000..009cc80 --- /dev/null +++ b/userAuthentication.go @@ -0,0 +1,39 @@ +package pusher + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "io" +) + +type UserAuthentication struct { + key, secret string +} + +type AuthInfo struct { + Auth string `json:"auth"` + ChannelData string `json:"channel_data,omitempty"` +} + +// Authenticate can handle user authentification for private and presence channels +func (ua *UserAuthentication) Authenticate(channelName string, socketID string, data interface{}) (*AuthInfo, error) { + + stringToSign := socketID + ":" + channelName + jsonStr := "" + + if data != nil { + b, err := json.Marshal(data) + if err != nil { + return nil, err + } + jsonStr = string(b) + stringToSign = stringToSign + ":" + jsonStr + } + + hash := hmac.New(sha256.New, []byte(ua.secret)) + io.WriteString(hash, stringToSign) + hexDigest := hex.EncodeToString(hash.Sum(nil)) + return &AuthInfo{ua.key + ":" + hexDigest, jsonStr}, nil +} diff --git a/userAuthentication_test.go b/userAuthentication_test.go new file mode 100644 index 0000000..39959d0 --- /dev/null +++ b/userAuthentication_test.go @@ -0,0 +1,68 @@ +package pusher + +import ( + "encoding/json" + "testing" +) + +func TestPrivateChannelAuthentication(t *testing.T) { + ua := &UserAuthentication{"278d425bdf160c739803", "7ad3773142a6692b25b8"} + + authExpected := "278d425bdf160c739803:58df8b0c36d6982b82c3ecf6b4662e34fe8c25bba48f5369f135bf843651c3a4" + authInfo, _ := ua.Authenticate("private-foobar", "1234.1234", nil) + + if authExpected != authInfo.Auth { + t.Errorf("Authenticate(): Expected %s, got %s", authExpected, authInfo.Auth) + } + + if "" != authInfo.ChannelData { + t.Errorf("Authenticate(): Expected %s, got %s", nil, authInfo.ChannelData) + } +} + +func TestPresenceChannelAuthentication(t *testing.T) { + ua := &UserAuthentication{"278d425bdf160c739803", "7ad3773142a6692b25b8"} + + authExpected := "278d425bdf160c739803:afaed3695da2ffd16931f457e338e6c9f2921fa133ce7dac49f529792be6304c" + channelDataExpected := "{\"user_id\":10,\"user_info\":{\"name\":\"Mr. Pusher\"}}" + + dataStr := channelDataExpected + var data interface{} + json.Unmarshal([]byte(dataStr), &data) + + authInfo, _ := ua.Authenticate("presence-foobar", "1234.1234", data) + + if authExpected != authInfo.Auth { + t.Errorf("Authenticate(): Expected %s, got %s", authExpected, authInfo.Auth) + } + + if channelDataExpected != authInfo.ChannelData { + t.Errorf("Authenticate(): Expected %s, got %s", channelDataExpected, authInfo.ChannelData) + } +} + +func TestAuthInfoJsonWithData(t *testing.T) { + authInfo := &AuthInfo{"auth", "{\"key\": \"value\"}"} + + jsonExpected := "{\"auth\":\"auth\",\"channel_data\":\"{\\\"key\\\": \\\"value\\\"}\"}" + + b, _ := json.Marshal(authInfo) + res := string(b) + + if res != jsonExpected { + t.Errorf("AuthInfo json: Expected %s, got %s", jsonExpected, res) + } +} + +func TestAuthInfoJsonWithoutData(t *testing.T) { + authInfo := &AuthInfo{"auth", ""} + + jsonExpected := "{\"auth\":\"auth\"}" + + b, _ := json.Marshal(authInfo) + res := string(b) + + if res != jsonExpected { + t.Errorf("AuthInfo json: Expected %s, got %s", jsonExpected, res) + } +}