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 +4 -4
- data/README.md +12 -26
- data/app/controllers/passwordless/sessions_controller.rb +46 -22
- data/app/mailers/passwordless/mailer.rb +3 -2
- data/app/views/passwordless/sessions/new.html.erb +6 -6
- data/app/views/passwordless/sessions/show.html.erb +5 -2
- data/lib/passwordless/config.rb +3 -0
- data/lib/passwordless/test_helpers.rb +5 -4
- data/lib/passwordless/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6dc17e0a0f2696a647ba5f5dffaededf6494a48f4cd3393135e041c91b3955f0
|
|
4
|
+
data.tar.gz: b27244139169a60b25c1251321aa6576d7fbbe7e9e122cb5886041f7a60a4886
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
38
|
+
# more of your code..
|
|
44
39
|
end
|
|
45
40
|
```
|
|
46
41
|
|
|
47
|
-
|
|
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,
|
|
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,
|
|
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 <
|
|
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
|
-
|
|
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
|
-
|
|
123
|
+
success_redirect_path = call_or_return(Passwordless.config.success_redirect_path)
|
|
132
124
|
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
176
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
5
|
-
|
|
4
|
+
t("passwordless.sessions.new.email.label"),
|
|
5
|
+
for: "passwordless_#{email_field}" %>
|
|
6
6
|
<%= email_field_tag email_field_name,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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 %>
|
data/lib/passwordless/config.rb
CHANGED
|
@@ -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)
|
data/lib/passwordless/version.rb
CHANGED
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.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:
|
|
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.
|
|
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.
|