passwordless 1.1.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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.