devise-passwordless 0.1.0 → 0.6.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/.gitignore +1 -0
- data/Gemfile +9 -0
- data/README.md +198 -19
- data/devise-passwordless.gemspec +1 -5
- data/lib/devise/models/{email_authenticatable.rb → magic_link_authenticatable.rb} +19 -7
- data/lib/devise/passwordless.rb +1 -1
- data/lib/devise/passwordless/login_token.rb +1 -1
- data/lib/devise/passwordless/mailer.rb +7 -5
- data/lib/devise/passwordless/version.rb +1 -1
- data/lib/devise/strategies/{email_authenticatable.rb → magic_link_authenticatable.rb} +22 -13
- data/lib/generators/devise/passwordless/install_generator.rb +102 -15
- data/lib/generators/devise/passwordless/templates/magic_links_controller.rb.erb +34 -0
- data/lib/generators/devise/passwordless/templates/sessions_controller.rb.erb +34 -0
- metadata +8 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c015970325061c0bd86aac15d0a2a90e200cd036cbe85249503d16881fb07d9a
|
4
|
+
data.tar.gz: 562b24b072376547b0e4bb6d503924dc58ad6d404a8e01397751af52e7a318cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9bd36c841f32e9e017e0ae672636ef0e2708064b7d10b9e001af148f3e6fbeee3b3ee117a21ee1947c3d91f7a036b9e33079f28f15d582681d75bdac11d995d
|
7
|
+
data.tar.gz: ab507245047decac1f9fbeb871dc54e7bef7e056c5c065550dcdedb1082ecf7db3c0caf911346b83ac93b636b2512b4735bdecf7754b9079327427b4160854f6
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -4,3 +4,12 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
|
4
4
|
|
5
5
|
# Specify your gem's dependencies in devise-passwordless.gemspec
|
6
6
|
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 10.0"
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
gem "rspec", "~> 3.0"
|
12
|
+
gem "generator_spec"
|
13
|
+
gem "timecop"
|
14
|
+
gem "pry"
|
15
|
+
end
|
data/README.md
CHANGED
@@ -1,50 +1,223 @@
|
|
1
1
|
# Devise::Passwordless
|
2
2
|
|
3
|
-
A passwordless login strategy for [Devise][]
|
3
|
+
A passwordless a.k.a. "magic link" login strategy for [Devise][]
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* No special database migrations needed - magic links are stateless encrypted tokens
|
8
|
+
* Magic links are sent from your app - not a mounted Rails engine - so path and URL helpers work as expected
|
9
|
+
* [Supports multiple user (resource) types](#multiple-user-resource-types)
|
10
|
+
* All the goodness of Devise!
|
4
11
|
|
5
12
|
## Installation
|
6
13
|
|
7
|
-
|
14
|
+
First, install and set up [Devise][].
|
15
|
+
|
16
|
+
Then add this gem to your application's Gemfile:
|
8
17
|
|
9
18
|
```ruby
|
10
19
|
gem "devise-passwordless"
|
11
20
|
```
|
12
21
|
|
13
|
-
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
```
|
25
|
+
$ bundle install
|
26
|
+
```
|
27
|
+
|
28
|
+
Finally, run the install generator:
|
14
29
|
|
15
30
|
```
|
16
|
-
rails g devise:passwordless:install
|
31
|
+
$ rails g devise:passwordless:install
|
32
|
+
```
|
33
|
+
|
34
|
+
See the [customization section](#customization) for details on what gets installed and how to configure and customize.
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
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)).
|
39
|
+
|
40
|
+
For example, if your Devise model is User, enable the strategy like this:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
# app/models/user.rb
|
44
|
+
class User < ApplicationRecord
|
45
|
+
devise :magic_link_authenticatable #, :registerable, :rememberable, ...
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
Then, you'll need to set up your Devise routes like so to use the passwordless controllers to modify Devise's default session create logic and to handle processing magic links:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
# config/routes.rb
|
53
|
+
Rails.application.routes.draw do
|
54
|
+
devise_for :users,
|
55
|
+
controllers: { sessions: "devise/passwordless/sessions" }
|
56
|
+
devise_scope :user do
|
57
|
+
get "/users/magic_link",
|
58
|
+
to: "devise/passwordless/magic_links#show",
|
59
|
+
as: "users_magic_link"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Finally, you'll want to update Devise's generated views to remove references to passwords, since you don't need them any more!
|
65
|
+
|
66
|
+
These files/directories can be deleted entirely:
|
67
|
+
|
68
|
+
```
|
69
|
+
app/views/devise/passwords
|
70
|
+
app/views/devise/mailer/password_change.html.erb
|
71
|
+
app/views/devise/mailer/reset_password_instructions.html.erb
|
72
|
+
```
|
73
|
+
|
74
|
+
And these should be edited to remove password references:
|
75
|
+
|
76
|
+
* `app/views/devise/registrations/new.html.erb`
|
77
|
+
* Delete fields `:password` and `:password_confirmation`
|
78
|
+
* `app/views/devise/registrations/edit.html.erb`
|
79
|
+
* Delete fields `:password`, `:password_confirmation`, `:current_password`
|
80
|
+
* `app/views/devise/sessions/new.html.erb`
|
81
|
+
* Delete field `:password`
|
82
|
+
|
83
|
+
#### Manually sending magic links
|
84
|
+
|
85
|
+
You can very easily send a magic link at any point like so:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
remember_me = true
|
89
|
+
User.send_magic_link(remember_me)
|
90
|
+
```
|
91
|
+
|
92
|
+
## Customization
|
93
|
+
|
94
|
+
Configuration options are stored in Devise's initializer at `config/initializers/devise.rb`:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# ==> Configuration for :magic_link_authenticatable
|
98
|
+
|
99
|
+
# Need to use a custom Devise mailer in order to send magic links
|
100
|
+
require "devise/passwordless/mailer"
|
101
|
+
config.mailer = "Devise::Passwordless::Mailer"
|
102
|
+
|
103
|
+
# Time period after a magic login link is sent out that it will be valid for.
|
104
|
+
# config.passwordless_login_within = 20.minutes
|
105
|
+
|
106
|
+
# The secret key used to generate passwordless login tokens. The default value
|
107
|
+
# is nil, which means defer to Devise's `secret_key` config value. Changing this
|
108
|
+
# key will render invalid all existing passwordless login tokens. You can
|
109
|
+
# generate your own secret value with e.g. `rake secret`
|
110
|
+
# config.passwordless_secret_key = nil
|
111
|
+
|
112
|
+
# When using the :trackable module, set to true to consider magic link tokens
|
113
|
+
# generated before the user's current sign in time to be expired. In other words,
|
114
|
+
# each time you sign in, all existing magic links will be considered invalid.
|
115
|
+
# config.passwordless_expire_old_tokens_on_sign_in = false
|
17
116
|
```
|
18
117
|
|
19
|
-
|
118
|
+
To customize the magic link email subject line and other status and error messages, modify these values in `config/locales/devise.en.yml`:
|
20
119
|
|
21
120
|
```yaml
|
22
121
|
en:
|
23
122
|
devise:
|
123
|
+
passwordless:
|
124
|
+
not_found_in_database: "Could not find a user for that email address"
|
125
|
+
magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account."
|
24
126
|
failure:
|
25
|
-
|
127
|
+
magic_link_invalid: "Invalid or expired login link."
|
26
128
|
mailer:
|
27
|
-
|
28
|
-
subject: "Here's your magic link"
|
129
|
+
magic_link:
|
130
|
+
subject: "Here's your magic login link ✨"
|
29
131
|
```
|
30
132
|
|
31
|
-
|
133
|
+
To customize the magic link email body, edit `app/views/devise/mailer/magic_link.html.erb`
|
134
|
+
|
135
|
+
### Multiple user (resource) types
|
32
136
|
|
33
|
-
|
137
|
+
Devise supports multiple resource types, so we do too.
|
34
138
|
|
35
|
-
For example,
|
139
|
+
For example, if you have a User and Admin model, enable the `:magic_link_authenticatable` strategy for each:
|
36
140
|
|
37
141
|
```ruby
|
142
|
+
# app/models/user.rb
|
38
143
|
class User < ApplicationRecord
|
39
|
-
devise :
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
144
|
+
devise :magic_link_authenticatable # , :registerable, :rememberable, ...
|
145
|
+
end
|
146
|
+
|
147
|
+
# app/models/admin.rb
|
148
|
+
class Admin < ApplicationRecord
|
149
|
+
devise :magic_link_authenticatable # , :registerable, :rememberable, ...
|
44
150
|
end
|
45
151
|
```
|
46
152
|
|
47
|
-
|
153
|
+
Then just set up your routes like this:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
# config/routes.rb
|
157
|
+
Rails.application.routes.draw do
|
158
|
+
devise_for :users,
|
159
|
+
controllers: { sessions: "devise/passwordless/sessions" }
|
160
|
+
devise_scope :user do
|
161
|
+
get "/users/magic_link",
|
162
|
+
to: "devise/passwordless/magic_links#show",
|
163
|
+
as: "users_magic_link"
|
164
|
+
end
|
165
|
+
devise_for :admins,
|
166
|
+
controllers: { sessions: "devise/passwordless/sessions" }
|
167
|
+
devise_scope :admin do
|
168
|
+
get "/admins/magic_link",
|
169
|
+
to: "devise/passwordless/magic_links#show",
|
170
|
+
as: "admins_magic_link"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
```
|
174
|
+
|
175
|
+
And that's it!
|
176
|
+
|
177
|
+
Messaging can be customized per-resource using [Devise's usual I18n support][devise-i18n]:
|
178
|
+
|
179
|
+
```yaml
|
180
|
+
en:
|
181
|
+
devise:
|
182
|
+
passwordless:
|
183
|
+
user:
|
184
|
+
not_found_in_database: "Could not find a USER for that email address"
|
185
|
+
magic_link_sent: "A USER login link has been sent to your email address. Please follow the link to log in to your account."
|
186
|
+
admin:
|
187
|
+
not_found_in_database: "Could not find an ADMIN for that email address"
|
188
|
+
magic_link_sent: "An ADMIN login link has been sent to your email address. Please follow the link to log in to your account."
|
189
|
+
failure:
|
190
|
+
user:
|
191
|
+
magic_link_invalid: "Invalid or expired USER login link."
|
192
|
+
admin:
|
193
|
+
magic_link_invalid: "Invalid or expired ADMIN login link."
|
194
|
+
mailer:
|
195
|
+
magic_link:
|
196
|
+
user_subject: "Here's your USER magic login link ✨"
|
197
|
+
admin_subject: "Here's your ADMIN magic login link ✨"
|
198
|
+
```
|
199
|
+
|
200
|
+
#### Scoped views
|
201
|
+
|
202
|
+
If you have multiple Devise models, some that are passwordless and some that aren't, you will probably want to enable [Devise's `scoped_views` setting](https://henrytabima.github.io/rails-setup/docs/devise/configuring-views) so that the models have different signup and login pages (since some models will need password fields and others won't).
|
203
|
+
|
204
|
+
If you need to generate fresh Devise views for your models, you can do so like so:
|
205
|
+
|
206
|
+
```
|
207
|
+
$ rails generate devise:views users
|
208
|
+
$ rails generate devise:views admins
|
209
|
+
```
|
210
|
+
|
211
|
+
Which will generate the whole set of Devise views under these paths:
|
212
|
+
|
213
|
+
```
|
214
|
+
app/views/users/
|
215
|
+
app/views/admins/
|
216
|
+
```
|
217
|
+
|
218
|
+
### Notes on other Devise strategies
|
219
|
+
|
220
|
+
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:
|
48
221
|
|
49
222
|
```ruby
|
50
223
|
change_table :users do |t|
|
@@ -52,12 +225,18 @@ change_table :users do |t|
|
|
52
225
|
end
|
53
226
|
```
|
54
227
|
|
55
|
-
|
228
|
+
If using the `:confirmable` strategy, you may want to override the default Devise behavior of requiring a fresh login after email confirmation (e.g. [this](https://stackoverflow.com/a/39010334/215168) or [this](https://stackoverflow.com/a/25865526/215168) approach). Otherwise, users will have to get a fresh login link after confirming their email, which makes little sense if they just confirmed they own the email address.
|
229
|
+
|
230
|
+
## Alternatives
|
231
|
+
|
232
|
+
Other Ruby libraries that offer passwordless authentication:
|
56
233
|
|
57
|
-
|
234
|
+
* [passwordless](https://github.com/mikker/passwordless)
|
235
|
+
* [magic-link](https://github.com/dvanderbeek/magic-link)
|
58
236
|
|
59
237
|
## License
|
60
238
|
|
61
239
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
62
240
|
|
63
241
|
[Devise]: https://github.com/heartcombo/devise
|
242
|
+
[devise-i18n]: https://github.com/heartcombo/devise#i18n
|
data/devise-passwordless.gemspec
CHANGED
@@ -35,11 +35,7 @@ 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
|
-
|
42
|
-
spec.add_development_dependency "bundler", "~> 1.17"
|
43
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
44
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
45
41
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require 'devise/strategies/
|
1
|
+
require 'devise/strategies/magic_link_authenticatable'
|
2
2
|
|
3
3
|
module Devise
|
4
4
|
module Models
|
5
|
-
module
|
5
|
+
module MagicLinkAuthenticatable
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
def password_required?
|
@@ -14,31 +14,40 @@ module Devise
|
|
14
14
|
nil
|
15
15
|
end
|
16
16
|
|
17
|
+
def send_magic_link(remember_me)
|
18
|
+
token = Devise::Passwordless::LoginToken.encode(self)
|
19
|
+
send_devise_notification(:magic_link, token, remember_me, {})
|
20
|
+
end
|
21
|
+
|
17
22
|
# A callback initiated after successfully authenticating. This can be
|
18
23
|
# used to insert your own logic that is only run after the user successfully
|
19
24
|
# authenticates.
|
20
25
|
#
|
21
26
|
# Example:
|
22
27
|
#
|
23
|
-
# def
|
28
|
+
# def after_magic_link_authentication
|
24
29
|
# self.update_attribute(:invite_code, nil)
|
25
30
|
# end
|
26
31
|
#
|
27
|
-
def
|
32
|
+
def after_magic_link_authentication
|
28
33
|
end
|
29
34
|
|
30
35
|
protected
|
31
36
|
|
32
37
|
module ClassMethods
|
33
38
|
# We assume this method already gets the sanitized values from the
|
34
|
-
#
|
39
|
+
# MagicLinkAuthenticatable strategy. If you are using this method on
|
35
40
|
# your own, be sure to sanitize the conditions hash to only include
|
36
41
|
# the proper fields.
|
37
|
-
def
|
42
|
+
def find_for_magic_link_authentication(conditions)
|
38
43
|
find_for_authentication(conditions)
|
39
44
|
end
|
40
45
|
|
41
|
-
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
|
+
)
|
42
51
|
end
|
43
52
|
end
|
44
53
|
end
|
@@ -50,4 +59,7 @@ module Devise
|
|
50
59
|
|
51
60
|
mattr_accessor :passwordless_secret_key
|
52
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
|
53
65
|
end
|
data/lib/devise/passwordless.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Devise::Passwordless
|
4
|
+
class Mailer < Devise::Mailer
|
5
|
+
def magic_link(record, token, remember_me, opts = {})
|
6
|
+
@token = token
|
4
7
|
@remember_me = remember_me
|
5
|
-
|
6
|
-
devise_mail(record, :passwordless_link, opts)
|
8
|
+
devise_mail(record, :magic_link, opts)
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -6,7 +6,7 @@ require "devise/passwordless/login_token"
|
|
6
6
|
|
7
7
|
module Devise
|
8
8
|
module Strategies
|
9
|
-
class
|
9
|
+
class MagicLinkAuthenticatable < Authenticatable
|
10
10
|
#undef :password
|
11
11
|
#undef :password=
|
12
12
|
attr_accessor :token
|
@@ -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
|
-
resource.
|
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
|
|
@@ -52,12 +62,11 @@ module Devise
|
|
52
62
|
end
|
53
63
|
end
|
54
64
|
|
55
|
-
Warden::Strategies.add(:
|
65
|
+
Warden::Strategies.add(:magic_link_authenticatable, Devise::Strategies::MagicLinkAuthenticatable)
|
56
66
|
|
57
|
-
Devise.add_module(:
|
67
|
+
Devise.add_module(:magic_link_authenticatable, {
|
58
68
|
strategy: true,
|
59
69
|
controller: :sessions,
|
60
|
-
|
61
|
-
|
62
|
-
route: :session
|
70
|
+
route: :session,
|
71
|
+
model: "devise/models/magic_link_authenticatable",
|
63
72
|
})
|
@@ -1,41 +1,128 @@
|
|
1
|
+
require "psych"
|
1
2
|
require "rails/generators"
|
2
3
|
require "yaml"
|
3
4
|
|
4
5
|
module Devise::Passwordless
|
5
|
-
module Generators
|
6
|
-
class InstallGenerator < ::Rails::Generators::Base
|
7
|
-
desc "
|
6
|
+
module Generators # :nodoc:
|
7
|
+
class InstallGenerator < ::Rails::Generators::Base # :nodoc:
|
8
|
+
desc "Creates default install and config files for the Devise :magic_link_authenticatable strategy"
|
9
|
+
|
10
|
+
def self.default_generator_root
|
11
|
+
File.dirname(__FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_sessions_controller
|
15
|
+
template "sessions_controller.rb.erb", "app/controllers/devise/passwordless/sessions_controller.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_magic_links_controller
|
19
|
+
template "magic_links_controller.rb.erb", "app/controllers/devise/passwordless/magic_links_controller.rb"
|
20
|
+
end
|
8
21
|
|
9
22
|
def update_devise_initializer
|
10
23
|
inject_into_file 'config/initializers/devise.rb', before: /^end$/ do <<~'CONFIG'.indent(2)
|
11
24
|
|
12
|
-
# ==> Configuration for :
|
25
|
+
# ==> Configuration for :magic_link_authenticatable
|
26
|
+
|
27
|
+
# Need to use a custom Devise mailer in order to send magic links
|
28
|
+
require "devise/passwordless/mailer"
|
29
|
+
config.mailer = "Devise::Passwordless::Mailer"
|
13
30
|
|
14
31
|
# Time period after a magic login link is sent out that it will be valid for.
|
15
32
|
# config.passwordless_login_within = 20.minutes
|
16
|
-
|
17
|
-
# The secret key used to generate passwordless login tokens. The default
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
33
|
+
|
34
|
+
# The secret key used to generate passwordless login tokens. The default value
|
35
|
+
# is nil, which means defer to Devise's `secret_key` config value. Changing this
|
36
|
+
# key will render invalid all existing passwordless login tokens. You can
|
37
|
+
# generate your own secret value with e.g. `rake secret`
|
21
38
|
# config.passwordless_secret_key = nil
|
39
|
+
|
40
|
+
# When using the :trackable module, set to true to consider magic link tokens
|
41
|
+
# generated before the user's current sign in time to be expired. In other words,
|
42
|
+
# each time you sign in, all existing magic links will be considered invalid.
|
43
|
+
# config.passwordless_expire_old_tokens_on_sign_in = false
|
22
44
|
CONFIG
|
23
45
|
end
|
24
46
|
end
|
25
47
|
|
48
|
+
def add_mailer_view
|
49
|
+
create_file "app/views/devise/mailer/magic_link.html.erb" do <<~'FILE'
|
50
|
+
<p>Hello <%= @resource.email %>!</p>
|
51
|
+
|
52
|
+
<p>You can login using the link below:</p>
|
53
|
+
|
54
|
+
<p><%= link_to "Log in to my account", send("#{@scope_name.to_s.pluralize}_magic_link_url", Hash[@scope_name, {email: @resource.email, token: @token, remember_me: @remember_me}]) %></p>
|
55
|
+
|
56
|
+
<p>Note that the link will expire in <%= Devise.passwordless_login_within.inspect %>.</p>
|
57
|
+
FILE
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
26
61
|
def update_devise_yaml
|
27
62
|
devise_yaml = "config/locales/devise.en.yml"
|
63
|
+
existing_config = {}
|
28
64
|
begin
|
29
|
-
|
65
|
+
in_root do
|
66
|
+
existing_config = YAML.load_file(devise_yaml)
|
67
|
+
end
|
30
68
|
rescue Errno::ENOENT
|
31
|
-
|
69
|
+
say_status :skip, devise_yaml, :yellow
|
32
70
|
return
|
33
71
|
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
72
|
+
default_config = {
|
73
|
+
en: {
|
74
|
+
devise: {
|
75
|
+
passwordless: {
|
76
|
+
not_found_in_database: "Could not find a user for that email address",
|
77
|
+
magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account.",
|
78
|
+
},
|
79
|
+
failure: {
|
80
|
+
magic_link_invalid: "Invalid or expired login link.",
|
81
|
+
},
|
82
|
+
mailer: {
|
83
|
+
magic_link: {
|
84
|
+
subject: "Here's your magic login link ✨",
|
85
|
+
},
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
merged_config = existing_config.deep_merge(default_config.deep_stringify_keys)
|
91
|
+
if existing_config.to_yaml == merged_config.to_yaml
|
92
|
+
say_status :identical, devise_yaml, :blue
|
93
|
+
else
|
94
|
+
in_root do
|
95
|
+
File.open(devise_yaml, "w") do |f|
|
96
|
+
f.write(force_double_quote_yaml(merged_config.to_yaml))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
say_status :insert, devise_yaml, :green
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
# https://github.com/ruby/psych/issues/322#issuecomment-328408276
|
106
|
+
def force_double_quote_yaml(yaml_str)
|
107
|
+
ast = Psych.parse_stream(yaml_str)
|
108
|
+
|
109
|
+
# First pass, quote everything
|
110
|
+
ast.grep(Psych::Nodes::Scalar).each do |node|
|
111
|
+
node.plain = false
|
112
|
+
node.quoted = true
|
113
|
+
node.style = Psych::Nodes::Scalar::DOUBLE_QUOTED
|
114
|
+
end
|
115
|
+
|
116
|
+
# Second pass, unquote keys
|
117
|
+
ast.grep(Psych::Nodes::Mapping).each do |node|
|
118
|
+
node.children.each_slice(2) do |k, _|
|
119
|
+
k.plain = true
|
120
|
+
k.quoted = false
|
121
|
+
k.style = Psych::Nodes::Scalar::ANY
|
122
|
+
end
|
38
123
|
end
|
124
|
+
|
125
|
+
ast.yaml(nil, {line_width: -1})
|
39
126
|
end
|
40
127
|
end
|
41
128
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
<% module_namespacing do -%>
|
4
|
+
class Devise::Passwordless::MagicLinksController < DeviseController
|
5
|
+
prepend_before_action :require_no_authentication, only: :show
|
6
|
+
prepend_before_action :allow_params_authentication!, only: :show
|
7
|
+
prepend_before_action(only: [:show]) { request.env["devise.skip_timeout"] = true }
|
8
|
+
|
9
|
+
def show
|
10
|
+
self.resource = warden.authenticate!(auth_options)
|
11
|
+
set_flash_message!(:notice, :signed_in)
|
12
|
+
sign_in(resource_name, resource)
|
13
|
+
yield resource if block_given?
|
14
|
+
redirect_to after_sign_in_path_for(resource)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def auth_options
|
20
|
+
mapping = Devise.mappings[resource_name]
|
21
|
+
{ scope: resource_name, recall: "#{mapping.controllers[:sessions]}#new" }
|
22
|
+
end
|
23
|
+
|
24
|
+
def translation_scope
|
25
|
+
"devise.sessions"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create_params
|
31
|
+
resource_params.permit(:email, :remember_me)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
<% end -%>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
<% module_namespacing do -%>
|
4
|
+
class Devise::Passwordless::SessionsController < Devise::SessionsController
|
5
|
+
def create
|
6
|
+
self.resource = resource_class.find_by(email: create_params[:email])
|
7
|
+
if self.resource
|
8
|
+
resource.send_magic_link(create_params[:remember_me])
|
9
|
+
set_flash_message(:notice, :magic_link_sent, now: true)
|
10
|
+
else
|
11
|
+
set_flash_message(:alert, :not_found_in_database, now: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
self.resource = resource_class.new(create_params)
|
15
|
+
render :new
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def translation_scope
|
21
|
+
if action_name == "create"
|
22
|
+
"devise.passwordless"
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create_params
|
31
|
+
resource_params.permit(:email, :remember_me)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
<% 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.6.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:
|
11
|
+
date: 2021-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: devise
|
@@ -24,62 +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
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: bundler
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.17'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.17'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rake
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '10.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '10.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: rspec
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '3.0'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '3.0'
|
83
27
|
description:
|
84
28
|
email:
|
85
29
|
- _@abevoelker.com
|
@@ -97,13 +41,15 @@ files:
|
|
97
41
|
- bin/console
|
98
42
|
- bin/setup
|
99
43
|
- devise-passwordless.gemspec
|
100
|
-
- lib/devise/models/
|
44
|
+
- lib/devise/models/magic_link_authenticatable.rb
|
101
45
|
- lib/devise/passwordless.rb
|
102
46
|
- lib/devise/passwordless/login_token.rb
|
103
47
|
- lib/devise/passwordless/mailer.rb
|
104
48
|
- lib/devise/passwordless/version.rb
|
105
|
-
- lib/devise/strategies/
|
49
|
+
- lib/devise/strategies/magic_link_authenticatable.rb
|
106
50
|
- lib/generators/devise/passwordless/install_generator.rb
|
51
|
+
- lib/generators/devise/passwordless/templates/magic_links_controller.rb.erb
|
52
|
+
- lib/generators/devise/passwordless/templates/sessions_controller.rb.erb
|
107
53
|
homepage: https://github.com/abevoelker/devise-passwordless
|
108
54
|
licenses:
|
109
55
|
- MIT
|
@@ -118,14 +64,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
118
64
|
requirements:
|
119
65
|
- - ">="
|
120
66
|
- !ruby/object:Gem::Version
|
121
|
-
version:
|
67
|
+
version: 2.1.0
|
122
68
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
69
|
requirements:
|
124
70
|
- - ">="
|
125
71
|
- !ruby/object:Gem::Version
|
126
72
|
version: '0'
|
127
73
|
requirements: []
|
128
|
-
rubygems_version: 3.
|
74
|
+
rubygems_version: 3.1.2
|
129
75
|
signing_key:
|
130
76
|
specification_version: 4
|
131
77
|
summary: Passwordless (email-only) login strategy for Devise
|