uninitialized constant SessionsController in API - ruby-on-rails

I'm writing an API and am trying to login with the API and am getting uninitialized constant SessionsController error followed by the tip to "Try running rake routes for more information on available routes." when I try to go to the URL
http://localhost:3000/api/v1/login as a POST
My routes show that the route and action exist as shown:
api_v1_users GET /api/v1/users(.:format) api/v1/users#index {:format=>"json"}
POST /api/v1/users(.:format) api/v1/users#create {:format=>"json"}
new_api_v1_user GET /api/v1/users/new(.:format) api/v1/users#new {:format=>"json"}
edit_api_v1_user GET /api/v1/users/:id/edit(.:format) api/v1/users#edit {:format=>"json"}
api_v1_user GET /api/v1/users/:id(.:format) api/v1/users#show {:format=>"json"}
PUT /api/v1/users/:id(.:format) api/v1/users#update {:format=>"json"}
DELETE /api/v1/users/:id(.:format) api/v1/users#destroy {:format=>"json"}
new_api_v1_user_session GET /api/v1/login(.:format) sessions#new {:format=>"json"}
api_v1_user_session POST /api/v1/login(.:format) sessions#create {:format=>"json"}
destroy_api_v1_user_session DELETE /api/v1/logout(.:format) sessions#destroy {:format=>"json"}
api_v1_user_omniauth_authorize GET|POST /auth/:provider(.:format) authentications#passthru {:provider=>/twitter|facebook/, :format=>"json"}
api_v1_user_omniauth_callback GET|POST /auth/:action/callback(.:format) authentications#(?-mix:twitter|facebook) {:format=>"json"}
api_v1_user_password POST /api/v1/password(.:format) api/v1/passwords#create {:format=>"json"}
new_api_v1_user_password GET /api/v1/password/new(.:format) api/v1/passwords#new {:format=>"json"}
edit_api_v1_user_password GET /api/v1/password/edit(.:format) api/v1/passwords#edit {:format=>"json"}
PUT /api/v1/password(.:format) api/v1/passwords#update {:format=>"json"}
cancel_api_v1_user_registration GET /api/v1/cancel(.:format) registrations#cancel {:format=>"json"}
api_v1_user_registration POST /api/v1(.:format) registrations#create {:format=>"json"}
new_api_v1_user_registration GET /api/v1/sign_up(.:format) registrations#new {:format=>"json"}
edit_api_v1_user_registration GET /api/v1/edit(.:format) registrations#edit {:format=>"json"}
PUT /api/v1(.:format) registrations#update {:format=>"json"}
DELETE /api/v1(.:format) registrations#destroy {:format=>"json"}
When I try to access the account using the authentication_token url parameter, it works fine.
http://localhost:3000/api/v1/users/39?user_token=DxwspVzYSpsiLosd9xcE
Using that I can make PUT and GET requests no problem.
My routes look like this:
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, :path => '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: {omniauth_callbacks: "authentications", registrations: "registrations", sessions: "sessions"}
end
end
I assume it's a problem in my SessionsController, but can't figure out why the PUT request would work, but the POST wouldn't. Here is the SessionsController:
module Api
module V1
class SessionsController < Devise::SessionsController
before_filter :authenticate_user!, except: [:create, :destroy]
before_filter :ensure_params_exist
skip_before_filter :verify_authenticity_token
def create
resource = User.find_for_database_authentication(email: params[:user_login][:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user_login][:password])
sign_in("user", resource)
resource.ensure_authentication_token!
render 'api/v1/sessions/new.json.jbuilder', status: 201
return
end
invalid_login_attempt
end
def destroy
current_user.reset_authentication_token
render json: {success: true}
end
protected
def ensure_params_exist
return unless params[:user_login].blank?
render json: {success: false, message: "missing user_login parameter"}, status: 422
end
def invalid_login_attempt
render 'api/v1/sessions/invalid.json.jbuilder', status: 401
end
end
end
end

So in my routes file I had
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, path: '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: {omniauth_callbacks: "authentications", registrations: "registrations", sessions: "sessions"}
end
end
I removed sessions: "sessions" and everything works great. I'm not going to mark this as the correct answer because I don't know why this fixed the problem, or more specifically, why adding sessions: "sessions" was causing the error in the first place. Hopefully someone can explain this for future readers.
EDIT:
I realized much later that the problem was that I was referencing the sessions_controller, when I wanted to reference the api/v1/sessoins_controller There is no regular sessions_controller I just use the Devise one, but there is a sessions_controller in the API/V1 namespace. So that is why the problem was happening.
For future reference for anyone else who comes across this problem, the error is telling you the controller you're looking for doesn't exist as it should, so to troubleshoot, make sure you are looking for the controller in the correct place, and with the correct name.

In a similar case for an API app with rails 6 and devise 4.8.1 I used scope and devise_scopeto configure routes according to devises doc. devise_for was skip-ed.
In my case:
scope :api, defaults: { format: :json } do
scope :v1 do
devise_for :users, skip: :all
devise_scope :user do
post '/sign_in', to: 'devise/sessions#create'
delete '/sign_out', to: 'devise/sessions#destroy'
post '/sign_up', to: 'devise/registrations#create'
put '/account_update', to: 'devise/registrations#update'
delete '/account_delete', to: 'devise/registrations#destroy'
end
end
end

Related

Create root and routes for Devise object

I have created an Admin model with the Devise gem. Using the Devise controller generator, I now have a app/controllers/admins folder containing all the stock controllers for me to modify if I choose, such as sessions_controller, passwords_controller, etc.
However, I can't figure out how to just get an Admin controller and simple admin routes like admin_path or new_admin_path.
Here's my rake routes | grep admin
new_admin_session GET /admin/sign_in(.:format) admins/sessions#new
admin_session POST /admin/sign_in(.:format) admins/sessions#create
destroy_admin_session DELETE /admin/sign_out(.:format) admins/sessions#destroy
new_admin_password GET /admin/password/new(.:format) devise/passwords#new
edit_admin_password GET /admin/password/edit(.:format) devise/passwords#edit
admin_password PATCH /admin/password(.:format) devise/passwords#update
PUT /admin/password(.:format) devise/passwords#update
POST /admin/password(.:format) devise/passwords#create
admin_root GET /admin(.:format) admins/sessions#portal
admin_sign_out GET /admin/sign_out(.:format) admin/sessions#destroy
And here are the relevant parts of my routes.rb
devise_for :admins, path: 'admin', controllers: { sessions: 'admins/sessions' }
devise_scope :admin do
get "/admin", to: 'admins/sessions#portal', as: 'admin_root'
get "/admin/sign_out", to: 'admin/sessions#destroy', as: 'admin_sign_out'
end
You'll see that I've currently got a portal method in my Admin::SessionsController, which is my current workaround. I know the right place for that page is in an AdminsController but I can't figure out how to set that up.
Adding admins: 'admins/admins' to the devise_for :admins, controllers: block doesn't give me any new routes. I tried adding an AdminsController with methods but that doesn't help either, trying to go to /admin/new or /admins/new says no route matches.
This is how I have my device and namespaces set up
# config/routes.rb
Rails.application.routes.draw do
devise_for :admins, :controllers => { registrations: 'admins/registrations',
sessions: 'admins/sessions',
passwords: 'admins/passwords',
confirmations: 'admins/confirmations'
}
authenticate :admin do
namespace :admins do
...
root 'dashboards#index'
end
end
...
root 'pages#index'
Now controllers are also important.
#app/controllers/admin_controller.rb
class AdminController < ApplicationController
layout 'admins/application'
before_filter :authenticate_admin!
end
The Devise controllers I have them set like this
#app/controllers/admins/sessions_controller.rb
class Admins::SessionsController < Devise::SessionsController
layout 'admins/application'
...
end
Repeat this process for all your other device controllers
Let me know if this helps you out

Devise multiple pathes for one model

I want to have two different routes for devise:
localhost:3000/login - standart devise auth
localhost:3000/api/login - auth for api calls using another, not standart controller /api/sessions_controller
routes.rb
devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout' }
namespace :api, defaults: { format: :json } do
devise_for :users,
path: '',
defaults: { format: :json },
path_names: {
sign_in: 'login',
sign_out: 'logout',
},
controllers: { sessions: 'api/sessions' }
end
rails routes | grep sessio
new_user_session GET /login(.:format) devise/sessions#new
user_session POST /login(.:format) devise/sessions#create
destroy_user_session DELETE /logout(.:format) devise/sessions#destroy
new_api_user_session GET /api/login(.:format) api/sessions#new {:format=>:json}
api_user_session POST /api/login(.:format) api/sessions#create {:format=>:json}
destroy_api_user_session DELETE /api/logout(.:format) api/sessions#destroy {:format=>:json}
api/sessions_controller.rb
class Api::SessionsController < Devise::SessionsController
skip_before_action :verify_authenticity_token
skip_before_action :authenticate_user!
respond_to :json
private
def respond_with(resource, _opts = {})
render json: resource
end
def respond_to_on_destroy
head :no_content
end
end
That code looks fine for me, but when i try to send POST-request to my localhost:3000/api/login with auth credentials it returns error:
{"error":"You need to sign in or sign up before continuing."}
If i comment out my default devise routes and move api routes from api namespace everything works fine, post-request return me json with resource:
routes.rb
#devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout' }
devise_for :users,
path: 'api',
defaults: { format: :json },
path_names: {
sign_in: 'login',
sign_out: 'logout',
},
controllers: { sessions: 'api/sessions' }
namespace :api, defaults: { format: :json } do
...
end
Why that happened? What i'm missing?
Having this in my routes:
Rails.application.routes.draw do
devise_for :users,
path: '',
path_names: {
sign_in: 'login',
sign_out: 'logout'
}
namespace :api, defaults: { format: :json } do
namespace :users do
devise_scope :user do
post 'login', to: 'sessions#create', defaults: { format: :json }
end
end
end
end
rails routes outputs:
Prefix Verb URI Pattern Controller#Action
new_user_session GET /login(.:format) devise/sessions#new
user_session POST /login(.:format) devise/sessions#create
destroy_user_session DELETE /logout(.:format) devise/sessions#destroy
api_v1_users_login POST /api/v1/users/login(.:format) api/v1/users/sessions#create {:format=>:json}
Making the POST request to http://localhost:3000/api/v1/users/login works as expected
And now accessing http://localhost:3000/login redirects to the default devise login page, and login works as it should. The problem was you can't have the same devise_for twice. You can have devise_for :users and devise_for :admin_users but not twice for the same, in this case :users.
My answer:
routes.rb
devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout' }
devise_scope :user do
post 'api/login', to: 'api/sessions#create', as: 'api_login', defaults: {format: :json}
get 'api/logout', to: 'api/sessions#destroy', as: 'api_logout', defaults: {format: :json}
end
It's similiar with Guilermo Aguirre answer.
I think in your initial route definition, you're already in the API namespace, so when you define your controller, you don't need
'api/sessions'
But just
'sessions'
I think this will make the request hit the correct controller, which means your authenticate_admin! method won't fire, which it is at the moment, resulting in your auth issue.

How to access devise token auth registration controller?

I am using Devise auth token gem for authenticating some parts of my rails app. But when I try to create a new user with the registration path, it is giving me the following error{"errors":["Authorized users only."]}.
Here is the rspec code that I am using for the test,
it 'creates a user using email/password combo' do
post api_user_registration_path, { email: 'xxx', password: 'yyy',password_confirmation: 'yyy'}
puts last_response.body
expect(last_response.body).not_to have_content('error')
end
Additional info: the model name is 'User' and the routes looks like,
namespace :api do
scope :v1 do
mount_devise_token_auth_for 'User', at: 'auth'
end
end
I understand that the devise is expecting the user to be authenticated before accessing this path, but this being the user registration, it needs to be outside the authentication. Can you suggest a solution for this? Is there any configuration that I am missing here?
Try with:
namespace :api do
namespace :v1 do
mount_devise_token_auth_for 'User', at: '/auth'
end
end
This will create the following routes:
new_api_v1_user_session GET /api/v1/auth/sign_in(.:format) devise_token_auth/sessions#new
api_v1_user_session POST /api/v1/auth/sign_in(.:format) devise_token_auth/sessions#create
destroy_api_v1_user_session DELETE /api/v1/auth/sign_out(.:format) devise_token_auth/sessions#destroy
api_v1_user_password POST /api/v1/auth/password(.:format) devise_token_auth/passwords#create
new_api_v1_user_password GET /api/v1/auth/password/new(.:format) devise_token_auth/passwords#new
edit_api_v1_user_password GET /api/v1/auth/password/edit(.:format) devise_token_auth/passwords#edit
PATCH /api/v1/auth/password(.:format) devise_token_auth/passwords#update
PUT /api/v1/auth/password(.:format) devise_token_auth/passwords#update
cancel_api_v1_user_registration GET /api/v1/auth/cancel(.:format) devise_token_auth/registrations#cancel
api_v1_user_registration POST /api/v1/auth(.:format) devise_token_auth/registrations#create
new_api_v1_user_registration GET /api/v1/auth/sign_up(.:format) devise_token_auth/registrations#new
edit_api_v1_user_registration GET /api/v1/auth/edit(.:format) devise_token_auth/registrations#edit
PATCH /api/v1/auth(.:format) devise_token_auth/registrations#update
PUT /api/v1/auth(.:format) devise_token_auth/registrations#update
DELETE /api/v1/auth(.:format) devise_token_auth/registrations#destroy
api_v1_auth_validate_token GET /api/v1/auth/validate_token(.:format) devise_token_auth/token_validations#validate_token
Also create an controller in app/controllers/api/v1/api_base_controller.rb
class Api::V1::BaseApiController < ActionController::Base
include DeviseTokenAuth::Concerns::SetUserByToken
end
Also add to your file app/controllers/application_controller.rb
before_action :configure_permitted_parameters, if: :devise_controller?

Rspec testing API versioning

I'm working with a web api in rails 4.1.8 and i'm doing some testing but always is returning me
Failure/Error: post '/v1/users', subdomain: 'api', user: #user.to_json, format: :json
ActionController::UrlGenerationError:
No route matches {:action=>"/v1/users", :controller=>"api/v1/users", :format=>:json, :subdomain=>"api", :user=>"JSON_OBJ"}
but if i test it in the browser with Advance Rest Console it works, i don't know what i'm doing wrong
here is me code
routes.rb
namespace :api, path: '/', constraints: {subdomain: 'api'}, defaults: { format: :json } do
namespace :v1 do
with_options except: [:edit, :new] do |except|
except.resources :users do
collection do
post 'login'
post 'showme'
end
end
except.resources :products
except.resources :locations
end
end
end
and my controller spec
module API
module V1
describe UsersController do
before do
request.host = "api.example.com"
expect({:post => "http://#{request.host}/v1/users"}).to(
route_to( controller: "api/v1/users",
action: "create",
subdomain: 'api',
format: :json
)
) # => PASS
# token expectations
#auth_token = allow(JWT::AuthToken).to(
receive(:make_token).and_return("mysecretkey")
)
expect(JWT::AuthToken.make_token({}, 3600)).to eq("mysecretkey")
end
describe "Create User" do
before(:each) do
#user = FactoryGirl.attributes_for :user
end
it 'should return a token' do
post '/v1/users', subdomain: 'api', user: #user.to_json, format: :json # Error
response_body = JSON.parse(response.body, symbolize_names: true)
expect(response_body['token']).to eql "mysecretkey"
end
end
end
end
end
rake routes
login_api_v1_users POST /v1/users/login(.:format) api/v1/users#login {:format=>:json, :subdomain=>"api"}
showme_api_v1_users POST /v1/users/showme(.:format) api/v1/users#showme {:format=>:json, :subdomain=>"api"}
api_v1_users GET /v1/users(.:format) api/v1/users#index {:format=>:json, :subdomain=>"api"}
POST /v1/users(.:format) api/v1/users#create {:format=>:json, :subdomain=>"api"}
api_v1_user GET /v1/users/:id(.:format) api/v1/users#show {:format=>:json, :subdomain=>"api"}
PATCH /v1/users/:id(.:format) api/v1/users#update {:format=>:json, :subdomain=>"api"}
PUT /v1/users/:id(.:format) api/v1/users#update {:format=>:json, :subdomain=>"api"}
DELETE /v1/users/:id(.:format) api/v1/users#destroy {:format=>:json, :subdomain=>"api"}
As described in http://api.rubyonrails.org/classes/ActionController/TestCase/Behavior.html which is referenced in https://www.relishapp.com/rspec/rspec-rails/docs/controller-specs, the first argument to post in your RSpec example is the name of the controller method to be called (i.e. :create in your case), not the route to that method.

Devise: edit_password_url in password reset email is sending users to url/api/v1/

I have my rails app and Devise set up to use a JSON API to do user registration and login. A side effect is that edit_password_url in the password reset email is accidentally sending users to:
http://localhost:3000/api/v1/password/edit?reset_password_token=ZzyPCgmspN2964ENUkSS
when it shouldn't have api/v1/, and should send them to:
http://localhost:3000/password/edit?reset_password_token=ZzyPCgmspN2964ENUkSS
I've been looking, but can't figure out where to fix this.
I've created the following:
Api::V1::SessionsController < Devise::SessionsController
and
Api::V1::RegistrationsController < RegistrationsController
I have a regular RegistrationsController that inherits from devise, but not a regular SessionsController, so I just inherit straight from devise there.
Thanks for the help!
EDIT:
routes.rb
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
devise_for :users, :path => '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: { omniauth_callbacks: "authentications", registrations: "registrations"}
end
end
devise_for :users, :path => '', path_names: {sign_in: "login", sign_out: "logout"},
controllers: { omniauth_callbacks: "authentications", registrations: "registrations"}
resources :users
EDIT 2: rake routes output
new_api_v1_user_session GET /api/v1/login(.:format) api/v1/sessions#new {:format=>"json"}
api_v1_user_session POST /api/v1/login(.:format) api/v1/sessions#create {:format=>"json"}
destroy_api_v1_user_session DELETE /api/v1/logout(.:format) api/v1/sessions#destroy {:format=>"json"}
api_v1_user_omniauth_authorize GET|POST /auth/:provider(.:format) authentications#passthru {:provider=>/twitter|facebook/, :format=>"json"}
api_v1_user_omniauth_callback GET|POST /auth/:action/callback(.:format) authentications#(?-mix:twitter|facebook) {:format=>"json"}
api_v1_user_password POST /api/v1/password(.:format) api/v1/passwords#create {:format=>"json"}
new_api_v1_user_password GET /api/v1/password/new(.:format) api/v1/passwords#new {:format=>"json"}
edit_api_v1_user_password GET /api/v1/password/edit(.:format) api/v1/passwords#edit {:format=>"json"}
PUT /api/v1/password(.:format) api/v1/passwords#update {:format=>"json"}
cancel_api_v1_user_registration GET /api/v1/cancel(.:format) registrations#cancel {:format=>"json"}
api_v1_user_registration POST /api/v1(.:format) registrations#create {:format=>"json"}
new_api_v1_user_registration GET /api/v1/sign_up(.:format) registrations#new {:format=>"json"}
edit_api_v1_user_registration GET /api/v1/edit(.:format) registrations#edit {:format=>"json"}
PUT /api/v1(.:format) registrations#update {:format=>"json"}
DELETE /api/v1(.:format) registrations#destroy {:format=>"json"}
sessions GET /sessions(.:format) sessions#index
POST /sessions(.:format) sessions#create
new_session GET /sessions/new(.:format) sessions#new
edit_session GET /sessions/:id/edit(.:format) sessions#edit
session GET /sessions/:id(.:format) sessions#show
PUT /sessions/:id(.:format) sessions#update
DELETE /sessions/:id(.:format) sessions#destroy
authentications GET /authentications(.:format) authentications#index
POST /authentications(.:format) authentications#create
new_authentication GET /authentications/new(.:format) authentications#new
edit_authentication GET /authentications/:id/edit(.:format) authentications#edit
authentication GET /authentications/:id(.:format) authentications#show
PUT /authentications/:id(.:format) authentications#update
DELETE /authentications/:id(.:format) authentications#destroy
new_user_session GET /login(.:format) devise/sessions#new
user_session POST /login(.:format) devise/sessions#create
destroy_user_session DELETE /logout(.:format) devise/sessions#destroy
user_omniauth_authorize GET|POST /auth/:provider(.:format) authentications#passthru {:provider=>/twitter|facebook/}
user_omniauth_callback GET|POST /auth/:action/callback(.:format) authentications#(?-mix:twitter|facebook)
user_password POST /password(.:format) devise/passwords#create
new_user_password GET /password/new(.:format) devise/passwords#new
edit_user_password GET /password/edit(.:format) devise/passwords#edit
PUT /password(.:format) devise/passwords#update
cancel_user_registration GET /cancel(.:format) registrations#cancel
user_registration POST / registrations#create
new_user_registration GET /sign_up(.:format) registrations#new
edit_user_registration GET /edit(.:format) registrations#edit
PUT / registrations#update
DELETE / registrations#destroy
EDIT 3:
So I've been testing some thing out, and in the devise email template, the path edit_password_url is there, and works to generate the above wrong url, but when I do rake routes, only edit_user_password_url exists.
Looking at the Devise Controller URL Helpers doc (found here), I would've used:
edit_password_path(:user) which translates to edit_user_password_path. path seems to be interchangeable with url.
I'm not 100% certain but this line defines a method called edit_password_path whereas this line creates a route in the Devise context...
You haven't posted your routes.rb but I am guessing you want /password/edit to route to 'Api/V1/RegistrationsController' without api/v1/ in URL?
If yes, then you need to use module option of routing DSL. like this:
scope module: 'api/v1/' do
resources :sessions, :registrations
end
Ofcourse you need to integrate the above in devise_for call. I am not a devise expert, I am guessing, you will need to use devise_scope instead of scope like this:
devise_scope module: 'api/v1/' do
resources :sessions, :registrations
end
Note: If the above doesn't work. Post back with your routes.rb. We will help you fix it
According to the routes that are generated you should try this. In my case its working fine. Try this:
edit_user_password_url(reset_password_token: #token)
So, strangely, I needed to change the path in the devise mailer template. I changed it from edit_password_url which worked to generate a url, but didn't show up in my rake routes output, to edit_user_password_url which I found in my rake routes output.
I would love to know why edit_password_url worked even though it doesn't show up in the rake routes output, and am more than happy to give assign the correct answer to someone who can explain what is going on to me.

Resources