devise-passwordless 0.3.0 → 0.6.1

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