devise-passwordless 0.2.0 → 0.3.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: 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
  - - ">="