passwordless 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/app/controllers/passwordless/sessions_controller.rb +15 -10
- data/app/mailers/passwordless/mailer.rb +8 -1
- data/app/models/passwordless/session.rb +5 -0
- data/app/views/passwordless/sessions/new.html.erb +2 -1
- data/config/locales/en.yml +2 -0
- data/db/migrate/20171104221735_create_passwordless_sessions.rb +1 -0
- data/lib/passwordless/context.rb +51 -0
- data/lib/passwordless/router_helpers.rb +4 -8
- data/lib/passwordless/test_helpers.rb +44 -14
- data/lib/passwordless/version.rb +1 -1
- data/lib/passwordless.rb +9 -0
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 399b4c9ace35d6780f2f22cf50c47c2717ead7c9b6b999d64120b7e574b465ec
         | 
| 4 | 
            +
              data.tar.gz: 5e7e35e3eab146d5e93c7588c49552fb3f46d27ed27e183ba7367e755ecfccfd
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d9fe89a70ba2f35cc417f03f5ca23a9a1bb1bc967d3c80828aba6a60a643e8de2d880f7bb038e1f17b60e80f250117df5ad41df1a15c96c9dcfa8317767ddd2f
         | 
| 7 | 
            +
              data.tar.gz: 315a2b802c1b21cf08ad61003c6b15d2d8eedfcfb7c440c58fbc7991bd73c254261dc3e7535d25469a01522e8476cb0ca9fdede7af3d15f0eaed14ff1b80ce2a
         | 
    
        data/README.md
    CHANGED
    
    | @@ -16,7 +16,7 @@ Add to your bundle and copy over the migrations: | |
| 16 16 |  | 
| 17 17 | 
             
            ```sh
         | 
| 18 18 | 
             
            $ bundle add passwordless
         | 
| 19 | 
            -
            $ bin/rails  | 
| 19 | 
            +
            $ bin/rails passwordless_engine:install:migrations
         | 
| 20 20 | 
             
            ```
         | 
| 21 21 |  | 
| 22 22 | 
             
            ### Upgrading
         | 
| @@ -122,7 +122,7 @@ class UsersController < ApplicationController | |
| 122 122 | 
             
                @user = User.new(user_params)
         | 
| 123 123 |  | 
| 124 124 | 
             
                if @user.save
         | 
| 125 | 
            -
                  sign_in( | 
| 125 | 
            +
                  sign_in(create_passwordless_session(@user)) # <-- This!
         | 
| 126 126 | 
             
                  redirect_to(@user, flash: { notice: 'Welcome!' })
         | 
| 127 127 | 
             
                else
         | 
| 128 128 | 
             
                  render(:new)
         | 
| @@ -37,7 +37,11 @@ module Passwordless | |
| 37 37 | 
             
                    end
         | 
| 38 38 |  | 
| 39 39 | 
             
                    redirect_to(
         | 
| 40 | 
            -
                       | 
| 40 | 
            +
                      Passwordless.context.path_for(
         | 
| 41 | 
            +
                        @session,
         | 
| 42 | 
            +
                        id: @session.to_param,
         | 
| 43 | 
            +
                        action: "show"
         | 
| 44 | 
            +
                      ),
         | 
| 41 45 | 
             
                      flash: {notice: I18n.t("passwordless.sessions.create.email_sent")}
         | 
| 42 46 | 
             
                    )
         | 
| 43 47 | 
             
                  else
         | 
| @@ -54,7 +58,7 @@ module Passwordless | |
| 54 58 | 
             
                #   Shows the form for confirming a Session record.
         | 
| 55 59 | 
             
                #   renders sessions/show.html.erb.
         | 
| 56 60 | 
             
                def show
         | 
| 57 | 
            -
                  @session =  | 
| 61 | 
            +
                  @session = passwordless_session
         | 
| 58 62 | 
             
                end
         | 
| 59 63 |  | 
| 60 64 | 
             
                # patch "/:resource/sign_in/:id"
         | 
| @@ -66,7 +70,7 @@ module Passwordless | |
| 66 70 | 
             
                # @see ControllerHelpers#sign_in
         | 
| 67 71 | 
             
                # @see ControllerHelpers#save_passwordless_redirect_location!
         | 
| 68 72 | 
             
                def update
         | 
| 69 | 
            -
                  @session =  | 
| 73 | 
            +
                  @session = passwordless_session
         | 
| 70 74 |  | 
| 71 75 | 
             
                  artificially_slow_down_brute_force_attacks(passwordless_session_params[:token])
         | 
| 72 76 |  | 
| @@ -86,7 +90,7 @@ module Passwordless | |
| 86 90 | 
             
                  # safe. We don't want to sign in the user in that case.
         | 
| 87 91 | 
             
                  return head(:ok) if request.head?
         | 
| 88 92 |  | 
| 89 | 
            -
                  @session =  | 
| 93 | 
            +
                  @session = passwordless_session
         | 
| 90 94 |  | 
| 91 95 | 
             
                  artificially_slow_down_brute_force_attacks(params[:token])
         | 
| 92 96 |  | 
| @@ -98,7 +102,12 @@ module Passwordless | |
| 98 102 | 
             
                # @see ControllerHelpers#sign_out
         | 
| 99 103 | 
             
                def destroy
         | 
| 100 104 | 
             
                  sign_out(authenticatable_class)
         | 
| 101 | 
            -
             | 
| 105 | 
            +
             | 
| 106 | 
            +
                  redirect_to(
         | 
| 107 | 
            +
                    passwordless_sign_out_redirect_path,
         | 
| 108 | 
            +
                    notice: I18n.t("passwordless.sessions.destroy.signed_out"),
         | 
| 109 | 
            +
                    **redirect_to_options
         | 
| 110 | 
            +
                  )
         | 
| 102 111 | 
             
                end
         | 
| 103 112 |  | 
| 104 113 | 
             
                protected
         | 
| @@ -161,10 +170,6 @@ module Passwordless | |
| 161 170 | 
             
                  authenticatable_type.constantize
         | 
| 162 171 | 
             
                end
         | 
| 163 172 |  | 
| 164 | 
            -
                def find_session
         | 
| 165 | 
            -
                  Session.find_by!(id: params[:id], authenticatable_type: authenticatable_type)
         | 
| 166 | 
            -
                end
         | 
| 167 | 
            -
             | 
| 168 173 | 
             
                def find_authenticatable
         | 
| 169 174 | 
             
                  email = passwordless_session_params[email_field].downcase.strip
         | 
| 170 175 |  | 
| @@ -196,7 +201,7 @@ module Passwordless | |
| 196 201 |  | 
| 197 202 | 
             
                def passwordless_session
         | 
| 198 203 | 
             
                  @passwordless_session ||= Session.find_by!(
         | 
| 199 | 
            -
                     | 
| 204 | 
            +
                    identifier: params[:id],
         | 
| 200 205 | 
             
                    authenticatable_type: authenticatable_type
         | 
| 201 206 | 
             
                  )
         | 
| 202 207 | 
             
                end
         | 
| @@ -12,7 +12,14 @@ module Passwordless | |
| 12 12 | 
             
                # is still in memory (optional)
         | 
| 13 13 | 
             
                def sign_in(session, token = nil)
         | 
| 14 14 | 
             
                  @token = token || session.token
         | 
| 15 | 
            -
             | 
| 15 | 
            +
             | 
| 16 | 
            +
                  @magic_link = Passwordless.context.url_for(
         | 
| 17 | 
            +
                    session,
         | 
| 18 | 
            +
                    action: "confirm",
         | 
| 19 | 
            +
                    id: session.to_param,
         | 
| 20 | 
            +
                    token: @token
         | 
| 21 | 
            +
                  )
         | 
| 22 | 
            +
             | 
| 16 23 | 
             
                  email_field = session.authenticatable.class.passwordless_email_field
         | 
| 17 24 |  | 
| 18 25 | 
             
                  mail(
         | 
| @@ -61,6 +61,10 @@ module Passwordless | |
| 61 61 | 
             
                  !expired?
         | 
| 62 62 | 
             
                end
         | 
| 63 63 |  | 
| 64 | 
            +
                def to_param
         | 
| 65 | 
            +
                  identifier
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 64 68 | 
             
                private
         | 
| 65 69 |  | 
| 66 70 | 
             
                def token_digest_available?(token_digest)
         | 
| @@ -68,6 +72,7 @@ module Passwordless | |
| 68 72 | 
             
                end
         | 
| 69 73 |  | 
| 70 74 | 
             
                def set_defaults
         | 
| 75 | 
            +
                  self.identifier = SecureRandom.uuid
         | 
| 71 76 | 
             
                  self.expires_at ||= Passwordless.config.expires_at.call
         | 
| 72 77 | 
             
                  self.timeout_at ||= Passwordless.config.timeout_at.call
         | 
| 73 78 |  | 
| @@ -3,9 +3,10 @@ | |
| 3 3 | 
             
              <%= f.label email_field_name,
         | 
| 4 4 | 
             
                      t("passwordless.sessions.new.email.label"),
         | 
| 5 5 | 
             
                      for: "passwordless_#{email_field}" %>
         | 
| 6 | 
            -
              <%=  | 
| 6 | 
            +
              <%= email_field_tag email_field_name,
         | 
| 7 7 | 
             
              params.fetch(email_field_name, nil),
         | 
| 8 8 | 
             
              required: true,
         | 
| 9 | 
            +
              autofocus: true,
         | 
| 9 10 | 
             
              placeholder: t("passwordless.sessions.new.email.placeholder") %>
         | 
| 10 11 | 
             
              <%= f.submit t("passwordless.sessions.new.submit") %>
         | 
| 11 12 | 
             
            <% end %>
         | 
    
        data/config/locales/en.yml
    CHANGED
    
    | @@ -17,6 +17,8 @@ en: | |
| 17 17 | 
             
                    invalid_token: "Token is invalid"
         | 
| 18 18 | 
             
                    session_expired: "Your session has expired, please sign in again."
         | 
| 19 19 | 
             
                    token_claimed: "This link has already been used, try requesting the link again"
         | 
| 20 | 
            +
                  destroy:
         | 
| 21 | 
            +
                    signed_out: "Signed out successfully"
         | 
| 20 22 | 
             
                mailer:
         | 
| 21 23 | 
             
                  sign_in:
         | 
| 22 24 | 
             
                    subject: "Signing in ✨"
         | 
| @@ -13,6 +13,7 @@ class CreatePasswordlessSessions < ActiveRecord::Migration[5.1] | |
| 13 13 | 
             
                  t.datetime(:expires_at, null: false)
         | 
| 14 14 | 
             
                  t.datetime(:claimed_at)
         | 
| 15 15 | 
             
                  t.string(:token_digest, null: false)
         | 
| 16 | 
            +
                  t.string(:identifier, null: false, index: {unique: true}, length: 36)
         | 
| 16 17 |  | 
| 17 18 | 
             
                  t.timestamps
         | 
| 18 19 | 
             
                end
         | 
| @@ -0,0 +1,51 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Passwordless
         | 
| 4 | 
            +
              class Resource
         | 
| 5 | 
            +
                def initialize(resource, controller:)
         | 
| 6 | 
            +
                  @resource = resource
         | 
| 7 | 
            +
                  @authenticatable = resource.to_s.singularize.to_sym
         | 
| 8 | 
            +
                  @controller = controller
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                attr_reader :resource, :authenticatable, :controller
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def defaults
         | 
| 14 | 
            +
                  @defaults ||= {
         | 
| 15 | 
            +
                    authenticatable: authenticatable,
         | 
| 16 | 
            +
                    resource: resource,
         | 
| 17 | 
            +
                    controller: controller
         | 
| 18 | 
            +
                  }
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              class Context
         | 
| 23 | 
            +
                def initialize
         | 
| 24 | 
            +
                  @resources = {}
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                attr_reader :resources
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def resource_for(session_or_authenticatable)
         | 
| 30 | 
            +
                  if session_or_authenticatable.is_a?(Session)
         | 
| 31 | 
            +
                    session_or_authenticatable = session_or_authenticatable.authenticatable.model_name.to_s.tableize.to_sym
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  resources[session_or_authenticatable.to_sym]
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def url_for(session_or_authenticatable, **options)
         | 
| 38 | 
            +
                  unless (resource = resource_for(session_or_authenticatable))
         | 
| 39 | 
            +
                    raise ArgumentError, "No resource registered for #{session_or_authenticatable}"
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  Rails.application.routes.url_helpers.url_for(
         | 
| 43 | 
            +
                    resource.defaults.merge(options)
         | 
| 44 | 
            +
                  )
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def path_for(session_or_authenticatable, **options)
         | 
| 48 | 
            +
                  url_for(session_or_authenticatable, only_path: true, **options)
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
            end
         | 
| @@ -23,17 +23,13 @@ module Passwordless | |
| 23 23 | 
             
                #  (Default: 'passwordless/sessions')
         | 
| 24 24 | 
             
                def passwordless_for(resource, at: :na, as: :na, controller: "passwordless/sessions")
         | 
| 25 25 | 
             
                  at == :na && at = "/#{resource.to_s}"
         | 
| 26 | 
            -
                  as == :na && as =  | 
| 26 | 
            +
                  as == :na && as = resource.to_s
         | 
| 27 27 |  | 
| 28 | 
            -
                   | 
| 29 | 
            -
                  singular = plural.singularize
         | 
| 28 | 
            +
                  as = as.to_s + "_" unless !as || as.to_s.end_with?("_")
         | 
| 30 29 |  | 
| 31 | 
            -
                   | 
| 32 | 
            -
                    authenticatable: singular,
         | 
| 33 | 
            -
                    resource: resource,
         | 
| 34 | 
            -
                  }
         | 
| 30 | 
            +
                  pwless_resource = Passwordless.add_resource(resource, controller: controller)
         | 
| 35 31 |  | 
| 36 | 
            -
                  scope(defaults: defaults) do
         | 
| 32 | 
            +
                  scope(defaults: pwless_resource.defaults) do
         | 
| 37 33 | 
             
                    get("#{at}/sign_in", to: "#{controller}#new", as: :"#{as}sign_in")
         | 
| 38 34 | 
             
                    post("#{at}/sign_in", to: "#{controller}#create")
         | 
| 39 35 | 
             
                    get("#{at}/sign_in/:id", to: "#{controller}#show", as: :"verify_#{as}sign_in")
         | 
| @@ -1,35 +1,65 @@ | |
| 1 1 | 
             
            module Passwordless
         | 
| 2 2 | 
             
              module TestHelpers
         | 
| 3 | 
            -
                module  | 
| 4 | 
            -
                   | 
| 5 | 
            -
                     | 
| 3 | 
            +
                module ControllerTestCase
         | 
| 4 | 
            +
                  class H
         | 
| 5 | 
            +
                    extend ControllerHelpers
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def passwordless_sign_out(cls = nil)
         | 
| 9 | 
            +
                    cls ||= "User".constantize
         | 
| 10 | 
            +
                    @request.session.delete(H.session_key(cls))
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def passwordless_sign_in(resource)
         | 
| 14 | 
            +
                    session = Passwordless::Session.create!(authenticatable: resource)
         | 
| 15 | 
            +
                    @request.session[H.session_key(resource.class)] = session.id
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                module RequestTestCase
         | 
| 20 | 
            +
                  def passwordless_sign_out(cls = nil)
         | 
| 21 | 
            +
                    cls ||= "User".constantize
         | 
| 22 | 
            +
                    resource = cls.model_name.to_s.tableize
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    dest = Passwordless.context.path_for(resource, action: "destroy")
         | 
| 25 | 
            +
                    delete(dest)
         | 
| 26 | 
            +
             | 
| 6 27 | 
             
                    follow_redirect!
         | 
| 7 28 | 
             
                  end
         | 
| 8 29 |  | 
| 9 30 | 
             
                  def passwordless_sign_in(resource)
         | 
| 10 31 | 
             
                    session = Passwordless::Session.create!(authenticatable: resource)
         | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 32 | 
            +
             | 
| 33 | 
            +
                    magic_link = Passwordless.context.path_for(
         | 
| 13 34 | 
             
                      session,
         | 
| 14 | 
            -
                       | 
| 35 | 
            +
                      action: "confirm",
         | 
| 36 | 
            +
                      id: session.to_param,
         | 
| 37 | 
            +
                      token: session.token
         | 
| 15 38 | 
             
                    )
         | 
| 39 | 
            +
             | 
| 16 40 | 
             
                    get(magic_link)
         | 
| 17 41 | 
             
                    follow_redirect!
         | 
| 18 42 | 
             
                  end
         | 
| 19 43 | 
             
                end
         | 
| 20 44 |  | 
| 21 45 | 
             
                module SystemTestCase
         | 
| 22 | 
            -
                  def passwordless_sign_out
         | 
| 23 | 
            -
                     | 
| 46 | 
            +
                  def passwordless_sign_out(cls = nil)
         | 
| 47 | 
            +
                    cls ||= "User".constantize
         | 
| 48 | 
            +
                    resource = cls.model_name.to_s.tableize
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    visit(Passwordless.context.url_for(resource, action: "destroy"))
         | 
| 24 51 | 
             
                  end
         | 
| 25 52 |  | 
| 26 53 | 
             
                  def passwordless_sign_in(resource)
         | 
| 27 54 | 
             
                    session = Passwordless::Session.create!(authenticatable: resource)
         | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 55 | 
            +
             | 
| 56 | 
            +
                    magic_link = Passwordless.context.url_for(
         | 
| 30 57 | 
             
                      session,
         | 
| 31 | 
            -
                       | 
| 58 | 
            +
                      action: "confirm",
         | 
| 59 | 
            +
                      id: session.to_param,
         | 
| 60 | 
            +
                      token: session.token
         | 
| 32 61 | 
             
                    )
         | 
| 62 | 
            +
             | 
| 33 63 | 
             
                    visit(magic_link)
         | 
| 34 64 | 
             
                  end
         | 
| 35 65 | 
             
                end
         | 
| @@ -37,7 +67,7 @@ module Passwordless | |
| 37 67 | 
             
            end
         | 
| 38 68 |  | 
| 39 69 | 
             
            if defined?(ActiveSupport::TestCase)
         | 
| 40 | 
            -
              ActiveSupport::TestCase.send(:include, ::Passwordless::TestHelpers:: | 
| 70 | 
            +
              ActiveSupport::TestCase.send(:include, ::Passwordless::TestHelpers::ControllerTestCase)
         | 
| 41 71 | 
             
            end
         | 
| 42 72 |  | 
| 43 73 | 
             
            if defined?(ActionDispatch::SystemTestCase)
         | 
| @@ -46,8 +76,8 @@ end | |
| 46 76 |  | 
| 47 77 | 
             
            if defined?(RSpec)
         | 
| 48 78 | 
             
              RSpec.configure do |config|
         | 
| 49 | 
            -
                config.include(::Passwordless::TestHelpers:: | 
| 50 | 
            -
                config.include(::Passwordless::TestHelpers:: | 
| 79 | 
            +
                config.include(::Passwordless::TestHelpers::ControllerTestCase, type: :controller)
         | 
| 80 | 
            +
                config.include(::Passwordless::TestHelpers::RequestTestCase, type: :request)
         | 
| 51 81 | 
             
                config.include(::Passwordless::TestHelpers::SystemTestCase, type: :system)
         | 
| 52 82 | 
             
              end
         | 
| 53 83 | 
             
            end
         | 
    
        data/lib/passwordless/version.rb
    CHANGED
    
    
    
        data/lib/passwordless.rb
    CHANGED
    
    | @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require "active_support"
         | 
| 4 4 | 
             
            require "passwordless/config"
         | 
| 5 | 
            +
            require "passwordless/context"
         | 
| 5 6 | 
             
            require "passwordless/errors"
         | 
| 6 7 | 
             
            require "passwordless/engine"
         | 
| 7 8 | 
             
            require "passwordless/token_digest"
         | 
| @@ -10,6 +11,14 @@ require "passwordless/token_digest" | |
| 10 11 | 
             
            module Passwordless
         | 
| 11 12 | 
             
              extend Configurable
         | 
| 12 13 |  | 
| 14 | 
            +
              def self.context
         | 
| 15 | 
            +
                @context ||= Context.new
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              def self.add_resource(resource, controller:, **defaults)
         | 
| 19 | 
            +
                context.resources[resource] = Resource.new(resource, controller: controller)
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 13 22 | 
             
              def self.digest(token)
         | 
| 14 23 | 
             
                TokenDigest.new(token).digest
         | 
| 15 24 | 
             
              end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: passwordless
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.1.1
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Mikkel Malmberg
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2023- | 
| 11 | 
            +
            date: 2023-11-11 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: rails
         | 
| @@ -62,6 +62,7 @@ files: | |
| 62 62 | 
             
            - lib/generators/passwordless/views_generator.rb
         | 
| 63 63 | 
             
            - lib/passwordless.rb
         | 
| 64 64 | 
             
            - lib/passwordless/config.rb
         | 
| 65 | 
            +
            - lib/passwordless/context.rb
         | 
| 65 66 | 
             
            - lib/passwordless/controller_helpers.rb
         | 
| 66 67 | 
             
            - lib/passwordless/engine.rb
         | 
| 67 68 | 
             
            - lib/passwordless/errors.rb
         |