How do you make sure that after sign in the user completes the profile form first before they can use the rest of the website functions. - ruby-on-rails

How do you make sure that after sign in the user completes the profile form first before they can use the rest of the website functions. I am trying to make sure that after the member has completed the sign up form and then completes there email confirmation with devise that when they sign in that when they are redirected to the new_member_profile_path(current_member) form that they stay on this page and that if they decide to go to a link and click that that they will automatically be redirected back to the complete your profile page with the notice before please complete your profile first. I have it set already once they have completed the form they will be redirected to their member's page. I have looked in multi-forms with wicked - I really feel that because I am still am a Novice rails developer that this would be unnecessary. I am thinking about putting an if clause in the application.html.erb where the site nav template is based and putting a clause with <% if current_member_profile.blank ? %> then redirect back to new_member_profile_path(current_member) with a flash notice tag written in the html file. I have tried this if clause but does not work - comes up as undefined method. Please could someone point me in the right direction or give me the simple solution of getting this idea to work. Thanks in advance ;)

There are various approaches to achieve what you're trying to do. Perhaps the cleanest with the least amount of code needed would be to first authenticate the user with Devise's own authenticate_user! filter and then check for a field that can only be there when the profile has been filled in.
# in user.rb
def has_completed_profile?
first_name.present?
end
# in application_controller.rb
before_action :authenticate_user!
before_action :require_user_profile
private
def require_user_profile
# nothing needs to be done if the profile was already filled in
return if user_signed_in? && current_user.has_completed_profile?
redirect_to edit_profile_url, alert: "Please complete your profile first!"
return false
end
Notice how I've extracted has_completed_profile? into the User model instead of putting the name check directly into the controller. This way, if you need to change the logic of what makes a profile "complete", you don't need to touch the controller at all ("complete profile" is a business concept, not a routing/HTTP concept and thus doesn't belong in the controller).
In controllers where you don't want the additional profile check – e.g. the controller where the user actually completes their profile, where they presumably need to be logged in but can't have a profile yet – you just skip the additional filter:
# in profiles_controller.rb
skip_before_action :require_user_profile
Side note: Over the years I've learned that it's best to keep things like profile data, address data, phone numbers and what not in a separate model and don't extend Devise's User model. This prevents various issues and keeps the already huge User model (Devise includes dozens of methods into it and turns it into a God Object as it is) a bit slimmer. And if you think about it, it also makes sense in terms of business logic: A user has_one profile, has_one address (or has_many addresses) etc.
Hope that's clear.

You can add a new method in the application_controller.rb
For eg
def current_member_profile
current_user.name.blank?
end
Check the params which should not be blank when creating a member profile. I have added 'name' here for example. if the params is blank, then it will redirect as you have specified in your application.html.erb

Instead for putting an if condition on the application.html.erb, You can use a before_action in your application_controller.
Something like this.
application_controller.rb:
before_action :check_for_profile_completion
def check_for_profile_completion
// your code of redirection to the page if the profile is incomplete
end
Also you can skip this action on controller which you don't wanna restrict user to go. Like
skip_before_action :check_for_profile_completion, only: [://actions you wanna skip seperated by comma]

Related

In rails 4.2, how to display a form for preview but ensure it cannot be submitted

I'd like to have a a form view that can, depending on circumstances, have submit functionality disabled in a bullet-proof way so that even a clever user could not edit the HTML source (via a browser extension) to re-add the submit button.
It seems one way to do that might be to somehow inject an invalid authenticity token that replaces the (valid) rails-generated one, so that even if a user somehow re-adds the submit button (by editing the HTML via a browser extension) it would still be an invalid submission.
My thought is to have some logic in the view:
- if #form_disabled # set by controller
- somehow_invalidate_the_authenticity_token?
How might one 'break' Rails form submission?
The purpose of doing this, instead of rendering the preview in a :show action, is to have the exact same view displaying both the live-form and the dead-form.
If I were you, I would use pundit.
It's pretty simple, and has few lines of code if you need to know how it works.
I'd start to write the code here, but I realize that the example at the readme fit your needs.
At the application controller add this
At the folder app/policies put the class PostPolicy, of course, you must replace "Post" with the name of your controller in singular (even if you have not a model with that name). The update? (and create?) actions should return true/false to indicate if user is allowed or not.
A few lines down on the readme, you will find the PostsController#update action, which call to authorize with the record before the update. I think you want do the same with create (then you need a create? method at the policy class).
Pundit needs current_user controller method, if you don't have it. Just follow the user customization instructions.
Of course, new and edit actions don't call authorize because they are allowed to everybody. Only the POST & the PUT/PATCH actions are forbidden.
Yes, it's more than a surgery of one line of code. But it's simple and the right way of give access to users.
After reading my other answer, I start thinking that you can do the same that Pundit does at the controller:
def update
if <unauthorized user>
flash[:alert] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
else
# all the update stuff
# ...
end
end

how to redirect the user to their own page (ROR and Devise Gem)

ok, bear with me here, please.
i'm asking a question in which i'm not fully conversant of all the technical details, etc. (in other words, please take it easy on me for not being fully up on the par).
i've created a ROR application with Devise's gem ... it allows you to sign in, sign up, and log out.
it's very simple, and it just has a page in which you fill in your name, favorite hobby, & book. (hey, it's just an example)
i've also used their redirect code to go this page:
def after_sign_in_path_for(resource)
posts_path
end
the thing is that ... this page is public to everyone who logs in.
how do i redirect the user to their own page? and not see everyone's else "notes."
i've looked around ... and Devise has a link on sessions_controllers ...
do i use that? i don't fully understand the purpose of it.
if yes, how? still trying to figure out how all the puzzle pieces fit together, and it's hard to do that when you don't have full understanding of the topic itself.
or is there another way that's simpler?
if this question is too broad, please let me know ... but i'm not really quite sure how to break it down.
in essence, what i'm really asking is how do i have a user be directed to their own page and not see anyone else's page.
thanks in advance for any help/advice/pointers/etc.
First, let's assume your User model has_many :posts. In your PostsController index action (the default RESTful route would be posts_path) you can put something like this:
#posts = current_user.posts
and when visiting the index view you will only see the current user's posts.
That being said, you are not limited to the index view. You can customize any view you'd like and have a corresponding Controller action with declared instance variables which you can manipulate in your views.
Does that makes sense?
Try with:
def after_sign_in_path_for(user) # note: user or w/e your model is named
users_path(current_user)
end

Rails + Devise: How to restrict a user from editing records that do not belong to him

I'm using Rails, Devise and Mongoid.
A user can only have one project (has_one :profile) but all users (and non authenticated users) can see a list of projects (I have this working). When a user is logged in they see "edit and delete" buttons next to the projects (via wrapping those buttons in <% if user_signed_in? %>). However, a signed in user sees these buttons next to all project and can edit or delete them all.
How do I restrict a logged on user to only be able to edit only his project?
As a bonus, is it possible to show specific content or html around the project that belongs to the signed in user (such as text that says "this is your project" or an additional class around the project's html)?
CanCan is great when you have multiple users being able to modify the same resource, and you need to keep track of who has which permissions. But if it's strictly the case that only the user who owns the project can modify it, CanCan is probably overkill.
Hide the links in the view as suggested by iouri, and then in your controller do something like:
def edit
if current_user != #project.user
# Redirect them to an error page
else
# Render the view
end
end
Better yet, create a method like:
def user_owns_project?
#project.user == current_user
end
Then set up a before filter for edit, update and destroy to redirect to the error page if the user doesn't own the project.
EDIT: Your before filter will also ned to find the project and set #project. CanCan takes care of this for you too with load_and_authorize_resource, but I'd still avoid using it unless you need, or expect to need, fine-grained permissions control.
Devise is to control "authentication", this should not to be your responsibility.
You want to control "authorizations", for that CanCan is better.

rails nested devise redirect

i have an app with nested resources. my routes are:
resources :teams do
resources :blogs
end
in my blogs controller, im using a different layout, by adding
layout "teamlayout"
to the controller.
Both layouts, the application.html.erb and the teamlayout.html.erb have included a login form itself. which i made working by this: https://github.com/plataformatec/devise/wiki/How-To:-Display-a-custom-sign_in-form-anywhere-in-your-app
now my question. when a user logs in, i want him redirected to the page from where he logs in.
You have a couple options:
Include a hidden field value in the login form that describes the source of the login (e.g. hidden_field_tag(:login_source, "team")) and define your own logic for SessionsController#create that uses the hidden field value to determine the location for response_with.
Or, you could keep track of the user's location by using a before_filter in the controllers with login forms by doing something like
def store_location
session['saved_location'] = request.request_uri
end
Then, you can override the after_sign_in_path_for(resource) method in your application controller to use the session saved_location value to determine where to redirect.
The second option seems a little less invasive to the Devise infrastructure to me, but is a little less flexible.

Preventing discoverability in a RESTfully routed model

I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.

Resources