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 %>
Related
How can I require user to create profile after he has signed up with Devise?
I am thinking about using Wicked Wizard gem, but it is possible for user to skip all steps and just access the website.
User must setup their profile first, only then they should have access to website.
I am thinking about this implementation:
SomeController.rb
before_action :authenticate_user!
before_action :check_if_profile_created?
private
def check_if_profile_created?
current_user.profile
end
end
But this solution will do this check on all requests made by user, which I think is not good. Is there any other ways to do this? Or how can it be implemented with Wicked Wizard gem? I haven't found how to make it redirect from all pages to current step in wizard.
I have a solution that I just implemented two days ago. There is no much documentation on the Internet about this matter. There might be many ways to do this. This is just the way I did it. So, without more delay, let's get to it.
The Concept:
The whole idea, at least how I conceive it, is to "force" the recently signed up user, after he/she also confirms its email, to additionally confirm his profile. As simple as that. That's because in my case many of the profile fields are obviously not mandatory, except the first_name, last_name, email and obviously the password. The rest (job_title, company, affiliation, etc, etc, etc), are not mandatory. But the thing is that at the same time, I would like to enforce that every single User has a profile as complete as possible. This is absolutely necessary for purposes that are not necessary to mention here.
Note:
This is based in that you already have a SettingsController, that allows to any logged user to update his own profile and to update his password as well (in a separated action method).
Step # 1: Add the related boolean field
Modify your users table by adding a boolean field.
class AddProfileWasConfirmedToUsersTable < ActiveRecord::Migration[6.1]
def change
add_column :users, :profile_was_confirmed, :boolean, null: false, default: false
end
end
The boolean field profile_was_confirmed will ensure that the User's profile has been confirmed. It's false by default, right from creation, and it will be updated once and only once to true, when the user confirms his profile. The system won't enforce a 100% complete profile after that, because in order to do such thing it would have to be mandatory on the User model, and that would mess up with the initial sign up process (using the usual devise).
Step # 2: Add the necessary routes.
This one is for the view where we will include a form (inn our case reuse the settings profile form):
get :confirm_profile, to: 'settings#confirm_profile'
And this one is for the updating of the incoming fields from the previously mentioned form:
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
So at the end you would have something like this (just an example):
get :confirm_profile, to: 'settings#confirm_profile'
resource :settings, only: [:edit, :update] do
patch :update_confirmed_profile, to: 'settings#update_confirmed_profile'
patch :change_password, to: "settings#change_password"
resource :site_terms, only: [:edit, :update]
end
Note:
Remember to place the additional get route before the resources declaration. Otherwise the framework will confuse your route with the show action method.
Step # 3: Modify your ApplicationsController
before_action :check_profile_was_confirmed
.
.
.
def check_profile_was_confirmed
return unless current_user.present?
unless current_user.profile_was_confirmed? || devise_controller?
redirect_to confirm_profile_path
end
end
Step # 3: Modify your SettingsController
skip_before_action :check_profile_was_confirmed
def confirm_profile
if current_user.profile_was_confirmed?
flash["notice"] = "Your profile has been already confirmed"
redirect_to dashboard_path
end
end
def update_confirmed_profile
if #user.update(update_params) && confirm_params_ready?
#user.update({ profile_was_confirmed: true })
flash["notice"] = "Profile confirmed"
redirect_to dashboard_path
else
flash["alert"] = "All the fields must be filled"
redirect_to confirm_profile_path
end
end
private
def confirm_params_ready?
params_base = params.require(:user).permit(:first_name, :last_name, :email, :company, :job_title)
params_base.values.all?(&:present?)
end
Step 4: Create your view with the form or reuse the same one you are using for updating the profile.
File:
app/views/settings/confirm_profile.html.erb
<% content_for(:title) { " | Profile Confirmation" } %>
<% content_for(:view_header) do %>
<%= render 'shared/view_header', view_title: 'Profile Confirmation' %>
<% end %>
<%#= render partial: 'nav' %>
<div class="w-prose mx-auto space-y-8">
<div class="card">
<div class="card-header">
<h4 class="mb-0">Fill all the fields</h4>
</div>
<div class="card-body">
<%= render partial: "settings/profile_form", locals: { form_url: update_confirmed_profile_settings_path } %>
</div>
</div>
</div>
Step 5: Update your User model's factory
Step 6: Update all your integration tests.
there is no way you can do it without a validation on every request, because you always have to be sure they fill the profile, no matter on what page you enter.
maybe you can add that validation to the application controller with a before action so you don't have to do it in every controller. and just validate if user is logged in and user.profile...
What do you mean ugly? If it's a matter of "the code will be duplicated everywhere", then you can create a controller that inherits from ActionController, and add before_action to that controller specifying that the profile needs to be filled up. Or you can create a concern which you can then plop in
class SomethingController < ApplicationController
before_action :ensure_profile_filled
def ensure_profile_filled
redirect_to 'wherever'
end
end
class ChildController < SomethingController
end
There are also route constraints but I think you want to do this on the controller layer.
I'm trying to get my head around rails validation. So far i have this
def live
person.live
end
However i have no idea what to do after this, The users have a button to click to make there person live. Which changes a value in the DB from false to true.
they then have a link to be able to edit the user which is here
/people/{{person.id}}/edit
How do i make it so the users cannot access the link (via typing it into the browser) after the event is live?
Thanks
I would recommend using some kind of authorization gem. Personally i use CanCanCan.
There you can specifiy what a user can access and not and where it should redirect to etc.
In Ability.rb specify this
can :update, Person, :live => false
In Controllers use this
authorize! :edit, #person
In views you can then use something like this
<% if can? :update, #person%>
<%= link_to "Edit", edit_person_path(#person) %>
<% end %>
"How do i make it so the users cannot access the link (via typing it into the browser) after the event is live?"
You can use before_action filter to check if live field is true. Something like:
Controller:
before_action :check_if_live_true
private
def check_if_live_true
if live_field.true?
redirect_to some_path
end
end
Which will redirect if the live field is true, then someone enters the matching URL.
I would like to know how I would take the user's input and use it to redirect them to a new URL. For example if the user entered "556859" into the input field and clicked "Go" it would take them to www.website.com/556859.
I'm using ruby on rails but am not sure how to achieve my goal. Any input would be appreciated.
If you need examples for the view and the rest as well, then ask. But this controller method should be enough to point you in the right direction:
def redirect_action
redirect_to "/#{params[:redirect_path]}"
end
You can send user input to controller method
and in that method you can use redirect_to helper method
For example
in view create form with action="controller/redirect"
and in controller create method redirect
and with params[] use your input
Best regards
You'd do something like this:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
def redirect
redirect_to root_path + param
end
end
#config/routes.rb
post "", to: "application#redirect", as: :redirect
#app/views/application/index.html.erb
<%= form_tag redirect_path do %>
<%= f.input :param %>
<%= f.submit "Go" %>
<% end %>
--
The next question becomes "why?"
Rails' routing infrastructure is such that it should give you the ability to access individual views for what you're trying to accomplish.
For example, if you're trying to show users in that redirect, you can do:
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
class UsersController < ActionController::Base
def show
#user = User.find params[:id]
end
end
This will give you the ability to access http://yoururl.com/users/12312321
Alternatively, you could also (if you had nothing else on that part of the app), do the following:
#config/routes.rb
resources :users, path: "" #-> http://yoururl.com/12313123
If you know what type of data you're trying to show, it should give us more to work with in regards how you'd construct routing structure for it.
I'm using Rails 4 and have a Policy model with a field policy_number.
I'd like to create a (search-like) form where you input a policy_number and it redirects you to that Policy's show page. I'm not sure how to go about this, should the form's action be policy_path or something?
Thanks!
The biggest problem here is that the user is inputting the policy number in the search form, so you don't have access to it at the time the form is rendered. Without using JavaScript, you won't be able to go directly to the policy by policy number entered.
Here's a possible starting point, though. Create a PolicySearchController with an index method, add a route for it, and create a simple form.
app/controllers/policy_search_controller.rb
class PolicySearchController < ApplicationController
def index
policy = Policy.where(policy_number: params[:policy_number]).first
if policy.present?
redirect_to policy
else
redirect_to :policies, alert: "No matching policy found."
end
end
end
config/routes.rb
resources :policy_search, only: :index
app/views/policies/index.html.erb
<%= form_tag policy_search_index_path, method: :get do -%>
<%= text_field_tag :policy_number -%>
<% end -%>
Now you can iterate on this to add JavaScript, fuzzy matching, etc. if desired.
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 %>