devise-passwordless 0.2.0 → 0.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 +4 -4
- data/README.md +22 -11
- data/devise-passwordless.gemspec +1 -1
- data/lib/devise/models/magic_link_authenticatable.rb +8 -1
- data/lib/devise/passwordless/version.rb +1 -1
- data/lib/devise/strategies/magic_link_authenticatable.rb +16 -6
- data/lib/generators/devise/passwordless/install_generator.rb +39 -5
- metadata +3 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15837103c0aa116898d6b9847813c8ddf11d5e9e42fb3592322521b1107a96b0
|
4
|
+
data.tar.gz: e4d86d9d88ed12151a91490d9360440d9bbf68570f9fc7fc9a49ecadffa17d00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a834c844bdc0e1cc638140ae5081f7714d24637837c7b5f84b6c14f32819c5e41316b1a29b57ac65f2d8e76781a8ce20a55b51dbb8f30489bdb4af94f131a25b
|
7
|
+
data.tar.gz: 9b5703b2c793916342f9f044ea81ec1a626b0450cab2c0d91f4c56bc811ed7ccfee736db88715bae4d920729b3b56bbafaac781062dbf44b9cb016d73c15e7ce
|
data/README.md
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
|
3
3
|
A passwordless a.k.a. "magic link" login strategy for [Devise][]
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* No database migrations / state needed - links are stateless encrypted tokens thanks to Rails's MessageEncryptor
|
8
|
+
* Magic links are sent from your app - not a mounted Rails engine - so path and URL helpers work as expected
|
9
|
+
* All the goodness of Devise!
|
6
10
|
|
7
11
|
## Installation
|
8
12
|
|
@@ -30,9 +34,9 @@ See the [customization section](#customization) for details on what gets install
|
|
30
34
|
|
31
35
|
## Usage
|
32
36
|
|
33
|
-
This gem adds
|
37
|
+
This gem adds a `:magic_link_authenticatable` strategy that can be used in your Devise models for passwordless authentication. This strategy plays well with most other Devise strategies (see [*notes on other Devise strategies*](#notes-on-other-devise-strategies)).
|
34
38
|
|
35
|
-
For example, for a User model, you
|
39
|
+
For example, for a User model, you can now do this (other strategies listed are optional and not exhaustive):
|
36
40
|
|
37
41
|
```ruby
|
38
42
|
# app/models/user.rb
|
@@ -51,7 +55,7 @@ Then, you'll need to generate two controllers to modify Devise's default session
|
|
51
55
|
$ rails g devise:passwordless:controller User
|
52
56
|
```
|
53
57
|
|
54
|
-
Then,
|
58
|
+
Then, set up your Devise routes like so to use these controllers:
|
55
59
|
|
56
60
|
```ruby
|
57
61
|
# config/routes.rb
|
@@ -63,13 +67,15 @@ Rails.application.routes.draw do
|
|
63
67
|
end
|
64
68
|
```
|
65
69
|
|
66
|
-
Finally, you'll want to update Devise's generated views to remove references to passwords
|
70
|
+
Finally, you'll want to update Devise's generated views to remove references to passwords, since you don't need them any more!
|
67
71
|
|
68
72
|
These files/directories can be deleted entirely:
|
69
73
|
|
70
|
-
|
71
|
-
|
72
|
-
|
74
|
+
```
|
75
|
+
app/views/devise/passwords
|
76
|
+
app/views/devise/mailer/password_change.html.erb
|
77
|
+
app/views/devise/mailer/reset_password_instructions.html.erb
|
78
|
+
```
|
73
79
|
|
74
80
|
And these should be edited to remove password references:
|
75
81
|
|
@@ -98,6 +104,11 @@ config.mailer = "PasswordlessMailer"
|
|
98
104
|
# key will render invalid all existing passwordless login tokens. You can
|
99
105
|
# generate your own secret value with e.g. `rake secret`
|
100
106
|
# config.passwordless_secret_key = nil
|
107
|
+
|
108
|
+
# When using the :trackable module, set to true to consider magic link tokens
|
109
|
+
# generated before the user's current sign in time to be expired. In other words,
|
110
|
+
# each time you sign in, all existing magic links will be considered invalid.
|
111
|
+
# config.passwordless_expire_old_tokens_on_sign_in = false
|
101
112
|
```
|
102
113
|
|
103
114
|
To customize the magic link email subject line and other status and error messages, modify these values in `config/locales/devise.en.yml`:
|
@@ -109,17 +120,17 @@ en:
|
|
109
120
|
not_found_in_database: "Could not find a user for that email address"
|
110
121
|
magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account."
|
111
122
|
failure:
|
112
|
-
|
123
|
+
magic_link_invalid: "Invalid or expired login link."
|
113
124
|
mailer:
|
114
125
|
magic_link:
|
115
|
-
subject: "Here's your magic login link
|
126
|
+
subject: "Here's your magic login link ✨"
|
116
127
|
```
|
117
128
|
|
118
129
|
To customize the magic link email body, edit `app/views/devise/mailer/magic_link.html.erb`
|
119
130
|
|
120
131
|
### Notes on other Devise strategies
|
121
132
|
|
122
|
-
If using the `:rememberable` strategy for "remember me" functionality, you'll need to add a `remember_token` column to your resource, as by default
|
133
|
+
If using the `:rememberable` strategy for "remember me" functionality, you'll need to add a `remember_token` column to your resource, as by default that strategy assumes you're using a password auth strategy and relies on comparing the password's salt to validate cookies:
|
123
134
|
|
124
135
|
```ruby
|
125
136
|
change_table :users do |t|
|
data/devise-passwordless.gemspec
CHANGED
@@ -35,9 +35,9 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.bindir = "exe"
|
36
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
37
|
spec.require_paths = ["lib"]
|
38
|
+
spec.required_ruby_version = ">= 2.1.0"
|
38
39
|
|
39
40
|
spec.add_dependency "devise"
|
40
|
-
spec.add_dependency "rails"
|
41
41
|
|
42
42
|
spec.add_development_dependency "bundler", "~> 1.17"
|
43
43
|
spec.add_development_dependency "rake", "~> 10.0"
|
@@ -43,7 +43,11 @@ module Devise
|
|
43
43
|
find_for_authentication(conditions)
|
44
44
|
end
|
45
45
|
|
46
|
-
Devise::Models.config(self,
|
46
|
+
Devise::Models.config(self,
|
47
|
+
:passwordless_login_within,
|
48
|
+
:passwordless_secret_key,
|
49
|
+
:passwordless_expire_old_tokens_on_sign_in
|
50
|
+
)
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -55,4 +59,7 @@ module Devise
|
|
55
59
|
|
56
60
|
mattr_accessor :passwordless_secret_key
|
57
61
|
@@passwordless_secret_key = nil
|
62
|
+
|
63
|
+
mattr_accessor :passwordless_expire_old_tokens_on_sign_in
|
64
|
+
@@passwordless_expire_old_tokens_on_sign_in = false
|
58
65
|
end
|
@@ -20,21 +20,31 @@ module Devise
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def authenticate!
|
23
|
-
|
24
|
-
|
25
|
-
x["data"]
|
23
|
+
begin
|
24
|
+
data = Devise::Passwordless::LoginToken.decode(self.token)
|
26
25
|
rescue Devise::Passwordless::LoginToken::InvalidOrExpiredTokenError
|
27
|
-
fail!(:
|
26
|
+
fail!(:magic_link_invalid)
|
28
27
|
return
|
29
28
|
end
|
30
29
|
|
31
|
-
resource = mapping.to.find_by(id: data["resource"]["key"])
|
30
|
+
resource = mapping.to.find_by(id: data["data"]["resource"]["key"])
|
31
|
+
|
32
|
+
if resource && Devise.passwordless_expire_old_tokens_on_sign_in
|
33
|
+
if (last_login = resource.try(:current_sign_in_at))
|
34
|
+
token_created_at = ActiveSupport::TimeZone["UTC"].at(data["created_at"])
|
35
|
+
if token_created_at < last_login
|
36
|
+
fail!(:magic_link_invalid)
|
37
|
+
return
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
32
42
|
if validate(resource)
|
33
43
|
remember_me(resource)
|
34
44
|
resource.after_magic_link_authentication
|
35
45
|
success!(resource)
|
36
46
|
else
|
37
|
-
fail!(:
|
47
|
+
fail!(:magic_link_invalid)
|
38
48
|
end
|
39
49
|
end
|
40
50
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "psych"
|
1
2
|
require "rails/generators"
|
2
3
|
require "yaml"
|
3
4
|
|
@@ -22,6 +23,11 @@ module Devise::Passwordless
|
|
22
23
|
# key will render invalid all existing passwordless login tokens. You can
|
23
24
|
# generate your own secret value with e.g. `rake secret`
|
24
25
|
# config.passwordless_secret_key = nil
|
26
|
+
|
27
|
+
# When using the :trackable module, set to true to consider magic link tokens
|
28
|
+
# generated before the user's current sign in time to be expired. In other words,
|
29
|
+
# each time you sign in, all existing magic links will be considered invalid.
|
30
|
+
# config.passwordless_expire_old_tokens_on_sign_in = false
|
25
31
|
CONFIG
|
26
32
|
end
|
27
33
|
end
|
@@ -60,7 +66,7 @@ module Devise::Passwordless
|
|
60
66
|
STDERR.puts "Couldn't find #{devise_yaml} - skipping patch"
|
61
67
|
return
|
62
68
|
end
|
63
|
-
|
69
|
+
default_config = {
|
64
70
|
en: {
|
65
71
|
devise: {
|
66
72
|
passwordless: {
|
@@ -68,17 +74,45 @@ module Devise::Passwordless
|
|
68
74
|
magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account.",
|
69
75
|
},
|
70
76
|
failure: {
|
71
|
-
|
77
|
+
magic_link_invalid: "Invalid or expired login link.",
|
72
78
|
},
|
73
79
|
mailer: {
|
74
80
|
magic_link: {
|
75
|
-
subject: "Here's your login link
|
81
|
+
subject: "Here's your magic login link ✨",
|
76
82
|
},
|
77
83
|
}
|
78
84
|
}
|
79
85
|
}
|
80
|
-
}
|
81
|
-
|
86
|
+
}
|
87
|
+
merged_config = config.deep_merge(default_config.deep_stringify_keys)
|
88
|
+
File.open(devise_yaml, "w") do |f|
|
89
|
+
f.write(force_double_quote_yaml(merged_config.to_yaml))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# https://github.com/ruby/psych/issues/322#issuecomment-328408276
|
96
|
+
def force_double_quote_yaml(yaml_str)
|
97
|
+
ast = Psych.parse_stream(yaml_str)
|
98
|
+
|
99
|
+
# First pass, quote everything
|
100
|
+
ast.grep(Psych::Nodes::Scalar).each do |node|
|
101
|
+
node.plain = false
|
102
|
+
node.quoted = true
|
103
|
+
node.style = Psych::Nodes::Scalar::DOUBLE_QUOTED
|
104
|
+
end
|
105
|
+
|
106
|
+
# Second pass, unquote keys
|
107
|
+
ast.grep(Psych::Nodes::Mapping).each do |node|
|
108
|
+
node.children.each_slice(2) do |k, _|
|
109
|
+
k.plain = true
|
110
|
+
k.quoted = false
|
111
|
+
k.style = Psych::Nodes::Scalar::ANY
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
ast.yaml(nil, {line_width: -1})
|
82
116
|
end
|
83
117
|
end
|
84
118
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devise-passwordless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Abe Voelker
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: devise
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: rails
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: bundler
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,7 +106,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
106
|
requirements:
|
121
107
|
- - ">="
|
122
108
|
- !ruby/object:Gem::Version
|
123
|
-
version:
|
109
|
+
version: 2.1.0
|
124
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
111
|
requirements:
|
126
112
|
- - ">="
|