authsignal-ruby 5.2.1 → 5.2.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/.editorconfig +19 -0
- data/.rubocop.yml +47 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +50 -32
- data/Rakefile +3 -3
- data/bin/console +3 -3
- data/lib/authsignal/api_error.rb +1 -1
- data/lib/authsignal/client.rb +112 -112
- data/lib/authsignal/configuration.rb +51 -48
- data/lib/authsignal/invalid_signature_error.rb +2 -3
- data/lib/authsignal/middleware/json_request.rb +1 -3
- data/lib/authsignal/middleware/json_response.rb +5 -4
- data/lib/authsignal/version.rb +1 -1
- data/lib/authsignal/webhook.rb +20 -23
- data/lib/authsignal.rb +103 -100
- metadata +8 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc7b157f7e5e965983377b879308969e0e64cdaa9e9c898befcb960882768d87
|
|
4
|
+
data.tar.gz: 3a52e2684801ab9e9825f5a053c9bbdeb58a663e714613e7a2994b064354793a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 814653fbdea511018febd90b03d4be4f75de814e12cf71c96ca560eaaad078ffbc496ae48b0e43a178ceab49f1dba7ea280a2fcd2bf4202393c0fa52e7970b5e
|
|
7
|
+
data.tar.gz: f2f939e03b2a02a572f69fe65c6907904ef0947d27ef5bb41274a0fe40e73c346656883af5ffa765504264041f5fb3f3584bde954da69fc5878d79869c19fa26
|
data/.editorconfig
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# EditorConfig helps maintain consistent coding styles
|
|
2
|
+
# https://editorconfig.org
|
|
3
|
+
|
|
4
|
+
root = true
|
|
5
|
+
|
|
6
|
+
[*]
|
|
7
|
+
indent_style = space
|
|
8
|
+
indent_size = 2
|
|
9
|
+
end_of_line = lf
|
|
10
|
+
charset = utf-8
|
|
11
|
+
trim_trailing_whitespace = true
|
|
12
|
+
insert_final_newline = true
|
|
13
|
+
|
|
14
|
+
[*.md]
|
|
15
|
+
trim_trailing_whitespace = false
|
|
16
|
+
|
|
17
|
+
[*.{yml,yaml}]
|
|
18
|
+
indent_size = 2
|
|
19
|
+
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
NewCops: enable
|
|
3
|
+
TargetRubyVersion: 2.6
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
Exclude:
|
|
6
|
+
- 'vendor/**/*'
|
|
7
|
+
|
|
8
|
+
# Enforce 2-space indentation
|
|
9
|
+
Layout/IndentationWidth:
|
|
10
|
+
Width: 2
|
|
11
|
+
|
|
12
|
+
Layout/LineLength:
|
|
13
|
+
Max: 150
|
|
14
|
+
|
|
15
|
+
# Be lenient in specs and gemspec
|
|
16
|
+
Metrics/BlockLength:
|
|
17
|
+
Exclude:
|
|
18
|
+
- 'spec/**/*'
|
|
19
|
+
- '*.gemspec'
|
|
20
|
+
|
|
21
|
+
# SDK methods can have more parameters
|
|
22
|
+
Metrics/ParameterLists:
|
|
23
|
+
Max: 15
|
|
24
|
+
|
|
25
|
+
# SDK classes can be longer
|
|
26
|
+
Metrics/ClassLength:
|
|
27
|
+
Max: 150
|
|
28
|
+
|
|
29
|
+
Metrics/MethodLength:
|
|
30
|
+
Max: 25
|
|
31
|
+
|
|
32
|
+
Metrics/AbcSize:
|
|
33
|
+
Max: 30
|
|
34
|
+
|
|
35
|
+
# Skip documentation requirement for SDK
|
|
36
|
+
Style/Documentation:
|
|
37
|
+
Enabled: false
|
|
38
|
+
|
|
39
|
+
# Gemspec specific
|
|
40
|
+
Gemspec/DevelopmentDependencies:
|
|
41
|
+
Enabled: false
|
|
42
|
+
|
|
43
|
+
Gemspec/RequireMFA:
|
|
44
|
+
Enabled: false
|
|
45
|
+
|
|
46
|
+
Gemspec/OrderedDependencies:
|
|
47
|
+
Enabled: false
|
data/Gemfile
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
source
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
4
|
|
|
5
5
|
# Specify your gem's dependencies in authsignal-ruby.gemspec
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
|
-
group :test do
|
|
8
|
+
group :development, :test do
|
|
9
9
|
gem 'dotenv'
|
|
10
|
+
gem 'rubocop', require: false
|
|
10
11
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
authsignal-ruby (5.2.
|
|
4
|
+
authsignal-ruby (5.2.2)
|
|
5
5
|
base64
|
|
6
6
|
faraday (>= 2.0.1)
|
|
7
7
|
faraday-retry (~> 2.2)
|
|
@@ -9,52 +9,69 @@ PATH
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
-
addressable (2.8.
|
|
13
|
-
public_suffix (>= 2.0.2, <
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
addressable (2.8.8)
|
|
13
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
14
|
+
ast (2.4.3)
|
|
15
|
+
base64 (0.3.0)
|
|
16
|
+
bigdecimal (4.0.1)
|
|
17
|
+
crack (1.0.1)
|
|
17
18
|
bigdecimal
|
|
18
19
|
rexml
|
|
19
|
-
diff-lcs (1.
|
|
20
|
-
dotenv (
|
|
21
|
-
faraday (2.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
faraday-net_http (3.
|
|
26
|
-
|
|
27
|
-
faraday-retry (2.2.1)
|
|
20
|
+
diff-lcs (1.6.2)
|
|
21
|
+
dotenv (2.8.1)
|
|
22
|
+
faraday (2.8.1)
|
|
23
|
+
base64
|
|
24
|
+
faraday-net_http (>= 2.0, < 3.1)
|
|
25
|
+
ruby2_keywords (>= 0.0.4)
|
|
26
|
+
faraday-net_http (3.0.2)
|
|
27
|
+
faraday-retry (2.4.0)
|
|
28
28
|
faraday (~> 2.0)
|
|
29
|
-
hashdiff (1.
|
|
30
|
-
json (2.7.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
hashdiff (1.2.1)
|
|
30
|
+
json (2.7.6)
|
|
31
|
+
parallel (1.24.0)
|
|
32
|
+
parser (3.3.10.0)
|
|
33
|
+
ast (~> 2.4.1)
|
|
34
|
+
racc
|
|
35
|
+
public_suffix (5.1.1)
|
|
36
|
+
racc (1.8.1)
|
|
37
|
+
rainbow (3.1.1)
|
|
38
|
+
rake (13.3.1)
|
|
39
|
+
regexp_parser (2.11.3)
|
|
40
|
+
rexml (3.4.4)
|
|
41
|
+
rspec (3.13.2)
|
|
38
42
|
rspec-core (~> 3.13.0)
|
|
39
43
|
rspec-expectations (~> 3.13.0)
|
|
40
44
|
rspec-mocks (~> 3.13.0)
|
|
41
|
-
rspec-core (3.13.
|
|
45
|
+
rspec-core (3.13.6)
|
|
42
46
|
rspec-support (~> 3.13.0)
|
|
43
|
-
rspec-expectations (3.13.
|
|
47
|
+
rspec-expectations (3.13.5)
|
|
44
48
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
45
49
|
rspec-support (~> 3.13.0)
|
|
46
|
-
rspec-mocks (3.13.
|
|
50
|
+
rspec-mocks (3.13.7)
|
|
47
51
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
48
52
|
rspec-support (~> 3.13.0)
|
|
49
|
-
rspec-support (3.13.
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
rspec-support (3.13.6)
|
|
54
|
+
rubocop (1.50.2)
|
|
55
|
+
json (~> 2.3)
|
|
56
|
+
parallel (~> 1.10)
|
|
57
|
+
parser (>= 3.2.0.0)
|
|
58
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
59
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
60
|
+
rexml (>= 3.2.5, < 4.0)
|
|
61
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
|
62
|
+
ruby-progressbar (~> 1.7)
|
|
63
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
64
|
+
rubocop-ast (1.30.0)
|
|
65
|
+
parser (>= 3.2.1.0)
|
|
66
|
+
ruby-progressbar (1.13.0)
|
|
67
|
+
ruby2_keywords (0.0.5)
|
|
68
|
+
unicode-display_width (2.6.0)
|
|
69
|
+
webmock (3.26.1)
|
|
52
70
|
addressable (>= 2.8.0)
|
|
53
71
|
crack (>= 0.3.2)
|
|
54
72
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
55
73
|
|
|
56
74
|
PLATFORMS
|
|
57
|
-
arm64-darwin-24
|
|
58
75
|
ruby
|
|
59
76
|
|
|
60
77
|
DEPENDENCIES
|
|
@@ -62,7 +79,8 @@ DEPENDENCIES
|
|
|
62
79
|
dotenv
|
|
63
80
|
rake (~> 13.0)
|
|
64
81
|
rspec (~> 3.2)
|
|
82
|
+
rubocop
|
|
65
83
|
webmock (~> 3.14)
|
|
66
84
|
|
|
67
85
|
BUNDLED WITH
|
|
68
|
-
|
|
86
|
+
1.17.2
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require
|
|
5
|
-
require
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'authsignal'
|
|
6
6
|
|
|
7
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
8
|
# with your gem easier. You can also use a different console, if you like.
|
|
@@ -11,5 +11,5 @@ require "authsignal"
|
|
|
11
11
|
# require "pry"
|
|
12
12
|
# Pry.start
|
|
13
13
|
|
|
14
|
-
require
|
|
14
|
+
require 'irb'
|
|
15
15
|
IRB.start(__FILE__)
|
data/lib/authsignal/api_error.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Authsignal
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def format_description(error_code, error_description)
|
|
28
|
-
error_description
|
|
28
|
+
error_description&.length&.positive? ? error_description : error_code
|
|
29
29
|
end
|
|
30
30
|
end
|
|
31
31
|
end
|
data/lib/authsignal/client.rb
CHANGED
|
@@ -1,117 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'erb'
|
|
2
4
|
|
|
3
5
|
module Authsignal
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
end
|
|
114
|
-
@client.public_send(method, path, body, headers)
|
|
115
|
-
end
|
|
6
|
+
class Client
|
|
7
|
+
USER_AGENT = 'authsignal-ruby'
|
|
8
|
+
NO_API_KEY_MESSAGE = 'No Authsignal API Secret Key Set'
|
|
9
|
+
|
|
10
|
+
RETRY_OPTIONS = {
|
|
11
|
+
max: 3,
|
|
12
|
+
interval: 0.1,
|
|
13
|
+
interval_randomness: 0.5,
|
|
14
|
+
backoff_factor: 2
|
|
15
|
+
}.freeze
|
|
16
|
+
private_constant :RETRY_OPTIONS
|
|
17
|
+
|
|
18
|
+
def initialize(retry_options: RETRY_OPTIONS)
|
|
19
|
+
@api_key = require_api_key
|
|
20
|
+
|
|
21
|
+
@client = Faraday.new do |builder|
|
|
22
|
+
builder.url_prefix = Authsignal.configuration.api_url
|
|
23
|
+
builder.adapter :net_http
|
|
24
|
+
builder.request :authorization, :basic, @api_key, nil
|
|
25
|
+
|
|
26
|
+
builder.headers['Accept'] = 'application/json'
|
|
27
|
+
builder.headers['Content-Type'] = 'application/json'
|
|
28
|
+
builder.headers['User-Agent'] = USER_AGENT
|
|
29
|
+
builder.headers['X-Authsignal-Version'] = Authsignal::VERSION
|
|
30
|
+
|
|
31
|
+
builder.request :json
|
|
32
|
+
builder.response :json, parser_options: { symbolize_names: true }
|
|
33
|
+
|
|
34
|
+
builder.use Middleware::JsonRequest
|
|
35
|
+
builder.use Middleware::JsonResponse
|
|
36
|
+
|
|
37
|
+
builder.request :retry, retry_options if Authsignal.configuration.retry
|
|
38
|
+
builder.response :logger, ::Logger.new($stdout), bodies: true if Authsignal.configuration.debug
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def get_user(user_id:)
|
|
43
|
+
path = "users/#{url_encode(user_id)}"
|
|
44
|
+
make_request(:get, path)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update_user(user_id:, attributes:)
|
|
48
|
+
make_request(:post, "users/#{url_encode(user_id)}", body: attributes)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def delete_user(user_id:)
|
|
52
|
+
make_request(:delete, "users/#{url_encode(user_id)}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get_authenticators(user_id:)
|
|
56
|
+
make_request(:get, "users/#{url_encode(user_id)}/authenticators")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def enroll_verified_authenticator(user_id:, attributes:)
|
|
60
|
+
make_request(:post, "users/#{url_encode(user_id)}/authenticators", body: attributes)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def delete_authenticator(user_id:, user_authenticator_id:)
|
|
64
|
+
make_request(:delete, "users/#{url_encode(user_id)}/authenticators/#{url_encode(user_authenticator_id)}")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def track(user_id:, action:, attributes:)
|
|
68
|
+
path = "users/#{user_id}/actions/#{action}"
|
|
69
|
+
|
|
70
|
+
make_request(:post, path, body: attributes)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_challenge(token:, user_id: nil, action: nil)
|
|
74
|
+
path = 'validate'
|
|
75
|
+
body = { user_id: user_id, token: token, action: action }
|
|
76
|
+
|
|
77
|
+
make_request(:post, path, body: body)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_action(user_id:, action:, idempotency_key:)
|
|
81
|
+
make_request(:get, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def update_action(user_id:, action:, idempotency_key:, attributes:)
|
|
85
|
+
make_request(:patch, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}", body: attributes)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# TODO: delete identify?
|
|
90
|
+
def identify(user_id, user_payload)
|
|
91
|
+
make_request(:post, "users/#{url_encode(user_id)}", body: user_payload)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def url_encode(str)
|
|
97
|
+
ERB::Util.url_encode(str)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def version
|
|
101
|
+
Authsignal.configuration.version
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def print_api_key_warning
|
|
105
|
+
warn(NO_API_KEY_MESSAGE)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def require_api_key
|
|
109
|
+
Authsignal.configuration.api_secret_key || print_api_key_warning
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def make_request(method, path, body: nil, headers: nil)
|
|
113
|
+
body = body.compact if body.is_a?(Hash)
|
|
114
|
+
@client.public_send(method, path, body, headers)
|
|
116
115
|
end
|
|
116
|
+
end
|
|
117
117
|
end
|
|
@@ -1,52 +1,55 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Authsignal
|
|
4
4
|
class Configuration
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
5
|
+
attr_reader :defaults
|
|
6
|
+
|
|
7
|
+
def self.config_option(name)
|
|
8
|
+
define_method(name) do
|
|
9
|
+
read_value(name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
define_method("#{name}=") do |value|
|
|
13
|
+
set_value(name, value)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config_option :api_secret_key
|
|
18
|
+
config_option :api_url
|
|
19
|
+
config_option :debug
|
|
20
|
+
config_option :retry
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@config_values = {}
|
|
24
|
+
|
|
25
|
+
# set default attribute values
|
|
26
|
+
@defaults = {
|
|
27
|
+
api_url: 'https://signal.authsignal.com/v1/',
|
|
28
|
+
retry: false,
|
|
29
|
+
debug: false
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def [](key)
|
|
34
|
+
read_value(key)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def []=(key, value)
|
|
38
|
+
set_value(key, value)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def read_value(name)
|
|
44
|
+
if @config_values.key?(name)
|
|
45
|
+
@config_values[name]
|
|
46
|
+
else
|
|
47
|
+
@defaults[name]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def set_value(name, value)
|
|
52
|
+
@config_values[name] = value
|
|
53
|
+
end
|
|
54
|
+
end
|
|
52
55
|
end
|
|
@@ -7,9 +7,7 @@ module Authsignal
|
|
|
7
7
|
return if env.body.nil?
|
|
8
8
|
|
|
9
9
|
parsed_body = JSON.parse(env.body)
|
|
10
|
-
if parsed_body.is_a?(Hash)
|
|
11
|
-
env.body = camelcase_keys(parsed_body).to_json
|
|
12
|
-
end
|
|
10
|
+
env.body = camelcase_keys(parsed_body).to_json if parsed_body.is_a?(Hash)
|
|
13
11
|
rescue JSON::ParserError
|
|
14
12
|
# noop
|
|
15
13
|
end
|
|
@@ -9,7 +9,7 @@ module Authsignal
|
|
|
9
9
|
# Otherwise, we can safe guard with: env.response_headers['content-type'] =~ /application\/json/
|
|
10
10
|
parsed_body = JSON.parse(env.body)
|
|
11
11
|
if parsed_body.is_a?(Hash)
|
|
12
|
-
parsed_body.delete(
|
|
12
|
+
parsed_body.delete('actionCode') # Remove deprecated actionCode from response
|
|
13
13
|
env.body = transform_to_snake_case(parsed_body)
|
|
14
14
|
end
|
|
15
15
|
rescue JSON::ParserError
|
|
@@ -20,9 +20,10 @@ module Authsignal
|
|
|
20
20
|
|
|
21
21
|
def underscore(camelcased)
|
|
22
22
|
return camelcased.to_s unless /[A-Z-]|::/.match?(camelcased)
|
|
23
|
-
|
|
24
|
-
word.gsub
|
|
25
|
-
word.
|
|
23
|
+
|
|
24
|
+
word = camelcased.to_s.gsub('::', '/')
|
|
25
|
+
word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { (::Regexp.last_match(1) || ::Regexp.last_match(2)) << '_' }
|
|
26
|
+
word.tr!('-', '_')
|
|
26
27
|
word.downcase!
|
|
27
28
|
word
|
|
28
29
|
end
|
data/lib/authsignal/version.rb
CHANGED
data/lib/authsignal/webhook.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'openssl'
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'base64'
|
|
@@ -6,7 +8,7 @@ module Authsignal
|
|
|
6
8
|
DEFAULT_TOLERANCE = 5
|
|
7
9
|
|
|
8
10
|
class Webhook
|
|
9
|
-
VERSION =
|
|
11
|
+
VERSION = 'v2'
|
|
10
12
|
|
|
11
13
|
attr_reader :api_secret_key
|
|
12
14
|
|
|
@@ -19,8 +21,8 @@ module Authsignal
|
|
|
19
21
|
|
|
20
22
|
seconds_since_epoch = Time.now.to_i
|
|
21
23
|
|
|
22
|
-
if tolerance
|
|
23
|
-
raise InvalidSignatureError,
|
|
24
|
+
if tolerance.positive? && parsed_signature[:timestamp] < seconds_since_epoch - (tolerance * 60)
|
|
25
|
+
raise InvalidSignatureError, 'Timestamp is outside the tolerance zone.'
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
hmac_content = "#{parsed_signature[:timestamp]}.#{payload}"
|
|
@@ -41,34 +43,29 @@ module Authsignal
|
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
|
|
44
|
-
unless match
|
|
45
|
-
raise InvalidSignatureError, "Signature mismatch."
|
|
46
|
-
end
|
|
46
|
+
raise InvalidSignatureError, 'Signature mismatch.' unless match
|
|
47
47
|
|
|
48
48
|
JSON.parse(payload, symbolize_names: true)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def parse_signature(value)
|
|
52
|
-
|
|
53
|
-
timestamp: -1,
|
|
54
|
-
signatures: []
|
|
55
|
-
}
|
|
52
|
+
handle_invalid_signature unless value
|
|
56
53
|
|
|
57
|
-
|
|
54
|
+
result = extract_signature_parts(value)
|
|
55
|
+
handle_invalid_signature if result[:timestamp] == -1 || result[:signatures].empty?
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
next unless kv.length == 2
|
|
57
|
+
result
|
|
58
|
+
end
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
def extract_signature_parts(value)
|
|
61
|
+
result = { timestamp: -1, signatures: [] }
|
|
62
|
+
|
|
63
|
+
value.split(',').each do |item|
|
|
64
|
+
key, val = item.split('=')
|
|
65
|
+
next unless key && val
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
result[:timestamp] = val.to_i if key == 't'
|
|
68
|
+
result[:signatures] << val if key == VERSION
|
|
72
69
|
end
|
|
73
70
|
|
|
74
71
|
result
|
|
@@ -77,7 +74,7 @@ module Authsignal
|
|
|
77
74
|
private
|
|
78
75
|
|
|
79
76
|
def handle_invalid_signature
|
|
80
|
-
raise InvalidSignatureError,
|
|
77
|
+
raise InvalidSignatureError, 'Signature format is invalid.'
|
|
81
78
|
end
|
|
82
79
|
end
|
|
83
80
|
end
|
data/lib/authsignal.rb
CHANGED
|
@@ -1,133 +1,136 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require
|
|
10
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday'
|
|
4
|
+
require 'faraday/retry'
|
|
5
|
+
require 'authsignal/version'
|
|
6
|
+
require 'authsignal/client'
|
|
7
|
+
require 'authsignal/configuration'
|
|
8
|
+
require 'authsignal/api_error'
|
|
9
|
+
require 'authsignal/invalid_signature_error'
|
|
10
|
+
require 'authsignal/webhook'
|
|
11
|
+
require 'authsignal/middleware/json_response'
|
|
12
|
+
require 'authsignal/middleware/json_request'
|
|
11
13
|
|
|
12
14
|
module Authsignal
|
|
13
|
-
|
|
15
|
+
NON_API_METHODS = %i[setup configuration default_configuration webhook].freeze
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
class << self
|
|
18
|
+
attr_writer :configuration
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def configuration
|
|
23
|
-
@configuration ||= Authsignal::Configuration.new
|
|
24
|
-
end
|
|
20
|
+
def setup
|
|
21
|
+
yield(configuration)
|
|
22
|
+
end
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
def configuration
|
|
25
|
+
@configuration ||= Authsignal::Configuration.new
|
|
26
|
+
end
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
def default_configuration
|
|
29
|
+
configuration.defaults
|
|
30
|
+
end
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
def webhook
|
|
33
|
+
@webhook ||= Webhook.new(configuration.api_secret_key)
|
|
34
|
+
end
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
def get_user(user_id:)
|
|
37
|
+
response = Client.new.get_user(user_id: user_id)
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
handle_response(response)
|
|
40
|
+
end
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
def update_user(user_id:, attributes:)
|
|
43
|
+
response = Client.new.update_user(user_id: user_id, attributes: attributes)
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
handle_response(response)
|
|
46
|
+
end
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
def delete_user(user_id:)
|
|
49
|
+
response = Client.new.delete_user(user_id: user_id)
|
|
51
50
|
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
handle_response(response)
|
|
52
|
+
end
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
def get_authenticators(user_id:)
|
|
55
|
+
response = Client.new.get_authenticators(user_id: user_id)
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
handle_response(response)
|
|
58
|
+
end
|
|
60
59
|
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
def enroll_verified_authenticator(user_id:, attributes:)
|
|
61
|
+
response = Client.new.enroll_verified_authenticator(user_id: user_id, attributes: attributes)
|
|
63
62
|
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
handle_response(response)
|
|
64
|
+
end
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
def delete_authenticator(user_id:, user_authenticator_id:)
|
|
67
|
+
response = Client.new.delete_authenticator(user_id: user_id, user_authenticator_id: user_authenticator_id)
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
handle_response(response)
|
|
73
|
-
end
|
|
69
|
+
handle_response(response)
|
|
70
|
+
end
|
|
74
71
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
72
|
+
def track(user_id:, action:, attributes:)
|
|
73
|
+
response = Client.new.track(user_id: user_id, action: action, attributes: attributes)
|
|
74
|
+
handle_response(response)
|
|
75
|
+
end
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
def validate_challenge(token:, user_id: nil, action: nil)
|
|
78
|
+
response = Client.new.validate_challenge(token: token, user_id: user_id, action: action)
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
handle_response(response)
|
|
81
|
+
end
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
def get_action(user_id:, action:, idempotency_key:)
|
|
84
|
+
response = Client.new.get_action(user_id: user_id, action: action, idempotency_key: idempotency_key)
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
handle_response(response)
|
|
87
|
+
end
|
|
92
88
|
|
|
93
|
-
|
|
89
|
+
def update_action(user_id:, action:, idempotency_key:, attributes:)
|
|
90
|
+
response = Client.new.update_action(user_id: user_id, action: action, idempotency_key: idempotency_key, attributes: attributes)
|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
handle_success_response(response)
|
|
98
|
-
else
|
|
99
|
-
handle_error_response(response)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
92
|
+
handle_response(response)
|
|
93
|
+
end
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
if response.body.is_a?(Array)
|
|
105
|
-
{ success?: true, data: response.body }
|
|
106
|
-
else
|
|
107
|
-
response.body.merge(success?: true)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
95
|
+
private
|
|
110
96
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
end
|
|
118
|
-
end
|
|
97
|
+
def handle_response(response)
|
|
98
|
+
if response.success?
|
|
99
|
+
handle_success_response(response)
|
|
100
|
+
else
|
|
101
|
+
handle_error_response(response)
|
|
102
|
+
end
|
|
119
103
|
end
|
|
120
104
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
def handle_success_response(response)
|
|
106
|
+
if response.body.is_a?(Array)
|
|
107
|
+
{ success?: true, data: response.body }
|
|
108
|
+
else
|
|
109
|
+
response.body.merge(success?: true)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
128
112
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
113
|
+
def handle_error_response(response)
|
|
114
|
+
case response.body
|
|
115
|
+
when Hash
|
|
116
|
+
{ status_code: response.status, success?: false, error_code: response.body[:error],
|
|
117
|
+
error_description: response.body[:error_description] }
|
|
118
|
+
else
|
|
119
|
+
{ status_code: response&.status || 500, success?: false }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
methods = Authsignal.singleton_class.public_instance_methods(false)
|
|
125
|
+
(methods - NON_API_METHODS).each do |method|
|
|
126
|
+
define_singleton_method("#{method}!") do |*args, **kwargs|
|
|
127
|
+
send(method, *args, **kwargs).tap do |response|
|
|
128
|
+
status_code = response[:status_code]
|
|
129
|
+
error_code = response[:error_code]
|
|
130
|
+
error_description = response[:error_description]
|
|
131
|
+
|
|
132
|
+
raise ApiError.new(status_code, error_code, error_description) unless response[:success?]
|
|
133
|
+
end
|
|
132
134
|
end
|
|
135
|
+
end
|
|
133
136
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: authsignal-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.2.
|
|
4
|
+
version: 5.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- justinsoong
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-01-07 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: faraday
|
|
@@ -101,7 +102,9 @@ executables: []
|
|
|
101
102
|
extensions: []
|
|
102
103
|
extra_rdoc_files: []
|
|
103
104
|
files:
|
|
105
|
+
- ".editorconfig"
|
|
104
106
|
- ".rspec"
|
|
107
|
+
- ".rubocop.yml"
|
|
105
108
|
- ".tool-versions"
|
|
106
109
|
- CHANGELOG.md
|
|
107
110
|
- Gemfile
|
|
@@ -127,6 +130,7 @@ licenses:
|
|
|
127
130
|
metadata:
|
|
128
131
|
homepage_uri: https://www.authsignal.com
|
|
129
132
|
source_code_uri: https://github.com/authsignal/authsignal-ruby
|
|
133
|
+
post_install_message:
|
|
130
134
|
rdoc_options: []
|
|
131
135
|
require_paths:
|
|
132
136
|
- lib
|
|
@@ -141,7 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
141
145
|
- !ruby/object:Gem::Version
|
|
142
146
|
version: '0'
|
|
143
147
|
requirements: []
|
|
144
|
-
rubygems_version: 3.
|
|
148
|
+
rubygems_version: 3.4.19
|
|
149
|
+
signing_key:
|
|
145
150
|
specification_version: 4
|
|
146
151
|
summary: The Authsignal ruby server side signal API.
|
|
147
152
|
test_files: []
|