From Rails devise auth to backbone & api? - ruby-on-rails-3

i want to rebuild an app which is a typical rails 3.2 mvc app into a API + Frontend (Backbone) only. As I have no experience in building APIs in rails including authenticatin:
What's the best way to authenticate with devise using backbone? Using auth_tokens?
How should I make he API? Just printing out JSON or use a gem like Grape?
thanks in advance!

I can explain you the way i do this :
First, i install a standard rails application with devise. After that, i create my own session controller :
class SessionsController < ApplicationController
def authenticate
# this method logs you in and returns you a single_access_token token for authentication.
#user = User.find_for_authentication(:email => params[:user][:email])
if #user && #user.valid_password?(params[:user][:password])
render :json => {:user => {:email => #user.email, :id => #user.id, :firsname => #user.firstname, :lastname => #user.lastname, :team_id => #user.team_id, :singleAccessToken => #user.generate_access_token}}
else
render :json => {:errors => ["Nom d'utilisateur ou mot de passe invalide"]}, :status => 401
end
end
end
As you can see, i send a request to this url with the json looking like :
{
user => {
email => "myemail#toto.com",
password => "monpass"
}
}
And my controller return me the json with user data if every thing is fine, or an error. On json with user, i return an access_token used on next requests to check that the user is allowed to request. I made this filters in my application controller :
class ApplicationController < ActionController::Base
protect_from_forgery
protected
def user_access_token
request.headers["HTTP_X_USER_ACCESS_TOKEN"] || request.headers["HTTP_USER_ACCESS_TOKEN"]
end
def current_user
if token = user_access_token
#user ||= User.find_by_access_token(token)
end
end
def require_user
unless current_user
render :json => {:error => "Invalid Access Token"}, :status => 401
end
end
def require_owner
unless current_user && current_user == object.user
render :json => {:error => "Unauthorized"}
end
end
end
As you can see, on each next request, i will add the access_token in html header on key : HTTP_USER_ACCESS_TOKEN
So, i can check if the user is allowed to make the request.
To make an API, you can use the Rails API gem as see here :
http://railscasts.com/episodes/348-the-rails-api-gem
Good luck.

Related

Devise: Logout *without* redirecting?

Im trying to logout with devise but without redirecting! So I want to disable the redirect after logout. The reason for this is I want to render a certain template instead.
How to logout a user from devise without redirecting them for --> this method only <--? Im already overriding the devise session controller.
My method ( called from before filter in application_controller.rb)
def check_concurrent_session
if user_signed_in?
if current_user && !(session[:token] == current_user.login_token)
render :template => "users/logout_duplex", :layout => "application", :locals => { sub_layout => "left" }
# The next line always redirects to signout_path
# I want to disable the complete redirect so it just logs out and shows the rendered template
Devise.sign_out_all_scopes ? sign_out : sign_out(#user)
return
end
end
end
Inherit sessions controller from devise and have your own implementation of after_sign_out_path_for. That's the method devise uses to redirect after signout.
class SessionsController < Devise::SessionsController
protected
def after_sign_out_path_for(resource)
//your implementation
end
end
Add this to your config/routes.rb
devise_for :users, :controllers => {:sessions => 'sessions'}
Devise implementation of the method looks like below
def after_sign_out_path_for(resource_or_scope)
respond_to?(:root_path) ? root_path : "/"
end

Rails testing, how to specify path for post? (No route matches error)

I try to test one of my REST api controllers which is placed at "controllers/api/v1/bookings_controller.rb". The controller only responds_to json at the moment as you can see here:
class Api::V1::BookingsController < ApplicationController
respond_to :json
before_filter :authenticate_user!
before_filter :get_user
def create
...
end
end
My functional test is located at "test/functional/api/v1/bookings_controller_test.rb" and looks like following:
require 'test_helper'
class Api::V1::BookingsControllerTest < ActionController::TestCase
include Devise::TestHelpers
setup do
#booking = bookings(:one)
#user = users(:one)
sign_in #user
end
test "should return a bad request" do
post :create, :booking => { }, :format => 'json'
assert_response :bad_request
end
end
The post path for creating a booking looks like this (and works, tested with HTTP Client):
api_v1_user_bookings
GET /api/v1/users/:user_id/bookings(.:format) api/v1/bookings#index
POST /api/v1/users/:user_id/bookings(.:format) api/v1/bookings#create
However when I run the test it seems that it uses some default route (see error message below), how can i specify the correct route in my test? Or is there some other mistake I do not see here?
test_should_return_a_bad_request(Api::V1::BookingsControllerTest):
ActionController::RoutingError: No route matches {:booking=>{}, :format=>"js
on", :controller=>"api/v1/bookings", :action=>"create"}
Your route expects a user_id parameter. Add it to your post:
post :create, :user_id => #user.id, :booking => {}, :format => :json
Ok, I think I figured it out now. I just had to add the user-id, otherwise it seems that rails does not select the right route. So the correct test method looks like this:
test "should return a bad request" do
post :create, :user_id => #user.id, :booking => { }, :format => 'json'
assert_response :bad_request
end

Paypal IPN on Rails app not being called

We are using Paypal Adaptive Chained payments to allow contributions on a crowd funding website. I've got everything working as far as making the payment and being returned to the 'return_url' after a successful payment (in the sandbox).
Paypals documentation on testing IPN in the Sandbox is sparse. Perhaps I missed a setting somewhere to enable IPN callbacks?
I would like to be able to use the IPN callback to verify a successful payment. I would like to see the callback JSON so I know which field to capture and compare with. I have found sample code, but the IPN callback doesn't seem to be called in the Sandbox. I've tried to output the params like so (also the puts statement isn't called):
****backers_controller.rb
class BackersController < ApplicationController
include ActiveMerchant::Billing::Integrations
def callback_ipn
puts "callback: ipn"
notify = PaypalAdaptivePayment::Notification.new(request.raw_post)
puts notify.to_yaml
end
def callback_return
puts "callback: return"
#backer = Backer.find(params[:id])
#reward = #project.rewards.find_by_id(#backer.reward_id)
#backer.callback
respond_to do |format|
format.html { render :layout => 'application_proj_back_blog' }
format.json { render json: #backer }
end
end
(The 'callback_return' action is working)
****backer.rb
class Backer < ActiveRecord::Base
include ActionDispatch::Routing::UrlFor
include Rails.application.routes.url_helpers
def purchase
project = Project.find(self.project_id)
default_url_options[:host] = 'localhost:3000'
proj_owner_email = User.find(project.user_id).email
recipients = [{:email => PRIMARY_EMAIL,
:amount => total_pledge,
:primary => true},
{:email => project.paypal_email,
:amount => project_owner_cut,
:primary => false}
]
response = GATEWAY.setup_purchase(
:action_type => "PAY_PRIMARY",
:return_url => callback_return_project_backer_url(project, self),
:cancel_url => callback_cancel_project_backer_url(project, self),
:ipn_notification_url => callback_ipn_project_backer_url(project, self),
:currency_code =>"USD",
:receiver_list => recipients
)
puts response.to_yaml
pay_key = response["payKey"]
pledge_transactions.create!(:action => "purchase", :amount => total_pledge, :response => response)
return response["payKey"]
end
Back in backers_controller.rb, 'def purchase' calls the purchase:
****backers_controller.rb
class BackersController < ApplicationController
def purchase
#backer = Backer.find(params[:id])
#backer.purchase
redirect_to (GATEWAY.redirect_url_for(#backer.purchase))
end
It turns out my authentication was preventing paypal from calling my ipn listener.

Rails Devise 2.0 own Log-out action

I use Devise and I want to do my logout action.
What I want to do is, that when I log out, I want to create a own JSON object to return. At this time, after I logt out, I get all my root articles.
How can I write my own destory action like I have found the create action?
class SessionsController < Devise::SessionsController
def create
resource = warden.authenticate!(:scope => resource_name, :recall => :failure)
return sign_in_and_redirect(resource_name, resource)
end
def sign_in_and_redirect(resource_or_scope, resource=nil)
scope = Devise::Mapping.find_scope!(resource_or_scope)
resource ||= resource_or_scope
sign_in(scope, resource) unless warden.user(scope) == resource
return render :json => {:success => true, :redirect => stored_location_for(scope) || after_sign_in_path_for(resource)}
end
def failure
return render:json => {:success => false, :errors => ["Login failed."]}
end
end
And my Routes in routes.rb
devise_for :users, :controllers => {:session => "sessions"} do
get "/users/sing_out" => "devise/sessions#destroy"
end
this is the destroy method of the sessions-controller.
you should be able to customize it to your needs. i think that it would be wiser to add another action and implementing your custom behavior there, as this will be less likely to cause unexpected errors with upgrading devise in the future.
# DELETE /resource/sign_out
def destroy
redirect_path = after_sign_out_path_for(resource_name)
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out
# We actually need to hardcode this as Rails default responder doesn't
# support returning empty response on GET request
respond_to do |format|
format.any(*navigational_formats) { redirect_to redirect_path }
format.all do
method = "to_#{request_format}"
text = {}.respond_to?(method) ? {}.send(method) : ""
render :text => text, :status => :ok
end
end
end

Rspec Rails 3.1 Integration test. How to send post request headers for mobile, http basic authentication and JSON?

I have an RSPEC integration test for a Rails 3.1 app that needs to test an api for a mobile client by issuing a POST request with JSON params and a mobile header that needs to use http_basic authentication
As the request object is not available in an integration test I'm kinda stuck
This is the code I have so far
it "successfully posts scores" do
# request.env["HTTP_ACCEPT"] = "application/json" #This causes an error as request is nly available in controller tests
post "scores", :score => {:mobile_user_id => #mobile_user.id, :points => 50, :time_taken => 7275}.to_json,
:format => :json, :user_agent => 'Mobile', 'HTTP_AUTHORIZATION' => get_basic_auth
end
The post request does not recognise that I am using http basic authentication but unsure if the format for json is correct. Any help appreciated
get_basic_auth is a helper me4thod that looks like this
def get_basic_auth
user = 'user'
pw = 'secret'
ActionController::HttpAuthentication::Basic.encode_credentials user, pw
end
I use a before_filter in my controllers that checks for mobile and http_basic_authentication that looks like this
def authorize
logger.debug("#### Authorizing request #{request.inspect}")
if mobile_device?
authenticate_or_request_with_http_basic do |username, password|
username == Mobile::Application.config.mobile_login_name && Mobile::Application.config.mobile_password
end
else
unless current_user
redirect_to login_url, :notice => "Please log in"
end
end
end
I get a redirect to login so obviously the mobile header is not being accepted so I have no idea really if any of the other headers are working
UPDATE
Figured it out
post("scores", {:score => {:mobile_user_id => #mobile_user.id, :points => 50, :time_taken => 7275}}.to_json,
{"HTTP_USER_AGENT" => "Mobile", 'HTTP_AUTHORIZATION' => get_basic_auth, 'HTTP_CONTENT_TYPE' => "application/json"})
Does the trick nicely
I figured out what I needed to do
post("scores", {:score => {:mobile_user_id => #mobile_user.id, :points => 50, :time_taken => 7275}}.to_json,
{"HTTP_USER_AGENT" => "Mobile", 'HTTP_AUTHORIZATION' => get_basic_auth, 'HTTP_CONTENT_TYPE' => "application/json"}
Obviously the "mobile" user agent is not needed to test normal json requests.
Hope that helps someone