ROR user authentication - ruby-on-rails

In my web app after a user logs in a new session is created so until he closes the browser he stays logged in. The problem appears when admin wants to ban the user who's browser is still open. Even though the user is banned and cannot log in anymore, he still stays logged in until he closes the browser or manually logs out. This definitely should be fixed.
Is it possible to add a verifying method to every action of every controller? Of course I mean a smart way - not copy/paste 100 times.

add the following to your application controller:
before_filter :sign_out_banned_user
def sign_out_banned_user
if current_user.banned?
session[:current_user_id] = nil
redirect_to root_path, :notice => "You are banned"
return false
end
end

You must reset the session also i think.

Related

omniauth - session empty after auth token returned

I'm hoping someone can help me figure out what's going wrong with an auth I'm adding into an app. The app itself uses normal authentication (username/password) using devise. Once a user is logged in, they are supposed to be able to connect to their email provider using OAuth (currently working on Microsoft365) which is what I'm working on right now.
I'm using the omniauth-oauth2 gem to implement the authentication, and as far as I can tell everything is working - the user is presented the MS login page, a POST request is returned with the token. All looks good, however when the callback comes in, the original user session is completely empty, and when I then redirect the user back to another page, they are kicked back to the login screen.
My callback is really simple at the moment
def auth_callback
# data = request.env['omniauth.auth']
if has_permission?
flash[:notice] = 'YAY!'
redirect_to root_url
else
flash[:alert] = "Could not authenticate with your mailbox"
redirect_to new_mailbox_path
end
end
I'm not doing anything with the data, and I know in a normal authentication system this is where I'd be getting/creating the user and signing in - but the user should already be signed in. And since they weren't created by oauth, I can't really use the MS user ID to find them anyway.
The only solution I can think of, is to send the user ID in the state when requesting auth from MS, so that when it is returned I can match that up and the "sign in" the user again but that feels wrong.
I am already bypassing CSRF so it shouldn't be that wiping it out
skip_before_action :verify_authenticity_token, only: %i[auth_callback]
skip_before_action :authenticate_user!, only: %i[auth_callback]
Is there anything else that would cause the session to be lost like this?

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.

Rails: sign out logged in user on event

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

Pros and cons closing a browser while session variable is set in rails 3 app

just wanted to know:
In my rails 3 app when a user logs in, i store their id in a session variable like this
session[:id] = #user.id
i noticed that when i login and then close the browser the session is destroyed. Is this wise to do or better to create a logout feature to destroy the sessions. What can go wrong if i leave it as is.
Thanks for help
I noticed that when I login and then
close the browser the session is
destroyed. Is this wise to do.
If this is happening then it's good if session get expired/destroyed when user closes browser. This is what mostly done in websites.But can be hard when you are trying to providing remember me or always sign in like functionality. In this case you might not have to destroy session when browser is closed.
or create a logout feature to destroy
the sessions.
You can create a logout feature to destroy session but call this when user hits logout link.

Flash message does not get cleared

Using Rails 3.0.3 running in Passenger we're experiencing an issue where the last flash set is retained for every request. For example we're setting a flash message if the user needs to be logged in in a before filter:
def require_user
unless current_user
store_location
flash[:notice] = "You must be logged in to access this page"
redirect_to login_url
return false
end
end
However on any subsequent request after the user has logged in successfully we still get the flash message persisting, it never clears for the entire session.
In this example this is the only place we are setting this message and I have ensured that it is only being set when the user is not logged in. In fact if I put flash[:notice] = 'Test' after the unless block refreshed and then removed that line and the 'Test' flash message remains for all subsequent requests.
This is happening if you run the app in either development or production env.
We had a custom override for Hash#symbolize_keys! that was hanging around from some old code and it appears that when the cookie middleware is calling symbolize_keys! on the cookie it was causing the flash to be re-applied on the end of the request.
I doubt anyone else will experience this issue, but in our case this is what the problem was.
Flash messages are automatically cleaned up at the beginning of each request.
Are you sure, your current_user method returns nil if user is not logged in ?
What library are you using for user session management ? Devise/Authlogic or other ?

Resources