I have a basic application that I want user to be able to access only if they have an access_token I have given them. In this example lets say the token is '131313'
Users without the access_token can only access the index page...
In my User model I have the following columns
name:
access_token:
items:
In my controller I have:
http_basic_authenticate_with name: "??not sure???", access_token: "131313", except: :index
I want verification that the right access token is passed '131313' and then to check the name provided by the user and list all of the items associated to that user.
i.e get redirected to a page that has the following
#user.each do |user|
user.item
I am guessing I will need a controller with something like:
#user = User.find(params[:name])
To set my user instance variable for my view and find them by the name provided. What I am having trouble here is understanding if its possible to do this with HTTP_Basic_Authentication. Is there a way to fetch the values provided(i.e 'name')and compare(i.e .find call) them to the database or even add them to the database(i.e .save call)?
Or would I have to roll out a full authentication system?
You add something like the following to your application_controller.rb:
before_action :authenticate, except: :index
private
attr_reader :current_user
helper_method :current_user
def authenticate
authenticate_or_request_with_http_basic('MyApplication') do |name, token|
#current_user = User.find_by(name: name)
current_user && current_user.access_token == token
end
end
That current_user method returns always the user currently logged in. That means you can that write something like the following in the view without loading an #user again:
<%= current_user.name %>
<% current_user.items.each do |item| %>
<%= item.name %>
<% end %>
Related
I want to send an email to customers that allows them to edit some of the information for their purchase, so my basic set-up is sending them a link to /follow_up_form/purchase.id.
This clearly won't work because we don't want anyone just typing in that URL and changing the user's information, but our site doesn't have any login necessary to make purchases.
Is there a way to autogenerate a secret URL, or pass through some sort of authenticity token? This feels like it should be simple but I don't have any good ideas.
Thanks!
You may use rails 5 has_secure_token and embed it into an URL as a parameter like /follow_up_form/purchase.id?tok='some_toke' And check this parameter when returning the form for edition. This will indeed make stuff more secure.
I would add a token column to the purchase, and set it after creation:
require 'secure_random'
class Purchase < ActiveRecord::Base
before_create :regenerate_token
def regenerate_token
self.token = SecureRandom.urlsafe_base64(24)
end
end
You can then setup follow ups just like you would any other nested resource:
resources :purchases do
resources :follow_ups, only: [:new, :create]
end
To create a link to the form where the user gives feedback use:
<%= link_to("Tell us what you think", new_purchase_follow_up_url(#purchase, token: #purchase.token)) %>
This adds an query string parameter to the URL containing the access token.
To authenticate in the controller we check if the access token matches what we have stored for the purchase:
class FollowUpsController < ApplicationConrtroller
before_action :set_purchase!
before_action :check_token!
# ...
private
def set_purchase!
#purchase = Purchase.find(params[:purchase_id])
end
def check_token!
unless params[:access_token] == #purchase.token
redirect_to root_path, error: 'You are not authorized' and return false
end
end
end
To pass the token between the new and create action add it to the form as a hidden input:
<%= form_for(#follow_up) do |f| %>
<%= hidden_field_tag :access_token, #purchase.token %>
<% end %>
I am working on a reservation project and after login I want to pass the current user information from the Sessions Controller to a Reservations Controller via a home page. How can I do that? I have been following Michael Hartl's Rails Tutorial for the login. Here's what I have tried in the create section of the Sessions Controller
render home_path :local_variables[:id => user_id]
and in the home_path(users#home)
<%= link_to new_reservation_path(:user_id => :id) %>
but it shows me an undefined method error. My Reservation model has a user_id column.I am confused regarding this matter. What should I do?
render home_path :local_variables[:id => user_id]
Seems weird to me to pass locals that way (don't even know if it's possible, never seen locals used outside of rendering views/partials).
I think the best way is to redirect instead and set the user in the sessions once they have been logged in successfully, so in your login action:
user = User.find_by_email(params[:user][:email]) # or however you are finding the person trying to login
session[:user] = user
redirect_to home_path
then in users#home
#user = session[:user]
and finally, in the view:
<%= link_to new_reservation_path(:user_id => #user.id) %>
EDIT
Actually, probably not a good idea to store an object in the session, instead of
session[:user] = user
You could try:
session[:user_id] = user.id
and then to find the user:
#user = User.find(session[:user_id])
If you still get an undefined error then it's probably because the user is nil (unless the User model actually has no id attribute, which would be strange) so there might be an issue with the login part, it's hard to say without knowing what that looks like.
If you need the logged in user on every page, you could abstract the functionality out into the application controller:
before_filter :check_user
def check_user
#user = User.find(session[:user_id]) if session[:user_id]
end
Then, you can use the #user instance variable anywhere in your app.
I have a pretty basic app. I've managed to install devise. The idea is to have users who have created challenges (1 to many relationship).
I want the logged in user to be able to see all the challenges they have created.
I order to do this am I correct in thinking that I can pass the current user id as a parameter to just get the challenges of the current user as follows (assuming the view is set up correctly)
<%= link_to challenges_path(user_id: current_user.id), class: 'expandable' %>
challenges controller
def index
#challenges = Challenge.all
render :layout => false
end
If this is the default behaviour you want for the index of the challenges, then you can simply change your controller action directly, and no need to modify your link_to to add user_id
challenges controller
def index
#challenges = Challenge.where(user: current_user)
end
Now if you want to change the behavior only if the user_id GET param is set, you can keep your link_to like this and modify your controller this way
def index
#challenges = params[:user_id] ? Challenge.where(user: current_user) : Challenge.all
end
I'm using the gem called omniauth-facebook, with which I succeeded at implementing facebook login auth.
It looks fine but it won't pass data of the whole object to the view. It just says nil.
It should show an array of things when using 'debug' or 'inspect'. It shows the content of session[:name] fine somehow.
Controller
class SessionsController < ApplicationController
def create
#auth = request.env["omniauth.auth"]
session[:oauth_token] = #auth.credentials.token
session[:username] = #auth.extra.raw_info.username
session[:name] = #auth.extra.raw_info.name
redirect_to bookd_url, :notice => "Signed in!"
end
end
View
<% if signed_in? %>
<%= #auth.inspect %>
<%= debug #auth %><br />
<%= session[:name] %>
<% end %>
Output HTML
nil
---
...
John
Your create controller action does a redirect. After the redirect, the process will start from scratch, and #auth will no longer be defined. If you render the view at this point, #auth will be nil. This is your problem.
You need to think about what you are trying to do here. You set an #auth variable from the authentication details in the initial request. You then use this to set some data in the session, which records who is logged in for example. Then, on the next page, where the user is logged in, you want to look at #auth. This doesn't really make sense: once you've authenticated a user, all you need to care about is remembering which user is currently logged in. You don't need to store details about HOW they logged in, and in fact you probably shouldn't.
You should instead be doing something like this:
#in ApplicationController, protected section
protected
def current_user
if #current_user
return #current_user
elsif session[:username]
#current_user = User.find_by_username(session[:username])
return #current_user
end
end
This will allow you to write current_user in your controller and view code, to access the user who authenticated, originally, which is the whole reason for logging someone in and keeping them logged in.
In the /views/layouts directory, how do I get the current user? I am using Devise, but current_user does not work here for some reason, it describes it as an unknown method. I want to do something like:
<% if User.role? == "gen_admin" %>
<li>
<%= link_to('Admin', users ) %>
</li>
<% end %>
I do have a role? method defined in my User model, but I still get this exception:
undefined method 'role?' for #<Class:0x3fcc1e0>
So how can I get the current user, and access its fields at this level of the source tree? Thanks!
Here is the roles? method:
# in User
ROLES = %w[gen_admin teacher_admin student]
def role?(base_role)
ROLES.index(base_role.to_s) <= ROLES.index(role)
end
Your code must account for when users are logged in, and when they are not logged in.
If no user is logged in, then current_user will return nil (as in your case, which you thought was an error on Devise's part).
Your view code must handle this - eg.
<% if current_user.present? && current_user.role?('gen_admin') %>
You have defined your role? method as an instance method. This means that in order to use it you should first create the instance from User class e.g.:
#user=User.new
Now you can call #user.role?
If you want role? to be available through the model User then you should define it as class method an pass in an object for verification
def self.role?(user)
...
end
You'll need to change the code in your view to use current_user rather than User, as well as call the role? method correctly (no ==). If this is used in a view that can be accessed by a non-logged in user, you'll want to confirm that the user is logged in first (so you don't try and call role? on nil, which would raise an exception):
<% if current_user && current_user.role?("gen_admin") %>
<li>
<%= link_to('Admin', users ) %>
</li>
<% end %>
Do you forget to add before_filter :authenticate_user! in your controller? This will ensure that current_user is available in your views