devise-passwordless 0.3.0 → 0.6.1
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 +7 -0
- data/README.md +112 -17
- data/devise-passwordless.gemspec +0 -4
- data/lib/devise/passwordless/mailer.rb +11 -0
- data/lib/devise/passwordless/version.rb +1 -1
- data/lib/devise/strategies/magic_link_authenticatable.rb +5 -1
- data/lib/generators/devise/passwordless/install_generator.rb +31 -21
- data/lib/generators/devise/passwordless/templates/magic_links_controller.rb.erb +3 -2
- data/lib/generators/devise/passwordless/templates/sessions_controller.rb.erb +1 -1
- metadata +4 -46
- data/lib/generators/devise/passwordless/controller_generator.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 31848f572c8380345caeadef53ae0ee19ef029f3e7db96b2a30d2d4a3252c1b2
|
4
|
+
data.tar.gz: 30221de571208310dd56224c9abb88a0d3b19c5c00203e93116936648ac4098d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdd553ce8ec4ddb9829585548ea28905b3355f1bc31808e7d60827fe7543c04e9287dfd9742fe123f75e2335e67a2421e96fe3aaefc53559b730b1954b358dab
|
7
|
+
data.tar.gz: 92256ff69b1bddcb7c17a0aff804da2a4c0740683d8d392960b9054a6dd51997e04268f1368b49b02b61f80bd12c28a0c437d98958a609ad6a2a619186ee1e01
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -4,8 +4,9 @@ A passwordless a.k.a. "magic link" login strategy for [Devise][]
|
|
4
4
|
|
5
5
|
## Features
|
6
6
|
|
7
|
-
* No database migrations
|
7
|
+
* No special database migrations needed - magic links are stateless encrypted tokens
|
8
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)
|
9
10
|
* All the goodness of Devise!
|
10
11
|
|
11
12
|
## Installation
|
@@ -36,33 +37,26 @@ See the [customization section](#customization) for details on what gets install
|
|
36
37
|
|
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)).
|
38
39
|
|
39
|
-
For example,
|
40
|
+
For example, if your Devise model is User, enable the strategy like this:
|
40
41
|
|
41
42
|
```ruby
|
42
43
|
# app/models/user.rb
|
43
44
|
class User < ApplicationRecord
|
44
|
-
devise :magic_link_authenticatable,
|
45
|
-
:registerable,
|
46
|
-
:rememberable,
|
47
|
-
:validatable,
|
48
|
-
:confirmable
|
45
|
+
devise :magic_link_authenticatable #, :registerable, :rememberable, ...
|
49
46
|
end
|
50
47
|
```
|
51
48
|
|
52
|
-
Then, you'll need to
|
53
|
-
|
54
|
-
```
|
55
|
-
$ rails g devise:passwordless:controller User
|
56
|
-
```
|
57
|
-
|
58
|
-
Then, set up your Devise routes like so to use these controllers:
|
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:
|
59
50
|
|
60
51
|
```ruby
|
61
52
|
# config/routes.rb
|
62
53
|
Rails.application.routes.draw do
|
63
|
-
devise_for :users,
|
54
|
+
devise_for :users,
|
55
|
+
controllers: { sessions: "devise/passwordless/sessions" }
|
64
56
|
devise_scope :user do
|
65
|
-
get "/users/
|
57
|
+
get "/users/magic_link",
|
58
|
+
to: "devise/passwordless/magic_links#show",
|
59
|
+
as: "users_magic_link"
|
66
60
|
end
|
67
61
|
end
|
68
62
|
```
|
@@ -86,6 +80,15 @@ And these should be edited to remove password references:
|
|
86
80
|
* `app/views/devise/sessions/new.html.erb`
|
87
81
|
* Delete field `:password`
|
88
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
|
+
|
89
92
|
## Customization
|
90
93
|
|
91
94
|
Configuration options are stored in Devise's initializer at `config/initializers/devise.rb`:
|
@@ -94,7 +97,8 @@ Configuration options are stored in Devise's initializer at `config/initializers
|
|
94
97
|
# ==> Configuration for :magic_link_authenticatable
|
95
98
|
|
96
99
|
# Need to use a custom Devise mailer in order to send magic links
|
97
|
-
|
100
|
+
require "devise/passwordless/mailer"
|
101
|
+
config.mailer = "Devise::Passwordless::Mailer"
|
98
102
|
|
99
103
|
# Time period after a magic login link is sent out that it will be valid for.
|
100
104
|
# config.passwordless_login_within = 20.minutes
|
@@ -128,6 +132,89 @@ en:
|
|
128
132
|
|
129
133
|
To customize the magic link email body, edit `app/views/devise/mailer/magic_link.html.erb`
|
130
134
|
|
135
|
+
### Multiple user (resource) types
|
136
|
+
|
137
|
+
Devise supports multiple resource types, so we do too.
|
138
|
+
|
139
|
+
For example, if you have a User and Admin model, enable the `:magic_link_authenticatable` strategy for each:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
# app/models/user.rb
|
143
|
+
class User < ApplicationRecord
|
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, ...
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
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
|
+
|
131
218
|
### Notes on other Devise strategies
|
132
219
|
|
133
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:
|
@@ -140,8 +227,16 @@ end
|
|
140
227
|
|
141
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.
|
142
229
|
|
230
|
+
## Alternatives
|
231
|
+
|
232
|
+
Other Ruby libraries that offer passwordless authentication:
|
233
|
+
|
234
|
+
* [passwordless](https://github.com/mikker/passwordless)
|
235
|
+
* [magic-link](https://github.com/dvanderbeek/magic-link)
|
236
|
+
|
143
237
|
## License
|
144
238
|
|
145
239
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
146
240
|
|
147
241
|
[Devise]: https://github.com/heartcombo/devise
|
242
|
+
[devise-i18n]: https://github.com/heartcombo/devise#i18n
|
data/devise-passwordless.gemspec
CHANGED
@@ -38,8 +38,4 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.required_ruby_version = ">= 2.1.0"
|
39
39
|
|
40
40
|
spec.add_dependency "devise"
|
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
|
@@ -21,7 +21,7 @@ module Devise
|
|
21
21
|
|
22
22
|
def authenticate!
|
23
23
|
begin
|
24
|
-
data =
|
24
|
+
data = decode_passwordless_token
|
25
25
|
rescue Devise::Passwordless::LoginToken::InvalidOrExpiredTokenError
|
26
26
|
fail!(:magic_link_invalid)
|
27
27
|
return
|
@@ -50,6 +50,10 @@ module Devise
|
|
50
50
|
|
51
51
|
private
|
52
52
|
|
53
|
+
def decode_passwordless_token
|
54
|
+
Devise::Passwordless::LoginToken.decode(self.token)
|
55
|
+
end
|
56
|
+
|
53
57
|
# Sets the authentication hash and the token from params_auth_hash or http_auth_hash.
|
54
58
|
def with_authentication_hash(auth_type, auth_values)
|
55
59
|
self.authentication_hash, self.authentication_type = {}, auth_type
|
@@ -5,7 +5,19 @@ require "yaml"
|
|
5
5
|
module Devise::Passwordless
|
6
6
|
module Generators # :nodoc:
|
7
7
|
class InstallGenerator < ::Rails::Generators::Base # :nodoc:
|
8
|
-
desc "Creates default install and config files for the Devise
|
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
|
9
21
|
|
10
22
|
def update_devise_initializer
|
11
23
|
inject_into_file 'config/initializers/devise.rb', before: /^end$/ do <<~'CONFIG'.indent(2)
|
@@ -13,7 +25,8 @@ module Devise::Passwordless
|
|
13
25
|
# ==> Configuration for :magic_link_authenticatable
|
14
26
|
|
15
27
|
# Need to use a custom Devise mailer in order to send magic links
|
16
|
-
|
28
|
+
require "devise/passwordless/mailer"
|
29
|
+
config.mailer = "Devise::Passwordless::Mailer"
|
17
30
|
|
18
31
|
# Time period after a magic login link is sent out that it will be valid for.
|
19
32
|
# config.passwordless_login_within = 20.minutes
|
@@ -32,26 +45,13 @@ module Devise::Passwordless
|
|
32
45
|
end
|
33
46
|
end
|
34
47
|
|
35
|
-
def add_custom_devise_mailer
|
36
|
-
create_file "app/mailers/passwordless_mailer.rb" do <<~'FILE'
|
37
|
-
class PasswordlessMailer < Devise::Mailer
|
38
|
-
def magic_link(record, token, remember_me, opts = {})
|
39
|
-
@token = token
|
40
|
-
@remember_me = remember_me
|
41
|
-
devise_mail(record, :magic_link, opts)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
FILE
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
48
|
def add_mailer_view
|
49
49
|
create_file "app/views/devise/mailer/magic_link.html.erb" do <<~'FILE'
|
50
50
|
<p>Hello <%= @resource.email %>!</p>
|
51
51
|
|
52
52
|
<p>You can login using the link below:</p>
|
53
53
|
|
54
|
-
<p><%= link_to "Log in to my account", send("#{@scope_name.to_s.pluralize}
|
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
55
|
|
56
56
|
<p>Note that the link will expire in <%= Devise.passwordless_login_within.inspect %>.</p>
|
57
57
|
FILE
|
@@ -60,10 +60,13 @@ module Devise::Passwordless
|
|
60
60
|
|
61
61
|
def update_devise_yaml
|
62
62
|
devise_yaml = "config/locales/devise.en.yml"
|
63
|
+
existing_config = {}
|
63
64
|
begin
|
64
|
-
|
65
|
+
in_root do
|
66
|
+
existing_config = YAML.load_file(devise_yaml)
|
67
|
+
end
|
65
68
|
rescue Errno::ENOENT
|
66
|
-
|
69
|
+
say_status :skip, devise_yaml, :yellow
|
67
70
|
return
|
68
71
|
end
|
69
72
|
default_config = {
|
@@ -84,9 +87,16 @@ module Devise::Passwordless
|
|
84
87
|
}
|
85
88
|
}
|
86
89
|
}
|
87
|
-
merged_config =
|
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
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
<% module_namespacing do -%>
|
4
|
-
class
|
4
|
+
class Devise::Passwordless::MagicLinksController < DeviseController
|
5
5
|
prepend_before_action :require_no_authentication, only: :show
|
6
6
|
prepend_before_action :allow_params_authentication!, only: :show
|
7
7
|
prepend_before_action(only: [:show]) { request.env["devise.skip_timeout"] = true }
|
@@ -17,7 +17,8 @@ class <%= class_name.pluralize %>::MagicLinksController < DeviseController
|
|
17
17
|
protected
|
18
18
|
|
19
19
|
def auth_options
|
20
|
-
|
20
|
+
mapping = Devise.mappings[resource_name]
|
21
|
+
{ scope: resource_name, recall: "#{mapping.controllers[:sessions]}#new" }
|
21
22
|
end
|
22
23
|
|
23
24
|
def translation_scope
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
<% module_namespacing do -%>
|
4
|
-
class
|
4
|
+
class Devise::Passwordless::SessionsController < Devise::SessionsController
|
5
5
|
def create
|
6
6
|
self.resource = resource_class.find_by(email: create_params[:email])
|
7
7
|
if self.resource
|
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.1
|
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-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: devise
|
@@ -24,48 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: bundler
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '1.17'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '1.17'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rake
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '10.0'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '10.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '3.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '3.0'
|
69
27
|
description:
|
70
28
|
email:
|
71
29
|
- _@abevoelker.com
|
@@ -86,9 +44,9 @@ files:
|
|
86
44
|
- lib/devise/models/magic_link_authenticatable.rb
|
87
45
|
- lib/devise/passwordless.rb
|
88
46
|
- lib/devise/passwordless/login_token.rb
|
47
|
+
- lib/devise/passwordless/mailer.rb
|
89
48
|
- lib/devise/passwordless/version.rb
|
90
49
|
- lib/devise/strategies/magic_link_authenticatable.rb
|
91
|
-
- lib/generators/devise/passwordless/controller_generator.rb
|
92
50
|
- lib/generators/devise/passwordless/install_generator.rb
|
93
51
|
- lib/generators/devise/passwordless/templates/magic_links_controller.rb.erb
|
94
52
|
- lib/generators/devise/passwordless/templates/sessions_controller.rb.erb
|
@@ -113,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
71
|
- !ruby/object:Gem::Version
|
114
72
|
version: '0'
|
115
73
|
requirements: []
|
116
|
-
rubygems_version: 3.
|
74
|
+
rubygems_version: 3.1.2
|
117
75
|
signing_key:
|
118
76
|
specification_version: 4
|
119
77
|
summary: Passwordless (email-only) login strategy for Devise
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require "rails/generators/named_base"
|
2
|
-
|
3
|
-
module Devise::Passwordless
|
4
|
-
module Generators # :nodoc:
|
5
|
-
class ControllerGenerator < ::Rails::Generators::NamedBase # :nodoc:
|
6
|
-
desc "Creates the session and magic link controllers needed for a Devise resource to use passwordless auth"
|
7
|
-
|
8
|
-
def self.default_generator_root
|
9
|
-
File.dirname(__FILE__)
|
10
|
-
end
|
11
|
-
|
12
|
-
def create_sessions_controller
|
13
|
-
template "sessions_controller.rb.erb", File.join("app/controllers", class_path, plural_name, "sessions_controller.rb")
|
14
|
-
end
|
15
|
-
|
16
|
-
def create_magic_links_controller
|
17
|
-
template "magic_links_controller.rb.erb", File.join("app/controllers", class_path, plural_name, "magic_links_controller.rb")
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|