devise-passwordless 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9bb24b5fe4c2794fc255797ca018533757e999c80ef5ac8f2992e245b48bec1
4
- data.tar.gz: c38c95a39eaae489078f4730f96c4dd8ebda460028cd2ef752196396e2b35837
3
+ metadata.gz: 15837103c0aa116898d6b9847813c8ddf11d5e9e42fb3592322521b1107a96b0
4
+ data.tar.gz: e4d86d9d88ed12151a91490d9360440d9bbf68570f9fc7fc9a49ecadffa17d00
5
5
  SHA512:
6
- metadata.gz: 821b144072819bf40fdc6687184262af929cfc066173700b7afdb92a56a344e6c01c923bd956a3ae928c8dab572f8ff2f84de73c56fde29139e2c28f8564fe0c
7
- data.tar.gz: 51bc2079671c704e365e43b7ad10d72359b8ff2588c31deee1ab001ab2f93f4d6fe3992a1de7ea890401dbf1be08ae912e4302fd7c673b6b61bc3ba29832faae
6
+ metadata.gz: a834c844bdc0e1cc638140ae5081f7714d24637837c7b5f84b6c14f32819c5e41316b1a29b57ac65f2d8e76781a8ce20a55b51dbb8f30489bdb4af94f131a25b
7
+ data.tar.gz: 9b5703b2c793916342f9f044ea81ec1a626b0450cab2c0d91f4c56bc811ed7ccfee736db88715bae4d920729b3b56bbafaac781062dbf44b9cb016d73c15e7ce
data/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  A passwordless a.k.a. "magic link" login strategy for [Devise][]
4
4
 
5
- No database migrations are needed as login links are stateless, encrypted tokens generated with Rails's MessageEncryptor.
5
+ ## Features
6
+
7
+ * No database migrations / state needed - links are stateless encrypted tokens thanks to Rails's MessageEncryptor
8
+ * Magic links are sent from your app - not a mounted Rails engine - so path and URL helpers work as expected
9
+ * All the goodness of Devise!
6
10
 
7
11
  ## Installation
8
12
 
@@ -30,9 +34,9 @@ See the [customization section](#customization) for details on what gets install
30
34
 
31
35
  ## Usage
32
36
 
33
- This gem adds an `: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)).
37
+ 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)).
34
38
 
35
- For example, for a User model, you could do this (other strategies listed are optional and not exhaustive):
39
+ For example, for a User model, you can now do this (other strategies listed are optional and not exhaustive):
36
40
 
37
41
  ```ruby
38
42
  # app/models/user.rb
@@ -51,7 +55,7 @@ Then, you'll need to generate two controllers to modify Devise's default session
51
55
  $ rails g devise:passwordless:controller User
52
56
  ```
53
57
 
54
- Then, modify your routes file like so to use these controllers:
58
+ Then, set up your Devise routes like so to use these controllers:
55
59
 
56
60
  ```ruby
57
61
  # config/routes.rb
@@ -63,13 +67,15 @@ Rails.application.routes.draw do
63
67
  end
64
68
  ```
65
69
 
66
- Finally, you'll want to update Devise's generated views to remove references to passwords.
70
+ Finally, you'll want to update Devise's generated views to remove references to passwords, since you don't need them any more!
67
71
 
68
72
  These files/directories can be deleted entirely:
69
73
 
70
- * `app/views/devise/passwords`
71
- * `app/views/devise/mailer/password_change.html.erb`
72
- * `app/views/devise/mailer/reset_password_instructions.html.erb`
74
+ ```
75
+ app/views/devise/passwords
76
+ app/views/devise/mailer/password_change.html.erb
77
+ app/views/devise/mailer/reset_password_instructions.html.erb
78
+ ```
73
79
 
74
80
  And these should be edited to remove password references:
75
81
 
@@ -98,6 +104,11 @@ config.mailer = "PasswordlessMailer"
98
104
  # key will render invalid all existing passwordless login tokens. You can
99
105
  # generate your own secret value with e.g. `rake secret`
100
106
  # config.passwordless_secret_key = nil
107
+
108
+ # When using the :trackable module, set to true to consider magic link tokens
109
+ # generated before the user's current sign in time to be expired. In other words,
110
+ # each time you sign in, all existing magic links will be considered invalid.
111
+ # config.passwordless_expire_old_tokens_on_sign_in = false
101
112
  ```
102
113
 
103
114
  To customize the magic link email subject line and other status and error messages, modify these values in `config/locales/devise.en.yml`:
@@ -109,17 +120,17 @@ en:
109
120
  not_found_in_database: "Could not find a user for that email address"
110
121
  magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account."
111
122
  failure:
112
- passwordless_invalid: "Invalid or expired login link."
123
+ magic_link_invalid: "Invalid or expired login link."
113
124
  mailer:
114
125
  magic_link:
115
- subject: "Here's your magic login link 🪄✨"
126
+ subject: "Here's your magic login link "
116
127
  ```
117
128
 
118
129
  To customize the magic link email body, edit `app/views/devise/mailer/magic_link.html.erb`
119
130
 
120
131
  ### Notes on other Devise strategies
121
132
 
122
- If using the `:rememberable` strategy for "remember me" functionality, you'll need to add a `remember_token` column to your resource, as by default it assumes you're using a password strategy and relies on comparing the password's salt to validate cookies:
133
+ 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:
123
134
 
124
135
  ```ruby
125
136
  change_table :users do |t|
@@ -35,9 +35,9 @@ 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
41
 
42
42
  spec.add_development_dependency "bundler", "~> 1.17"
43
43
  spec.add_development_dependency "rake", "~> 10.0"
@@ -43,7 +43,11 @@ module Devise
43
43
  find_for_authentication(conditions)
44
44
  end
45
45
 
46
- 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
+ )
47
51
  end
48
52
  end
49
53
  end
@@ -55,4 +59,7 @@ module Devise
55
59
 
56
60
  mattr_accessor :passwordless_secret_key
57
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
58
65
  end
@@ -1,5 +1,5 @@
1
1
  module Devise
2
2
  module Passwordless
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -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
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
 
@@ -1,3 +1,4 @@
1
+ require "psych"
1
2
  require "rails/generators"
2
3
  require "yaml"
3
4
 
@@ -22,6 +23,11 @@ module Devise::Passwordless
22
23
  # key will render invalid all existing passwordless login tokens. You can
23
24
  # generate your own secret value with e.g. `rake secret`
24
25
  # config.passwordless_secret_key = nil
26
+
27
+ # When using the :trackable module, set to true to consider magic link tokens
28
+ # generated before the user's current sign in time to be expired. In other words,
29
+ # each time you sign in, all existing magic links will be considered invalid.
30
+ # config.passwordless_expire_old_tokens_on_sign_in = false
25
31
  CONFIG
26
32
  end
27
33
  end
@@ -60,7 +66,7 @@ module Devise::Passwordless
60
66
  STDERR.puts "Couldn't find #{devise_yaml} - skipping patch"
61
67
  return
62
68
  end
63
- config.deep_merge!({
69
+ default_config = {
64
70
  en: {
65
71
  devise: {
66
72
  passwordless: {
@@ -68,17 +74,45 @@ module Devise::Passwordless
68
74
  magic_link_sent: "A login link has been sent to your email address. Please follow the link to log in to your account.",
69
75
  },
70
76
  failure: {
71
- passwordless_invalid: "Invalid or expired login link.",
77
+ magic_link_invalid: "Invalid or expired login link.",
72
78
  },
73
79
  mailer: {
74
80
  magic_link: {
75
- subject: "Here's your login link 🪄✨",
81
+ subject: "Here's your magic login link ",
76
82
  },
77
83
  }
78
84
  }
79
85
  }
80
- })
81
- File.open(devise_yaml, "w"){ |f| f.write(config.to_yaml) }
86
+ }
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
+ end
91
+ end
92
+
93
+ private
94
+
95
+ # https://github.com/ruby/psych/issues/322#issuecomment-328408276
96
+ def force_double_quote_yaml(yaml_str)
97
+ ast = Psych.parse_stream(yaml_str)
98
+
99
+ # First pass, quote everything
100
+ ast.grep(Psych::Nodes::Scalar).each do |node|
101
+ node.plain = false
102
+ node.quoted = true
103
+ node.style = Psych::Nodes::Scalar::DOUBLE_QUOTED
104
+ end
105
+
106
+ # Second pass, unquote keys
107
+ ast.grep(Psych::Nodes::Mapping).each do |node|
108
+ node.children.each_slice(2) do |k, _|
109
+ k.plain = true
110
+ k.quoted = false
111
+ k.style = Psych::Nodes::Scalar::ANY
112
+ end
113
+ end
114
+
115
+ ast.yaml(nil, {line_width: -1})
82
116
  end
83
117
  end
84
118
  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.2.0
4
+ version: 0.3.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-11 00:00:00.000000000 Z
11
+ date: 2020-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: devise
@@ -24,20 +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
27
  - !ruby/object:Gem::Dependency
42
28
  name: bundler
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -120,7 +106,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
120
106
  requirements:
121
107
  - - ">="
122
108
  - !ruby/object:Gem::Version
123
- version: '0'
109
+ version: 2.1.0
124
110
  required_rubygems_version: !ruby/object:Gem::Requirement
125
111
  requirements:
126
112
  - - ">="