how to forbidden page cache on rails - ruby-on-rails

i'm design a shopping cart,there are two web page,
first is checkout page,and the second is order_success page
if user use the go back button on webbrowser then it will go back to checkout page after user have go to order_success page.
so i want to find some way to forbidden let use go back,
is there some way on rails to archieve this?

"i know this but user will use the go back button,if there some cache in client,
user still could see something & re-submit, this is not what I expect" [was that the meaning?]
If you want to prevent the user resubmit the same request, you may add a hidden field (just something random) and store it in a session after the request has been processed:
if params[:token] == session[:used_token]
render_already_processed_notice
return
end
if #cart.save
session[:used_token] = params[:token]
....
If you use a cart ID in a request, then you just may use the status of the Cart model:
#cart = Cart.find(params[:id])
render_some_notice and return if #cart.done?
....
#cart.done = true
#cart.save
Personally I would not create a checkout and order_success pages. I would just create a single page - cart status, and depending on the model status I would just display different content.
If some action may be executed only once (like closing the cart or finalizing the transaction) there is no problem: render_something and return if #cart.already_closed?
On pages, where multiple submits are possible, but not always welcome (like adding a product to the cart - the user can add two identical products) you may do two things:
1) Use the mentioned token, which will detect when the user just pressed F5, and ask him whether the action really should be done twice, or
2) Just accept the requests, but always provide the user with methods to 'rollback' the actions (allow him to delete products from the cart), and ensure that the cart content will be verified before final acceptance.

clear shopping cart
redirect user to new action
After a purchase is successful you should clear the shopping cart and then redirect the user to a new action.
In your order_controller.rb
def create
#shopping_cart = nil
redirect_to some_path
end

Related

Sharing the variables between the actions in the controller in Rails

I am working on a module where I have to take the consent of the user to save the set of records.
those set of records are created in an action, which has to be made available in another action of the same controller, the records are being saved by the user consent.
now I can send these set of records to UI, from UI to again controller, if the user continues to save, if not cancel.
Problem is there will be thousands of records, which is painful to carry between UI and controller so My plan is to make the set of records available to the action which is being called by the continue button
the code
def create
#valid_members = generate_member_upload_results(params[:member_upload_user][:members_list])
end
in this action #valid_members is going to have the set of records. after this action executes in UI we will ask user whether the records are to be saved if no then cancels if yes then the following action will takes palce
def create_member
count = 0
unless #valid_members.blank?
#valid_members.each do |m|
count = count + 1
m.save(:validate => false)
end
end
redirect_to :back , notice:'#{count} members records created'
end
I want my #valid member should the same object which I used in create def.
I'm not entirely sure this is feasible with the flow you're suggesting. This sounds like something that could be resolved with a multi-step form but you would need to pass the data across or temporarily store it, which is seemingly what you're trying to avoid.
Alternatively, can you create a Rails endpoint that services the first step via javascript directly from the frontend? That can return the data without the user leaving the page, they can then confirm they are happy and submit the page once with approval.

Some questions about security in Rails 5

I've got a number of security concerns about my current application and wondering if I am leaving myself open to abuse, in the following arenas.
a) .My main access control method is by maining a current_user, current_company current_project method in my application controller. These methods return object based on stored session keys established when a user logs in and cleared when they log out. I.e if I want to know something about the current user, I can call "current_user.role" or if I want see whether the account a user is trying to change belongs to him, I check whether the associated account id which is requested in the url actually belongs to that user, essentially as follows
in Account controller
def account_info
redirect_to login_path if !user.logged_in
account_id=params[:account_id]
#account = Account.find(account_id)
unless account_belongs_to_user(account_id)
redirect_to unauthorized_path
end
end
In my application controller, when a user is initially authenticated, I do something like this:
session[:current_user_id] = user.id
and clear that session key when the user logs out.
Then when account is requested, and account_belongs_to_user is called, the application controller processes it, more or less like this:
def account_belongs_to_user(account_id)
account = Account.find(account_id)
return account.user_id==session[:current_user_id]
end
So I guess my security scheme ultimately relies on whether the session data is secure and not trivially spoofable.
b) When I render pages I sometimes pass objects which have senstive data to my erb pages to generate the page text.
For example, I might pass a "company" object (ActiveRecord) to the view to generate an invoice screen. But the company object, passed as #company, has a lot of sensitive data like access keys and the like. Not really being fully aware of the the internals, if I don't specifically include something like:
<%= #company.access_token %>
on my web page, can I be confident that the attributes of #company won't somehow be passed into the browser unless I specifically ask for them to be rendered on the page?
This is obviously an issue when using rails to serve data for say, AngularJS single page applications, as everything I pass for Angular to render the page I assume is probably accessible to an evil-doer even if not on the page itself, but I'm hoping that's not the case with pages generated server side by rails.
This may be a naive question, but thanks as I just want to be certain what I am doing before start spilling secrets all over the place.
put an authentication for the token using active_record callback
https://guides.rubyonrails.org/active_record_callbacks.html

Show page only once after sign up - Ruby on Rails

I have an app that is using Devise, I would like that after a user signs up, they are directed to a specific page, this page calls an API and saves the value from the API, I need this page to only be accessible or available after a user completes the sign-up form and clicks submit, and is then redirected to this page.
I do not want this page or URL accessible any other way but after sign-up, as the API will send a new value if accessed again. How can I accomplish this?
Once a user signs up they will be redirected to the page calling the API:
def after_sign_up_path_for(resource)
api_call_path ##path that can only be accessed after sign_up
end
The API is called and the response from the JSON data is automatically saved to the database once the page is opened, if the page gets opened again a new JSON response will be received with new data, which is what I would like to avoid.
So in a nutshell, my question is how can I restrict access to a specific path, and only make that path accessible if a user completes the sign-up form (devise) OR is there a way that I can run the code from the controller using a callback/filter after the user is created through the User model?
I was just busy with something similar. You do not need to direct the user to a page to run the code, you can just run the code that needs to be run after the user logs in the first time.
You need to create a Session Controller, and create a conditional statement that checks if the user has logged in previously:
#config/routes.rb
devise_for :users, controllers: { sessions: "sessions" }
#app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
def after_sign_in_path_for(resource)
if resource.sign_in_count == 1
##Do something awesome
else
root_path
end
end
end
As Emmanuel advised you can check futher info on the Devise Controllers.
Let's call the moment between after sign_up and showing the specific page - state A. So in the specific page controller you need to know - is the user in state A. You can achieve it by
1) saving to db (server side) that user is in state A after sign up and resetting state after showing specific page (you can do resetting triggered by client side to guarantee that page is showed).
2) saving to cookies (client side) after sign up then do as above.
Second solution is more clean in my opinion, but I do not know how strict is the rule to show only once
You can customize devise users controller by issuing
rails generate devise:controllers [scope]
Then customise UsersController such that after user is saved you can call your api code there
eg
def create
#user = ....
if #user.save
#user.call_api_method()
else
......
end
end
For more information check Configuring controllers

Rails controller actions & cache - handling simultaneous/overlapping requests

My Rails app depends on the Rails cache to temporarily hold user input and pass it between controller actions during the user log in process. However, I realized that I have made a pretty serious error (since I am super-new to Rails and MVC, in general) and could use some help/advice/wisdom - basically, if two users are simultaneously (or nearly simultaneously) submitting data and going through the controller steps below, there's going to be some trouble - lost data, one user's data being entered as another, etc. When I was building this, I did not understand the nature of controllers, though I for sure have a better idea now....
In short, here's how the code below is intended to work: a non-logged-in user fills out a form and submits that data which is passed to the submission action and held in a Rails cache object called incoming_report; the user is then prompted to log in (via Devise), and a successful log in triggers the approval controller action which reads the incoming_report cache object, saves that data to the database, and then creates a new cache object called ids to hang on to the id #'s of the user's entries; this redirects to the summary action which gathers up those ids and shows the corresponding data to the user as a summary of their entry.
report_controller.rb
#ON SUBMITTING THE FORM...
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#SUBMITTED DATA IS HELD IN THE CACHE AS 'incoming_report' TO PASS TO approval CONTROLLER ACTION ON SUCCESSFUL LOGIN
Rails.cache.write("incoming_report",#incomingReport)
end
#ON SUCCESSFUL LOG IN...
def approval
#incomingReport = Rails.cache.read("incoming_report")
#newReportIDArray = Array.new
#incomingReport.each do |x|
#DATA PROCESSING STUFF HERE...
end
#DELETE 'incoming_report' FROM CACHE
Rails.cache.delete("incoming_report")
#ID NUMBERS OF ENTRIES ARE HELD TO PASS TO summary CONTROLLER ACTION
Rails.cache.write("ids",#newReportIDArray)
redirect_to report_summary_path
end
#AFTER DATA IS ENTERED...
def summary
#newReportIDs = Rails.cache.read("ids")
#DELETE 'ids' FROM CACHE
Rails.cache.delete("ids")
end
For the most part, this works very well, but if users cross each other during any of these actions, it's disastrous. For instance, if one user hits the submission action while another user is busy logging in, the incoming_report object of the second user will overwrite that of the first user... and so on and so forth.
The main source of this problem is the fact that users log in after they have clicked the form submit button - the app must be structured that way - and the data can't be held in local/session storage objects because it includes base64 strings and is too long to employ those methods in certain browsers. Holding this data on the server seemed like the best course of action until I noticed this issue...
Is there any way that I can prevent users from getting in each other's way in the controllers? Or have I made a pretty fatal error?
Editing to show solution
Ultimately, using session variables instead of the Rails cache saved the day. Here's the reconfigured controller to show the solution...
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#newReportIDArray = Array.new
#incomingReport.each do |x|
#DATA PROCESSING STUFF HERE
#newReportIDArray.push(#new_report.id)
end
session[:ids] = #newReportIDArray
respond_to do |format|
format.json do
render json: {
success: 200
}.to_json
end
end
end
def approval
#reportIDs = session[:ids]
#reportIDs.each do |x|
#new_report = Report.find(x)
#new_report.user_id = current_user.id
#new_report.save
end
redirect_to report_summary_path
end
def summary
#reportIDs = session[:ids]
end
Many thanks to the two folks who helped me on this and showed me the ways of the session variable.
I ran into a problem with the session variable being 'lost' between controller actions - Rails / Devise - updating session variables between controller actions
I would suggest that you store the initial information into the database. From there I would either save the id in a cookie or session variable. You would then be able to retrieve the information from the database in the approval controller. Another option I would consider would be to generate a guid as an id and pass that along to the sign in page as a query string parameter. Store the guid in a hidden input tag, under the form tag. Then when the user submits their credentials, you can also pass the guid. At that point you log them in. If successful, pull the guid from the posted data and then retrieve the database information with the guid.
Wade

Create first, login or signup later

Using Rails 3.
In usual app, a form only appears if the user is logged in. Then in the controller, we will run another check on authentication before we save the new record.
However, I want to show the form to everyone regardless if he's logged in. When the visitor submits the form with attachment, etc., it will then check if the user is logged in. If he isn't, then redirect him to login/signup page, and when he's completed that, only then the new record is saved.
I understand this can be achieved with the create and save, but can someone elaborate more on how to achieve this in a clearer explanation?
User submits form
Controller sees user is not logged in
Controller persists submitted form to the database
Controller sets cookie with id of new object
Controller redirects to login
User logs in
Controller retrieves persisted form information from database
Controller assigns object to user
Run a periodic job to clean old unclaimed form data.
Well, in the view you'd show the form always without any kind of login check. The tricky part here is to save the data the user has sent before he's redirected to the login.
One solution would be to save the post data in a cookie and then redirect the user to the same page after the login, triggering the create again
On the controller you'd do exactly as you described:
class ThingController
def create
if user_logged_in?
if cookie[:stuff]
# create the stuff from cookie and remove it
else
# create the stuff
end
else
# redirect him to login with a callback to this same place
end
end
end

Resources