Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into remove-load-path
Browse files Browse the repository at this point in the history
* origin/master:
  Update changelog
  Use flash.alert (mikker#215)
  Update changelog
  Add support for scoped routes (mikker#209)
  Add "token" on show page to locale definition (mikker#214)
  v1.5.0
  Update changelog
  Include TestHelpers in ActionDispatch::IntegrationTest (mikker#211)
  Add url_options param to sign_in email (mikker#208)
  Evaluate callable redirects in context of controller (mikker#203)
  • Loading branch information
schpet committed Apr 16, 2024
2 parents fd18dad + 971093e commit a97a7ab
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 23 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# Changelog

## Unreleased

### Added

- Add support for scoped routes with `default_url_options` (#209)

### Fixed

- Use flash.alert of flash[:error] (#215)

## 1.5.0

### Changed

- Evaluate callable redirects in context of controller (#203)

### Added

- Add url_options param to sign_in email (#208)
- Include TestHelpers in ActionDispatch::IntegrationTest (#211)

## 1.4.0

### Changed
Expand Down
38 changes: 27 additions & 11 deletions app/controllers/passwordless/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,20 @@ def create
Passwordless.context.path_for(
@session,
id: @session.to_param,
action: "show"
action: "show",
**default_url_options
),
flash: {notice: I18n.t("passwordless.sessions.create.email_sent")}
)
else
flash[:error] = I18n.t("passwordless.sessions.create.error")
flash.alert = I18n.t("passwordless.sessions.create.error")
render(:new, status: :unprocessable_entity)
end

rescue ActiveRecord::RecordNotFound
@session = Session.new

flash[:error] = I18n.t("passwordless.sessions.create.not_found")
flash.alert = I18n.t("passwordless.sessions.create.not_found")
render(:new, status: :not_found)
end

Expand Down Expand Up @@ -119,8 +120,15 @@ def passwordless_query_redirect_path
nil
end

def passwordless_success_redirect_path
success_redirect_path = call_or_return(Passwordless.config.success_redirect_path)
def passwordless_success_redirect_path(authenticatable)
success_redirect_path = Passwordless.config.success_redirect_path

if success_redirect_path.respond_to?(:call)
success_redirect_path = call_or_return(
success_redirect_path,
*[authenticatable].first(success_redirect_path.arity)
)
end

if Passwordless.config.redirect_back_after_sign_in
session_redirect_url = reset_passwordless_redirect_location!(authenticatable_class)
Expand All @@ -142,17 +150,21 @@ def artificially_slow_down_brute_force_attacks(token)
def authenticate_and_sign_in(session, token)
if session.authenticate(token)
sign_in(session)
redirect_to(passwordless_success_redirect_path, status: :see_other, **redirect_to_options)
redirect_to(
passwordless_success_redirect_path(session.authenticatable),
status: :see_other,
**redirect_to_options
)
else
flash[:error] = I18n.t("passwordless.sessions.errors.invalid_token")
flash.alert = I18n.t("passwordless.sessions.errors.invalid_token")
render(status: :forbidden, action: "show")
end

rescue Errors::TokenAlreadyClaimedError
flash[:error] = I18n.t("passwordless.sessions.errors.token_claimed")
flash.alert = I18n.t("passwordless.sessions.errors.token_claimed")
redirect_to(passwordless_failure_redirect_path, status: :see_other, **redirect_to_options)
rescue Errors::SessionTimedOutError
flash[:error] = I18n.t("passwordless.sessions.errors.session_expired")
flash.alert = I18n.t("passwordless.sessions.errors.session_expired")
redirect_to(passwordless_failure_redirect_path, status: :see_other, **redirect_to_options)
end

Expand All @@ -168,8 +180,12 @@ def authenticatable_class
authenticatable_type.constantize
end

def call_or_return(value)
value.respond_to?(:call) ? value.call : value
def call_or_return(value, *args)
if value.respond_to?(:call)
instance_exec(*args, &value)
else
value
end
end

def find_authenticatable
Expand Down
3 changes: 2 additions & 1 deletion app/mailers/passwordless/mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ class Mailer < Passwordless.config.parent_mailer.constantize
# @param session [Session] An instance of Passwordless::Session
# @param token [String] The token in plaintext. Falls back to `session.token` hoping it
# is still in memory (optional)
def sign_in(session, token = nil)
def sign_in(session, token = nil, url_options = {})
@token = token || session.token

@magic_link = Passwordless.context.url_for(
session,
action: "confirm",
id: session.to_param,
token: @token,
**url_options,
**default_url_options
)

Expand Down
2 changes: 1 addition & 1 deletion app/views/passwordless/sessions/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<%= form_with(model: @session, url: url_for(action: 'update'), scope: 'passwordless', method: 'patch', data: { turbo: false }) do |f| %>
<%= f.label :token %>
<%= f.label :token, t(".token") %>
<%= f.text_field :token,
required: true,
autofocus: true,
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ en:
not_found: "We couldn't find a user with that email address"
error: "An error occured"
show:
token: "Token"
confirm: "Confirm"
errors:
invalid_token: "Token is invalid"
Expand Down
4 changes: 4 additions & 0 deletions lib/passwordless/test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ def passwordless_sign_in(resource, only_path: false)
ActionDispatch::SystemTestCase.send(:include, ::Passwordless::TestHelpers::SystemTestCase)
end

if defined?(ActionDispatch::IntegrationTest)
ActionDispatch::IntegrationTest.send(:include, ::Passwordless::TestHelpers::RequestTestCase)
end

if defined?(RSpec)
RSpec.configure do |config|
config.include(::Passwordless::TestHelpers::ControllerTestCase, type: :controller)
Expand Down
2 changes: 1 addition & 1 deletion lib/passwordless/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

module Passwordless
# :nodoc:
VERSION = "1.4.0"
VERSION = "1.5.0"
end
90 changes: 81 additions & 9 deletions test/controllers/passwordless/sessions_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def create_pwless_session(attrs = {})

assert_equal 200, status
assert_template "passwordless/sessions/new"
assert_match "E-mail address", response.body
end

test("GET /:passwordless_for/sign_in/:id") do
Expand Down Expand Up @@ -113,7 +114,7 @@ class << User
assert_equal 0, ActionMailer::Base.deliveries.size

assert_template "passwordless/sessions/new"
assert_match "We couldn't find a user with that email address", flash[:error]
assert_match "We couldn't find a user with that email address", flash.alert
end

test("POST /:passwordless_for/sign_in -> ERROR / other error") do
Expand All @@ -128,7 +129,7 @@ class << User
assert_equal 0, ActionMailer::Base.deliveries.size

assert_template "passwordless/sessions/new"
assert_match "An error occured", flash[:error]
assert_match "An error occured", flash.alert
end

test("PATCH /:passwordless_for/sign_in/:id -> SUCCESS") do
Expand All @@ -145,20 +146,26 @@ class << User
assert_equal pwless_session(User), Session.last!.id
end

test("PATCH /:passwordless_for/sign_in/:id -> SUCCESS / callable success path") do
test("PATCH /:passwordless_for/sign_in/:id -> SUCCESS / callable success path with no args") do
passwordless_session = create_pwless_session(token: "hi")

with_config(success_redirect_path: lambda { "/" }) do
patch("/users/sign_in/#{passwordless_session.identifier}", params: {passwordless: {token: "hi"}})
end

assert_equal 303, status

follow_redirect!
assert_equal 200, status
assert_equal "/", path
end

assert_equal pwless_session(User), Session.last!.id
test("PATCH /:passwordless_for/sign_in/:id -> SUCCESS / callable success path with 1 arg") do
passwordless_session = create_pwless_session(token: "hi")

with_config(success_redirect_path: lambda { |user| "/#{user.id}" }) do
patch("/users/sign_in/#{passwordless_session.identifier}", params: {passwordless: {token: "hi"}})
end

follow_redirect!
assert_equal "/#{passwordless_session.authenticatable.id}", path
end

test("PATCH /:passwordless_for/sign_in/:id -> ERROR") do
Expand Down Expand Up @@ -189,7 +196,7 @@ class << User
follow_redirect!
assert_equal 200, status
assert_equal "/", path
assert_match "This link has already been used, try requesting the link again", flash[:error]
assert_match "This link has already been used, try requesting the link again", flash.alert

assert_nil pwless_session(User)
end
Expand All @@ -208,7 +215,7 @@ class << User
follow_redirect!
assert_equal 200, status
assert_equal "/", path
assert_match "Your session has expired", flash[:error]
assert_match "Your session has expired", flash.alert

assert_nil pwless_session(User)
end
Expand Down Expand Up @@ -260,3 +267,68 @@ def pwless_session(cls)
end
end
end

module Passwordless
class SessionsControllerParamsTest < ActionDispatch::IntegrationTest
def create_user(attrs = {})
attrs.reverse_merge!(email: next_email)
User.create!(attrs)
end

def create_pwless_session(attrs = {})
attrs[:authenticatable] = create_user unless attrs.key?(:authenticatable)
Session.create!(attrs)
end

class Passwordless::LocaleParentController < ActionController::Base
around_action :switch_locale

def default_url_options
{locale: I18n.locale}
end

def switch_locale(&action)
locale = params[:locale] || I18n.default_locale
I18n.with_locale(locale, &action)
end
end

setup do
with_config({parent_controller: "Passwordless::LocaleParentController"}) do
reload_controller!
end
end

teardown do
reload_controller!
end

test("GET /locale/en/:passwordless_for/sign_in") do
get "/locale/en/users/sign_in"

assert_match "E-mail address", response.body
end

test("GET /locale/test/:passwordless_for/sign_in") do
get "/locale/test/users/sign_in"

assert_match "Gimme dat email", response.body
end

test("GET /locale/test/:passwordless_for/sign_in/:id") do
passwordless_session = create_pwless_session

get("/locale/test/users/sign_in/#{passwordless_session.identifier}")

assert_equal "/locale/test/users/sign_in/#{passwordless_session.to_param}", path
end

test("POST /locale/test/:passwordless_for/sign_in -> SUCCESS ") do
create_user(email: "a@a")

post("/locale/test/users/sign_in", params: {passwordless: {email: "a@a"}})

assert_equal 302, status
end
end
end
1 change: 1 addition & 0 deletions test/dummy/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ class Application < Rails::Application

config.action_mailer.default_url_options = {host: "localhost", port: "3000"}
routes.default_url_options[:host] = "localhost:3000"
config.i18n.available_locales = %i[en test]
end
end
7 changes: 7 additions & 0 deletions test/dummy/config/locales/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
test:
passwordless:
sessions:
new:
email:
label: "Gimme dat email"
4 changes: 4 additions & 0 deletions test/dummy/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
get("/secret-alt", to: "secrets#index")

root(to: "users#index")

scope("/locale/:locale") do
passwordless_for(:users, as: :locale_user)
end
end

0 comments on commit a97a7ab

Please sign in to comment.