passwordless 1.1.1 → 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: 399b4c9ace35d6780f2f22cf50c47c2717ead7c9b6b999d64120b7e574b465ec
4
- data.tar.gz: 5e7e35e3eab146d5e93c7588c49552fb3f46d27ed27e183ba7367e755ecfccfd
3
+ metadata.gz: 2242a4b95f1a99d5be1b889539dc6dd9f1eda711ce616f3b090f12dd68337254
4
+ data.tar.gz: afd9ea1fd2d3b3f15f10a4772d231c53adfd524e22de00d833a9183b34d69397
5
5
  SHA512:
6
- metadata.gz: d9fe89a70ba2f35cc417f03f5ca23a9a1bb1bc967d3c80828aba6a60a643e8de2d880f7bb038e1f17b60e80f250117df5ad41df1a15c96c9dcfa8317767ddd2f
7
- data.tar.gz: 315a2b802c1b21cf08ad61003c6b15d2d8eedfcfb7c440c58fbc7991bd73c254261dc3e7535d25469a01522e8476cb0ca9fdede7af3d15f0eaed14ff1b80ce2a
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
@@ -146,7 +141,13 @@ passwordless_for :users, at: '/', as: :auth
146
141
  ```
147
142
 
148
143
  Also be sure to
149
- [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views).
144
+ [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views) and tell the routes as well:
145
+
146
+ ```ruby
147
+ # config/application.rb for example:
148
+ config.action_mailer.default_url_options = {host: "www.example.com"}
149
+ routes.default_url_options[:host] ||= "www.example.com"
150
+ ```
150
151
 
151
152
  ## Configuration
152
153
 
@@ -157,6 +158,7 @@ The default values are shown below. It's recommended to only include the ones th
157
158
  ```ruby
158
159
  Passwordless.configure do |config|
159
160
  config.default_from_address = "CHANGE_ME@example.com"
161
+ config.parent_controller = "ApplicationController"
160
162
  config.parent_mailer = "ActionMailer::Base"
161
163
  config.restrict_token_reuse = false # Can a token/link be used multiple times?
162
164
  config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.
@@ -169,6 +171,8 @@ Passwordless.configure do |config|
169
171
  config.success_redirect_path = '/' # After a user successfully signs in
170
172
  config.failure_redirect_path = '/' # After a sign in fails
171
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.
172
176
  end
173
177
  ```
174
178
 
@@ -254,18 +258,6 @@ class User < ApplicationRecord
254
258
  end
255
259
  ```
256
260
 
257
- ### Claiming tokens
258
-
259
- By default, a token/magic link **can** be used more than once.
260
-
261
- To change, in `config/initializers/passwordless.rb`:
262
-
263
- ```ruby
264
- Passwordless.configure do |config|
265
- config.restrict_token_reuse = true
266
- end
267
- ```
268
-
269
261
  ## Test helpers
270
262
 
271
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
@@ -137,6 +129,8 @@ module Passwordless
137
129
  private
138
130
 
139
131
  def artificially_slow_down_brute_force_attacks(token)
132
+ return unless Passwordless.config.combat_brute_force_attacks
133
+
140
134
  # Make it "slow" on purpose to make brute-force attacks more of a hassle
141
135
  BCrypt::Password.create(token)
142
136
  end
@@ -171,12 +165,36 @@ module Passwordless
171
165
  end
172
166
 
173
167
  def find_authenticatable
174
- email = passwordless_session_params[email_field].downcase.strip
175
-
176
168
  if authenticatable_class.respond_to?(:fetch_resource_for_passwordless)
177
- 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)
178
196
  else
179
- authenticatable_class.where("lower(#{email_field}) = ?", email).first
197
+ Passwordless.config.after_session_save.call(@session)
180
198
  end
181
199
  end
182
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,9 +28,11 @@ 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
35
+ option :combat_brute_force_attacks, default: !Rails.env.test?
34
36
 
35
37
  option :expires_at, default: lambda { 1.year.from_now }
36
38
  option :timeout_at, default: lambda { 10.minutes.from_now }
@@ -47,6 +49,8 @@ module Passwordless
47
49
  end
48
50
  )
49
51
 
52
+ option :paranoid, default: false
53
+
50
54
  def initialize
51
55
  set_defaults!
52
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.1.1"
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.1.1
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-11-11 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.21
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.