devise-passwordless 0.1.0 → 0.6.0

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: '08511edac883e2b94811e349d41ce761a7d5771e55b628450bd1e02d835f0374'
4
- data.tar.gz: 9d0f442ffed9f59818a370c8bdc815f362367bd24a6c09fc66b0097009992cd9
3
+ metadata.gz: c015970325061c0bd86aac15d0a2a90e200cd036cbe85249503d16881fb07d9a
4
+ data.tar.gz: 562b24b072376547b0e4bb6d503924dc58ad6d404a8e01397751af52e7a318cd
5
5
  SHA512:
6
- metadata.gz: 405e6a4ee5dbb3c66dcd079a92642e01dfb10b336d5e220f8a05f0814a2ac50ef47e78aa13e734b4aec4ce2891eb8cde900854984c3c84193afb1ff05e5b8481
7
- data.tar.gz: 933e273120b795b3b1eb1bb96a4672c9f3da7645ed7df55046eddc57197307afd9aadee5612fb047980cceed3fc5c2348f5388bb5b19df87191e9ee57e2fdb0f
6
+ metadata.gz: f9bd36c841f32e9e017e0ae672636ef0e2708064b7d10b9e001af148f3e6fbeee3b3ee117a21ee1947c3d91f7a036b9e33079f28f15d582681d75bdac11d995d
7
+ data.tar.gz: ab507245047decac1f9fbeb871dc54e7bef7e056c5c065550dcdedb1082ecf7db3c0caf911346b83ac93b636b2512b4735bdecf7754b9079327427b4160854f6
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,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
- You should already have Devise installed. Then add this gem:
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
- Then run the generator to automatically update your Devise initializer:
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
- Merge these YAML values into your `devise.en.yml` file:
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
- passwordless_invalid: "Invalid or expired login link."
127
+ magic_link_invalid: "Invalid or expired login link."
26
128
  mailer:
27
- passwordless_link:
28
- subject: "Here's your magic link"
129
+ magic_link:
130
+ subject: "Here's your magic login link"
29
131
  ```
30
132
 
31
- ## Usage
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
- This gem adds an `:email_authenticatable` strategy that can be used in your Devise models for passwordless authentication. This strategy plays well with most other Devise strategies.
137
+ Devise supports multiple resource types, so we do too.
34
138
 
35
- For example, for a User model, you could do this (other strategies optional and not an exhaustive list):
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 :email_authenticatable,
40
- :registerable,
41
- :rememberable,
42
- :validatable,
43
- :confirmable
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
- **Note** if using the `:rememberable` strategy for "remember me" functionality, you'll need to add a `remember_token` column to your resource, as there is no password salt to use for validating cookies:
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
- **Note** 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 no sense if they just confirmed they own the email address.
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
- ## Configuration
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
@@ -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/email_authenticatable'
1
+ require 'devise/strategies/magic_link_authenticatable'
2
2
 
3
3
  module Devise
4
4
  module Models
5
- module EmailAuthenticatable
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 after_passwordless_authentication
28
+ # def after_magic_link_authentication
24
29
  # self.update_attribute(:invite_code, nil)
25
30
  # end
26
31
  #
27
- def after_passwordless_authentication
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
- # EmailAuthenticatable strategy. If you are using this method on
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 find_for_email_authentication(conditions)
42
+ def find_for_magic_link_authentication(conditions)
38
43
  find_for_authentication(conditions)
39
44
  end
40
45
 
41
- Devise::Models.config(self, :passwordless_login_within, :passwordless_secret_key)
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
@@ -1,3 +1,3 @@
1
1
  require "devise/passwordless/version"
2
- require "devise/models/email_authenticatable"
2
+ require "devise/models/magic_link_authenticatable"
3
3
  require "generators/devise/passwordless/install_generator"
@@ -54,4 +54,4 @@ module Devise::Passwordless
54
54
  end
55
55
  end
56
56
  end
57
- end
57
+ end
@@ -1,9 +1,11 @@
1
- if defined?(Devise::Mailer)
2
- Devise::Mailer.class_eval do
3
- def passwordless_link(record, remember_me, opts = {})
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
- @token = Devise::Passwordless::LoginToken.encode(record)
6
- devise_mail(record, :passwordless_link, opts)
8
+ devise_mail(record, :magic_link, opts)
7
9
  end
8
10
  end
9
11
  end
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module Passwordless
3
- VERSION = "0.1.0"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -6,7 +6,7 @@ require "devise/passwordless/login_token"
6
6
 
7
7
  module Devise
8
8
  module Strategies
9
- class EmailAuthenticatable < Authenticatable
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
- data = begin
24
- x = Devise::Passwordless::LoginToken.decode(self.token)
25
- x["data"]
23
+ begin
24
+ data = Devise::Passwordless::LoginToken.decode(self.token)
26
25
  rescue Devise::Passwordless::LoginToken::InvalidOrExpiredTokenError
27
- fail!(:passwordless_invalid)
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.after_passwordless_authentication
44
+ resource.after_magic_link_authentication
35
45
  success!(resource)
36
46
  else
37
- fail!(:passwordless_invalid)
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(:email_authenticatable, Devise::Strategies::EmailAuthenticatable)
65
+ Warden::Strategies.add(:magic_link_authenticatable, Devise::Strategies::MagicLinkAuthenticatable)
56
66
 
57
- Devise.add_module(:email_authenticatable, {
67
+ Devise.add_module(:magic_link_authenticatable, {
58
68
  strategy: true,
59
69
  controller: :sessions,
60
- model: "devise/models/email_authenticatable",
61
- #route: { email_authenticatable: [nil, :new, :edit] }
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 "Updates the Devise initializer to add passwordless config options"
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 :email_authenticatable
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
- # value is nil, which means defer to Devise's `secret_key` config value.
19
- # Changing this key will render invalid all existing passwordless login
20
- # tokens. You can generate your own value with e.g. `rake secret`
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
- config = YAML.load_file(devise_yaml)
65
+ in_root do
66
+ existing_config = YAML.load_file(devise_yaml)
67
+ end
30
68
  rescue Errno::ENOENT
31
- STDERR.puts "Couldn't find devise.en.yml - skipping patch"
69
+ say_status :skip, devise_yaml, :yellow
32
70
  return
33
71
  end
34
- config["en"]["devise"]["failure"]["passwordless_invalid"] = "Invalid or expired login link."
35
- config["en"]["devise"]["mailer"]["passwordless_link"] = {subject: "Your login link"}
36
- File.open(devise_yaml, "w") do |f|
37
- f.write(config.to_yaml)
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.1.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: 2020-11-09 00:00:00.000000000 Z
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/email_authenticatable.rb
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/email_authenticatable.rb
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: '0'
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.0.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