diff --git a/lib/slanger/request_validation.rb b/lib/slanger/api/request_validation.rb similarity index 64% rename from lib/slanger/request_validation.rb rename to lib/slanger/api/request_validation.rb index b79924bf..179ab316 100644 --- a/lib/slanger/request_validation.rb +++ b/lib/slanger/api/request_validation.rb @@ -1,8 +1,29 @@ module Slanger - class RequestValidation < Struct.new :raw_body, :raw_params + class RequestValidation < Struct.new :raw_body, :raw_params, :path_info + def initialize(*args) + super(*args) + + validate! + authenticate! + end + + def authenticate! + # Raises Signature::AuthenticationError if request does not authenticate. + byebug + Signature::Request.new('POST', path_info, auth_params). + authenticate { |key| Signature::Token.new key, Slanger::Config.secret } + end + + def auth_params + params.except('channel_id', 'app_id') + end + + def validate! + determine_valid_socket_id + end + def socket_id - return validate_socket_id!(data["socket_id"]) if data["socket_id"] - return validate_socket_id!(params["socket_id"]) if params["socket_id"] + @socket_id ||= determine_valid_socket_id end def params @@ -15,6 +36,11 @@ def data private + def determine_valid_socket_id + return validate_socket_id!(data["socket_id"]) if data["socket_id"] + return validate_socket_id!(params["socket_id"]) if params["socket_id"] + end + def validate_raw_params! restricted = user_params.slice "body_md5", "auth_version", "auth_key", "auth_timestamp", "auth_signature", "app_id" diff --git a/lib/slanger/api/server.rb b/lib/slanger/api/server.rb new file mode 100644 index 00000000..732842fa --- /dev/null +++ b/lib/slanger/api/server.rb @@ -0,0 +1,75 @@ +# encoding: utf-8 +require 'sinatra/base' +require 'signature' +require 'json' +require 'active_support/core_ext/hash' +require 'eventmachine' +require 'em-hiredis' +require 'rack' +require 'fiber' +require 'rack/fiber_pool' + +module Slanger + module Api + class Server < Sinatra::Base + use Rack::FiberPool + set :raise_errors, lambda { false } + set :show_exceptions, false + + # Respond with HTTP 401 Unauthorized if request cannot be authenticated. + error(Signature::AuthenticationError) { |e| halt 401, "401 UNAUTHORIZED: #{e}" } + + before do + validate_request! + end + + post '/apps/:app_id/events' do + socket_id = validated_request.socket_id + data = validated_request.data + + # Event and channel data are now serialized in the JSON data + # So, extract and use it + # Send event to each channel + data["channels"].each { |channel| publish(channel, data['name'], data['data'], socket_id) } + + status 202 + return {}.to_json + end + + post '/apps/:app_id/channels/:channel_id/events' do + params = validated_request.params + + publish(params[:channel_id], params['name'], body) + + status 202 + return {}.to_json + end + + def body + @body ||= request.body.read.tap{|s| s.force_encoding("utf-8")} + end + + def validate_request! + validated_request + end + + def validated_request + @validated_reqest ||= RequestValidation.new(body, params, env["PATH_INFO"]) + end + + def payload(channel, event, data, socket_id) + { + event: event, + data: data, + channel: channel, + socket_id: socket_id + }.select { |_,v| v }.to_json + end + + + def publish(channel, event, data, socket_id) + Slanger::Redis.publish(channel, payload(channel, event, data, socket_id)) + end + end + end +end diff --git a/lib/slanger/api_server.rb b/lib/slanger/api_server.rb deleted file mode 100644 index 4e122be8..00000000 --- a/lib/slanger/api_server.rb +++ /dev/null @@ -1,75 +0,0 @@ -# encoding: utf-8 -require 'sinatra/base' -require 'signature' -require 'json' -require 'active_support/core_ext/hash' -require 'eventmachine' -require 'em-hiredis' -require 'rack' -require 'fiber' -require 'rack/fiber_pool' - -module Slanger - class ApiServer < Sinatra::Base - use Rack::FiberPool - set :raise_errors, lambda { false } - set :show_exceptions, false - - # Respond with HTTP 401 Unauthorized if request cannot be authenticated. - error(Signature::AuthenticationError) { |e| halt 401, "401 UNAUTHORIZED: #{e}" } - - post '/apps/:app_id/events' do - rv = RequestValidation.new(body, params) - socket_id = rv.socket_id - data = rv.data - - authenticate - # Event and channel data are now serialized in the JSON data - # So, extract and use it - # Send event to each channel - data["channels"].each { |channel| publish(channel, data['name'], data['data'], socket_id) } - - status 202 - return {}.to_json - end - - def body - @body ||= request.body.read.tap{|s| s.force_encoding("utf-8")} - end - - post '/apps/:app_id/channels/:channel_id/events' do - rv = RequestValidation.new(body) - socket_id = rv.socket_id - data = rv.data - - - authenticate - params = rv.user_params - - publish(params[:channel_id], params['name'], body) - - status 202 - return {}.to_json - end - - def payload(channel, event, data, socket_id) - { - event: event, - data: data, - channel: channel, - socket_id: socket_id - }.select { |_,v| v }.to_json - end - - def authenticate - # authenticate request. exclude 'channel_id' and 'app_id' included by sinatra but not sent by Pusher. - # Raises Signature::AuthenticationError if request does not authenticate. - Signature::Request.new('POST', env['PATH_INFO'], params.except('captures', 'splat' , 'channel_id', 'app_id')). - authenticate { |key| Signature::Token.new key, Slanger::Config.secret } - end - - def publish(channel, event, data, socket_id) - Slanger::Redis.publish(channel, payload(channel, event, data, socket_id)) - end - end -end