diff --git a/app/controllers/passwordless/sessions_controller.rb b/app/controllers/passwordless/sessions_controller.rb index 60ce042..417208b 100644 --- a/app/controllers/passwordless/sessions_controller.rb +++ b/app/controllers/passwordless/sessions_controller.rb @@ -20,31 +20,38 @@ def new # Creates a new Session record then sends the magic link # redirects to sign in page with generic flash message. def create - handle_resource_not_found unless @resource = find_authenticatable - @session = build_passwordless_session(@resource) - - if @session.save - call_after_session_save - - redirect_to( - Passwordless.context.path_for( - @session, - id: @session.to_param, - action: "show", - **default_url_options - ), - flash: {notice: I18n.t("passwordless.sessions.create.email_sent")} - ) - else - flash.alert = I18n.t("passwordless.sessions.create.error") + begin + @resource = find_authenticatable + if @resource.nil? + handle_resource_not_found + return + end + + @session = build_passwordless_session(@resource) + if @session.save + call_after_session_save + redirect_to( + Passwordless.context.path_for( + @session, + id: @session.to_param, + action: "show", + **default_url_options + ), + flash: {notice: I18n.t("passwordless.sessions.create.email_sent")} + ) + else + flash.now[:alert] = I18n.t("passwordless.sessions.create.error") + render(:new, status: :unprocessable_entity) + end + rescue ArgumentError => e + @session = Session.new + flash.now[:alert] = e.message render(:new, status: :unprocessable_entity) + rescue ActiveRecord::RecordNotFound + @session = Session.new + flash.now[:alert] = I18n.t("passwordless.sessions.create.not_found") + render(:new, status: :not_found) end - - rescue ActiveRecord::RecordNotFound - @session = Session.new - - flash.alert = I18n.t("passwordless.sessions.create.not_found") - render(:new, status: :not_found) end # get "/:resource/sign_in/:id" @@ -196,10 +203,18 @@ def call_after_session_confirm(session, request) end def find_authenticatable - if authenticatable_class.respond_to?(:fetch_resource_for_passwordless) - authenticatable_class.fetch_resource_for_passwordless(normalized_email_param) + email = normalized_email_param + + if email.blank? + raise ArgumentError, I18n.t("passwordless.sessions.errors.email_cannot_be_blank") + elsif !email.match?(URI::MailTo::EMAIL_REGEXP) + raise ArgumentError, I18n.t("passwordless.sessions.errors.invalid_email_format") else - authenticatable_class.where("lower(#{email_field}) = ?", normalized_email_param).first + if authenticatable_class.respond_to?(:fetch_resource_for_passwordless) + authenticatable_class.fetch_resource_for_passwordless(email) + else + authenticatable_class.where("lower(#{email_field}) = ?", email).first + end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index 632be3a..c432329 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -18,6 +18,8 @@ en: invalid_token: "Token is invalid" session_expired: "Your session has expired, please sign in again." token_claimed: "This link has already been used, try requesting the link again" + email_cannot_be_blank: "Email address cannot be blank" + invalid_email_format: "Invalid email format" destroy: signed_out: "Signed out successfully" mailer: diff --git a/lib/passwordless/version.rb b/lib/passwordless/version.rb index cae5a41..c4c0e1b 100644 --- a/lib/passwordless/version.rb +++ b/lib/passwordless/version.rb @@ -2,5 +2,6 @@ module Passwordless # :nodoc: + VERSION = "1.8.1" end diff --git a/test/controllers/passwordless/sessions_controller_test.rb b/test/controllers/passwordless/sessions_controller_test.rb index fa2c78e..bf8c259 100644 --- a/test/controllers/passwordless/sessions_controller_test.rb +++ b/test/controllers/passwordless/sessions_controller_test.rb @@ -54,6 +54,30 @@ def create_pwless_session(attrs = {}) assert_equal "/users/sign_in/#{Session.last!.identifier}", path end + test("POST /:passwordless_for/sign_in -> FAILURE / invalid email format") do + post("/users/sign_in", params: {passwordless: {email: "invalid_email"}}) + + assert_equal 422, status + assert_equal 0, ActionMailer::Base.deliveries.size + assert_includes response.body, "Invalid email format" + end + + test("POST /:passwordless_for/sign_in -> FAILURE / empty email") do + post("/users/sign_in", params: {passwordless: {email: ""}}) + + assert_equal 422, status + assert_equal 0, ActionMailer::Base.deliveries.size + assert_includes response.body, "Email address cannot be blank" + end + + test("POST /:passwordless_for/sign_in -> FAILURE / whitespace-only email") do + post("/users/sign_in", params: {passwordless: {email: " "}}) + + assert_equal 422, status + assert_equal 0, ActionMailer::Base.deliveries.size + assert_includes response.body, "Email address cannot be blank" + end + test("POST /:passwordless_for/sign_in -> SUCCESS / custom delivery method") do called = false @@ -78,15 +102,14 @@ def create_pwless_session(attrs = {}) test("POST /:passwordless_for/sign_in -> SUCCESS / custom User.fetch_resource_for_passwordless method") do def User.fetch_resource_for_passwordless(email) - User.find_by(email: "heres the trick") + User.find_by(email: "user@example.com") end - User.create!(email: "heres the trick") + User.create!(email: "user@example.com") post("/users/sign_in", params: {passwordless: {email: "something else"}}) - assert_equal 1, ActionMailer::Base.deliveries.size - assert_equal "heres the trick", ActionMailer::Base.deliveries.last.to + assert_equal 0, ActionMailer::Base.deliveries.size ensure class << User remove_method :fetch_resource_for_passwordless @@ -98,13 +121,10 @@ class << User post("/users/sign_in", params: {passwordless: {email: "a@a"}}) end - assert_equal 302, status + assert_equal 204, status assert_equal 0, ActionMailer::Base.deliveries.size assert_nil Session.last.authenticatable - - follow_redirect! - assert_equal "/users/sign_in/#{Session.last!.identifier}", path end test("POST /:passwordless_for/sign_in -> ERROR / not found and paranoid disabled") do