Differentiate between multiple registration pages for same user model - ruby-on-rails

I have a fairly vanilla registration system for my Rails 4 app, and I'd like to add a strange feature. I'd like to provide multiple registration forms, and secretly set a value on the new User object, based on which page the data is being submitted from.
My current way of doing it is bad for security reasons. It works fine, but a savvy user could use a browser's dev tools to change the value of the hidden field and indicate that they came in from a different page.
routes.rb
get '/signup', to: 'users#new'
get '/red-signup', to: 'users#new', color: 'red'
get '/blue-signup', to: 'users#new', color: 'blue'
users_controller.rb
def new
#user = User.new
#color = params[:color] || 'grey' # default of grey
end
users/new.html.erb
<%= form_for(#user) do |f| %>
...
<%= f.hidden_field :color, value: #color %> # VULNERABLE TO TAMPERING
<%= f.submit "Create my account" %>
<% end %>
So, I'd like a tamper-proof way to differentiate between new registrations that came from different pages. I assume that means keeping any signifying tokens out of the form data.
Any suggestions for an approach to explore?

If your aim is to pass the #color value safely between new and create action, you can use the session for this purpose. Inside your new action you set the color:
def new
#user = User.new
session[:color] = params[:color] || 'grey' # default of grey
end
Then in the create action you can retrieve it:
def create
#color = session[:color]
end
Rails session data is stored in Cookies by default and is cryptographically signed to make it tamper-proof. It is also encrypted so anyone with access to it can't read its contents.

What is the final purpose?
If it is to know which registration forms has a better conversion rate, you are reinventing the wheel and you should have a look on A/B testing and those gems for doing it right.

Related

Instance variables are purged when user fails his authentication

I have trouble getting instances variables while overriding Devise::SessionsController.
When an unauthenticated user add an item to its cart, I want to redirect him to the sign in page and apply the addition after he's authenticated.
1 . I pass the item_id as a URL parameters:
localhost:3000/user_accounts/sign_in?item_id=42
I get back in a hidden field in the form, I can have it through the session creation form:
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.hidden_field :item_id, value: params[:item_id] || #item_id %>
...
<% end %>
When Rails starts his form flow, the URL parameters are removed:
Before:
localhost:3000/user_accounts/sign_in?item_id=42
After:
localhost:3000/user_accounts/sign_in
I don't want to loose the item_id when the user fails his authentication, so I ensure #item_id is exposed as an instance variable, so I can inject it back to the form.
However, on failure, Rails(?) / Devise(?) / Warden(?) seems to purge the instance variables (for security reasons?). As a result, #item_id is nil.
Note that works well when the user successfully authenticate (through URL parameters). Also, I use this very same technique in the registration process and it works in both success (URL parameters) and failure (instances variables) case.
Here is my implementation of SessionsController:
class MySessionsController < Devise::SessionsController
def create
#item_id = parse_id(params[:user_account][:item])
super do |account|
add_to_cart(account, #item_id) if #item_id
end
flash.delete(:notice)
end
end
Do you know how I can fix this ?
I am open to alternatives as well.
I was wrong. Rails, Devise and Warden aren't doing shady things to my instance variables. I found out that a redirection was made, explaining why the context was lost.
In the end, I used cookies to fix the problem as #nattfodd suggested it. This was trivial I don't know why I didn't think of it.
class MySessionsController < Devise::SessionsController
def create
item_id = parse_id(cookies.encrypted[:item])
super do |account|
add_to_cart(account, item_id) if item_id
cookies.encrypted[:item] = ""
end
flash.delete(:notice)
end
end
Cleaner, no hack. Good.

create edit URL route that doesn't show :id

I'm in the midst of trying to clean up my routing. I have a company model that can log in and create applications. They can create several.
Currently this is my setup:
Routes
get 'applications/edit/:id', to: 'applications#edit'
Applications_controller
def edit
#application = current_company.applications.find(params[:id])
end
def update
#application = Application.find(params[:id])
if #application.update(application_params)
redirect_to dashboard_path
else
render 'edit'
end
end
Each company have their own dashboard. Here's my code from
/dashboard
Your active applications
<% #applications.all.each do |f| %>
<%= link_to "Application", show_path + "/#{f.id}" %> | <%= link_to "Edit", edit_application_path("#{f.id}") %>
<br>
<% end %>
Now this all works, if I go to edit_application/11 f.ex I see it.
The thing I'd like changed is to remove the :id from the URL.
Thus make it more secure and to give a nicer feel. Now it took me 5 minutes before I realised I could just change the :id url and edit everything. Thus I added the current_company.applications to stop that. Yet I don't feel like this is very secure.
If you want to remove the :id, you'll still need a way to find the data you want.
As long as you have the url /edit/12 and as long as you use the id 12 in the GET url to find your content, it will show in the browser bar. The only way to "hide" it (but it's not more secure at all, because it's easily found out), is to use a POST request with a form containing a hidden field with the id (can be made in JavaScript).
You are asking the application to get the id from the link in the #applications.all.each but the only way it can do that is to include it somewhere in the request (be it GET, POST, COOKIES/SESSION, ...).
For another (possibly better) solution, read on.
A very common practice is to use slugs: you create a unique key for each content, for example, if your title is "My great app", the slug will be my-great-app. Thus there is no id in your URL (and it cannot be found out if you always use slugs as references). The advantage is that you'll still find a quick match for what you're searching for (creating an unique index on the slugs).
Some further reading about slugs:
http://rubysnippets.com/2013/02/04/rails-seo-pretty-urls-in-rails/
What is the etymology of 'slug'?

Rails Forms for custom actions

I'm trying to link the input of a form to a specific action in my rails app.
Currently if I go to www.myapp.com/check/:idNumber, I'll be able to trigger the action just fine (which means routes is setup properly?). This action is basically a function call to a ruby/rails script with the parameter "idNumber" being passed to it. If the function is successful, it would return a newly created "Person" object and nil otherwise. This is different than the standard new operation as it determines the the attributes based on some information that it obtained from a database somewhere else.
Rake routes does give me the following:
check /check/:idNumber(.:format) person#check {:id=>/\d+/}
What I'm having trouble implementing is the form itself.
<%= form_tag("/check", :method => "get") do %>
<%= text_field_tag(:idNumber) %>
<% end %>
Controller action:
def check
regCheck = RegCheck.new
#person = regCheck.check_id(params[:idNumber])
if #person.name == nil
redirect_to root_path
end
end
submitting the form above would bring me to myapp.com/check?utf8=✓&idNumber=1234 instead. Can someone tell me what am I doing wrong?
I believe that using the check_path helper that is generated from the routes file is your best bet.
The form should look like this then.
<%= form_tag(check_path) do %>
<%= text_field_tag(:idNumber) %>
<% end %>
Rails forms can be finicky, especially when trying to build really customized forms.
This line
= form_for [#object]
Determines where the form goes, as well as the object that is being implemented. If you want to route the form to a different place, you can user the :url option. This options determines the path of the form, however you must keep in mind that the method is determined by the #object. If it is a new object, the method will be POST, an existing object will use a PUT method.
Let's suppose you want to update an existing object, but you want to send in data for a new object belonging to the existing object. That would look like
= form_for [#object], :as => #child_object, :url => my_optional_custom_path do |f|
# etc...
This generates a form sending a PUT request to the custom url (or the update path for #object if no custom url is supplied. The PUT request is sent with the parameter params[:child_object].
Hopefully this helps!
Best,
-Brian
I don't think it's possible the way you're trying.. The URL for the form is created before the user inputs any data.. So you need to remove the :idNumber from your routing..
If you do you get the following route:
check /check(.:format) person#check
Because the regex is removed now, you need to do this in you're controller:
def check
# Make sure ID is digits only
idNumber = params[:idNumber].gsub(/[^\d]/, '')
regCheck = RegCheck.new
#person = regCheck.check_id(idNumber)
if #person.name == nil
redirect_to root_path
end
end
You're form is allright, but you may want to use check_path like TheBinaryhood suggests..
If you really want it to be check/:idNumber you may also be able to submit the form to another action and redirect it to the right path from there..

Newbie rails database relation question

I have two models Station and Broadcast. Broadcast belongs_to Station and has a station_id column.
I don't know how to make new method in BroadcastController to expect the station_id value and how to create a new Broadcast with right station_id in it.
I'd recommend against using a link_to here. Using a 'get' action to change/add data on the server is generally frowned upon. Also, I wouldn't use the 'new' action, as it's used by convention in a Rails Restful route to present a form, not actually persist data. All that said, the simple answer to your question is to pass a value through the link_to helper like so:
link_to 'Add broadcast', new_broadcast_path(:station_id => station.id)
...and your 'new' method on BroadcastsController would do:
def new
#broadcast = BroadCast.new(:station_id => params[:station_id])
if #broadcast.save
flash[:notice] = "New broadcast created!"
redirect :back # or whatever
else
# etc.
end
end
But, again, this is not the right way to do this. What you probably want to do is stay within the Rails (and web) conventions and use a form to create the new broadcast record by way of the 'create' action on the controller. You might place this form next to your stations on the index view which presents a button that points to the correct 'create' action of BroadcastsController, and uses a hidden_field to set the station_id. Something like (EDIT: better use of hidden_field):
<% form_for :broadcast do |f| %>
<%= f.hidden_field :station_id, :value=> station.id %>
<%= submit_tag 'Add broadcast' %>
<% end %>
Assuming you've set a restful route in routes.rb for broadcast like:
map.resources :broadcasts
...then your form will automatically point to a 'create' action on BroadcastsController, which you should write to look something like:
def create
#broadcast = BroadCast.new(params[:broadcast])
if #broadcast.save
# etc., etc.
end
The Rails Guides are a good place to get more comfortable with the Rails controller actions. I'd also spend some time looking over the RESTful actions and how they're used in Rails as well.
You actually do specify a station_id for your Broadcast model, such as
script/generate scaffold broadcast name:string station_id:integer ...
so when you add a broadcast record, it will ask you for a station_id.

Allowing users to choose custom theme in Rails

I want to give my users the ability to choose how their public page is displayed from 5 different layouts. I assume I'll need 5 different css files according to layout and then need to pass that into stylesheet_link_tag
I know only how to do it using if then statements. I don't suppose that is the best way. Any help...also could it be done?
Thanks
You should store the layout that the user has chosen in the session variable (easiest, but lost when the user clears cookies or uses a different computer), or in your database.
Lets say the stylesheets have five names, each corresponding to a color:
blue_stylesheet.css
green_stylesheet.css
red_stylesheet.css
orange_stylesheet.css
white_stylesheet.css
Place these files inside of public/stylesheets.
Store the user's choice of stylesheet into the session[:style] variable like so:
session[:style] = 'green'
This value will persist for as long as the user does not clear their cookies.
Create an application.erb file in your layouts if one does not already exist. The code in this file will be rendered for every template on your site. It should contain a line like <%= yield %>. In this file place the following:
<%=stylesheet_link_tag session[:style]+'_stylesheet'%>
That's it!
Good luck!
First, try to add 'theme' field to user's model (using migrations).
Then add some links in a view (user's settings):
link_to 'Change to green theme', :controller => "user", :action => "set_theme", :id => "green"
Controller:
def set_theme
# don't forget to check, is there a theme with such params
current_user.update_attributes :theme => params[:id]
end
Public profile's controller:
def public_profile
#theme = 'default'
user = User.find(params[:user_id]) # profile's owner
#theme ||= user.theme # overriding default theme to custom one
end
layout:
<%=stylesheet_link_tag #theme %>

Resources