From 1f5ce5983e19466083e61c87f6713e4d52a40d32 Mon Sep 17 00:00:00 2001 From: Hans Lemuet Date: Wed, 18 Jan 2023 22:17:45 +0100 Subject: [PATCH 1/5] Implement #login! helper --- README.md | 1 + lib/sorcery/controller.rb | 12 +++++++ spec/controllers/controller_spec.rb | 33 +++++++++++++++++++ .../app/controllers/sorcery_controller.rb | 5 +++ spec/rails_app/config/routes.rb | 1 + 5 files changed, 52 insertions(+) diff --git a/README.md b/README.md index f5de6ece..510fb41f 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 an `Sorcery::InvalidCredentials` exception on failure auto_login(user) # Login without credentials logout logged_in? # Available in views diff --git a/lib/sorcery/controller.rb b/lib/sorcery/controller.rb index f6cda708..2d457afe 100644 --- a/lib/sorcery/controller.rb +++ b/lib/sorcery/controller.rb @@ -1,4 +1,6 @@ module Sorcery + class InvalidCredentials < StandardError; end + module Controller def self.included(klass) klass.class_eval do @@ -63,6 +65,16 @@ def login(*credentials) end end + def login!(*credentials) + user = login(*credentials) + + if user.nil? + raise Sorcery::InvalidCredentials + else + user + end + 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..0a4a136c 100644 --- a/spec/controllers/controller_spec.rb +++ b/spec/controllers/controller_spec.rb @@ -85,6 +85,39 @@ 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 Sorcery::InvalidCredentials exception' do + expect do + get :test_login_bang, params: { email: 'bla@bla.com', password: 'opensesame!' } + end.to raise_error(Sorcery::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..8252e6d3 100644 --- a/spec/rails_app/app/controllers/sorcery_controller.rb +++ b/spec/rails_app/app/controllers/sorcery_controller.rb @@ -28,6 +28,11 @@ def test_login head :ok end + def test_login_bang + @user = login!(params[:email], params[:password]) + head :ok + 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..01e7d467 100644 --- a/spec/rails_app/config/routes.rb +++ b/spec/rails_app/config/routes.rb @@ -3,6 +3,7 @@ controller :sorcery do get :test_login + get :test_login_bang get :test_logout get :some_action post :test_return_to From 9ebaf3166c99b63aa2f3c14dc48a4f4c5b537387 Mon Sep 17 00:00:00 2001 From: Hans Lemuet Date: Wed, 18 Jan 2023 22:19:10 +0100 Subject: [PATCH 2/5] Add CHANGELOG entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 029dbf0a..f56b81b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## HEAD +* Add a `#login!` helper that raises an exception if the login fails [#332](https://github.com/Sorcery/sorcery/pull/322) + ## 0.16.4 * Adapt to open request protection strategy of rails 7.0 [#318](https://github.com/Sorcery/sorcery/pull/318) From b644d62f2892bf27b2d16f7fd09af298df5c4337 Mon Sep 17 00:00:00 2001 From: Hans Lemuet Date: Thu, 19 Jan 2023 21:58:33 +0100 Subject: [PATCH 3/5] Improve code style --- lib/sorcery/controller.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/sorcery/controller.rb b/lib/sorcery/controller.rb index 2d457afe..6bfa0449 100644 --- a/lib/sorcery/controller.rb +++ b/lib/sorcery/controller.rb @@ -68,11 +68,9 @@ def login(*credentials) def login!(*credentials) user = login(*credentials) - if user.nil? - raise Sorcery::InvalidCredentials - else - user - end + raise Sorcery::InvalidCredentials if user.nil? + + user end def reset_sorcery_session From 0592c8496bf42cf5d01e11bd142a575b7142f3b1 Mon Sep 17 00:00:00 2001 From: Hans Lemuet Date: Thu, 19 Jan 2023 22:07:39 +0100 Subject: [PATCH 4/5] Refactor with Sorcery::Errors --- README.md | 2 +- lib/errors.rb | 12 ++++++++++++ lib/sorcery.rb | 1 + lib/sorcery/controller.rb | 6 ++---- spec/controllers/controller_spec.rb | 4 ++-- 5 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 lib/errors.rb diff --git a/README.md b/README.md index 510fb41f..7a9511d3 100644 --- a/README.md +++ b/README.md @@ -55,7 +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 an `Sorcery::InvalidCredentials` exception on failure +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 6bfa0449..7b588489 100644 --- a/lib/sorcery/controller.rb +++ b/lib/sorcery/controller.rb @@ -1,6 +1,4 @@ module Sorcery - class InvalidCredentials < StandardError; end - module Controller def self.included(klass) klass.class_eval do @@ -68,8 +66,8 @@ def login(*credentials) def login!(*credentials) user = login(*credentials) - raise Sorcery::InvalidCredentials if user.nil? - + raise Sorcery::Errors::InvalidCredentials if user.nil? + user end diff --git a/spec/controllers/controller_spec.rb b/spec/controllers/controller_spec.rb index 0a4a136c..3155088e 100644 --- a/spec/controllers/controller_spec.rb +++ b/spec/controllers/controller_spec.rb @@ -110,10 +110,10 @@ expect(User).to receive(:authenticate).with('bla@bla.com', 'opensesame!').and_return(nil) end - it 'raises Sorcery::InvalidCredentials exception' do + it 'raises InvalidCredentials exception' do expect do get :test_login_bang, params: { email: 'bla@bla.com', password: 'opensesame!' } - end.to raise_error(Sorcery::InvalidCredentials) + end.to raise_error(Sorcery::Errors::InvalidCredentials) end end end From b46de4f069fd53213574fde8c51fa4b3a15d05c5 Mon Sep 17 00:00:00 2001 From: Hans Lemuet Date: Mon, 6 Feb 2023 22:22:31 +0100 Subject: [PATCH 5/5] Add support & spec for #login! with block --- lib/sorcery/controller.rb | 4 +-- spec/controllers/controller_spec.rb | 33 +++++++++++++++++++ .../app/controllers/sorcery_controller.rb | 18 ++++++++++ spec/rails_app/config/routes.rb | 1 + 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/lib/sorcery/controller.rb b/lib/sorcery/controller.rb index 7b588489..237380d4 100644 --- a/lib/sorcery/controller.rb +++ b/lib/sorcery/controller.rb @@ -63,8 +63,8 @@ def login(*credentials) end end - def login!(*credentials) - user = login(*credentials) + def login!(*credentials, &block) + user = login(*credentials, &block) raise Sorcery::Errors::InvalidCredentials if user.nil? diff --git a/spec/controllers/controller_spec.rb b/spec/controllers/controller_spec.rb index 3155088e..0c2ef242 100644 --- a/spec/controllers/controller_spec.rb +++ b/spec/controllers/controller_spec.rb @@ -118,6 +118,39 @@ 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 8252e6d3..e5969141 100644 --- a/spec/rails_app/app/controllers/sorcery_controller.rb +++ b/spec/rails_app/app/controllers/sorcery_controller.rb @@ -33,6 +33,24 @@ def test_login_bang 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 01e7d467..198252a4 100644 --- a/spec/rails_app/config/routes.rb +++ b/spec/rails_app/config/routes.rb @@ -4,6 +4,7 @@ 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