diff --git a/CHANGELOG.md b/CHANGELOG.md index 24e15520..cce4bf0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## HEAD +* Add a `#login!` helper that raises an exception if the login fails [#332](https://github.com/Sorcery/sorcery/pull/322) * Raise ArgumentError when calling change_password! with blank password [#333](https://github.com/Sorcery/sorcery/pull/333) ## 0.16.4 diff --git a/README.md b/README.md index f5de6ece..7a9511d3 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ explaining and the rest are commented: ```ruby require_login # This is a before action login(email, password, remember_me = false) +login!(email, password, remember_me = false) # Raises a `Sorcery::Errors::InvalidCredentials` exception on failure auto_login(user) # Login without credentials logout logged_in? # Available in views diff --git a/lib/errors.rb b/lib/errors.rb new file mode 100644 index 00000000..0ea75b1f --- /dev/null +++ b/lib/errors.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Sorcery + ## + # Custom error class for rescuing from all Sorcery errors. + # + class Error < StandardError; end + + module Errors + class InvalidCredentials < Sorcery::Error; end + end +end diff --git a/lib/sorcery.rb b/lib/sorcery.rb index 2a0af8cc..fd7474b4 100644 --- a/lib/sorcery.rb +++ b/lib/sorcery.rb @@ -1,4 +1,5 @@ require 'sorcery/version' +require 'errors' module Sorcery require 'sorcery/model' diff --git a/lib/sorcery/controller.rb b/lib/sorcery/controller.rb index f6cda708..237380d4 100644 --- a/lib/sorcery/controller.rb +++ b/lib/sorcery/controller.rb @@ -63,6 +63,14 @@ def login(*credentials) end end + def login!(*credentials, &block) + user = login(*credentials, &block) + + raise Sorcery::Errors::InvalidCredentials if user.nil? + + user + end + def reset_sorcery_session reset_session # protect from session fixation attacks end diff --git a/spec/controllers/controller_spec.rb b/spec/controllers/controller_spec.rb index def1891b..0c2ef242 100644 --- a/spec/controllers/controller_spec.rb +++ b/spec/controllers/controller_spec.rb @@ -85,6 +85,72 @@ end end + describe '#login!' do + context 'when succeeds' do + before do + expect(User).to receive(:authenticate).with('bla@bla.com', 'secret') { |&block| block.call(user, nil) } + get :test_login_bang, params: { email: 'bla@bla.com', password: 'secret' } + end + + it 'assigns user to @user variable' do + expect(assigns[:user]).to eq user + end + + it 'writes user id in session' do + expect(session[:user_id]).to eq user.id.to_s + end + + it 'sets csrf token in session' do + expect(session[:_csrf_token]).not_to be_nil + end + end + + context 'when fails' do + before do + expect(User).to receive(:authenticate).with('bla@bla.com', 'opensesame!').and_return(nil) + end + + it 'raises InvalidCredentials exception' do + expect do + get :test_login_bang, params: { email: 'bla@bla.com', password: 'opensesame!' } + end.to raise_error(Sorcery::Errors::InvalidCredentials) + end + end + end + + describe '#login! with block' do + context 'when succeeds' do + before do + expect(User).to receive(:authenticate).with('bla@bla.com', 'secret') { |&block| block.call(user, nil) } + get :test_login_bang_with_block, params: { email: 'bla@bla.com', password: 'secret' } + end + + it 'writes user id in session' do + expect(session[:user_id]).to eq user.id.to_s + end + + it 'sets csrf token in session' do + expect(session[:_csrf_token]).not_to be_nil + end + + it 'redirects to root' do + expect(response).to redirect_to(root_url) + end + end + + context 'when fails' do + before do + expect(User).to receive(:authenticate).with('bla@bla.com', 'opensesame!').and_return(nil) + end + + it 'raises InvalidCredentials exception' do + expect do + get :test_login_bang_with_block, params: { email: 'bla@bla.com', password: 'opensesame!' } + end.to raise_error(Sorcery::Errors::InvalidCredentials) + end + end + end + describe '#logout' do it 'clears the session' do cookies[:remember_me_token] = nil diff --git a/spec/rails_app/app/controllers/sorcery_controller.rb b/spec/rails_app/app/controllers/sorcery_controller.rb index 8214e36a..e5969141 100644 --- a/spec/rails_app/app/controllers/sorcery_controller.rb +++ b/spec/rails_app/app/controllers/sorcery_controller.rb @@ -28,6 +28,29 @@ def test_login head :ok end + def test_login_bang + @user = login!(params[:email], params[:password]) + head :ok + end + + def test_login_bang_with_block + @user = login!(params[:email], params[:password]) do |user, failure| + if user && !failure + redirect_to :root + else + case failure + when :invalid_login + flash.now[:alert] = 'Wrong login provided.' + when :invalid_password + flash.now[:alert] = 'Wrong password provided.' + when :inactive + flash.now[:alert] = 'Your have not yet activated your account.' + end + render action: 'new' + end + end + end + def test_auto_login @user = User.first auto_login(@user) diff --git a/spec/rails_app/config/routes.rb b/spec/rails_app/config/routes.rb index 4bf82c29..198252a4 100644 --- a/spec/rails_app/config/routes.rb +++ b/spec/rails_app/config/routes.rb @@ -3,6 +3,8 @@ controller :sorcery do get :test_login + get :test_login_bang + get :test_login_bang_with_block get :test_logout get :some_action post :test_return_to