simple_auth-magic_link 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +20 -11
- data/lib/simple_auth/magic_link/code_verifier.rb +24 -5
- data/lib/simple_auth/magic_link/model.rb +1 -1
- data/lib/simple_auth/magic_link/verifier.rb +16 -13
- data/lib/simple_auth/magic_link/version.rb +1 -1
- data/lib/simple_auth/magic_link.rb +12 -8
- data/simple_auth-magic_link.gemspec +1 -3
- metadata +11 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7735f278bf1499620fa1b4602dd88b477f54fd701bd09ada4236571f8c496d9d
|
4
|
+
data.tar.gz: b532899b3f5222fb78010dc54d67b63fd1514e298f1177f4581c3294e9aaea5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 418ec9e52d1e9bc879440599533a1360b982fc6589208858fb5627e98f0cc3be25fea052eed4f0ee173566138ec6a45ae32c0ff9c897abd8d239a26dc12311cc
|
7
|
+
data.tar.gz: 99265424767eed276c8afbf07fd8567b51e2245ae14e51a3e26718f18cfd0be92d6edb249428bcd0c8fdf4112b6c5939bc5221a672abd2479efa73216db6f06f
|
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
|
39
|
-
magic_link.code =
|
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 = :
|
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)`,
|
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`.
|
82
|
-
|
86
|
+
you'll get the email tied to the token back. Otherwise, you'll get `nil`.
|
87
|
+
|
88
|
+
> [!INFO]
|
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)`.
|
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
|
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
|
-
|
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
|
6
|
-
def self.call(code
|
7
|
-
new.call(code
|
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
|
11
|
-
verify_model(code, purpose
|
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
|
@@ -3,28 +3,31 @@
|
|
3
3
|
module SimpleAuth
|
4
4
|
module MagicLink
|
5
5
|
class Verifier
|
6
|
-
def self.call(url
|
7
|
-
new.call(url
|
6
|
+
def self.call(id:, url:, purpose:, time:)
|
7
|
+
new.call(id:, url:, purpose:, time:)
|
8
8
|
end
|
9
9
|
|
10
|
-
def call(url
|
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
|
-
|
15
|
+
actual_id = params.fetch("id")
|
16
16
|
|
17
|
-
verify_model(
|
17
|
+
verify_model(wanted_id: id, actual_id:, purpose:, time:)
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
|
@@ -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
|
-
|
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
|
-
|
60
|
+
id:,
|
61
|
+
url:,
|
59
62
|
time: Time.current,
|
60
63
|
purpose: MagicLink.purpose,
|
61
64
|
verifier: Verifier
|
62
65
|
)
|
63
|
-
verifier.call(url
|
66
|
+
verifier.call(id:, url:, purpose:, time:)
|
64
67
|
end
|
65
68
|
|
66
69
|
def self.verify_code(
|
67
|
-
|
70
|
+
id:,
|
71
|
+
code:,
|
68
72
|
time: Time.current,
|
69
73
|
purpose: MagicLink.purpose,
|
70
74
|
verifier: CodeVerifier
|
71
75
|
)
|
72
|
-
verifier.call(code
|
76
|
+
verifier.call(id:, code:, purpose:, time:)
|
73
77
|
end
|
74
78
|
|
75
79
|
def self.restore_defaults!
|
@@ -38,17 +38,15 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.add_dependency "activerecord"
|
39
39
|
spec.add_dependency "attr_keyring"
|
40
40
|
spec.add_dependency "defaults"
|
41
|
-
spec.add_dependency "haikunate"
|
42
41
|
spec.add_dependency "simple_auth"
|
43
42
|
spec.add_dependency "url_signature"
|
44
43
|
spec.add_development_dependency "minitest"
|
45
44
|
spec.add_development_dependency "minitest-utils"
|
46
45
|
spec.add_development_dependency "mocha"
|
47
|
-
spec.add_development_dependency "pry-meta"
|
48
46
|
spec.add_development_dependency "rails"
|
49
47
|
spec.add_development_dependency "rake"
|
50
48
|
spec.add_development_dependency "rubocop"
|
51
49
|
spec.add_development_dependency "rubocop-fnando"
|
52
50
|
spec.add_development_dependency "simplecov"
|
53
|
-
spec.add_development_dependency "sqlite3"
|
51
|
+
spec.add_development_dependency "sqlite3", "~> 1.7"
|
54
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_auth-magic_link
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- me@fnando.com
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: haikunate
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: simple_auth
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,20 +122,6 @@ dependencies:
|
|
136
122
|
- - ">="
|
137
123
|
- !ruby/object:Gem::Version
|
138
124
|
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
125
|
- !ruby/object:Gem::Dependency
|
154
126
|
name: rails
|
155
127
|
requirement: !ruby/object:Gem::Requirement
|
@@ -224,16 +196,16 @@ dependencies:
|
|
224
196
|
name: sqlite3
|
225
197
|
requirement: !ruby/object:Gem::Requirement
|
226
198
|
requirements:
|
227
|
-
- - "
|
199
|
+
- - "~>"
|
228
200
|
- !ruby/object:Gem::Version
|
229
|
-
version: '
|
201
|
+
version: '1.7'
|
230
202
|
type: :development
|
231
203
|
prerelease: false
|
232
204
|
version_requirements: !ruby/object:Gem::Requirement
|
233
205
|
requirements:
|
234
|
-
- - "
|
206
|
+
- - "~>"
|
235
207
|
- !ruby/object:Gem::Version
|
236
|
-
version: '
|
208
|
+
version: '1.7'
|
237
209
|
description: Passwordless sign-in for simple_auth.
|
238
210
|
email:
|
239
211
|
- me@fnando.com
|
@@ -277,10 +249,10 @@ metadata:
|
|
277
249
|
rubygems_mfa_required: 'true'
|
278
250
|
homepage_uri: https://github.com/fnando/simple_auth-magic_link
|
279
251
|
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.
|
281
|
-
changelog_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.
|
282
|
-
documentation_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.
|
283
|
-
license_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.
|
252
|
+
source_code_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.2
|
253
|
+
changelog_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.2/CHANGELOG.md
|
254
|
+
documentation_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.2/README.md
|
255
|
+
license_uri: https://github.com/fnando/simple_auth-magic_link/tree/v0.0.2/LICENSE.md
|
284
256
|
post_install_message:
|
285
257
|
rdoc_options: []
|
286
258
|
require_paths:
|
@@ -296,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
296
268
|
- !ruby/object:Gem::Version
|
297
269
|
version: '0'
|
298
270
|
requirements: []
|
299
|
-
rubygems_version: 3.5.
|
271
|
+
rubygems_version: 3.5.9
|
300
272
|
signing_key:
|
301
273
|
specification_version: 4
|
302
274
|
summary: Passwordless sign-in for simple_auth.
|