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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15837103c0aa116898d6b9847813c8ddf11d5e9e42fb3592322521b1107a96b0
4
- data.tar.gz: e4d86d9d88ed12151a91490d9360440d9bbf68570f9fc7fc9a49ecadffa17d00
3
+ metadata.gz: 31848f572c8380345caeadef53ae0ee19ef029f3e7db96b2a30d2d4a3252c1b2
4
+ data.tar.gz: 30221de571208310dd56224c9abb88a0d3b19c5c00203e93116936648ac4098d
5
5
  SHA512:
6
- metadata.gz: a834c844bdc0e1cc638140ae5081f7714d24637837c7b5f84b6c14f32819c5e41316b1a29b57ac65f2d8e76781a8ce20a55b51dbb8f30489bdb4af94f131a25b
7
- data.tar.gz: 9b5703b2c793916342f9f044ea81ec1a626b0450cab2c0d91f4c56bc811ed7ccfee736db88715bae4d920729b3b56bbafaac781062dbf44b9cb016d73c15e7ce
6
+ metadata.gz: fdd553ce8ec4ddb9829585548ea28905b3355f1bc31808e7d60827fe7543c04e9287dfd9742fe123f75e2335e67a2421e96fe3aaefc53559b730b1954b358dab
7
+ data.tar.gz: 92256ff69b1bddcb7c17a0aff804da2a4c0740683d8d392960b9054a6dd51997e04268f1368b49b02b61f80bd12c28a0c437d98958a609ad6a2a619186ee1e01
data/.gitignore CHANGED
@@ -5,6 +5,7 @@
5
5
  /doc/
6
6
  /pkg/
7
7
  /spec/reports/
8
+ /spec/tmp
8
9
  /tmp/
9
10
  Gemfile.lock
10
11
 
data/Gemfile CHANGED
@@ -4,3 +4,10 @@ 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
+ end
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 / state needed - links are stateless encrypted tokens thanks to Rails's MessageEncryptor
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, for a User model, you can now do this (other strategies listed are optional and not exhaustive):
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 generate two controllers to modify Devise's default session create logic and to handle processing magic links:
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, controllers: { sessions: "users/sessions" }
54
+ devise_for :users,
55
+ controllers: { sessions: "devise/passwordless/sessions" }
64
56
  devise_scope :user do
65
- get "/users/magic_links" => "users/magic_links#show"
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
- config.mailer = "PasswordlessMailer"
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
@@ -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
@@ -0,0 +1,11 @@
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
7
+ @remember_me = remember_me
8
+ devise_mail(record, :magic_link, opts)
9
+ end
10
+ end
11
+ end
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module Passwordless
3
- VERSION = "0.3.0"
3
+ VERSION = "0.6.1"
4
4
  end
5
5
  end
@@ -21,7 +21,7 @@ module Devise
21
21
 
22
22
  def authenticate!
23
23
  begin
24
- data = Devise::Passwordless::LoginToken.decode(self.token)
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 passwordless auth strategy"
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
- config.mailer = "PasswordlessMailer"
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}_magic_links_url", Hash[@scope_name, {email: @resource.email, token: @token, remember_me: @remember_me}]) %></p>
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
- config = YAML.load_file(devise_yaml)
65
+ in_root do
66
+ existing_config = YAML.load_file(devise_yaml)
67
+ end
65
68
  rescue Errno::ENOENT
66
- STDERR.puts "Couldn't find #{devise_yaml} - skipping patch"
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 = 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
+ 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 <%= class_name.pluralize %>::MagicLinksController < DeviseController
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
- { scope: resource_name, recall: "#{resource_name.to_s.pluralize}/sessions#new" }
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 <%= class_name.pluralize %>::SessionsController < Devise::SessionsController
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.3.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: 2020-11-12 00:00:00.000000000 Z
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.0.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