How to enable guest account for a specific controller - ruby-on-rails

I'm stuck with an implementation for a project I'm working on. Using rails 7 with devise, all users can sign in so I have the methods authenticate_user! and current_user, which, again, works fine.
I've introduced a Customer model. For now, I do not want the customer to register with password etc, I want them to click a link, sent to them from my application, then visit a page:
# Observer
class CustomerObserver < ActiveRecord::Observer
def after_create(customer)
secret_param = customer.to_sgid(expires_in: nil).to_s
url = ENV['HOST'] + "?csig=#{secret_param}"
# Send email with the url...
end
end
Once customer clicks that link, they should be taken to a special page and should have access to this controller only.
Been looking at GlobalID and not sure how to use the for: name in .to_sgid so that I could restrict the customer to access only the CustomersController and the show action.
In all controllers I have before_action :authenticate_user!. First thing came to mine is overriding that method. Feels wrong. How to have a customer access the CustomersController via the signed link and still be protected from unauthorize users?
I'm currently exploring this method where I could potentially have the customer model authenticatable? This means having two logins, one for user and other for customer. Would be nice to have one sign in path /sign_in/ for both user and customer. I feel this post is changing its direction.

How about some lazy check like this;
before_action :authenticate_user!, :if => :check_csig
# ...
private
def check_csig
# if csig param do not exists, call the authenticate_user!
true unless params[:csig].exists?
# if csig param exists and csig is valid do not call the authenticate_user!
false if check_csig
true
end
OR
before_action :authenticate_user!, :except => [:csgi]
before_action :check_csgi, :only => [:csgi]
# ...
private
def check_csig
#check if csgi is valid
end

Related

Using devise for user registration/login redirect user to a different form page based on their response

I am using devise for user registration/login, after the user has successfully signed up, I want to show a page/a dialog box and redirect to another page based on user response. How can I do that?
User Model (By devise)
username
password
Student Model
name
student_id
Teacher Model
name
grade
First_page:
signup signin links
Signup link will show the devise views/devise/registrations/new.html.erb page.
After successful signup, it takes the user to root page. I have defined the root page in routes.rb:
` Rails.application.routes.draw do
devise_for :users
resources :students, :teachers
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
root to: "students#index"
end `
At this point, the application doesn't have any idea who the user is.
So, I want to get the identity information(student/teacher) from the user.
How will I get this information?
Student/Teacher controller:
`class StudentsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def index
#students = Student.all
end
def new
#student = Student.new
end
def create
current_user.create_student(student_params)
redirect_to root_path
end
private
def student_params
params.require(:student).permit(:name, :skypid)
end
end`
After the user has successfully signed in, I want to ask if the user is a student or teacher. Based on what they select, redirect them to a student form page or teacher form page.
How can I do that in rails?
Thank you
You can write a custom after_sign_in_path_for function in your ApplicationController assuming you're using all the default Devise controllers otherwise. Any named path helper or other route that it returns will be where the user is redirected, so you could do something simple like always redirect to a selection page that presents the options and handles the choice on a subsequent action:
def after_sign_in_path_for(resource)
user_type_selection_path # whatever route in your app manages the selection
end
Alternately, you could invoke a custom method on the user model in that function to make a choice right there:
def after_sign_in_path_for(resource)
resource.student? ? student_path : teacher_path
end
You could hybridize these of course as well to do the latter when the selection has already been made and redirect otherwise, with something similar to the following:
def after_sign_in_path_for(resource)
if resource.user_type_chosen?
resource.student? ? student_path : teacher_path
else
user_type_selection_path
end
Bear in mind that none of those functions or paths are real, since I can't be more specific on the information you've provided, but hopefully this gets you headed in the right direction. The after_sign_in_path_for hook is your primary tool here, unless you get into the world of overriding the default devise controllers and interrupting the usual workflow there to accommodate this step, which doesn't seem strictly necessary by your description.

Devise not calling overridden function - Ruby on Rails

I'm trying to store a list of permissions within a session variable after a user signs up or logs in to their account. The following code only works when a user logs into a currently saved account. What method do I need to override to save the permissions when the account is created via sign up?
class UserSessionsController < Devise::SessionsController
after_action :after_login, :only => :create
def after_login
session[:permissions] = current_user.list_permissions
end
end
class Users::RegistrationsController < Devise::RegistrationsController
after_action :after_signup, :only => :create
def after_signup
## your data permissions
end
It might be necessary to inject via resource. In this case try (i commented it out quickly, just in case you wondering whats going on):
class Users::RegistrationsController < Devise::RegistrationsController
def create
## GET THE SIGN UP DATA
super do |resource|
##CHECK IF DATA IS VALID
if params[:your_data]
##SET THE RESOURCE TO THE DATA
resource.your_data = params[:your_data]
##CHECK (IF NECESSARY) DATA TO TYPE
if resource.you == 2
## SAVE IT
resource.save_with_your_data
## SAVE WITH STANDARD SETTINGS
else
resource.save
end
end
end
end
end
What method do I need to override to save the permissions when the account is created via sign up?
It seems like you want to store this information in the database when the user is created. Remember that current_user will give you the entire database record in the object, and then you can do whatever you need with it. I wouldn't recommend messing the session directly, instead, work with the current_user object that Devise provides.

Rails always redirect to edit page if email is empty

I have a Ruby on Rails web application, where a user needs to provide his nickname and password to register. After successful registration, the user is redirected to an edit page, where he should enter his email.
The use case requires to always redirect the user to the edit page until he has submitted a valid email address. However, in the current state, the user can click on any menu item and is then taken to the corresponding page. How do I prevent this behavior and redirect the user back to the edit page when clicking on a menu link?
You can create a before_action in your ApplicationController which checks if the user is logged in and has submitted his email, something like this:
class ApplicationController < ActionController::Base
before_action :validate_email_confirmed
def validate_email_confirmed
return unless current_user
redirect_to user_edit_path unless current_user.email?
end
end
Keep in mind that you have to skip this before_action for both the user edit and update actions, or you'll end up having a redirect loop. This is how to skip an existing before_action for specific actions:
class UsersController < ApplicationController
skip_before_action :validate_email_confirmed, only: [:edit, :update]
end
Did some more digging, and found this on the official docs, seems to fit your needs:
class ApplicationController < ActionController::Base
before_action LoginFilter
end
class LoginFilter
def self.before(controller)
unless controller.send(:logged_in?)
controller.flash[:error] = "You must be logged in to access this section"
controller.redirect_to controller.new_login_url
end
end
end
You'd of course have to rework this some, to get awaiting_email or such, but the principle is the same.

Ruby on Rails app planning - login system routing when not logged in

I'm building a rails app, and so far I've set up a pretty basic user registration/login system, mainly by following this railcast I found thanks to stack overflow. I've left everything the same as the railcast, only used strong parameters instead of attr_accessible and added some additional fields (username, bio, img url) to the table.
Now I want my app to redirect users onto my login page if they're not logged in, no matter what page they try to access, and if they are, then redirect to the normal path. My loging page is currently my root_path. Do I need to do this in all the controllers separately or can I just write this into my appController? How would I go about writing the controller? I was thinking something like this:
if session[:user_id] == nil
redirect_to login_path
else
redirect_to current_controller_path
end
Now how do I check if user is logged in, and how do I redirect to current controller path (for instance articles_index_path?
I am new to ruby on rails, and still trying to wrap my head around models, views and controllers, so please assume I know nothing when writing up explanations :) Thanks for the help
Oh I'm using Rails 4 with ruby 2.2.1
You need to add a before_filter in your ApplicationController to check user's authentication.
class ApplicationController < ActionController::Base
...
before_filter :authenticate_user!
...
private
def authenticate_user!
redirect_to login_path unless session[:user_id]
end
end
Now it will make sure that user should be logged in for accessing any action of any controller, including signup, signin and other actions which should be accessible to non-logged in users too.
You need to make sure that you skip above before_filter where you don't want user to be logged in such as signup, signin, about us, contact us etc actions like this.
For Example:
class SessionsController < ApplicationController
skip_before_filter :authenticate_user!, :except => :destroy
...
def new
...
end
def create
...
end
...
end
You can read more about skip_before_filter on APIDock

How to add this specific authorization feature to my rails app?

My rails app has a few cab operators and they have a few cabs associated with them, and they are related as follows:
class Operator < ActiveRecord::Base
has_many :cabs
end
I have used Devise as my authentication gem. It authenticates users, admins and super admins in my app. I have created separate models for users, admins and super admins (and have not assigned roles to users per se).
I now wish to add the authorization feature to the app, so that an admin (who essentially would be the cab operator in my case) can CRUD only its own cabs. For e.g., an admins belonging to operator# 2 can access only the link: http://localhost:3000/operators/2/cabs and not the link: http://localhost:3000/operators/3/cabs.
My admin model already has an operator_id that associates it to an operator when an admin signs_up. I tried to add the authorization feature through CanCan, but I am unable to configure CanCan to provide restriction such as the one exemplified above.
I also tried to extend my authentication feature in the cabs_controller, as follows:
class CabsController < ApplicationController
before_action :authenticate_admin!
def index
if current_admin.operator_id != params[:operator_id]
redirect_to new_admin_session_path, notice: "Unauthorized access!"
else
#operator = Operator.find(params[:operator_id])
#cabs = Operator.find(params[:operator_id]).cabs
end
end
But this redirects me to the root_path even if the operator_id of the current_admin is equal to the params[:operator_id]. How should I proceed?
EDIT:
Following is my routes.rb file:
Rails.application.routes.draw do
devise_for :super_admins
devise_for :users
resources :operators do
resources :cabs
end
scope "operators/:operator_id" do
devise_for :admins
end
end
I have three tables: users, admins and super_admins. I created these coz I wanted my admins to hold operator_ids so that the admins corresponding to an operator can be identified. Also, I wanted the admin sign_in paths to be of the type /operators/:operator_id/admins/sign_in, hence the tweak in the routes file.
Unfortunately, initially I didn't understand that you actually have 3 different tables for users and (super)admins... Not sure that Pundit can help you in this case, but I'll keep the old answer for future visitors.
Coming back to your problem, let's try to fix just the unexpected redirect.
Routes seems fine, so the problem can be one of this:
You're getting redirected because you're currently not logged in as an admin, so you don't pass the :authenticate_admin! before_action.
You say "even if the operator_id of the current_admin is equal to the params[:operator_id]", but this condition is probably not true. Can you debug or print somewhere the value of both current_admin.operator_id and params[:operator_id] to see if they're actually equals?
Another interesting thing, is that you have a redirect for new_admin_session_path in your code, but then you say "this redirects me to the root_path". Can you please double check this?
OLD ANSWER
If you want to setup a good authorization-logic layer, I advice you to use pundit.
You've probably heard about cancan, but it's not supported anymore...
Leave Devise managing only the authentication part and give it a try ;)
PUNDIT EXAMPLE
First of all, follow pundit installation steps to create the app/policies folder and the base ApplicationPolicy class.
Then, in your case, you'll need to create a CabPolicy class in that folder:
class CabPolicy < ApplicationPolicy
def update?
user.is_super_admin? or user.cabs.include?(record)
end
end
This is an example for the update action. The update? function have to return true if the user has the authorisation to update the cab (You'll see later WHICH cab), false otherwise. So, what I'm saying here is "if the user is a super_admin (is_super_admin? is a placeholder function, use your own) is enough to return true, otherwise check if the record (which is the cab your checking) is included in the cabs association of your user".
You could also use record.operator_id == record.id, but I'm not sure the association for cab is belongs_to :operator. Keep in mind that in CabPolicy, record is a Cab object, and user is the devise current_user, so implement the check that you prefer.
Next, in your controller, you just need to add a line in your update function:
def update
#cab = Cab.find(params[:id]) # this will change based on your implementation
authorize #cab # this will call CabPolicy#update? passing current_user and #cab as user and record
#cab.update(cab_params)
end
If you want to make things even better, I recommend you to use a before_action
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :update, :delete]
def update
#cab.update(cab_params)
end
#def delete and show...
private
def set_cab
#cab = Cab.find(params[:id])
authorize #cab
end
And of course, remember to define also show? and delete? methods in your CabPolicy.

Resources