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 +4 -4
- data/.github/workflows/ruby-tests.yml +4 -3
- 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 -2
- metadata +9 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: eaaaefbf87386dc65f459af3b70eb63836b21d6fc0656b5d3f74604de02ccc09
|
|
4
|
+
data.tar.gz: 4c0ec412f551cdc7ab743d47473a76b754fa29fe294300fa9e41d320cddbb70b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
22
|
+
ruby: ["3.2", "3.3"]
|
|
23
23
|
gemfile:
|
|
24
24
|
- Gemfile
|
|
25
25
|
|
|
26
26
|
steps:
|
|
27
|
-
- uses: actions/checkout@
|
|
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-${{
|
|
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
|
|
39
|
-
magic_link.code = -> { SecureRandom.
|
|
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
|
+
> [!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)`.
|
|
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!
|
|
@@ -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.
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
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.
|
|
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:
|
|
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: []
|