Devise multiple pathes for one model - ruby-on-rails

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.

Related

"undefined method `user_signed_in?' when `devise_For` is missing

I wanted to remove the devise routes, as I built custom routes and controllers for my purpose. However, I noticed that when I delete my devise_for :users line in routes and replace it with only...
Routes file
devise_scope :user do
delete 'logout', to: 'devise/sessions#destroy'
end
I am not getting an error:
View:
<% if user_signed_in? %>
Error:
"undefined method `user_signed_in?' for #<#Class:0x00007f984411aab0:0x00007f98441266f8>"
Why would a helper need routes? Which routes are required for it? Can I define only the helpers?
I also plan to use current_user.present? and sign_in #user, but do not know if they will error later, as I can not get passed the undefined Method.
From the Devise Gem: https://github.com/heartcombo/devise/blob/45b831c4ea5a35914037bd27fe88b76d7b3683a4/lib/devise/rails/routes.rb
# Let's say you have an User model configured to use authenticatable,
# confirmable and recoverable modules. After creating this inside your routes:
#
# devise_for :users
#
# This method is going to look inside your User model and create the
# needed routes:
#
# # Session routes for Authenticatable (default)
# new_user_session GET /users/sign_in {controller:"devise/sessions", action:"new"}
# user_session POST /users/sign_in {controller:"devise/sessions", action:"create"}
# destroy_user_session DELETE /users/sign_out {controller:"devise/sessions", action:"destroy"}
#
# # Password routes for Recoverable, if User model has :recoverable configured
# new_user_password GET /users/password/new(.:format) {controller:"devise/passwords", action:"new"}
# edit_user_password GET /users/password/edit(.:format) {controller:"devise/passwords", action:"edit"}
# user_password PUT /users/password(.:format) {controller:"devise/passwords", action:"update"}
# POST /users/password(.:format) {controller:"devise/passwords", action:"create"}
#
# # Confirmation routes for Confirmable, if User model has :confirmable configured
# new_user_confirmation GET /users/confirmation/new(.:format) {controller:"devise/confirmations", action:"new"}
# user_confirmation GET /users/confirmation(.:format) {controller:"devise/confirmations", action:"show"}
# POST /users/confirmation(.:format) {controller:"devise/confirmations", action:"create"}
devise_for is how Devise hooks into the entire framework and sets up its helpers. So skipping entirely is just not how you want to go about it. If you just want to change the name of the paths use the correct option instead:
# availabe path_names are
# :sign_in, :sign_out, :sign_up, :password, :confirmation, :unlock.
devise_for :users, only: :sessions,
path_names: {
sign_out: 'logout'
}
The skip: and only: options can be used to limit the generated routes:
devise_for :users,
only: :sessions,
path_names: {
sign_out: 'logout'
}
devise_for :users,
skip: [:registrations, :passwords, :confirmations],
path_names: {
sign_out: 'logout'
}
If you really want to use the path /user/... instead of /users/...:
devise_for :users, only: :sessions,
path: 'user',
path_names: {
sign_out: 'logout'
}
Not really something I would recommend though.
See ActionDispatch::Routing::Mapper#devise_for.

Rails new path in nested routes

I've got controller below controller:
module Api
module V1
module Account
class PasswordsController < Devise::PasswordsController
respond_to :json
def create
# some code
end
def update
# some code
end
def is_token_valid
::Account.find_by(reset_password_token: params[:token])
end
end
end
end
end
I want to setup an endpoint for front-end dev where he will check if reset_password_token exist in DB (devise here). I don't know how to made a path like: /api/v1/account/password/is_token_valid
My routes:
namespace :api, defaults: { format: :json } do
namespace :v1 do
namespace :account do
devise_for :accounts, singular: 'account', path: '', controllers: {
sessions: 'api/v1/account/sessions',
registrations: 'api/v1/account/registrations',
confirmations: 'api/v1/account/confirmations',
passwords: 'api/v1/account/passwords',
}
end
resource :account, only: [:show]
EDIT
routes:
root#b2faabb49f91:/usr/src/app# rake routes | grep account
new_account_session GET /api/v1/account/sign_in(.:format) api/v1/account/sessions#new {:format=>:json}
account_session POST /api/v1/account/sign_in(.:format) api/v1/account/sessions#create {:format=>:json}
destroy_account_session DELETE /api/v1/account/sign_out(.:format) api/v1/account/sessions#destroy {:format=>:json}
new_account_password GET /api/v1/account/password/new(.:format) api/v1/account/passwords#new {:format=>:json}
edit_account_password GET /api/v1/account/password/edit(.:format) api/v1/account/passwords#edit {:format=>:json}
account_password PATCH /api/v1/account/password(.:format) api/v1/account/passwords#update {:format=>:json}

Why doesn't the Devise registrations edit action need params?

I am learning a Rails course, and I have this in my routes
devise_for :users,
path: '',
path_names: { sign_up: 'register', sign_in: 'login', edit: 'profile', sign_out: 'logout' },
controllers: { omniauth_callbacks: 'omniauth_callbacks', registrations: 'registrations' }
This will generates routes like
edit_user_registration GET /profile(.:format) registrations#edit
In my view I just need to use edit_user_registration_path while normally it should be sth like user_path(:id). If I write edit_user_registration_path(1) it will redirect to .../profile.1
As I know normal Rails edit route should have params[:id]. Why in this case it doesn't need and how edit_user_registration_path(1) generates .../profile.1?
The routes don't need and don't take an ID parameter since they act on the current user which is stored in the session and not passed through the parameters.
Similarly if you wanted to create a controller that deals with items belonging to the current user you could do:
scope :user do
resources :items, controller: :user_items
end
class UserItemsController
before_action :authenticate_user
# GET /user/items
def index
#items = current_user.items
end
end
If you instead where building something like an admin interface where you can edit other users on the system an id param would be necessary.

Routing Error in feature testing with RSpec

I'm working on a Project and trying to write some tests for it. At the Moment i'm doing the feature tests.
The Problem: I just can't use visit. Doesn't matter where i am using it. I receive the error: ActionController::RoutingError: No route matches [GET] "/".
Here is a simple example of the test:
require 'rails_helper'
RSpec.feature "Welcome", type: :feature do
context 'sign in user and load index' do
visit new_user_session_path #didn't worked with "/" nor "/login" either
end
end
May it's a problem with RSpec? I just don't know how to fix it.
I'm really glad if someone would try to help me.
EDIT:
The routes.rb:
Rails.application.routes.draw do
REGEX_NAME = /[^\/]+/
constraints OperatorSubdomain do
get '/', to: 'operators#index'
devise_for :operators, controllers: {
sessions: 'operators/sessions'
}, skip: %i[registrations passwords unlocks omniauth_callbacks confirmations]
authenticate :operator do
require 'sidekiq/web'
mount Sidekiq::Web => '/sidekiq'
Sidekiq::Web.set :sessions, false
ActiveAdmin.routes(self)
end
end
constraints AppSubdomain do
root 'welcome#index'
scope '/', module: 'welcome' do
get '/faq', action: :faq
get '/impressum', action: :imprint
end
get '/partners', to: 'companies#my_partners', as: :partners
delete '/partners', to: 'companies#destroy_partnership'
devise_scope :user do
get '/login' => 'users/sessions#new', as: :new_user_session
post '/login' => 'users/sessions#create', as: :user_session
get '/register' => 'users/registrations#new', as: :new_user_registration
post '/register' => 'users/registrations#create', as: :user_registration
delete '/logout', to: 'users/sessions#destroy', as: :destroy_user_session
post '/users/notification_token', to: 'users/sessions#set_login_time', as: :notification_token
get '/users/invitation', to: 'users#invitation', as: :users_invitation
post '/users/invitation', to: 'users#complete_invitation'
end
scope '/setup', module: :setup do
get '/', action: :index, as: :setup
post '/', action: :create
end
devise_for :users, controllers: {
omniauth_callbacks: 'users/omniauth_callbacks',
confirmations: 'users/confirmations',
registrations: 'users/registrations',
passwords: 'users/passwords'
}, skip: :session
scope '/profiles/:name', module: :users, constraints: { name: REGEX_NAME } do
get '/', action: :show, as: :profile
put '/', action: :update
end
That's the important part of the routes.rb i think.
You should use scenario instead of context:
RSpec.feature "Welcome", type: :feature do
scenario 'sign in user and load index' do
visit new_user_session_path #didn't worked with "/" nor "/login" either
end
end
EDIT:
Looks like you're using subdomains, is it right? If yes, you should take a look https://robots.thoughtbot.com/acceptance-tests-with-subdomains

uninitialized constant SessionsController in API

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

Resources