mikker / passwordless

🗝 Authentication for your Rails app without the icky-ness of passwords
MIT License
1.26k stars 85 forks source link

Add controller to routes and add test to cover the feature #160

Closed henrikbjorn closed 11 months ago

henrikbjorn commented 11 months ago

@mikker This got lost in the next branch

mikker commented 11 months ago

Sorry about that! Thank you Henrik.

Now that you're here: How do you feel about the other breaking changes?

henrikbjorn commented 11 months ago

@mikker Makes sense to have the possibility for entering a token, to keep the current tab 👍🏻

I think the new SessionsController is kind of complex and needs some extension points. Personally I don't like the usage of flashes and would instead prefer to attach such errors to the form elements.

My current SessionsController

# frozen_string_literal: true

class SessionsController < Passwordless::SessionsController
  layout 'passwordless'

  unauthenticated except: %i[destroy]

  before_action :build_request, only: %i[show new]
  before_action :build_request_with_params, only: %i[create update]

  class Request
    include ActiveModel::Model
    include ActiveModel::Attributes

    attribute :email
    attribute :token
  end

  def create
    @resource = find_authenticatable

    unless @resource
      @request.errors.add(:email, :invalid)

      return render :new, status: :unprocessable_entity
    end

    @session = build_passwordless_session(@resource)

    Passwordless.config.after_session_save.call(@session, request) if @session.save

    return redirect_to_confirm_path if Rails.env.development?

    redirect_to url_for(id: @session.id, action: :show), status: :see_other
  end

  protected

  def build_request
    @request = Request.new
  end

  def build_request_with_params
    @request = Request.new(passwordless_session_params)
  end

  def authenticate_and_sign_in(session, token)
    unless session.authenticate(token)
      @request.errors.add(:token, I18n.t("passwordless.sessions.errors.invalid_token"))

      return render(status: :forbidden, action: "show")
    end

    sign_in(session)

    redirect_to(passwordless_success_redirect_path, status: :see_other, **redirect_to_options)
  rescue \
    Errors::TokenAlreadyClaimedError,
    Errors::SessionTimedOutError

    @request.errors.add(:token, I18n.t("passwordless.sessions.errors.invalid_token"))

    render :show, status: :forbidden
  end

  def redirect_to_confirm_path
    return redirect_to url_for(action: :new), status: :see_other unless @session.persisted?

    redirect_to url_for(id: @session.id, token: @session.token, action: :confirm), status: :see_other
  end
end

new.html.erb

<%= custom_form_with model: @request, scope: :passwordless, url: url_for(action: :create), data: { turbo: false } do |f| %>
  <%= f.input :email, as: :email_field, autofocus: true, autocomplete: "email" %>

  <%= f.actions do %>
      <%= f.submit t('passwordless.sessions.new.submit'), class: 'w-full' %>
    <% end %>
<% end %> 

show.html.erb:

<%= custom_form_with model: @request, scope: :passwordless, url: url_for(action: :update), method: :patch, data: { turbo: false } do |f| %>
  <%= f.input :token, autofocus: true, autocomplete: "one-time-password", error: 'something' %>

  <%= f.actions do %>
      <%= f.submit t('passwordless.sessions.new.submit'), class: 'w-full' %>
    <% end %>
<% end %> 

Using ViewComponent::Form makes the input have an error message directly attached to the input.

henrikbjorn commented 11 months ago

Would like a new release instead of doing the github: 'mikker/passwordless' in gemfiles :)