Adding secure tag to rails 5.2.x API only session cookies - ruby-on-rails-5

I am using a rails API only setup for a project.
I have managed to get it to use custom cookie information, however I CANNOT seem to get it to add the secure tag even though in production the server is on an HTTPS connection.
This is causing my cookies to not get saved since the api is not on the same url as the front end.
here is the setup for the cookies
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
config.middleware.insert_after ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, :key => '_session_id', same_site: :none, secure: :true, expire_after: 14.days, httponly: true
I also tried secure: Rails.env.production? to no success
For reference here is the session controller:
class SessionsController < ApplicationController
def create
#user = User.find_by(email: session_params[:email])
if #user && #user.authenticate(session_params[:password])
login!
render json: {
logged_in: true,
user: #user
}
response.set_header('Set-Cookie', 'Secure; SameSite=None')
else
render json: {
status: 401,
errors: ['no such user', 'verify credentials and try again']
}
end
end
def is_logged_in?
if logged_in? && current_user
render json: {
logged_in: true,
user: current_user
}
else
render json: {
logged_in: false,
message: 'no session found'
}
end
end
def destroy
logout!
render json: {
status: 200,
logged_out: true
}
end
private
def session_params
params.require(:user).permit(:username, :email, :password)
end
end
Image added for comment:

Related

#<ActionController::ParameterMissing: param is missing or the value is empty: user>

Gone through a lot of answers but still couldn't find the solution.
I have been trying to get a successful response for a POST request to the following controller,
def create
#user = User.new(user_params)
if #user.save
render json: #user, status: :created, location: #user
else
render json: #user.errors, status: :unprocessable_entity
end
private
def user_params
params.require(:user).permit(:name, :email, :phone, :password)
end
end
Although I send all the parameters mentioned,in the request,I am still facing the error.
"status": 400,
"error": "Bad Request",
"exception": "#<ActionController::ParameterMissing: param is missing or the value is empty: user>"
I am using rails version 5.2.1
If you look at the logs, what does the data transferred in your post request look like?
When you get that type of error, often it's because you send the data like this:
{ first_name: 'John', last_name: 'Doe', ...}
When the server expects you to nest this into a user object (hence the require(:user) in your strong_params:
{ user: { first_name: 'John', ... } }
This is Worked for me.
I made just only one hash in params method instead of nested hash.
my user_params method was:-
def user_params
params.permit(:name, :email, :phone, :password)
end
OR You can you your method but your paramter passed in user hash like below.
{user: {name: "bittu", email: "abc#yopmail.com", phome: 123456, password: "123456" } }

Hartl test errors - redirect when (not) logged in as wrong user, not allow admin edited

I have a few errors that I can't resolve. All the tests pass. If there are errors, I only need to check the test, right? I only need to check the code if they fail, right?
Below are the errors:
Error:
UsersControllerTest#test_should_redirect_update_when_logged_in_as_wrong_user:
ArgumentError: wrong number of arguments (given 2, expected 1)
test/controllers/users_controller_test.rb:45:in `block in '
bin/rails test test/controllers/users_controller_test.rb:43
Error:
UsersControllerTest#test_should_not_allow_the_admin_attribute_to_be_edited_via_the_web:
ArgumentError: unknown keywords: id, user
test/controllers/users_controller_test.rb:36:in `block in '
bin/rails test test/controllers/users_controller_test.rb:33
Error:
UsersControllerTest#test_should_redirect_index_when_not_logged_in:
URI::InvalidURIError: bad URI(is not URI?): http://www.example.com:80index
test/controllers/users_controller_test.rb:11:in `block in '
bin/rails test test/controllers/users_controller_test.rb:10
Error:
PasswordResetsTest#test_password_resets:
NameError: undefined local variable or method 'expired' for #
test/integration/password_resets_test.rb:62:in `block in '
bin/rails test test/integration/password_resets_test.rb:10
Below are my files:
test/integration/password_resets_test.rb
require 'test_helper'
class PasswordResetsTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
#user = users(:michael)
end
test "password resets" do
get new_password_reset_path
assert_template 'password_resets/new'
# Invalid email
post password_resets_path, params: { password_reset: { email: "" } }
assert_not flash.empty?
assert_template 'password_resets/new'
# Valid email
post password_resets_path,
params: { password_reset: { email: #user.email } }
assert_not_equal #user.reset_digest, #user.reload.reset_digest
assert_equal 1, ActionMailer::Base.deliveries.size
assert_not flash.empty?
assert_redirected_to root_url
# Password reset form
user = assigns(:user)
# Wrong email
get edit_password_reset_path(user.reset_token, email: "")
assert_redirected_to root_url
# Inactive user
user.toggle!(:activated)
get edit_password_reset_path(user.reset_token, email: user.email)
assert_redirected_to root_url
user.toggle!(:activated)
# Right email, wrong token
get edit_password_reset_path('wrong token', email: user.email)
assert_redirected_to root_url
# Right email, right token
get edit_password_reset_path(user.reset_token, email: user.email)
assert_template 'password_resets/edit'
assert_select "input[name=email][type=hidden][value=?]", user.email
# Invalid password & confirmation
patch password_reset_path(user.reset_token),
params: { email: user.email,
user: { password: "foobaz",
password_confirmation: "barquux" } }
assert_select 'div#error_explanation'
# Empty password
patch password_reset_path(user.reset_token),
params: { email: user.email,
user: { password: "",
password_confirmation: "" } }
assert_select 'div#error_explanation'
# Valid password & confirmation
patch password_reset_path(user.reset_token),
params: { email: user.email,
user: { password: "foobaz",
password_confirmation: "foobaz" } }
assert is_logged_in?
assert_not flash.empty?
assert_redirected_to user
#passes w percent now, may need to change
assert_match(/%#{expired}/i, response.body)
end
end
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
#other_user = users(:archer)
end
test "should redirect index when not logged in" do
get :index
assert_redirected_to login_url
end
test "should get new" do
get signup_path
assert_response :success
end
test "should redirect edit when not logged in" do
get edit_user_path(#user)
assert_not flash.empty?
assert_redirected_to login_url
end
test "should redirect update when not logged in" do
patch user_path(#user), params: { user: { name: #user.name,
email: #user.email } }
assert_not flash.empty?
assert_redirected_to login_url
end
test "should not allow the admin attribute to be edited via the web" do
log_in_as(#other_user)
assert_not #other_user.admin?
patch :update, id: #other_user, user: { password: #other_user.password,
password_confirmation: #other_user.password_confirmation,
admin: true }
assert_not #other_user.reload.admin?
end
test "should redirect update when logged in as wrong user" do
log_in_as(#other_user)
patch :update, user_path(#user), user: { name: #user.name, email: #user.email }
assert flash.empty?
assert_redirected_to root_url
end
test "should redirect destroy when not logged in" do
assert_no_difference 'User.count' do
delete user_path(#user)
end
assert_redirected_to login_url
end
test "should redirect destroy when logged in as a non-admin" do
log_in_as(#other_user)
assert_no_difference 'User.count' do
delete user_path(#user)
end
assert_redirected_to root_url
end
end
Below are supporting files:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
# Shows all users, delete for sups app but useful for BRBBaby
def index
#users = User.where(activated: true).paginate(page: params[:page])
end
def show
#user = User.find(params[:id])
redirect_to root_url and return unless :active
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
#user.send_activation_email
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
# Before filters
# Confirms the correct user.
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
class AccountActivationsController < ApplicationController
def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
class PasswordResetsController < ApplicationController
before_action :get_user, only: [:edit, :update]
before_action :valid_user, only: [:edit, :update]
before_action :check_expiration, only: [:edit, :update]
def new
end
def create
#user = User.find_by(email: params[:password_reset][:email].downcase)
if #user
#user.create_reset_digest
#user.send_password_reset_email
flash[:info] = "Email sent with password reset instructions"
redirect_to root_url
else
flash.now[:danger] = "Email address not found"
render 'new'
end
end
def edit
end
def update
if params[:user][:password].empty? # Case (3)
#user.errors.add(:password, "can't be empty")
render 'edit'
elsif #user.update_attributes(user_params) # Case (4)
log_in #user
#user.update_attribute(:reset_digest, nil)
flash[:success] = "Password has been reset."
redirect_to #user
else
render 'edit' # Case (2)
end
end
private
def user_params
params.require(:user).permit(:password, :password_confirmation)
end
def get_user
#user = User.find_by(email: params[:email])
end
# Confirms a valid user.
def valid_user
unless (#user && #user.activated? &&
#user.authenticated?(:reset, params[:id]))
redirect_to root_url
end
end
# Checks expiration of reset token.
def check_expiration
if #user.password_reset_expired?
flash[:danger] = "Password reset has expired."
redirect_to new_password_reset_url
end
end
end
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
if user.activated?
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
message = "Account not activated. "
message += "Check your email for the activation link."
flash[:warning] = message
redirect_to root_url
end
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out if logged_in?
redirect_to root_url
end
end
module SessionsHelper
# Logs in the given user.
def log_in(user)
session[:user_id] = user.id
end
# Remembers a user in a persistent session.
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
# Returns true if the given user is the current user.
def current_user?(user)
user == current_user
end
# Returns the current logged-in user (if any).
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(:remember, cookies[:remember_token])
log_in user
#current_user = user
end
end
end
# Returns true if the user is logged in, false otherwise.
# FIXES - this was !current_user.nil? and had lots of errors and was fixed with below, but not sure right now how it might affect other parts of app.
def logged_in?
!current_user.nil?
end
# Forgets a persistent session.
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# Logs out the current user.
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil
end
# Redirects to stored location (or to the default).
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
# Stores the URL trying to be accessed.
def store_location
session[:forwarding_url] = request.original_url if request.get?
end
end
module UsersHelper
# Returns the Gravatar for the given user.
def gravatar_for(user, options = { size: 80 })
gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
size = options[:size]
gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}"
image_tag(gravatar_url, alt: user.name, class: "gravatar")
end
end
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /[A-Z0-9._%+-]+#(?:[A-Z0-9-]+\.)+[A-Z]{2,}/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
# Activates an account.
def activate
update_columns(activated: true, activated_at: Time.zone.now)
# The above line should count for the below two lines
# update_attribute(:activated, true)
# update_attribute(:activated_at, Time.zone.now)
end
# Sends activation email.
def send_activation_email
UserMailer.account_activation(self).deliver_now
end
# Sets the password reset attributes.
def create_reset_digest
self.reset_token = User.new_token
update_columns(reset_digest: User.digest(reset_token), reset_sent_at: Time.zone.now)
end
# Sends password reset email.
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
private
# Converts email to all lower-case.
def downcase_email
self.email = email.downcase
end
# Creates and assigns the activation token and digest.
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "unsuccessful edit" do
log_in_as(#user)
get edit_user_path(#user)
assert_template 'users/edit'
patch user_path(#user), params: { user: { name: "",
email: "foo#invalid",
password: "foo",
password_confirmation: "bar" } }
assert_template 'users/edit'
end
test "successful edit with friendly forwarding" do
get edit_user_path(#user)
log_in_as(#user)
assert_redirected_to edit_user_path(#user)
name = "Foo Bar"
email = "foo#bar.com"
patch user_path(#user), params: { user: { name: name,
email: email,
password: "",
password_confirmation: "" } }
assert_not flash.empty?
assert_redirected_to #user
#user.reload
assert_equal name, #user.name
assert_equal email, #user.email
end
end
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
#admin = users(:michael)
#non_admin = users(:archer)
end
test "index as admin including pagination and delete links" do
log_in_as(#admin)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
unless user == #admin
assert_select 'a[href=?]', user_path(user), text: 'delete'
end
end
assert_difference 'User.count', -1 do
delete user_path(#non_admin)
end
end
test "index as non-admin" do
log_in_as(#non_admin)
get users_path
assert_select 'a', text: 'delete', count: 0
end
end
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
#user = users(:michael)
end
test "login with invalid information" do
get login_path
assert_template 'sessions/new'
post login_path, params: { session: { email: "", password: "" } }
assert_template 'sessions/new'
assert_not flash.empty?
get root_path
assert flash.empty?
end
# Validations may have been cheated; confirm platonic result
test "login with valid information followed by logout" do
get login_path
post login_path, params: { session: { email: #user.email,
password: 'password' } }
assert is_logged_in?
assert_redirected_to #user
follow_redirect!
assert_template 'users/show'
assert_select "a[href=?]", login_path, count: 0
assert_select "a[href=?]", logout_path
assert_select "a[href=?]", user_path(#user)
delete logout_path
assert_not is_logged_in?
assert_redirected_to root_url
# Simulate a user clicking logout in a second window.
delete logout_path
follow_redirect!
assert_select "a[href=?]", login_path
assert_select "a[href=?]", logout_path, count: 0
assert_select "a[href=?]", user_path(#user), count: 0
end
test "login with remembering" do
log_in_as(#user, remember_me: '1')
assert_not_nil cookies['remember_token']
end
test "login without remembering" do
# Log in to set the cookie.
log_in_as(#user, remember_me: '0')
# Log in again and verify that the cookie is deleted.
assert_nil cookies['remember_token']
end
end
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
def setup
ActionMailer::Base.deliveries.clear
end
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, params: { user: { name: "",
email: "user#invalid",
password: "foo",
password_confirmation: "bar" } }
end
assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'
end
test "valid signup information with account activation" do
get signup_path
assert_difference 'User.count', 1 do
post users_path, params: { user: { name: "Example User",
email: "user#example.com",
password: "password",
password_confirmation: "password" } }
end
assert_equal 1, ActionMailer::Base.deliveries.size
user = assigns(:user)
assert_not user.activated?
# Try to log in before activation.
log_in_as(user)
assert_not is_logged_in?
# Invalid activation token
get edit_account_activation_path("invalid token", email: user.email)
assert_not is_logged_in?
# Valid token, wrong email
get edit_account_activation_path(user.activation_token, email: 'wrong')
assert_not is_logged_in?
# Valid activation token
get edit_account_activation_path(user.activation_token, email: user.email)
assert user.reload.activated?
follow_redirect!
assert_template 'users/show'
assert is_logged_in?
end
end
require 'test_helper'
#######
## Amp up password security in the future
## https://www.google.com/search?q=rails+enforce+password+strength
######
class UserTest < ActiveSupport::TestCase
def setup
#user = User.new(name: "Example User", email: "user#example.com",
password: "foobar", password_confirmation: "foobar")
end
test "should be valid" do
assert #user.valid?
end
test "name should be present" do
#user.name = " "
assert_not #user.valid?
end
test "email should be present" do
#user.email = " "
assert_not #user.valid?
end
test "name should not be too long" do
#user.name = "a" * 51
assert_not #user.valid?
end
test "email should not be too long" do
#user.email = "a" * 244 + "#example.com"
assert_not #user.valid?
end
test "email validation should accept valid addresses" do
valid_addresses = %w[user#example.com USER#foo.COM A_US-ER#foo.bar.org
first.last#foo.jp alice+bob#baz.cn]
valid_addresses.each do |valid_address|
#user.email = valid_address
assert #user.valid?, "#{valid_address.inspect} should be valid"
end
end
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user#example,com user_at_foo.org user.name#example.
foo#bar_baz.com foo#bar+baz.com]
invalid_addresses.each do |invalid_address|
#user.email = invalid_address
assert_not #user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
test "email addresses should be unique" do
duplicate_user = #user.dup
duplicate_user.email = #user.email.upcase
#user.save
assert_not duplicate_user.valid?
end
test "password should be present (nonblank)" do
#user.password = #user.password_confirmation = " " * 6
assert_not #user.valid?
end
test "password should have a minimum length" do
#user.password = #user.password_confirmation = "a" * 5
assert_not #user.valid?
end
test "authenticated? should return false for a user with nil digest" do
assert_not #user.authenticated?(:remember, '')
end
end
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!
class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all
# Returns true if a test user is logged in.
def is_logged_in?
!session[:user_id].nil?
end
# Logs in a test user.
def log_in_as(user, options = {})
password = options[:password] || 'password'
remember_me = options[:remember_me] || '1'
if integration_test?
post login_path, params: { session: { email: user.email,
password: password,
remember_me: remember_me } }
else
session[:user_id] = user.id
end
end
private
# Returns true inside an integration test.
def integration_test?
defined?(post_via_redirect)
end
end
Any help would be appreciated. Let me know if there is any info I can provide or if you have questions for me. Thank you for your help. Cheers.
1)
Error: UsersControllerTest#test_should_redirect_update_when_logged_in_as_wrong_user: ArgumentError: wrong number of arguments (given 2, expected 1) test/controllers/users_controller_test.rb:45:in `block in ' bin/rails test test/controllers/users_controller_test.rb:43
For this one you've used patch :update user_path(#user)... when you should just have patch user_path(#user)...
2)
Error: UsersControllerTest#test_should_not_allow_the_admin_attribute_to_be_edited_via_the_web: ArgumentError: unknown keywords: id, user test/controllers/users_controller_test.rb:36:in `block in ' bin/rails test test/controllers/users_controller_test.rb:33
This is because you have used patch :update id: #othr_user ... instead of patch user_path(#other_user) ...
3)
Error: UsersControllerTest#test_should_redirect_index_when_not_logged_in: URI::InvalidURIError: bad URI(is not URI?): http://www.example.com:80index test/controllers/users_controller_test.rb:11:in `block in ' bin/rails test test/controllers/users_controller_test.rb:10
http://www.example.com:80index is indeed a bad URL and should be http://www.example.com:80/index
I'd be guessing that login_url is setup incorrectly in your routes file... have a look at what it is and try to add the missing /?
EDIT: Actually I think there might be something else here
You have get :index where all the other tests use an actual test path eg get users_path.
4)
Error: PasswordResetsTest#test_password_resets: NameError: undefined local variable or method 'expired' for # test/integration/password_resets_test.rb:62:in `block in ' bin/rails test test/integration/password_resets_test.rb:10
This line: assert_match(/%#{expired}/i, response.body) contains the local variable expired but nowhere have you assigned a value to expired... what do you intend for this value to be?

Ruby Motion, Rails 3.x, Devise login through custom api/v1/sessions_controller.rb

I'm using this lucatironi tutorial to do my RM/rails/devise native authentication. I got everything working except one piece where I authenticate in the sessions_controller.rb
I'm sending in
{ session : { email: "test#five.com", password: "password" } } (bubblewrap is forcing a 'sessions' node onto my JSON!)
resource_name is :api_v1_user
controller_path is "api/v1/sessions"
class Api::V1::SessionsController < Devise::SessionsController
def create
warden.authenticate!(:scope => resource_name, :store => false, :recall => "#{controller_path}#failure")
render :status => 200,
:json => { :success => true,
:info => "Logged in",
:data => { :auth_token => current_user.authentication_token } }
end
end
All I get is an alert in the app "Login Failed"
Thanks for any help
Did you remember to call skip_before_filter :verify_authenticity_token? Given that the request is not coming from a rails-generated form, the token wouldn't be there.
So I figured this out.
authenticate! doesn't return annything
has to be "authenticate" with no "!"
that was it!

CanCan AccessDenied Error thrown for Update and Destroy despite ability set

I am trying to get some controller tests passing but when they hit the update and delete action, CanCan keeps throwing the Access Denied error despite being set in the abilities. These errors only seem to occur for members, as admins work fine.
Abilities.rb
def initialize(user)
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :member
can :manage, PaymentMethod, :user_id => user.id
end
end
User_Factory
FactoryGirl.define do
factory :user do
sequence(:first_name) { |n| "John_#{n}" }
sequence(:last_name) { |n| "Rambo_#{n}" }
sequence(:email) { |n| "john_rambo_#{n}#example.com" }
sequence(:username) { |n| "john_rambo_#{n}" }
date_of_birth "03/12/1982"
password 'password'
password_confirmation 'password'
picture_url File.open('spec/support/pictures/test.png')
address
factory :member do
sequence(:last_name) { |n| "Member_#{n}" }
roles :member
end
end
end
Controller_Spec.rb
describe "PUT /api/users/:user_id/payment_method/:id" do
before(:each) do
#user = FactoryGirl.create(:member)
sign_in_user #user
#payment_method = FactoryGirl.create(:credit_card, {:user_id => #user.id})
end
it "updates a users payment method" do
attr_to_change = {
brand: "mastercard",
user_id: #user.id,
id: #payment_method.id
}
put :update, attr_to_change
response.status.should == 200
JSON.parse(response.body)["payment_method"]["brand"]
.should == "mastercard"
end
end
describe "DELETE /api/users/:user_id/payment_methods/:id" do
before(:each) do
#user = FactoryGirl.create(:member)
sign_in_user #user
#payment_method = FactoryGirl.create(:credit_card, {:user_id => #user.id})
end
it "destroys a users payment method" do
delete :destroy, {:user_id => #user, :id => #payment_method.id}
response.status.should == 200
end
end
Controller
class Api::PaymentMethodsController < Api::ApiController
before_filter :clean_params, only: [:update, :create]
def index
#user = User.find(params["user_id"])
render json: #user.payment_methods
end
def update
pm_id = params.delete("id")
params.delete("user_id")
#payment_method = PaymentMethod.find(pm_id)
if #payment_method.update_attributes(params)
return render status: 200, json: #payment_method, root: :payment_method
else
return render status: 422, json: {success: false, errors: #payment_method.errors.full_messages.map{|error|{error: error}}}
end
end
def create
#payment_method = PaymentMethod.create_payment_method(params)
if #payment_method
render json: #payment_method, root: :payment_method
else
return render status: 422, json: {success: false, errors: #payment_method.errors.full_messages.map{|error|{error: error}}}
end
end
def destroy
#payment_method = PaymentMethod.find(params["id"])
if #payment_method.destroy
return render status: 200, json: {:message => "PaymentMethod Destroyed"}
else
return render status: 422, json: {success: false, errors: #payment_method.errors.full_messages.map{|error|{error: error}}}
end
end
def clean_params
["controller", "action"].each do |delete_me|
params.delete(delete_me)
end
params
end
end
ApiController
class Api::ApiController < ApplicationController
before_filter :authenticate_user!
load_and_authorize_resource
rescue_from CanCan::AccessDenied do |exception|
return render :status => 401, :json => {:success => false, :errors => [exception.message]}
end
end
Result of calling the delete action in the test:
delete :destroy, {:user_id => #user, :id => #payment_method.id}
#<ActionController::TestResponse:0x007fb999cf0080
#blank=false,
#block=nil,
#body=
["{\"success\":false,\"errors\":[\"You are not authorized to access this page.\"]}"],
#cache_control={},
#charset="utf-8",
#content_type=application/json,
#etag=nil,
#header={"Content-Type"=>"application/json; charset=utf-8"},
#length=0,
#request=
The other actions seem to work but for Update and Destroy, I keep getting that AccessDenied error. Any idea what I could be doing wrong?
Your ApiController appears to be namespaced, you'll need to change the before_filter to the following:
before_filter :authenticate_api_user!
Then, you need to adjust Cancan to use the current_api_user instead of current_user:
def current_ability
#current_ability ||= ::Ability.new(current_api_user)
end
These links will help:
http://rubydoc.info/github/plataformatec/devise/master/Devise/Controllers/Helpers
https://github.com/ryanb/cancan/issues/656
http://mikepackdev.com/blog_posts/12-managing-devise-s-current-user-current-admin-and-current-troll-with-cancan

From Rails devise auth to backbone & api?

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.