Rails: sign out logged in user on event - ruby-on-rails

I'm using Rail3 with Devise gem. It does a great job when you need to lock user from signing in.
But it works just for new login attempts.
If he is already logged in - it won't sign him out immediately.
Here's is the typical use case:
Given admin user
when detects suspicious activity of certain user he locks it with malicious_user.lock('locking-reason')
% can config/initializers/session_store.rb
AppFoo::Application.config.session_store :cookie_store, :key => '_foo_session'

Given HTTP's statelessness, you can't immediately log out a user because you will need to wait until they make another request to your server. You could get around this via a push service I suppose, but that would be overkill.
My solution would be to add that person to a blacklist and then check if they're on the blacklist whenever they try to access a section intended for logged-on users only. This will render them unable to log on until you decide whether or not their activity is suspicious.
Example:
User is suspected of intolerable activity
Admin wants to check this out, so they temporarily add the user to the blacklist.
User clicks on an area of the page they were currently on when added to the blacklist.
Code checks for loggin status and blacklisted users.
Since the user is blacklisted, they are informed that they need to sign in to access the content
Once the user tries to sign in again you can inform them that their account has been temporarily disabled (or you can do this in the previous step).

perhaps the easiest way would be to redirect the user to the logout action when you lock them so:
malicious_user.lock('locking-reason')
redirect_to '/logout' and return
I'm not familiar with Devise so this may not be the best solution or even possible but it's how I would approach the problem

Use a before_filter in the ApplicationController that will do the following
before_filter :kick_out_blocked_user
protected
def kick_out_blocked_user
unless current_user.try(:active?)
redirect_to destroy_user_session_path
end
end

Related

Invalidating Devise user session identifier after password update

Scenario: As an Administrator I need to invalidate a user's session (log them out) after I update the user's password. This is in accordance with best practices as per https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html#Renew_the_Session_ID_After_Any_Privilege_Level_Change
I am using Devise and I saw here https://stackoverflow.com/a/45756884/664675 there is a config to log the user out: config.sign_in_after_reset_password = false
However, I have enabled this config in my devise.rb but the user remains logged in. Not sure why that is?
I am also using Redis as the session_store
Example::Application.config.session_store :cache_store,
key: '_example_session',
secure: true
Is it feasible to delete the particular user's session store from Redis upon Password reset by the Administrator? And if so how could I find their particular session key within Redis?
the flag sign_in_after_reset_password does not relate to logout user at all, sign_in_after_reset_password = false imply that in case a user update his account password by himself then do not automatically sign-in his account again, and that logic happen only on PasswordsController#update.
So you as admin try to change password of another user in a custom controller, of course it's not logout user no matter the value of sign_in_after_reset_password is.
devise use gem warden to logout user (in other word: destroy user session) and warden base on request session not base on database, that mean there's no way an admin can get another user's session to reset, so you can not force logout another user by only devise, you need to handle this feature outside devise (such as add session to user table or a devise hook something like timeoutable)
reference: https://github.com/heartcombo/devise/issues/5262
What you're looking for is the setting sign_in_after_change_password which you should set to false (it defaults to true). The method has a slightly confusing name – it means "should we sign the user in after changing their password" instead of "should the user have to sign in after changing their password".
References:
https://github.com/heartcombo/devise/blob/master/lib/devise.rb#L296-L298
https://github.com/heartcombo/devise/blob/c82e4cf47b02002b2fd7ca31d441cf1043fc634c/app/controllers/devise/registrations_controller.rb#L163-L167

How to allow sign in with devise if user is already authenticated?

There's an App which creates a new user when you first time open the site (if you don't have a cookie). Also you can sign in as different user. The problem is Devise doesn't allow to sign in as already authenticated user. Even if I override require_no_authentication in SessionsController warden still remembers the user through session.
I do aware about guest user pattern but there's no guest user. The app is intended to create real user accounts and allow to sign in as another users.
So the question is should I create new Warden strategy and if so, what checks shall I do there? Or there's another way?
Just reset the session in the desired action. You can just make the first line be reset_session, or make it a before filter, like so:
before_filter :deauthorize_user, only: [ :some_action ]
def deauthorize_user
reset_session
end

Kiosk-like web application - how do I "lock" the app?

Question: What's a good way to "lock" a Rails web application so a user must enter credentials to unlock it (but such that the user is still logged into the app and simply can't use the app without unlocking it)?
What I'm doing
I'm building a task management app that behaves like a kiosk to be used by multiple users. Users can log in to the app using their email address and password (full credentials), but once they're in, they can "swap" users by selecting their name from a list and entering a four-digit PIN (easy credentials).
Why have two types of credentials?
This is a multi-tenant app, and users use "full credentials" to simply get into the app and choose a tenant. Once they're in, they will frequently swap out as new employees come on shift and other employees leave because multiple employees will share a single work station.
Rather than forcing users to repeatedly enter their "full" credentials (by doing a full logout/login), I've created a name/PIN set of credentials they can use to easily swap. Once they're logged in with full credentials and have chosen their tenant, the app provides an autocomplete list of user's names, so it's very easy to select one's name from the list, enter the PIN and swap users.
Note that this "swapping" functionality is already built and working.
Why do I need to lock the app?
When a user is logged in to the app and a tenant, they may need to leave the app to go do other work. Without a lock screen, they only have two choices: fully log out so that the next user has to enter "full" credentials to use the app or just leave their account logged in for anyone to mess with.
I would like to allow them to click a button to "lock" the app when they walk away so that users can leave the app "logged in" without allowing other users to access others' accounts. Once they click "lock" all they can do is enter a name/PIN to unlock it, or click "sign out" to fully log out.
Here's what a typical use-case would look like:
Frank logs into the app using his email address and password.
He completes some tasks.
Jane wants to use the app, so she "swaps in" by choosing her name from a list and entering her four-digit PIN.
Now Jane is the logged-in user, meaning she can complete tasks, edit her profile, and other activities.
Jane completes some tasks and needs to go do something else in the store, so she clicks the "Lock" button on the screen.
The app is now locked and requires authentication to use (name and PIN) or the user can click "sign out" to fully go out of the app. The screen simply says something like "The app is locked. Enter your name and PIN to unlock it."
The options as I understand them
It seems like there are two ways to go here:
JavaScript of some kind
Some sort of controller logic using session variables or cookies
JavaScript makes me nervous because it's pretty hackable. I want to make sure the app is actually secure.
So I've been messing with ways of using session variables to do this. Here is what I've been trying:
User is authenticated to the app for a particular tenant
User clicks the "Lock" button
A "lock_ui" action is called in the Sessions controller
That action sets a few session variables (session[:locked], session[:locked_by], session[:locked_at])
The app then always redirects to Sessions#locked, which displays the "This is locked. Enter name and PIN to unlock" form unless it's unlocked or the user clicks "sign out".
Once a user enters a valid name/PIN, those session variables are deleted and the app functions normally.
Any suggestions on how I might do this so that it's secure?
Things I'm hung up on:
Should I be using some sort of application controller filter (before or around) to check to see if the app is locked? What does the application controller do if the app is locked? (Does it call some other action, render a the "locked" page directly, or something else?)
Or is this something that should be handled in the view layer? For example, I could update my application layout so it has an "if locked_ui render the 'locked' screen, else yield".
Some notes on how I'm currently set up:
This is a Rails 4.1.0 app using Ruby 1.9.3 hosted on heroku.
I'm using CanCan for authorization
I'm using multi-tenancy with scopes (and I'm not using sub-domains)
Each user has only one account that may have access to multiple tenants
I've seen JQuery BlockUI, but it seems more like a vanity thing than a functional security device
Here's what I ended up building.
To answer my own questions:
Should I be using some sort of application controller filter (before or around) to check to see if the app is locked? I am using a before_filter in my Application Controller to check whether the UI is locked. If it's locked, I redirect to the "locked" screen. I then added a skip_before_filter to the relevant actions in my Sessions Controller (essentially ignoring that before_filter when the user is navigating to the "locked" screen and related lock/unlock actions in the Sessions controller.
Or is this something that should be handled in the view layer? In the view layer, I mostly just needed to create the actual "locked" screen (where a user enters credentials to unlock the app), and I make sure to hide navigation elements while the UI is locked (so the user isn't confused as to why they click "Edit Profile" and don't leave the lock screen).
I'm using cookies to record the current state of the UI (locked or not). Here are some code snippets:
application_controller.rb
before_filter :confirm_unlocked_ui
private
def confirm_unlocked_ui
if signed_in? && locked_ui?
redirect_to locked_path
end
end
sessions_helper.rb
def locked_ui?
session[:locked] == '1'
end
def lock_ui
session[:locked] = '1'
session[:locked_by] = current_user.id
session[:locked_at] = Time.zone.now
end
def unlock_ui
session.delete(:locked)
session.delete(:locked_by)
session.delete(:locked_at)
end
sessions_controller.rb
def lock
session[:return_to] = params[:return_to] if params[:return_to]
lock_ui
redirect_to locked_path
end
def locked
#essentially just a view
end
def unlock
user = User.find_by_id(params[:session][:unlock_user_id])
if user && user.authenticate_with_pin(params[:session][:pin])
cookies[:auth_token] = user.auth_token
unlock_ui
redirect_back_or root_path
else
flash.now[:error] = "Invalid PIN."
render 'locked'
end
end
def destroy
sign_out
unlock_ui
redirect_to root_path
end
I really don't know how "good" this solution is, and one reason I'm posting it here is to see if others come up with better ideas or see any issues lurking with how I've done this.
Some more subtle things you might want to think about if you're trying this:
I'm using CanCan for authorization, and I've left that out here. If you have role-based authorization or something like that, be sure to check that this works for each role.
Notice that I allow users to 'sign out' if they don't want to 'unlock'. I do this using the 'destroy' action my sessions controller and I make sure to 'unlock_ui' before I redirect them after signing out. If I didn't do this, then the app would still be "locked" the next time they log in from that browser.
locked_path is an alias for Sessions#locked (basically just a view/form) that responds to a get request.
Sessions#Lock also responds to get requests.
Sessions#Unlock is what is called when the form is submitted from the "locked" view.

Resetting basic-authenticate-with

I am trying to do a very basic authentication. I've already tried the one ryan bates used in his screencast with bcrypt-ruby and user authentication.
For a really small project I want to use something else:
http_basic_authenticate_with :name => 'user', :password => 'secret'
I've got a global called $admin and I've got a method to set its value to false (similar to logout).
Is there a way to reset this authentication so that the user (admin) has to fill in the "login credentials" again?
Kind regards
Unfortunately in case of basic_auth the user stays logged in until the browser window is closed. If user logs in with basic_auth, the browser stores the authentication information, and sends the authentication parameters through the http headers with every request.
There is a small catch to this though:
After logging in with basic_auth, when user goes browsing though your app and goes from one link to another (e.g. from http://appdomain.com/link1 to http://appdomain.com/link2 he is really going from http://username:password#appdomain.com/link1 to http://username:password#appdomain.com/link2. The browser hides the username:password# part in you addressbar, so you do not know about it.
A dirty way to logout a user that has authenticated through basic_auth would be to create a link or redirect to http://invaliduser#appdomain.com/ that the browser does not hold authentication credentials to...
EDIT: or as an alternative redirect AND login a user into a no-privilege account that cannot view or do anything within your app through http://guest:password#appdomain.com
Hope it helped.

Ruby on Rails: Authlogic Facebook integration: Link accounts with user input instead of automatically using email address

I'm developing a web app in Ruby on Rails. I'm using authlogic to do authentication. My site has its own logins, but I'm hoping to also use Facebook. I tried out authlogic_facebook_connect (using Facebooker). After a lot of hiccups (the docs haven't really been fully kept up), I did get authlogic_facebook_connect to work and it's OK. The default "connect" behavior works perfectly when I'm faced with users who have never used by site before, but it results in a lot of duplicate logins for people that are using different email addresses for Facebook and for my site. Here's what I want:
When the user hits the Facebook "Connect" button (and after they go through the Facebook auth step of clicking 'Allow'), I want a box to pop up asking the user if they want to connect to a pre-existing account on my site or if they want to have an account automatically generated for them.
If they want it automatically generated for them, we're good and we proceed as normal, but if -- on the other hand -- they want to link their Facebook account to an account on my site, I actually want them to enter in their local credentials and find the correct account. In other words, I do not want my solution to automatically figure out which account looks like the right one, I want the user to do this.
Is there any gem / plugin / quick hack that will allow me to pull this off either using authlogic_facebook_connect or OAuth or something else?
--David
Someone else may be able to point you at the perfect gem for this, but I can tell you that I've worked on a similar problem and it wasn't much work to roll our own, based on the oauth2 gem.
Here's a sketch of the code/flow I use.
1) User clicks on 'Connect to Facebook' and this sends you to an action like this
def to_facebook
options = {
:redirect_uri => facebook_callback_url,
:scope => "email,publish_stream" # whatever you want to do
}
client = OAuth2::Client.new(FACEBOOK_API_KEY, FACEBOOK_API_SECRET, :site => FACEBOOK_API_SITE)
redirect_to client.web_server.authorize_url(options)
end
2) User goes over to facebook and gives you all the access you want, then facebook calls the callback you specified facebook_callback_url which should take you to an action like this:
def facebook_callback
client = OAuth2::Client.new(FACEBOOK_API_KEY, FACEBOOK_API_SECRET, :site => FACEBOOK_API_SITE)
access_token = client.web_server.get_access_token(params[:code], :redirect_uri => facebook_callback_url)
do_my_custom_user_association(access_token)
end
Then you can specify whatever you want in do_my_custom_user_association, you should have a current_user available from authlogic if someone is logged in, so you can redirect to a flow that lets the logged in user select if they want to merge into their current account or a different one. If there's no current user, you can send them to a create account flow, with some facebook data attached.
Note that this is just a sketch, there are error cases to handle (e.g. facebook_callback will be hit with the param error_reason if the get_acccess_token fails) and I'm not recommending you do all the oauth2 interaction right in your controller, but the basic idea is there.
See http://developers.facebook.com/docs/authentication/ if any of the oauth2 interactions don't make sense.

Resources