simple_auth-magic_link 0.0.1 → 0.0.3

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: 1db6e4dd1f065742e9b94a0e1c617b26fb72546c8b492ba555563586c62d3bea
4
- data.tar.gz: 2cd9d5f2496cdd1f38acc16b01dd218caba4ffca1f983118939fc6b85455102f
3
+ metadata.gz: eaaaefbf87386dc65f459af3b70eb63836b21d6fc0656b5d3f74604de02ccc09
4
+ data.tar.gz: 4c0ec412f551cdc7ab743d47473a76b754fa29fe294300fa9e41d320cddbb70b
5
5
  SHA512:
6
- metadata.gz: 76dcc931ee1084fae469bef697184761b644249ff636735dcaa3bf124d1966cea33b5d6ef2d47f46210b0a74be3bfed8bdf5d70c8cdfd5cf5260ffc8645f1088
7
- data.tar.gz: 97f72c2ab9dcf34c7c360798c2421ec99c9d8e496614882b5dbb1f23619b597ade34042888ace29fb94558892ebfa86282387006116b59ff064f71b73ff579b8
6
+ metadata.gz: 8ef24d5c24be96ab1a52b010c42d1b5e3dd42cd76134f243e0d54d636003999e106f93085a91d4e0c754c6baf90bc47ad85623d7a729f74fcfeab58e63e489bd
7
+ data.tar.gz: 872c526193ac7343a9d4e26aca66933e0efecfe91ae4097f21272d26f55201b17761a787cb3a0d74f94c3561529b6317d7705aba5907b1e9cea2b5cd233d116b
@@ -19,18 +19,19 @@ jobs:
19
19
  strategy:
20
20
  fail-fast: false
21
21
  matrix:
22
- ruby: ["2.7", "3.0", "3.1"]
22
+ ruby: ["3.2", "3.3"]
23
23
  gemfile:
24
24
  - Gemfile
25
25
 
26
26
  steps:
27
- - uses: actions/checkout@v3
27
+ - uses: actions/checkout@v4
28
28
 
29
29
  - uses: actions/cache@v3
30
30
  with:
31
31
  path: vendor/bundle
32
32
  key: >
33
- ${{ runner.os }}-${{ matrix.ruby }}-gems-${{ hashFiles(matrix.gemfile) }}
33
+ ${{ runner.os }}-${{ matrix.ruby }}-gems-${{
34
+ hashFiles(matrix.gemfile) }}
34
35
 
35
36
  - name: Set up Ruby
36
37
  uses: ruby/setup-ruby@v1
data/README.md CHANGED
@@ -35,14 +35,14 @@ setting these options directly to `SimpleAuth::MagicLink`:
35
35
 
36
36
  ```ruby
37
37
  SimpleAuth::MagicLink.tap do |magic_link|
38
- # Optional. By default uses <https://github.com/fnando/haikunate>
39
- magic_link.code = -> { SecureRandom.hex(10) }
38
+ # Optional. By default 6 random numbers.
39
+ magic_link.code = -> { Array.new(6) { SecureRandom.random_number(0..9) }.join }
40
40
 
41
41
  # Optional. By default, links expires 3 minutes from now.
42
42
  magic_link.ttl = 1.minute
43
43
 
44
44
  # Optional. By default, uses "default".
45
- magic_link.purpose = :generic
45
+ magic_link.purpose = :default
46
46
 
47
47
  # Required. The lambda that will be used to generate the magic link.
48
48
  # This will require the default url options like in
@@ -71,20 +71,28 @@ Then, you can create magic links by using
71
71
 
72
72
  After you create a link, you can send it by email by using `magic_link.url`.
73
73
 
74
+ > [!IMPORTANT]
75
+ >
76
+ > Save `magic_link.id` on the session. This id will be used later on to ensure
77
+ > that the link was generated on the same browser/device.
78
+
74
79
  To verify the magic link, you can use
75
- `email = SimpleAuth::MagicLink.verify(request.original_url, **options)`, where
76
- options can be:
80
+ `email = SimpleAuth::MagicLink.verify(url: request.original_url, id: session[:magic_link_id], **options)`,
81
+ where options can be:
77
82
 
78
83
  - `purpose`: required. The purpose of the code being verified.
79
84
 
80
85
  If the url is valid (i.e. it hasn't been tempered and it hasn't expired), then
81
- you'll get the email tied to the token back. Otherwise, you'll get `nil`. Notice
82
- that verified tokens are automatically removed upon verification.
86
+ you'll get the email tied to the token back. Otherwise, you'll get `nil`.
87
+
88
+ > [!NOTE]
89
+ >
90
+ > Verified magic links are automatically removed upon verification.
83
91
 
84
92
  You can also verify the magic link by using just the code (maybe you sent this
85
93
  by SMS instead). In this case, you need to call something like
86
- `email = SimpleAuth::MagicLink.verify_code(code, **options)`. It expects the
87
- same options as `SimpleAuth::MagicLink.verify`.
94
+ `email = SimpleAuth::MagicLink.verify_code(code:, id: session[:magic_link_id], **options)`.
95
+ It expects the same options as `SimpleAuth::MagicLink.verify`.
88
96
 
89
97
  To remove expires links, use `SimpleAuth::MagicLink.clean!`.
90
98
 
@@ -94,9 +102,10 @@ something like this:
94
102
 
95
103
  1. User fills in log-in form with email address
96
104
  2. You call `magic_link = SimpleAuth::MagicLink.create!(email: params[:email])`
97
- 3. You then pass this in to your mailer with something like
105
+ 3. You persist the magic link id with `session[:magic_link_id] = magic_link.id`
106
+ 4. You then pass this in to your mailer with something like
98
107
  `Mailer.login(magic_link).send_later`
99
- 4. On your mailer, you can then have access to the user email via
108
+ 5. On your mailer, you can then have access to the user email via
100
109
  `magic_link.email`, the code via `magic_link.code` and the signed url via
101
110
  `magic_link.url`.
102
111
 
@@ -2,13 +2,32 @@
2
2
 
3
3
  module SimpleAuth
4
4
  module MagicLink
5
- class CodeVerifier < Verifier
6
- def self.call(code, purpose, time)
7
- new.call(code, purpose, time)
5
+ class CodeVerifier
6
+ def self.call(id:, code:, purpose:, time:)
7
+ new.call(id:, code:, purpose:, time:)
8
8
  end
9
9
 
10
- def call(code, purpose, time)
11
- verify_model(code, purpose, time)
10
+ def call(id:, code:, purpose:, time:)
11
+ verify_model(wanted_id: id, actual_code: code, purpose:, time:)
12
+ end
13
+
14
+ def secure_compare(a, b)
15
+ ActiveSupport::SecurityUtils.secure_compare(a, b)
16
+ end
17
+
18
+ def verify_model(wanted_id:, actual_code:, purpose:, time:)
19
+ magic_link = MagicLink.model.find_by(id: wanted_id)
20
+
21
+ return unless magic_link
22
+ return unless secure_compare(magic_link.code, actual_code.to_s)
23
+ return unless magic_link.expires_at >= time
24
+ return unless secure_compare(magic_link.purpose, purpose.to_s)
25
+
26
+ return unless magic_link
27
+
28
+ magic_link.destroy!
29
+
30
+ magic_link.email
12
31
  end
13
32
  end
14
33
  end
@@ -16,7 +16,7 @@ module SimpleAuth
16
16
  url,
17
17
  key: MagicLink.keyring.current_key.encryption_key,
18
18
  expires: expires_at,
19
- params: {code:, purpose:}
19
+ params: {id:}
20
20
  )
21
21
  end
22
22
  end
@@ -3,28 +3,31 @@
3
3
  module SimpleAuth
4
4
  module MagicLink
5
5
  class Verifier
6
- def self.call(url, purpose, time)
7
- new.call(url, purpose, time)
6
+ def self.call(id:, url:, purpose:, time:)
7
+ new.call(id:, url:, purpose:, time:)
8
8
  end
9
9
 
10
- def call(url, purpose, time)
10
+ def call(id:, url:, purpose:, time:)
11
11
  return unless SignedURL.verified?(url, key: MagicLink.encryption_key)
12
12
 
13
13
  uri = URI(url)
14
14
  params = Rack::Utils.parse_query(uri.query)
15
- code = params.fetch("code")
15
+ actual_id = params.fetch("id")
16
16
 
17
- verify_model(code, purpose, time)
17
+ verify_model(wanted_id: id, actual_id:, purpose:, time:)
18
18
  end
19
19
 
20
- def verify_model(code, purpose, time)
21
- magic_link = MagicLink
22
- .model
23
- .where("expires_at >= ?", time)
24
- .find_by(
25
- code_digest: MagicLink.keyring.digest(code),
26
- purpose:
27
- )
20
+ def secure_compare(a, b)
21
+ ActiveSupport::SecurityUtils.secure_compare(a, b)
22
+ end
23
+
24
+ def verify_model(wanted_id:, actual_id:, purpose:, time:)
25
+ magic_link = MagicLink.model.find_by(id: wanted_id)
26
+
27
+ return unless magic_link
28
+ return unless secure_compare(magic_link.id.to_s, actual_id.to_s)
29
+ return unless magic_link.expires_at >= time
30
+ return unless secure_compare(magic_link.purpose, purpose.to_s)
28
31
 
29
32
  return unless magic_link
30
33
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SimpleAuth
4
4
  module MagicLink
5
- VERSION = "0.0.1"
5
+ VERSION = "0.0.3"
6
6
  end
7
7
  end
@@ -3,7 +3,6 @@
3
3
  require "active_record"
4
4
  require "attr_keyring"
5
5
  require "defaults"
6
- require "haikunate"
7
6
  require "url_signature"
8
7
  require "rails/engine"
9
8
 
@@ -22,9 +21,7 @@ module SimpleAuth
22
21
  end
23
22
 
24
23
  def self.default_code
25
- lambda do
26
- Haikunate.call(variant: -> { SecureRandom.hex(3) })
27
- end
24
+ -> { Array.new(6) { SecureRandom.random_number(0..9) }.join }
28
25
  end
29
26
 
30
27
  def self.encryption_key
@@ -54,22 +51,29 @@ module SimpleAuth
54
51
  )
55
52
  end
56
53
 
54
+ # Verify whether a url is valid or not.
55
+ # The provided `id` must match whatever is set on the url.
56
+ # Ideally, this `id` is saved on the user's session and compared whenever
57
+ # the user visits the link. This is the only way you can ensure that the
58
+ # url hasn't be generated on a different device.
57
59
  def self.verify(
58
- url,
60
+ id:,
61
+ url:,
59
62
  time: Time.current,
60
63
  purpose: MagicLink.purpose,
61
64
  verifier: Verifier
62
65
  )
63
- verifier.call(url, purpose, time)
66
+ verifier.call(id:, url:, purpose:, time:)
64
67
  end
65
68
 
66
69
  def self.verify_code(
67
- code,
70
+ id:,
71
+ code:,
68
72
  time: Time.current,
69
73
  purpose: MagicLink.purpose,
70
74
  verifier: CodeVerifier
71
75
  )
72
- verifier.call(code, purpose, time)
76
+ verifier.call(id:, code:, purpose:, time:)
73
77
  end
74
78
 
75
79
  def self.restore_defaults!
@@ -37,14 +37,13 @@ Gem::Specification.new do |spec|
37
37
 
38
38
  spec.add_dependency "activerecord"
39
39
  spec.add_dependency "attr_keyring"
40
+ spec.add_dependency "cgi"
40
41
  spec.add_dependency "defaults"
41
- spec.add_dependency "haikunate"
42
42
  spec.add_dependency "simple_auth"
43
43
  spec.add_dependency "url_signature"
44
44
  spec.add_development_dependency "minitest"
45
45
  spec.add_development_dependency "minitest-utils"
46
46
  spec.add_development_dependency "mocha"
47
- spec.add_development_dependency "pry-meta"
48
47
  spec.add_development_dependency "rails"
49
48
  spec.add_development_dependency "rake"
50
49
  spec.add_development_dependency "rubocop"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_auth-magic_link
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - me@fnando.com
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-02-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -39,7 +38,7 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: defaults
41
+ name: cgi
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - ">="
@@ -53,7 +52,7 @@ dependencies:
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: haikunate
55
+ name: defaults
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - ">="
@@ -136,20 +135,6 @@ dependencies:
136
135
  - - ">="
137
136
  - !ruby/object:Gem::Version
138
137
  version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: pry-meta
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
138
  - !ruby/object:Gem::Dependency
154
139
  name: rails
155
140
  requirement: !ruby/object:Gem::Requirement
@@ -277,11 +262,10 @@ metadata:
277
262
  rubygems_mfa_required: 'true'
278
263
  homepage_uri: https://github.com/fnando/simple_auth-magic_link
279
264
  bug_tracker_uri: https://github.com/fnando/simple_auth-magic_link/issues
280
- source_code_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.1
281
- changelog_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.1/CHANGELOG.md
282
- documentation_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.1/README.md
283
- license_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.1/LICENSE.md
284
- post_install_message:
265
+ source_code_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.3
266
+ changelog_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.3/CHANGELOG.md
267
+ documentation_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.3/README.md
268
+ license_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.3/LICENSE.md
285
269
  rdoc_options: []
286
270
  require_paths:
287
271
  - lib
@@ -296,8 +280,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
296
280
  - !ruby/object:Gem::Version
297
281
  version: '0'
298
282
  requirements: []
299
- rubygems_version: 3.5.5
300
- signing_key:
283
+ rubygems_version: 4.0.3
301
284
  specification_version: 4
302
285
  summary: Passwordless sign-in for simple_auth.
303
286
  test_files: []