Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow re-sending of magic link emails #137

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Allow re-sending of magic link emails
When the config setting Passwordless.allow_token_resend = true a visit
to /users/sign_in/TOKEN_URL for an expired or already claimed user will
return a page with a button which can be used to resend an email with
this token. Extra care is taken to record a query parameter
`destination_path` into the current session, so opening the new email
immediately from the same browser redirects you to the proper location.
  • Loading branch information
Daan van Vugt committed Apr 14, 2023
commit 1237a8b5a4a9acb210bcd796d012fc078569967e
27 changes: 19 additions & 8 deletions app/controllers/passwordless/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def create
session = build_passwordless_session(@resource)

if session.save
if params[:passwordless][:destination_path]
save_passwordless_redirect_location!(@resource.class, params[:passwordless][:destination_path])
end

if Passwordless.after_session_save.arity == 2
Passwordless.after_session_save.call(session, request)
else
Expand All @@ -45,15 +49,18 @@ def create
def show
# Make it "slow" on purpose to make brute-force attacks more of a hassle
BCrypt::Password.create(params[:token])
sign_in(passwordless_session)
@email_field = email_field
@session = passwordless_session
@destination_path = params[:destination_path] || ''
sign_in(@session)

redirect_to(passwordless_success_redirect_path)
rescue Errors::TokenAlreadyClaimedError
flash[:error] = I18n.t(".passwordless.sessions.create.token_claimed")
redirect_to(passwordless_failure_redirect_path)
redirect_to(passwordless_failure_redirect_path) unless Passwordless.allow_token_resend
rescue Errors::SessionTimedOutError
flash[:error] = I18n.t(".passwordless.sessions.create.session_expired")
redirect_to(passwordless_failure_redirect_path)
redirect_to(passwordless_failure_redirect_path) unless Passwordless.allow_token_resend
end

# match '/sign_out', via: %i[get delete].
Expand Down Expand Up @@ -107,12 +114,16 @@ def email_field
end

def find_authenticatable
email = params[:passwordless][email_field].downcase.strip

if authenticatable_class.respond_to?(:fetch_resource_for_passwordless)
authenticatable_class.fetch_resource_for_passwordless(email)
if params[:passwordless][:token]
Passwordless::Session.find_by(token: params[:passwordless][:token]).authenticatable
else
authenticatable_class.where("lower(#{email_field}) = ?", email).first
email = params[:passwordless][email_field].downcase.strip

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

Expand Down
7 changes: 7 additions & 0 deletions app/views/passwordless/sessions/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= form_with model: @session, method: :post, url: send(Passwordless.mounted_as).sign_in_path, data: { turbo: 'false' } do |f| %>
<%= hidden_field_tag 'passwordless[destination_path]', @destination_path %>
<%= hidden_field_tag 'passwordless[token]', @session.token %>
<%= I18n.t('passwordless.sessions.show.explanation') %>
<%= f.submit I18n.t('passwordless.sessions.show.submit') %>
<% end %>
<%= link_to I18n.t('passwordless.sessions.show.new'), send(Passwordless.mounted_as).sign_in_path %>
4 changes: 4 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ en:
token_claimed: "This link has already been used, try requesting the link again"
new:
submit: 'Send magic link'
show:
explanation: 'Your magic link is no longer valid, but you can request a new one.'
submit: 'Resend magic link'
new: 'Or go to the sign in page.'
mailer:
subject: "Your magic link ✨"
magic_link: "Here's your link: %{link}"
1 change: 1 addition & 0 deletions lib/passwordless.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module Passwordless
mattr_accessor(:default_from_address) { "[email protected]" }
mattr_accessor(:token_generator) { UrlSafeBase64Generator.new }
mattr_accessor(:restrict_token_reuse) { false }
mattr_accessor(:allow_token_resend) { false }
mattr_accessor(:redirect_back_after_sign_in) { true }
mattr_accessor(:mounted_as) { :configured_when_mounting_passwordless }

Expand Down
4 changes: 2 additions & 2 deletions lib/passwordless/controller_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ def sign_out(authenticatable_class)
# passwordless Model.
# @param (see #authenticate_by_session)
# @return [String] the redirect url that was just saved.
def save_passwordless_redirect_location!(authenticatable_class)
session[redirect_session_key(authenticatable_class)] = request.original_url
def save_passwordless_redirect_location!(authenticatable_class, location = request.original_url)
session[redirect_session_key(authenticatable_class)] = location
end

# Resets the redirect_location to root_path by deleting the redirect_url
Expand Down