The app from last week is functional but has huge security issues! Let's fix them in our extended lab for this week.
First, find a partner! You and your partner will alternate between questions. Pretend it's your actual project by branching, commititng, making a pull request, and having your partner merge it in before starting on the next question.
Designate one person as person A, and the other as person B. Have person A fork this repo on Github, go to Settings -> Collaborators, and add person B to the repo.
Person A: Mandy Huang
Person B: Timothy Tran
Now both partners should run
git clone https://github.com/<your username>/lab9
to start working on the app. There is a written portion of this lab, so write
down your answers in lab9.txt and submit them through the form!
-
Make your first pull requests! Both teammates should edit the README.md file so that the blank "Person A:" lines have the person's name. At the end the two blank lines above should look like:
Person A: Sam Lau
Person B: Jessica Lin
Start with
git checkout -b <readme-A or readme-B>. The-boption togit checkouttells git to make a new branch and switch to it right away. Then make your changes, add, commit, and push to person A's Github. Make a pull request for your changes, and have the other person merge it into master. Congratulations! You just made your first pull request. -
(Person A codes) Remember how we included devise in step 2 of the lecture app? We never actually had the user sign in at any point! Since devise is already set up we should be able to log in. Test it out by first finding the route that ends in
/sign_inusingrake routesand visiting it in your browser.a. What happens when you log in? What happens if you try to log in again? Does it work as you expect?
Let's now add log in and log out links to the navbar so it's easier to see when we're logged in. Devise has some helpers that allow you to check if a user is signed in or not, we'll have to use them now.
b. What is the helper that devise creates that checks if a user is logged in or not?
Add two
<li>elements below the existing link to 'Users' inapplication.html.erbthat link to the sign in/out paths depending on if the user is signed in (only one of those li elements will be visible at any time to the user). You should add 5-6 lines of extra code. -
(Person B codes) So now we have logging in and out. Now we want to make sure that if a user is not signed in that we only allow them to view the sign in page - all pages should redirect to the sign in page if there isn't a signed in user.
a. How would you go about implementing this with the knowledge you have now? (If you know about
before_filteryou can't use it - that's coming up.)Luckily Rails provides a handy
before_filtermethod! It takes in a procedure name as a symbol as an argument and runs that procedure before any of the actions in the controller are hit (if you want to limit the methods that it runs before,before_filtertakes in another argument,except. See the docs for more info.) which is exactly what we need! Devise also has a handy method to redirect someone to the log in page if they aren't logged in; how convenient. Write one line of code inapplication_controller.rbthat enforces a signed in user. Hint: you should probably check the devise docs again.b. Why does that line of code work for all actions in all controllers?
-
(Person A codes) As you may have noticed, the app isn't very informative when you try to visit a page without signing in: it just throws you back to the sign in page without any explanation. And, if you log in with bad credentials nothing shows up to tell you you messed up. It turns out that devise is trying to tell the user that something messed up but you aren't showing it! Stick a
<%= 1/0 %>somewhere inapplication.html.erband try visiting the home page while not logged in.a. In the console in the error page, check the value of
flash. What do you notice about it?In general, we use
flashto store temporary messages we want to show the user only once. We've actually used it inquits_controller.rbto tell the user that a quit was updated.flashis a hash where the key is the type of message and the value is the message. Devise doesflash[:alert] = '<some message>'when you try to visit a page while not logged in, so let's show the user that message.Write 3 lines of code right above the
yieldstatement inapplication.html.erbthat loops through each key-value pair inflashand outputs the following code wherekeyis the key andvalueof each thing inflash:<%= content_tag(:div, value, class: "alert alert-#{key}") %>You should now see pretty messages when you log in, update a quit, and more!
-
(Person B codes) Remember how we allowed
user_idto pass through in the params and how it caused problems in the past? In fact, the way we implemented quit creation in general isn't ideal since we're forced to pass the user_id through the form. Since we're always going to create/update quits in the context of a specific user, it makes more sense to route our app to make this easier.a. Check the Rails Guides for something related to routes that are made for a belongs_to/has_many relationship. What was the section title?
Change
routes.rbso that quits are nested under users. To verify that your answer is on the right track, the URL for a new quit should look like/users/:user_id/quits/newnow (all the other quit routes will be changed too). You should have to modify/add very few lines of code, 2-3 at max.b. A lot of routes will now be broken! Why?
Update the routes everywhere in the controllers/views so that they use the new routes which you just created. This is a difficult question since it involves multiple views/controllers! Start by changing all the old routes to new ones. If you're not sure if the routes work or not, you can always check them with a
1/0and using the console that better errors starts.Hint: just like how we could do
edit_quit_path(@quit)before, now we can doedit_user_quit_path(@user, @quit)given that @user and @quit are defined.Hint 2: Notice that a lot of routes have
:user_id. That means you can get the user related to that URL withUser.find params[:user_id]and not have to worry about setting it manually in your routes.Hint 3: You'll get an error of 'undefined method `quit_path'' when you try to render the form view for a quit. To fix this, use
simple_form_for [@user, @quit], .... Of course, this only works if @user and @quit are not nil.Hint 4: You'll make changes to 1 line in each view in the
quitsfolder, and 2 line changes inusers/show.html.erb. Inquits_controller.rb, you only have to add one line to theeditaction and a one line addition + one line modification in thecreateaction.Now you should be able to stop permitting
:user_idin thequit_paramsmethod ofquits_controller.rb. Verify that all actions still work correctly. -
(Person A codes) So we have some basic authorization going on since we require a user to sign in before viewing quits. But, a logged in user can still create/edit quits for other people! Verify this is true. Make changes to
quits_controller.rbso that a person can only create/modify quits that belong to him. If a person tries to create/change a quit that belongs to another person, redirect hiim to theroot_pathwith an alert message in flash"Can't create/edit a quit for another person!".a. Why don't we need to make changes to
application_controller.rb?b. Why don't we need to make changes to
users_controller.rb? -
(Person B codes) Now, if we have administrators, they should be able to create and edit quits at will. First, we'll have to designate some users as administrators. Create a migration that adds the
:adminattribute to users, and default it to false. After migration, create one admin in your seeds. You can now do something likeUser.first.admin?, which returns true or false. Then make the necessary changes so that users who are admins can create and edit quits of all users.
You made it! Submit the written portion of the lab here.