simple_auth-magic_link 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|