How do I use a Twitter username as my user id? - ruby-on-rails

I'm running a rails 4 app using Omniauth with Twitter. I'm trying to achieve something close to producthunt.com, where they authenticate users and use their Twitter username as their url id.

From what I understand, you want the url to look like this: example.com/users/username
instead of example.com/users/123
If so, all you have to do is change the way you find the de correct user in your Users (or whatever you call your user model) controller. Currently it probably looks like this:
#Users Controller
def show
#user = User.find(params[:id])
end
# Your path to that user is probably this:
user_path(123) #basically you pass the user.id
The code above is using Model.find(#) to look for the user. The .find() looks the user up by its id#. Instead, you want to find it by the username, not id. To do this use the Model.find_by You can see all the ways of querying here.
Also, whenever you look for the path to find the user show page, istead of sending the id # to the url, you now have to send the username string.
Your new setup should look like this:
#Users Controller
def show
#user = User.find_by :username params[:id]
#this assumes you have it in your DB as username. Some twitter apps save it as screen_name.
end
# Your path to that user is probably this:
user_path('username') #basically you pass the username instead. current_user.username? I dont know what you call in in your app.
Hope that helps. Let me know if you have questions.

Related

How to send a variable from an action without using the URL in Rails?

How to send a variable/parameter from an action without using the URL?
I need to create a user in multiple steps, and the user is created on the 2nd step. I need to receive the email of the user from the first step.
One first step, I receive the email in params, and use this line of code: redirect_to new_user_registration_path(email: params[:email]) to send it out to the next page/action.
For some reasons, I have been told that I can't use emails in URLs, so now I need to send the email under the hood which surely is possible through the POST method, but redirect_to doesn't support POSTs requests.
There could be a suggestion of using render instead of redirect_to, but I'm using Devise, so I would like to hand over the functionality to Devise, instead of manually doing it all by myself.
There is another idea of using cookies to store the email address, but I'm interested in more of a Rails way.
There can be another way too, one way is to using session
On the first step of form submission store email in session variable and use it on the further step and after that clear that session variable.
Eg -
def first_step
#user = User.new
end
def second_step
# Assuming after first step form is being submitted to second step
session[:email] = params[:email]
end
def next_step
user_email = session[:email]
end
Hereby session[:email] will be available everywhere except model layer unless it is set to blank (session[:email] = nil), that should be set to blank after the user is created.
You can use the flash for this
flash[:email] = params[:email]
redirect_to new_user_registration_path
in your view, something like this
<%= hidden_field_tag :email, flash[:email]
You will need to add this line
class ApplicationController < ActionController::Base
add_flash_types :email
I'm just posting this as a possible solution, I realize this is not some best-practice solution for every case

Getting a username from url with params

I seem to find it difficult to get the username from a url with params, but works when finding IDs.
To get the id from this url: www.foo.com/users/1/post/new, you'd use:
User.find(params[:id]) # same as User.find(1)
I thought I could do the same, based on the docs, for: www.foo.com/users/joe/post/new
User.find_by(name: params[:name])
How to grab the user's name from url with params using the find or find_by method? Thanks
In your User model, override the to_param method like this:
def to_param
name
end
With this code, you're overriding the ActiveRecord default behaviour, so when you link to a User, it will use the name for the parameter instead of id.
Also, I recommend you to take a look at the friendly_id gem as well, using which you can achieve this goal.

Is there a way to rename/override a file in CarrierWave to the username of the user?

I have an avatar for each user, and the username is always going to be unique. This is why I wanted to rename the image to reflect the username of the person uploading said picture to make for easier fetching and solve any problems with image names clashing.
However, I'm having problems accessing the username of the person in my app/uploader/avatar_uploader.rb file. I had something along the lines of this.
def filename
if original_filename
#user = User.find_by_username(params[:id])
"#{#user.username}.#{file.extension}"
end
end
I'm getting errors here that the params doesn't exist, and without that, the method username does not exist.
Overriding it to something such as "foo.jpg" does work and the file is successfully uploading, I'm just wondering how to access the username of the user uploading the file.
params[:id] is nil unless you pass it with the request or embed in the route. But I don't recommend this way, because a user can change it to pass any id.
You should get the user who`s uploading the file from the session, for example using devise
#user = current_user
or if you have stored user_id in session:
#user = User.find(session[:user_id])
EDIT
The above code works only in controllers, that is, you need to get the user in the controller and pass to your uploader method:
def filename(user)

Rails routes - using associated model attribute as route root

I have a user that signs up and logs in.
Right now their route is rails standard "user/3".
A user belongs to an organization. An org has many users.
I want all users for that org, when they sign in, to have the url http://mysite.com/:organization name.
How would I accomplish this?
I'm not sure how your authentication is setup or what you're using to handle your authentication, but presumably when a user logs in successfully you just change the redirection in your log in action. Something like:
redirect_to user.organization
instead of redirect_to user
You need to browse on nested resources. Please look at http://guides.rubyonrails.org/routing.html#nested-resources
UPDATE: more appropriate answer
I think this is more for your scenario http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html#method-i-scope
You have to do something like this...
First add a method to your organization model to show name instead of id in url
def to_param
name
end
and update your routes as required, and functionality also to show the users of that organization
Organization Controller..
def show
#organization = Organization.find(params[:organization])
#users = #organization.users
end
Redirect user to user organization show path after login.

Ruby on Rails security vulnerability with user enumeration via id

With Ruby on Rails, my models are being created with increasing unique ids. For example, the first user has a user id of 1, the second 2, the third 3.
This is not good from a security perspective because if someone can snoop on the user id of the last created user (perhaps by creating a new user), they can infer your growth rate. They can also easily guess user ids.
Is there a good way to use random ids instead?
What have people done about this? Google search doesn't reveal much of anything.
I do not consider exposing user IDs to public as a security flaw, there should be other mechanisms for security. Maybe it is a "marketing security flaw" when visitors find out you do not have that million users they promise ;-)
Anyway:
To avoid IDs in urls at all you can use the user's login in all places. Make sure the login does not contain some special characters (./\#? etc.), that cause problems in routes (use a whitelist regex). Also login names may not be changed later, that can cause trouble if you have hard links/search engine entries to your pages.
Example calls are /users/Jeff and /users/Jeff/edit instead of /users/522047 and /users/522047/edit.
In your user class you need to override the to_param to use the login for routes instead of the user's id. This way there is no need to replace anything in your routes file nor in helpers like link_to #user.
class User < ActiveRecord::Base
def to_param
self.login
end
end
Then in every controller replace User.find by User.find_by_login:
class UsersController < ApplicationController
def show
#user = User.find_by_login(params[:id])
end
end
Or use a before_filter to replace the params before. For other controllers with nested resources use params[:user_id]:
class UsersController < ApplicationController
before_filter :get_id_from_login
def show
#user = User.find(params[:id])
end
private
# As users are not called by +id+ but by +login+ here is a function
# that converts a params[:id] containing an alphanumeric login to a
# params[:id] with a numeric id
def get_id_from_login
user = User.find_by_login(params[:id])
params[:id] = user.id unless user.nil?
end
end
Even if you would generate random INTEGER id it also can be compromted very easy. You should generate a random token for each user like MD5 or SHA1 ("asd342gdfg4534dfgdf"), then it would help you. And you should link to user profile with this random hash.
Note, this is not actually the hash concept, it just a random string.
Another way is to link to user with their nick, for example.
However, my guess is knowing the users ID or users count or users growth rate is not a vulnerability itself!
Add a field called random_id or whatever you want to your User model. Then when creating a user, place this code in your UsersController:
def create
...
user.random_id = User.generate_random_id
user.save
end
And place this code in your User class:
# random_id will contain capital letters and numbers only
def self.generate_random_id(size = 8)
alphanumerics = ('0'..'9').to_a + ('A'..'Z').to_a
key = (0..size).map {alphanumerics[Kernel.rand(36)]}.join
# if random_id exists in database, regenerate key
key = generate_random_id(size) if User.find_by_random_id(key)
# output the key
return key
end
If you need lowercase letters too, add them to alphanumerics and make sure you get the correct random number from the kernel, i.e. Kernel.rand(62).
Also be sure to modify your routes and other controllers to utilize the random_id instead of the default id.
You need to add a proper authorization layer to prevent un-authorized access.
Let us say you you display the user information in show action of the Users controller and the code is as shown below:
class UsersController < ActionController::Base
before_filter :require_user
def show
#user = User.find(params[:id])
end
end
This implementation is vulnerable to id guessing. You can easily fix it by ensuring that show action always shows the information of the logged in user:
def show
#user = current_user
end
Now regardless of what id is given in the URL you will display the current users profile.
Let us say that we want to allow account admin and account owner to access the show action:
def show
#user = current_user.has_role?(:admin) ? User.find(params[:id]) : current_user
end
OTH authorization logic is better implemented using a gem like CanCan.

Resources