// Authenticate pusher // see: https://gist.github.com/mloughran/376898 // // The signature is a HMAC SHA256 hex digest. // This is generated by signing a string made up of the following components concatenated with newline characters \n. // // * The uppercase request method (e.g. POST) // * The request path (e.g. /some/resource) // * The query parameters sorted by key, with keys converted to lowercase, then joined as in the query string. // Note that the string must not be url escaped (e.g. given the keys auth_key: foo, Name: Something else, you get auth_key=foo&name=Something else) func restAuthenticationHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) appID := vars["app_id"] app, err := conf.GetAppByAppID(appID) if err != nil { log.Error(err) http.Error(w, "Not authorized", http.StatusUnauthorized) return } params := r.URL.Query() signature := params.Get("auth_signature") params.Del("auth_signature") queryString := prepareQueryString(params) toSign := strings.ToUpper(r.Method) + "\n" + r.URL.Path + "\n" + queryString if utils.HashMAC([]byte(toSign), []byte(app.Secret)) == signature { h.ServeHTTP(w, r) } else { log.Error("Not authorized") http.Error(w, "Not authorized", http.StatusUnauthorized) } }) }
func triggerHook(name string, a *app, c *channel, event hookEvent) { if !a.WebHooks { log.Infof("Webhooks are not enabled for app: %s", a.Name) return } go func() { log.Infof("Triggering %s event", name) hook := webHook{TimeMs: time.Now().Unix()} hook.Events = append(hook.Events, event) var js []byte var err error js, err = json.Marshal(hook) if err != nil { log.Errorf("Error decoding json: %+v", err) return } var req *http.Request req, err = http.NewRequest("POST", a.URLWebHook, bytes.NewReader(js)) if err != nil { log.Errorf("Error creating request: %+v", err) return } req.Header.Set("Content-Type", "application/json") req.Header.Set("X-Pusher-Key", a.Key) req.Header.Set("X-Pusher-Signature", utils.HashMAC(js, []byte(a.Secret))) log.V(1).Infof("%+v", req.Header) log.V(1).Infof("%+v", string(js)) if _, err := http.DefaultClient.Do(req); err != nil { log.Errorf("Error posting %s event: %+v", name, err) } }() }
// Handle messages // // If there is an unrecoverable error then break the loop, // otherwise just keep going. func onMessage(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, sessionID string, app *app) { var event struct { Event string `json:"event"` } for { _, message, err := conn.ReadMessage() if err != nil { log.Errorf("%+v", err) switch err { case io.EOF: onClose(sessionID, app) default: emitWSError(newGenericReconnectImmediatelyError(), conn) } break } if err := json.Unmarshal(message, &event); err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) break } log.Infof("websockets: Handling %s event", event.Event) switch event.Event { case "pusher:ping": if err := conn.WriteJSON(newPongEvent()); err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) } case "pusher:subscribe": subscribeEvent := subscribeEvent{} if err := json.Unmarshal(message, &subscribeEvent); err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) break } connection, err := app.FindConnection(sessionID) if err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) break } channelName := strings.TrimSpace(subscribeEvent.Data.Channel) if !utils.IsChannelNameValid(channelName) { emitWSError(newGenericError(fmt.Sprintf("This channel name is not valid")), conn) break } isPresence := strings.HasPrefix(channelName, "presence-") isPrivate := strings.HasPrefix(channelName, "private-") if isPresence || isPrivate { toSign := []string{connection.SocketID, channelName} if isPresence { toSign = append(toSign, subscribeEvent.Data.ChannelData) } expectedAuthKey := fmt.Sprintf("%s:%s", app.Key, utils.HashMAC([]byte(strings.Join(toSign, ":")), []byte(app.Secret))) if subscribeEvent.Data.Auth != expectedAuthKey { emitWSError(newGenericError(fmt.Sprintf("Auth value for subscription to %s is invalid", channelName)), conn) continue } } channel := app.FindOrCreateChannelByChannelID(channelName) log.Info(subscribeEvent.Data.ChannelData) if err := app.Subscribe(channel, connection, subscribeEvent.Data.ChannelData); err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) } case "pusher:unsubscribe": unsubscribeEvent := unsubscribeEvent{} if err := json.Unmarshal(message, &unsubscribeEvent); err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) } connection, err := app.FindConnection(sessionID) if err != nil { emitWSError(newGenericError(fmt.Sprintf("Could not find a connection with the id %s", sessionID)), conn) } channel, err := app.FindChannelByChannelID(unsubscribeEvent.Data.Channel) if err != nil { emitWSError(newGenericError(fmt.Sprintf("Could not find a channel with the id %s", unsubscribeEvent.Data.Channel)), conn) } if err := app.Unsubscribe(channel, connection); err != nil { emitWSError(newGenericReconnectImmediatelyError(), conn) break } default: // CLient Events ?? // see http://pusher.com/docs/client_api_guide/client_events#trigger-events if strings.HasPrefix(event.Event, "client-") { if !app.UserEvents { emitWSError(newGenericError("To send client events, you must enable this feature in the Settings."), conn) } clientEvent := rawEvent{} if err := json.Unmarshal(message, &clientEvent); err != nil { log.Error(err) emitWSError(newGenericReconnectImmediatelyError(), conn) break } channel, err := app.FindChannelByChannelID(clientEvent.Channel) if err != nil { emitWSError(newGenericError(fmt.Sprintf("Could not find a channel with the id %s", clientEvent.Channel)), conn) } if !channel.IsPresenceOrPrivate() { emitWSError(newGenericError("Client event rejected - only supported on private and presence channels"), conn) break } if err := app.Publish(channel, clientEvent, sessionID); err != nil { log.Error(err) emitWSError(newGenericReconnectImmediatelyError(), conn) break } } } // switch } // For }