I have a Rails app running Rails 3.2.6 and Devise 2.1.2 and I want to be able to limit concurrent logins to 1. I've tried using the Devise Security Extension but keep getting an undefined method for unique_session_id.
Instead I want to see if I can override Devise's behavior as in this SO article: devise limit one session per user at a time
I understand how it works and I've generated a sessions controller, but I'm not sure how to force the app to use it as an override for Devise. Also in the controller should I be inheriting from the Application Controller or the Devise Controller?
Everything in place right now however the app doesn't write a login_token which tells me that I'm not using the controller properly or something else might be amiss.
Any help is appreciated.
I would strongly recommend you check out the Devise Wiki. Chances are when doing something like this, the answer can be found there. Your new controller will need to inherit from the Devise Sessions Controller as such:
class MySessionsController < Devise::SessionsController
This way, any methods you did not specifically override will still be called, thus not breaking other functionality.
In order to make sure that Devise uses your controller instead of it's own, make your devise_for call in routes.rb look as follows:
devise_for :users, :controllers => { :sessions => "my_sessions" }
From there Devise will pick up your controller. It's probably a good idea to restart your server in there just to be sure.
Related
I have this in routes.rb:
namespace :api do
namespace :v1 do
...
devise_for :users, constraints: { format: :json },
:controllers => { :omniauth_callbacks => "auths" }
...
end
end
And produces among others, these routes:
new_api_v1_user_confirmation GET /api/v1/users/confirmation/new(.:format) api/v1/confirmations#new {:format=>:json}
GET /api/v1/users/confirmation(.:format) api/v1/confirmations#show {:format=>:json}
api_v1_user_omniauth_authorize /users/auth/:provider(.:format) auths#passthru {:provider=>/facebook|twitter|linkedin/, :format=>:json}
api_v1_user_omniauth_callback /users/auth/:action/callback(.:format) auths#(?-mix:facebook|twitter|linkedin) {:format=>:json}
How could a get last two routes namespaced, something like:
/api/v1/auth/:provider(.:format)
/api/v1/auth/:provider/callback(.:format)
Guess I should convert my comments into an answer:
For our app we are doing the pure json api thing, with backbone/marionette. To get oAuth working with devise - I removed it from devise. :) Removed the omniauthable property I had set up and removed the omniauth settings from my initializers/devise.rb. Then reading on the omniauth page I implemented it by itself.
My api lives under "/api/v1"
Created the initializers/omniauth.rb file listing my providers and keys. For each provider I also gave it a :path_prefix=>"/api/v1/auth" property.
Create a callback controller within my api called api/v1/oauth_controller.rb This was properly namespaced with modules and contains my callback path from the services.
Updated my routes to setup the callback route for omni. See here: gist.github.com/DaveSanders/5835642
Within OAuthController.create I consumed the details from the provider and go through the basic flow of "does the social network user exist and have a mapped account?" if so, log them in via devise's user.sign_in? If not, create the user and then sign them in.
Redirect back to my app, which then boots up backbone again, which can then go get the logged in user details and use them as needed.
Your implementation may vary, but the way I handle my oAuth accounts is put them in their own tables (Twitters, Facebooks, etc) and then link them into my devise user. This way I can have multiple accounts associated and the user can log in with any of them.
Also, be sure to set your twitter/facebook callback to something like:
http://127.0.0.1:3000/api/v1/auth/twitter/callback
to match your route in dev.
Hope this helps others. If I forgot a step or you get lost, please ask.
Gems
ruby 1.9.3
rails 3.2.11
devise 2.2.3
acts_as_tenant 0.2.9
Code
All my models are scoped by a domain_id:
class User < ActiveRecord::Base
acts_as_tenant(:domain)
#...
end
Then, in my application_controller, I set the current tenant from the domain:
class ApplicationController < ActionController::Base
set_current_tenant_through_filter
before_filter :set_tenant
protect_from_forgery
#...
def set_tenant
#...
#domain = Domain.find_or_create_by_name(request.host)
set_current_tenant(#domain)
end
end
All works well for all models except for sessions: Everytime a page is loaded, it will log-out the first user who load a page with another tenant. By loading this page, it will log-out the first user who [... etc.]
Hypothesis: when Alice visits a domain, Rails loads current_tenant=alice_domain (ok). All works as expected, until Bob visits another domain, load current_tenant=bob_domain. When Alice refreshes her page, Rails still has current_tenant==bob_domain. Rails checks session: Alice does not exist with bob_domain scope, so Devise forces Alice logout. Then application_controller sets current_tenant=alice_domain... which logs-out Bob.
Dirty workaround: do not use acts_as_tenant in user model, scope users by domain myself in every controllers, then overwrite devise to scope login and registration by domain. And I'm unsure how to get Devise aware of current domain in sessions stuff. By the way, replacing acts_as_tenant by a manual default_scope in user falls in the same strange bugs. It seems very dirty to go this way.
I'm looking for a clean solution for days. I would be very grateful for any help.
Fixed, in application_controller, change
before_filter :set_tenant
to
prepend_before_filter :set_tenant
in order to default_scope everything, including User, before Devise checks the user's session.
I am working on an app where a company admin should be able to create and update users in his company. (Users can also create and update their accounts independently).
I used Devise and Cancan for registration and permission management. I configured Devise to get the required signup and user update processes. I created a namespace for the admin views.
My admin controller (/app/controllers/admin/base_controller.rb) looks like this:
class Admin::BaseController < ApplicationController
authorize_resource :class => false
layout 'admin'
def dashboard
end
end
In addition to my "regular" users_controller, I have a controller (/app/controllers/admin/users_controller.rb) and associated views dedicated to Admin user management.
class Admin::UsersController < Admin::BaseController
...
end
Now, what's the cleanest way to implement Devise-related user admin features (at this point, create and update users)?
Have a conditional (based on user permissions) in my "regular" registrations_controller and confirmations_controller to render and redirect different views? (cf. Devise form within a different controller)
Or create new controllers in my admin namespace? If the latest is better, what are the main steps to follow?
Herein lies your issue, once you namespace or move any user management to another controller, you leave the scope of Devise. So at the point where you are in Admin::UsersController, Devise doesn't care about what you do, there are no 'devise-related' admin features as you stated. You can implement your Admin::UsersController as a standard RESTful controller if you wish.
In this manner, creating users through a namespaced controller, Devise will still perform actions such as confirmations. There is one small thing to keep in mind when creating and updating users this way. If you do not set a password as the admin, you will have to delete the password and password_confirmation from the params hash. To do so, the start of your create and update actions would look like so:
if params[:user][:password].blank?
params[:user].delete(:password)
params[:user].delete(:password_confirmation)
end
I employ this same method in many of my applications, and have yet to have it fail.
EDIT
namespace :admin do
resources :users
end
I recently added the Devise engine to a project with an existing authentication system (from the Rails Tutorial).
Before I added Devise, my UsersController handled all user registration facilities new, create, edit, destroy and I had a lot of rspec tests written for them. Of course now that registration is handled by Devise, I've had to remove those tests from users_controller_spec.rb.
Where can I put those tests to ensure that the Devise registration process is working correctly?
EDIT:
Ok I believe I've made some progress with this since I posted the question.
First I overrided the Devise registrations controller by making my own RegistrationsController in /app/controllers/registrations_controller.rb.
class RegistrationsController < Devise::RegistrationsController
end
Then I tell Devise to use this controller for registrations in my /config/routes.rb file:
devise_for :users, :controllers => { :registrations => "registrations" }
Now all I need to do it make my tests in /spec/controllers/registrations_controller_spec.rb work.
Am I on the right track?
That would be a good solution if you planned on overriding some of the controller methods. Typically, you don't test other frameworks like this that already have their own suite of tests. You are just duplicating the tests. That said, if you are more comfortable having your own, go for it.
I'm working on a Rails 3 web app at the moment with RSpec 2 and we're using Devise for authentication. One (and soon many) of our controllers require the user to be logged in. I know Devise provides the sign_in test helper, but can it be used with an RSpec or Mocha mock object?
I originally tried #user = mock_model(User) where user is the Devise class. This wouldn't work with sign_in :user, #user as get 'index' would redirect to the sign in form.
Does anyone have any experience testing with Devise and can help?
We had a similar problem, but using Factory Girl. We solved it like so:
In spec_helper.rb:
config.include Devise::TestHelpers, :type => :controller
In the controller spec (just a wrapper method):
def login_user(user)
sign_in user
end
Then in each method you require, you can do:
login_user(Factory(:user))
... where you have defined a user object in factories.rb. Not sure if this will work with mocks though.
A mock is never going to work. When you say sign in, the user is stored in session (basically, the user class and its id). When you access the controller, another user object is retrieved based on the stored data. The best way to solve the problem is using something that persists the object, like Factory Girl.
I hit the same issue. I'm doing the following for now:
before(:each) do
# sign_in mock_user
request.env['warden'] = mock(Warden, :authenticate => mock_user,
:authenticate! => mock_user)
end
I've created an issue for this here: https://github.com/plataformatec/devise/issues#issue/928
Go vote!
None of them worked for me (MRI 1.9.3-preview1, rails 3.0.1.rc5).
this is the solution i found : http://blog.joshmcarthur.com/post/6407481655/integration-tests-with-devise-and-rspec