passwordless 1.2.0 → 1.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b88192b582d0e4f8cb601b00f2cf5d51250dbe9f747d6413728cf2e5da7ddd1b
4
- data.tar.gz: 054c733891aa4e4f98a1f684b6d6107414b4214589f76ec5aa20c7337a5b2098
3
+ metadata.gz: 6dc17e0a0f2696a647ba5f5dffaededf6494a48f4cd3393135e041c91b3955f0
4
+ data.tar.gz: b27244139169a60b25c1251321aa6576d7fbbe7e9e122cb5886041f7a60a4886
5
5
  SHA512:
6
- metadata.gz: a760c9c2ade52b80be4a482abb17a2ff9c71579d07bc55eb1268980a39f69540d354bf1988bb4a7ea35d08e801042d9ff266cc2c98913b7ea5c331a1556b882c
7
- data.tar.gz: f66c443aa783a9f490dac97e4a9720ce077594e4d170ad10053bd42168033ca5c57e1b4f20288e0b5a7bd63eaac67b90b8cd52ba80ac8b9a290d2de2bfb2078e
6
+ metadata.gz: 66be83e8a5b1f8cd06d83196d94ce42e9fe25dbbd49a650ca553c8874ac47503758c782b10831187d7005666fbe73e1468fea82a9c6cdad21dae8abe85cfca22
7
+ data.tar.gz: 266ec15bbc186604c4ab0eda90171499052fa22e11d8eeb0c8f96196fe3363ee45c42d183d76a16dc90670308a1ae9a7bc84bfbb321a8c01fca815ae7e24b6cf
data/README.md CHANGED
@@ -25,26 +25,21 @@ See [Upgrading to Passwordless 1.0](docs/upgrading_to_1_0.md) for more details.
25
25
 
26
26
  ## Usage
27
27
 
28
- Passwordless creates a single model called `Passwordless::Session`. It doesn't come with its own `User` model, it expects you to create one:
28
+ Passwordless creates a single model called `Passwordless::Session`, so it doesn't come with its own user model. Instead, it expects you to provide one, with an email field in place. If you don't yet have a user model, check out the wiki on [creating the user model](https://github.com/mikker/passwordless/wiki/Creating-the-user-model).
29
29
 
30
- ```sh
31
- $ bin/rails generate model User email
32
- ```
33
-
34
- Then specify which field on your `User` record is the email field with:
30
+ Enable Passwordless on your user model by pointing it to the email field:
35
31
 
36
32
  ```ruby
37
33
  class User < ApplicationRecord
38
- validates :email,
39
- presence: true,
40
- uniqueness: { case_sensitive: false },
41
- format: { with: URI::MailTo::EMAIL_REGEXP }
34
+ # your other code..
35
+
36
+ passwordless_with :email # <-- here! this needs to be a column in `users` table
42
37
 
43
- passwordless_with :email # <-- here!
38
+ # more of your code..
44
39
  end
45
40
  ```
46
41
 
47
- Finally, mount the engine in your routes:
42
+ Then mount the engine in your routes:
48
43
 
49
44
  ```ruby
50
45
  Rails.application.routes.draw do
@@ -75,7 +70,7 @@ class ApplicationController < ActionController::Base
75
70
  def require_user!
76
71
  return if current_user
77
72
  save_passwordless_redirect_location!(User) # <-- optional, see below
78
- redirect_to root_path, flash: { error: 'You are not worthy!' }
73
+ redirect_to root_path, alert: "You are not worthy!"
79
74
  end
80
75
  end
81
76
  ```
@@ -163,6 +158,7 @@ The default values are shown below. It's recommended to only include the ones th
163
158
  ```ruby
164
159
  Passwordless.configure do |config|
165
160
  config.default_from_address = "CHANGE_ME@example.com"
161
+ config.parent_controller = "ApplicationController"
166
162
  config.parent_mailer = "ActionMailer::Base"
167
163
  config.restrict_token_reuse = false # Can a token/link be used multiple times?
168
164
  config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.
@@ -175,6 +171,8 @@ Passwordless.configure do |config|
175
171
  config.success_redirect_path = '/' # After a user successfully signs in
176
172
  config.failure_redirect_path = '/' # After a sign in fails
177
173
  config.sign_out_redirect_path = '/' # After a user signs out
174
+
175
+ config.paranoid = false # Display email sent notice even when the resource is not found.
178
176
  end
179
177
  ```
180
178
 
@@ -239,7 +237,7 @@ class ApplicationController < ActionController::Base
239
237
  def require_user!
240
238
  return if current_user
241
239
  save_passwordless_redirect_location!(User) # <-- this one!
242
- redirect_to root_path, flash: {error: 'You are not worthy!'}
240
+ redirect_to root_path, alert: "You are not worthy!"
243
241
  end
244
242
  end
245
243
  ```
@@ -260,18 +258,6 @@ class User < ApplicationRecord
260
258
  end
261
259
  ```
262
260
 
263
- ### Claiming tokens
264
-
265
- By default, a token/magic link **can** be used more than once.
266
-
267
- To change, in `config/initializers/passwordless.rb`:
268
-
269
- ```ruby
270
- Passwordless.configure do |config|
271
- config.restrict_token_reuse = true
272
- end
273
- ```
274
-
275
261
  ## Test helpers
276
262
 
277
263
  To help with testing, a set of test helpers are provided.
@@ -4,7 +4,7 @@ require "bcrypt"
4
4
 
5
5
  module Passwordless
6
6
  # Controller for managing Passwordless sessions
7
- class SessionsController < ApplicationController
7
+ class SessionsController < Passwordless.config.parent_controller.constantize
8
8
  include ControllerHelpers
9
9
 
10
10
  helper_method :email_field
@@ -20,21 +20,11 @@ module Passwordless
20
20
  # Creates a new Session record then sends the magic link
21
21
  # redirects to sign in page with generic flash message.
22
22
  def create
23
- unless @resource = find_authenticatable
24
- raise(
25
- ActiveRecord::RecordNotFound,
26
- "Couldn't find #{authenticatable_type} with email #{passwordless_session_params[email_field]}"
27
- )
28
- end
29
-
23
+ handle_resource_not_found unless @resource = find_authenticatable
30
24
  @session = build_passwordless_session(@resource)
31
25
 
32
26
  if @session.save
33
- if Passwordless.config.after_session_save.arity == 2
34
- Passwordless.config.after_session_save.call(@session, request)
35
- else
36
- Passwordless.config.after_session_save.call(@session)
37
- end
27
+ call_after_session_save
38
28
 
39
29
  redirect_to(
40
30
  Passwordless.context.path_for(
@@ -50,6 +40,8 @@ module Passwordless
50
40
  end
51
41
 
52
42
  rescue ActiveRecord::RecordNotFound
43
+ @session = Session.new
44
+
53
45
  flash[:error] = I18n.t("passwordless.sessions.create.not_found")
54
46
  render(:new, status: :not_found)
55
47
  end
@@ -113,11 +105,11 @@ module Passwordless
113
105
  protected
114
106
 
115
107
  def passwordless_sign_out_redirect_path
116
- Passwordless.config.sign_out_redirect_path
108
+ call_or_return(Passwordless.config.sign_out_redirect_path)
117
109
  end
118
110
 
119
111
  def passwordless_failure_redirect_path
120
- Passwordless.config.failure_redirect_path
112
+ call_or_return(Passwordless.config.failure_redirect_path)
121
113
  end
122
114
 
123
115
  def passwordless_query_redirect_path
@@ -128,10 +120,14 @@ module Passwordless
128
120
  end
129
121
 
130
122
  def passwordless_success_redirect_path
131
- return Passwordless.config.success_redirect_path unless Passwordless.config.redirect_back_after_sign_in
123
+ success_redirect_path = call_or_return(Passwordless.config.success_redirect_path)
132
124
 
133
- session_redirect_url = reset_passwordless_redirect_location!(authenticatable_class)
134
- passwordless_query_redirect_path || session_redirect_url || Passwordless.config.success_redirect_path
125
+ if Passwordless.config.redirect_back_after_sign_in
126
+ session_redirect_url = reset_passwordless_redirect_location!(authenticatable_class)
127
+ return passwordless_query_redirect_path || session_redirect_url || success_redirect_path
128
+ end
129
+
130
+ success_redirect_path
135
131
  end
136
132
 
137
133
  private
@@ -172,13 +168,41 @@ module Passwordless
172
168
  authenticatable_type.constantize
173
169
  end
174
170
 
175
- def find_authenticatable
176
- email = passwordless_session_params[email_field].downcase.strip
171
+ def call_or_return(value)
172
+ value.respond_to?(:call) ? value.call : value
173
+ end
177
174
 
175
+ def find_authenticatable
178
176
  if authenticatable_class.respond_to?(:fetch_resource_for_passwordless)
179
- authenticatable_class.fetch_resource_for_passwordless(email)
177
+ authenticatable_class.fetch_resource_for_passwordless(normalized_email_param)
178
+ else
179
+ authenticatable_class.where("lower(#{email_field}) = ?", normalized_email_param).first
180
+ end
181
+ end
182
+
183
+ def normalized_email_param
184
+ passwordless_session_params[email_field].downcase.strip
185
+ end
186
+
187
+ def handle_resource_not_found
188
+ if Passwordless.config.paranoid
189
+ @resource = authenticatable_class.new(email: normalized_email_param)
190
+ @skip_after_session_save_callback = true
191
+ else
192
+ raise(
193
+ ActiveRecord::RecordNotFound,
194
+ "Couldn't find #{authenticatable_type} with email #{normalized_email_param}"
195
+ )
196
+ end
197
+ end
198
+
199
+ def call_after_session_save
200
+ return if @skip_after_session_save_callback
201
+
202
+ if Passwordless.config.after_session_save.arity == 2
203
+ Passwordless.config.after_session_save.call(@session, request)
180
204
  else
181
- authenticatable_class.where("lower(#{email_field}) = ?", email).first
205
+ Passwordless.config.after_session_save.call(@session)
182
206
  end
183
207
  end
184
208
 
@@ -3,7 +3,7 @@
3
3
  module Passwordless
4
4
  # The mailer responsible for sending Passwordless' mails.
5
5
  class Mailer < Passwordless.config.parent_mailer.constantize
6
- default from: Passwordless.config.default_from_address
6
+ default(from: Passwordless.config.default_from_address) if Passwordless.config.default_from_address
7
7
 
8
8
  # Sends a token and a magic link
9
9
  #
@@ -17,7 +17,8 @@ module Passwordless
17
17
  session,
18
18
  action: "confirm",
19
19
  id: session.to_param,
20
- token: @token
20
+ token: @token,
21
+ **default_url_options
21
22
  )
22
23
 
23
24
  email_field = session.authenticatable.class.passwordless_email_field
@@ -1,12 +1,12 @@
1
1
  <%= form_with(model: @session, url: url_for(action: 'new'), data: { turbo: 'false' }) do |f| %>
2
2
  <% email_field_name = :"passwordless[#{email_field}]" %>
3
3
  <%= f.label email_field_name,
4
- t("passwordless.sessions.new.email.label"),
5
- for: "passwordless_#{email_field}" %>
4
+ t("passwordless.sessions.new.email.label"),
5
+ for: "passwordless_#{email_field}" %>
6
6
  <%= email_field_tag email_field_name,
7
- params.fetch(email_field_name, nil),
8
- required: true,
9
- autofocus: true,
10
- placeholder: t("passwordless.sessions.new.email.placeholder") %>
7
+ params.fetch(email_field_name, nil),
8
+ required: true,
9
+ autofocus: true,
10
+ placeholder: t("passwordless.sessions.new.email.placeholder") %>
11
11
  <%= f.submit t("passwordless.sessions.new.submit") %>
12
12
  <% end %>
@@ -1,5 +1,8 @@
1
1
  <%= form_with(model: @session, url: url_for(action: 'update'), scope: 'passwordless', method: 'patch', data: { turbo: false }) do |f| %>
2
- <%= f.label :token, autocomplete: "off" %>
3
- <%= f.text_field :token %>
2
+ <%= f.label :token %>
3
+ <%= f.text_field :token,
4
+ required: true,
5
+ autofocus: true,
6
+ autocomplete: "one-time-code" %>
4
7
  <%= f.submit t(".confirm") %>
5
8
  <% end %>
@@ -28,6 +28,7 @@ module Passwordless
28
28
  include Options
29
29
 
30
30
  option :default_from_address, default: "CHANGE_ME@example.com"
31
+ option :parent_controller, default: "ApplicationController"
31
32
  option :parent_mailer, default: "ActionMailer::Base"
32
33
  option :restrict_token_reuse, default: true
33
34
  option :token_generator, default: ShortTokenGenerator.new
@@ -48,6 +49,8 @@ module Passwordless
48
49
  end
49
50
  )
50
51
 
52
+ option :paranoid, default: false
53
+
51
54
  def initialize
52
55
  set_defaults!
53
56
  end
@@ -43,21 +43,22 @@ module Passwordless
43
43
  end
44
44
 
45
45
  module SystemTestCase
46
- def passwordless_sign_out(cls = nil)
46
+ def passwordless_sign_out(cls = nil, only_path: false)
47
47
  cls ||= "User".constantize
48
48
  resource = cls.model_name.to_s.tableize
49
49
 
50
- visit(Passwordless.context.url_for(resource, action: "destroy"))
50
+ visit(Passwordless.context.url_for(resource, action: "destroy", only_path: only_path))
51
51
  end
52
52
 
53
- def passwordless_sign_in(resource)
53
+ def passwordless_sign_in(resource, only_path: false)
54
54
  session = Passwordless::Session.create!(authenticatable: resource)
55
55
 
56
56
  magic_link = Passwordless.context.url_for(
57
57
  session,
58
58
  action: "confirm",
59
59
  id: session.to_param,
60
- token: session.token
60
+ token: session.token,
61
+ only_path: only_path
61
62
  )
62
63
 
63
64
  visit(magic_link)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Passwordless
4
4
  # :nodoc:
5
- VERSION = "1.2.0"
5
+ VERSION = "1.4.0"
6
6
  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.2.0
4
+ version: 1.4.0
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-12-05 00:00:00.000000000 Z
11
+ date: 2024-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -91,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  - !ruby/object:Gem::Version
92
92
  version: '0'
93
93
  requirements: []
94
- rubygems_version: 3.4.22
94
+ rubygems_version: 3.5.5
95
95
  signing_key:
96
96
  specification_version: 4
97
97
  summary: Add authentication to your app without all the ickyness of passwords.