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