passwordless 1.2.0 → 1.3.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: 2242a4b95f1a99d5be1b889539dc6dd9f1eda711ce616f3b090f12dd68337254
4
+ data.tar.gz: afd9ea1fd2d3b3f15f10a4772d231c53adfd524e22de00d833a9183b34d69397
5
5
  SHA512:
6
- metadata.gz: a760c9c2ade52b80be4a482abb17a2ff9c71579d07bc55eb1268980a39f69540d354bf1988bb4a7ea35d08e801042d9ff266cc2c98913b7ea5c331a1556b882c
7
- data.tar.gz: f66c443aa783a9f490dac97e4a9720ce077594e4d170ad10053bd42168033ca5c57e1b4f20288e0b5a7bd63eaac67b90b8cd52ba80ac8b9a290d2de2bfb2078e
6
+ metadata.gz: 9b2ceb4c68972744e10ac6dd56e2e863b5e229bb31053b7159a16c020a2cfa55ffe1affcbef92ca52ee03de79d4d675cb5b3f4340e1c888e064e9ab246f3e455
7
+ data.tar.gz: 7dd102d25dd4cbb60ab73d30732f78a6cd1d6757e345e3b304c9b1abe4394b6b1d883c1368bd7eaa12ecee986ecdfabf3d82cea081048694807388fc5a1bfd69
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
@@ -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
 
@@ -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
@@ -173,12 +165,36 @@ module Passwordless
173
165
  end
174
166
 
175
167
  def find_authenticatable
176
- email = passwordless_session_params[email_field].downcase.strip
177
-
178
168
  if authenticatable_class.respond_to?(:fetch_resource_for_passwordless)
179
- authenticatable_class.fetch_resource_for_passwordless(email)
169
+ authenticatable_class.fetch_resource_for_passwordless(normalized_email_param)
170
+ else
171
+ authenticatable_class.where("lower(#{email_field}) = ?", normalized_email_param).first
172
+ end
173
+ end
174
+
175
+ def normalized_email_param
176
+ passwordless_session_params[email_field].downcase.strip
177
+ end
178
+
179
+ def handle_resource_not_found
180
+ if Passwordless.config.paranoid
181
+ @resource = authenticatable_class.new(email: normalized_email_param)
182
+ @skip_after_session_save_callback = true
183
+ else
184
+ raise(
185
+ ActiveRecord::RecordNotFound,
186
+ "Couldn't find #{authenticatable_type} with email #{normalized_email_param}"
187
+ )
188
+ end
189
+ end
190
+
191
+ def call_after_session_save
192
+ return if @skip_after_session_save_callback
193
+
194
+ if Passwordless.config.after_session_save.arity == 2
195
+ Passwordless.config.after_session_save.call(@session, request)
180
196
  else
181
- authenticatable_class.where("lower(#{email_field}) = ?", email).first
197
+ Passwordless.config.after_session_save.call(@session)
182
198
  end
183
199
  end
184
200
 
@@ -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.3.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.3.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-24 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.