From 7bf4d4c5f9d3fbe19e59d37277cc86c0419e9923 Mon Sep 17 00:00:00 2001 From: Thibault Jouan Date: Thu, 4 Aug 2011 09:50:17 +0000 Subject: [PATCH] Add authentication and User model * Add User model * Add SessionsController * Add password authentication on User * Request authentication for all actions except sign in * Add some helpers for ApplicationController * Update features to work with mandatory authentication --- app/controllers/application_controller.rb | 16 +++++++ app/controllers/sessions_controller.rb | 16 +++++++ app/models/session.rb | 2 + app/models/user.rb | 11 +++++ app/views/sessions/new.html.haml | 6 +++ config/routes.rb | 2 + db/migrate/20110805201426_create_users.rb | 14 ++++++ db/schema.rb | 9 +++- features/home.feature | 3 ++ features/playlists.feature | 3 ++ features/sessions.feature | 17 +++++++ features/step_definitions/sessions_step.rb | 26 +++++++++++ features/step_definitions/tracks_step.rb | 3 +- features/tracks.feature | 3 ++ .../application_controller_spec.rb | 21 +++++++++ spec/controllers/home_controller_spec.rb | 4 ++ spec/controllers/playlists_controller_spec.rb | 4 ++ spec/controllers/sessions_controller_spec.rb | 40 +++++++++++++++++ spec/controllers/tracks_controller_spec.rb | 4 ++ spec/factories.rb | 5 +++ spec/models/user_spec.rb | 44 +++++++++++++++++++ spec/views/sessions/new.html.haml_spec.rb | 26 +++++++++++ 22 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 app/controllers/sessions_controller.rb create mode 100644 app/models/session.rb create mode 100644 app/models/user.rb create mode 100644 app/views/sessions/new.html.haml create mode 100644 db/migrate/20110805201426_create_users.rb create mode 100644 features/sessions.feature create mode 100644 features/step_definitions/sessions_step.rb create mode 100644 spec/controllers/application_controller_spec.rb create mode 100644 spec/controllers/sessions_controller_spec.rb create mode 100644 spec/models/user_spec.rb create mode 100644 spec/views/sessions/new.html.haml_spec.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e8065d9..5b08613 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,19 @@ class ApplicationController < ActionController::Base protect_from_forgery + + before_filter :authenticate! + + def current_user=(user) + session[:user_id] = user.id + end + + def current_user + @current_user ||= User.find(session[:user_id]) if session[:user_id] + end + + protected + + def authenticate! + redirect_to new_session_path if current_user.nil? + end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..4d75d5e --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,16 @@ +class SessionsController < ApplicationController + skip_before_filter :authenticate!, :only => [:new, :create] + + def create + user = User.authenticate( + params[:session][:email], + params[:session][:password] + ) + if ! user + render 'new' + else + self.current_user = user + redirect_to :root + end + end +end diff --git a/app/models/session.rb b/app/models/session.rb new file mode 100644 index 0000000..54fee45 --- /dev/null +++ b/app/models/session.rb @@ -0,0 +1,2 @@ +class Session < ActiveRecord::Base +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..19f6cc3 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,11 @@ +class User < ActiveRecord::Base + validates_presence_of :email + validates_presence_of :password + + def self.authenticate(email, password) + user = find_by_email(email) + return false if user.nil? + #FIXME use bcrypt + return user if user.password == password + end +end diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml new file mode 100644 index 0000000..d6fe311 --- /dev/null +++ b/app/views/sessions/new.html.haml @@ -0,0 +1,6 @@ += form_for(:session, :url => sessions_path) do |f| + = f.label :email + = f.text_field :email + = f.label :password + = f.password_field :password + = f.submit 'Sign in' diff --git a/config/routes.rb b/config/routes.rb index 5c96b51..206586f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Scube::Application.routes.draw do + resources :sessions, :only => [:new, :create] + resources :tracks do get 'stream', :on => :member end diff --git a/db/migrate/20110805201426_create_users.rb b/db/migrate/20110805201426_create_users.rb new file mode 100644 index 0000000..8552060 --- /dev/null +++ b/db/migrate/20110805201426_create_users.rb @@ -0,0 +1,14 @@ +class CreateUsers < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.string :email + t.string :password + + t.timestamps + end + end + + def self.down + drop_table :users + end +end diff --git a/db/schema.rb b/db/schema.rb index 7ea3b43..d895941 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110804225816) do +ActiveRecord::Schema.define(:version => 20110805201426) do create_table "playlists", :force => true do |t| t.string "name" @@ -36,4 +36,11 @@ ActiveRecord::Schema.define(:version => 20110804225816) do t.string "sha256" end + create_table "users", :force => true do |t| + t.string "email" + t.string "password" + t.datetime "created_at" + t.datetime "updated_at" + end + end diff --git a/features/home.feature b/features/home.feature index 4d49101..36392e2 100644 --- a/features/home.feature +++ b/features/home.feature @@ -4,6 +4,9 @@ Feature: Home As a listener I want to access the main features and valuable content from the homepage + Background: + Given I am signed in + Scenario: Playlist access Given a playlist named "Electro" When I am on the home page diff --git a/features/playlists.feature b/features/playlists.feature index 2c82f4e..92dedcb 100644 --- a/features/playlists.feature +++ b/features/playlists.feature @@ -4,6 +4,9 @@ Feature: Playlists As a listener I want to manage some playlists + Background: + Given I am signed in + Scenario: List playlists Given a playlist named "Electro" And a playlist named "Reggae" diff --git a/features/sessions.feature b/features/sessions.feature new file mode 100644 index 0000000..765973e --- /dev/null +++ b/features/sessions.feature @@ -0,0 +1,17 @@ +Feature: User + + So that I can manage my own content + As a listener + I want the application to require a valid authentication + + Scenario: Unauthenticated user + Given I am not signed in + When I go to the home page + Then I should be redirected to the sign in page + + Scenario: User authentication + Given I am not signed in + When I go to the home page + Then I should be redirected to the sign in page + When I submit valid credentials + Then I should be redirected to the home page diff --git a/features/step_definitions/sessions_step.rb b/features/step_definitions/sessions_step.rb new file mode 100644 index 0000000..6f0ff50 --- /dev/null +++ b/features/step_definitions/sessions_step.rb @@ -0,0 +1,26 @@ +Given /^I am not signed in$/ do + +end + +Given /^I am signed in$/ do + user = Factory.create(:user) + visit new_session_path + fill_in('Email', :with => user.email) + fill_in('Password', :with => user.password) + click_button('Sign in') +end + +Then /^I should be redirected to the sign in page$/ do + current_path.should == new_session_path +end + +Then /^I should be redirected to the home page$/ do + current_path.should == root_path +end + +When /^I submit valid credentials$/ do + user = Factory.create(:user) + fill_in('Email', :with => user.email) + fill_in('Password', :with => user.password) + click_button('Sign in') +end diff --git a/features/step_definitions/tracks_step.rb b/features/step_definitions/tracks_step.rb index ffd722f..74a3ee6 100644 --- a/features/step_definitions/tracks_step.rb +++ b/features/step_definitions/tracks_step.rb @@ -16,6 +16,5 @@ end Then /^it should provide an audio stream for "([^"]*)"$/ do |name| page.should have_xpath "//audio[@src='#{stream_track_path(@track)}']" - get find('audio')[:src] - last_response.status.should == 200 + visit find('audio')[:src] end diff --git a/features/tracks.feature b/features/tracks.feature index 08741d8..72642a6 100644 --- a/features/tracks.feature +++ b/features/tracks.feature @@ -4,6 +4,9 @@ Feature: Tracks As a listener I want to add, manage and listen some tracks + Background: + Given I am signed in + Scenario: Show track Given a track named "Mega song" When I go to the track page for "Mega song" diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb new file mode 100644 index 0000000..18946ba --- /dev/null +++ b/spec/controllers/application_controller_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe ApplicationController do + let(:user) { Factory.create(:user) } + + describe '#current_user=' do + it 'stores the user id in the session as :user_id' do + controller.current_user = user + session[:user_id].should == user.id + end + end + + describe '#current_user' do + context 'when session[:user_id] is set' do + it 'returns the User instance from the session' do + session[:user_id] = user.id + controller.current_user.should == user + end + end + end +end diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb index c5026da..e2c0bb8 100644 --- a/spec/controllers/home_controller_spec.rb +++ b/spec/controllers/home_controller_spec.rb @@ -1,6 +1,10 @@ require 'spec_helper' describe HomeController do + before do + controller.current_user = Factory.create(:user) + end + describe 'GET index' do it 'assigns all playlists as @playlists' do playlist = Factory.create(:playlist) diff --git a/spec/controllers/playlists_controller_spec.rb b/spec/controllers/playlists_controller_spec.rb index 1ef894c..7ab9ef7 100644 --- a/spec/controllers/playlists_controller_spec.rb +++ b/spec/controllers/playlists_controller_spec.rb @@ -1,6 +1,10 @@ require 'spec_helper' describe PlaylistsController do + before do + controller.current_user = Factory.create(:user) + end + describe 'GET index' do it 'assigns all playlists as @playlists' do playlist = Factory.create(:playlist) diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 0000000..d57c20e --- /dev/null +++ b/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +describe SessionsController do + describe 'GET new' do + it 'responds successfully' do + response.should be_success + end + end + + describe 'POST create' do + context 'when the user submit invalid credentials' do + it 'renders the new template' do + User.stub(:authenticate).and_return(false) + post :create, + :session => Factory.attributes_for(:user, :password => 'WRONG') + response.should render_template('new') + end + end + + context 'when the user submit valid credentials' do + let(:user) { Factory.create(:user) } + before do + User.stub(:authenticate).and_return(user) + end + + it 'signs the user in' do + post :create, :session => Factory.attributes_for(:user) + controller.current_user.should == user + end + + it 'redirects to the home page' do + post :create, :session => Factory.attributes_for(:user) + response.should redirect_to(:root) + end + end + end + + describe 'DELETE destroy' do + end +end diff --git a/spec/controllers/tracks_controller_spec.rb b/spec/controllers/tracks_controller_spec.rb index 95b49cb..77f1bbe 100644 --- a/spec/controllers/tracks_controller_spec.rb +++ b/spec/controllers/tracks_controller_spec.rb @@ -1,6 +1,10 @@ require 'spec_helper' describe TracksController do + before do + controller.current_user = Factory.create(:user) + end + describe 'GET show' do it 'assigns the requested track as @track' do track = Factory.create(:track) diff --git a/spec/factories.rb b/spec/factories.rb index bca6527..c736d0c 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -8,4 +8,9 @@ FactoryGirl.define do mime_type 'audio/ogg' sha256 '94a5486a69a7261da350c57f9e5a1eaa789e08752cfc56a1989976a6ad82f7a8' end + + factory :user do + email 'alice@example.net' + password '733tP4s5' + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..027aae6 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe User do + subject { user } + let(:user) { Factory.build(:user) } + + context 'with valid attributes' do + it { should be_valid } + end + + context 'when email empty' do + before do + user.email = '' + end + + it { should_not be_valid } + end + + context 'when password empty' do + before do + user.password = '' + end + + it { should_not be_valid } + end + + describe '.authenticate' do + let (:user) { Factory.create(:user) } + + it 'returns the user with valid credentials' do + User.authenticate( + user.email, + user.password + ).should == user + end + + it 'returns false with invalid credentials' do + User.authenticate( + user.email, + 'WRONG' + ).should be_false + end + end +end diff --git a/spec/views/sessions/new.html.haml_spec.rb b/spec/views/sessions/new.html.haml_spec.rb new file mode 100644 index 0000000..0b4cffd --- /dev/null +++ b/spec/views/sessions/new.html.haml_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe 'sessions/new.html.haml' do + it 'renders a form to sign in' do + render + rendered.should \ + have_selector("form[method=post][action='#{sessions_path}']") + rendered.should have_selector('input[type=submit]') + end + + it 'renders a text field with a label for the mail address' do + render + rendered.should \ + have_selector("input[type=text][name='session[email]']") + rendered.should \ + have_selector('label[for=session_email]', :text => 'Email') + end + + it 'renders a password field with a label for the password' do + render + rendered.should \ + have_selector("input[type=password][name='session[password]']") + rendered.should \ + have_selector('label[for=session_password]', :text => 'Password') + end +end