authsignal-ruby 5.2.1 → 5.3.0

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: eb8afa36ec62970a06618e860b3f343d4e7d7c81ce70e45d905e69c5cccf9c83
4
- data.tar.gz: a55a775d64b0569d044f1f4f4362717a160fbb214f63f56af2e11c5d391dc1de
3
+ metadata.gz: bf8ccfc3244f86d9e99c7c481d5c23fb9f341aad018e371522f3755908e9bc9d
4
+ data.tar.gz: c6b7cc9b3c50fcc043ffdd73cbd14dd7114fa8b7977f654f1ade619444061b9d
5
5
  SHA512:
6
- metadata.gz: 1d0bf703634455560509482b03ff746828ac10dd824ebe4d83bfc2795e5c6f8db7f45c1a7655233bfb762a0034b8fa98285275510170b4acd03194b4e9630ffd
7
- data.tar.gz: 6d724b25f111840ff0517ccf6e7c6c5a203c4de48e00a459e296b3ab6ea15a86ba6c31fb3db9961cc5ca0616e82fb7cec5b8c8e52027459a231561409ad249cc
6
+ metadata.gz: c83bb4ab1de9b24b12820ebe82328741150e7f3821e954588f79df4b8e8b2d4ccfc3b259c36a0b86d23424940e0dda04107b8dfd33bb0e2fee9514bed41a3f2a
7
+ data.tar.gz: 97b80dcf590ec5f8f0ffa196e908669dcfdefaf5ea90a8228e2378add33d522a565488373c8ac8884936f3119c91965c48ea2aa7df5d1f003aa309694399d846
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,50 @@
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/modules can be longer
26
+ Metrics/ClassLength:
27
+ Max: 250
28
+
29
+ Metrics/ModuleLength:
30
+ Max: 150
31
+
32
+ Metrics/MethodLength:
33
+ Max: 25
34
+
35
+ Metrics/AbcSize:
36
+ Max: 30
37
+
38
+ # Skip documentation requirement for SDK
39
+ Style/Documentation:
40
+ Enabled: false
41
+
42
+ # Gemspec specific
43
+ Gemspec/DevelopmentDependencies:
44
+ Enabled: false
45
+
46
+ Gemspec/RequireMFA:
47
+ Enabled: false
48
+
49
+ Gemspec/OrderedDependencies:
50
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.6.10
data/Gemfile CHANGED
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source "https://rubygems.org"
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.1)
4
+ authsignal-ruby (5.3.0)
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.7)
13
- public_suffix (>= 2.0.2, < 7.0)
14
- base64 (0.2.0)
15
- bigdecimal (3.1.8)
16
- crack (1.0.0)
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.5.1)
20
- dotenv (3.1.7)
21
- faraday (2.12.0)
22
- faraday-net_http (>= 2.0, < 3.4)
23
- json
24
- logger
25
- faraday-net_http (3.3.0)
26
- net-http
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.1.1)
30
- json (2.7.2)
31
- logger (1.6.1)
32
- net-http (0.4.1)
33
- uri
34
- public_suffix (6.0.1)
35
- rake (13.2.1)
36
- rexml (3.3.8)
37
- rspec (3.13.0)
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.2)
45
+ rspec-core (3.13.6)
42
46
  rspec-support (~> 3.13.0)
43
- rspec-expectations (3.13.3)
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.2)
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.1)
50
- uri (0.13.1)
51
- webmock (3.24.0)
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
- 2.5.14
86
+ 1.17.2
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- task default: :spec
8
+ task default: :spec
data/bin/console CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "bundler/setup"
5
- require "authsignal"
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 "irb"
14
+ require 'irb'
15
15
  IRB.start(__FILE__)
@@ -25,7 +25,7 @@ module Authsignal
25
25
  end
26
26
 
27
27
  def format_description(error_code, error_description)
28
- error_description && error_description.length > 0 ? error_description : error_code
28
+ error_description&.length&.positive? ? error_description : error_code
29
29
  end
30
30
  end
31
31
  end
@@ -1,117 +1,259 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'erb'
2
4
 
3
5
  module Authsignal
4
- class Client
5
- USER_AGENT = "authsignal-ruby"
6
- NO_API_KEY_MESSAGE = "No Authsignal API Secret Key Set"
7
-
8
- RETRY_OPTIONS = {
9
- max: 3,
10
- interval: 0.1,
11
- interval_randomness: 0.5,
12
- backoff_factor: 2,
13
- }.freeze
14
- private_constant :RETRY_OPTIONS
15
-
16
- def initialize(retry_options: RETRY_OPTIONS)
17
- @api_key = require_api_key
18
-
19
- @client = Faraday.new do |builder|
20
- builder.url_prefix = Authsignal.configuration.api_url
21
- builder.adapter :net_http
22
- builder.request :authorization, :basic, @api_key, nil
23
-
24
- builder.headers['Accept'] = 'application/json'
25
- builder.headers['Content-Type'] = 'application/json'
26
- builder.headers['User-Agent'] = USER_AGENT
27
- builder.headers['X-Authsignal-Version'] = Authsignal::VERSION
28
-
29
- builder.request :json
30
- builder.response :json, parser_options: { symbolize_names: true }
31
-
32
- builder.use Middleware::JsonRequest
33
- builder.use Middleware::JsonResponse
34
-
35
- builder.request :retry, retry_options if Authsignal.configuration.retry
36
- builder.response :logger, ::Logger.new(STDOUT), bodies: true if Authsignal.configuration.debug
37
- end
38
- end
39
-
40
- def get_user(user_id:)
41
- path = "users/#{url_encode(user_id)}"
42
- make_request(:get, path)
43
- end
44
-
45
- def update_user(user_id:, attributes:)
46
- make_request(:post, "users/#{url_encode(user_id)}", body: attributes)
47
- end
48
-
49
- def delete_user(user_id:)
50
- make_request(:delete, "users/#{url_encode(user_id)}")
51
- end
52
-
53
- def get_authenticators(user_id:)
54
- make_request(:get, "users/#{url_encode(user_id)}/authenticators")
55
- end
56
-
57
- def enroll_verified_authenticator(user_id:, attributes:)
58
- make_request(:post, "users/#{url_encode(user_id)}/authenticators", body: attributes)
59
- end
60
-
61
- def delete_authenticator(user_id:, user_authenticator_id:)
62
- make_request(:delete, "users/#{url_encode(user_id)}/authenticators/#{url_encode(user_authenticator_id)}")
63
- end
64
-
65
- def track(user_id:, action:, attributes:)
66
- path = "users/#{user_id}/actions/#{action}"
67
-
68
- make_request(:post, path, body: attributes)
69
- end
70
-
71
- def validate_challenge(token:, user_id: nil, action: nil)
72
- path = "validate"
73
- body = { user_id: user_id, token: token, action: action }
74
-
75
- make_request(:post, path, body: body)
76
- end
77
-
78
- def get_action(user_id:, action:, idempotency_key:)
79
- make_request(:get, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}")
80
- end
81
-
82
- def update_action(user_id:, action:, idempotency_key:, attributes:)
83
- make_request(:patch, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}", body: attributes)
84
- end
85
-
86
- ##
87
- # TODO: delete identify?
88
- def identify(user_id, user_payload)
89
- make_request(:post , "users/#{url_encode(user_id)}", body: user_payload)
90
- end
91
-
92
- private
93
-
94
- def url_encode(s)
95
- ERB::Util.url_encode(s)
96
- end
97
-
98
- def version
99
- Authsignal.configuration.version
100
- end
101
-
102
- def print_api_key_warning
103
- $stderr.puts(NO_API_KEY_MESSAGE)
104
- end
105
-
106
- def require_api_key
107
- Authsignal.configuration.api_secret_key || print_api_key_warning
108
- end
109
-
110
- def make_request(method, path, body: nil, headers: nil)
111
- if body.is_a?(Hash)
112
- body = body.reject { |_, v| v.nil? }
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 query_users(
56
+ username: nil,
57
+ email: nil,
58
+ phone_number: nil,
59
+ token: nil,
60
+ limit: nil,
61
+ last_evaluated_user_id: nil
62
+ )
63
+ params = {
64
+ username: username,
65
+ email: email,
66
+ phoneNumber: phone_number,
67
+ token: token,
68
+ limit: limit&.to_s,
69
+ lastEvaluatedUserId: last_evaluated_user_id
70
+ }.compact
71
+
72
+ path = params.empty? ? 'users' : "users?#{URI.encode_www_form(params)}"
73
+ make_request(:get, path)
74
+ end
75
+
76
+ def get_authenticators(user_id:)
77
+ make_request(:get, "users/#{url_encode(user_id)}/authenticators")
78
+ end
79
+
80
+ def enroll_verified_authenticator(user_id:, attributes:)
81
+ make_request(:post, "users/#{url_encode(user_id)}/authenticators", body: attributes)
82
+ end
83
+
84
+ def delete_authenticator(user_id:, user_authenticator_id:)
85
+ make_request(:delete, "users/#{url_encode(user_id)}/authenticators/#{url_encode(user_authenticator_id)}")
86
+ end
87
+
88
+ def track(user_id:, action:, attributes:)
89
+ path = "users/#{user_id}/actions/#{action}"
90
+
91
+ make_request(:post, path, body: attributes)
92
+ end
93
+
94
+ def validate_challenge(token:, user_id: nil, action: nil)
95
+ path = 'validate'
96
+ body = { user_id: user_id, token: token, action: action }
97
+
98
+ make_request(:post, path, body: body)
99
+ end
100
+
101
+ def get_action(user_id:, action:, idempotency_key:)
102
+ make_request(:get, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}")
103
+ end
104
+
105
+ def query_user_actions(
106
+ user_id:,
107
+ from_date: nil,
108
+ action_codes: [],
109
+ state: nil
110
+ )
111
+ params = {
112
+ fromDate: from_date,
113
+ codes: action_codes.empty? ? nil : action_codes.join(','),
114
+ state: state
115
+ }.compact
116
+
117
+ base_path = "users/#{url_encode(user_id)}/actions"
118
+ path = params.empty? ? base_path : "#{base_path}?#{URI.encode_www_form(params)}"
119
+ make_request(:get, path)
120
+ end
121
+
122
+ def update_action(user_id:, action:, idempotency_key:, attributes:)
123
+ make_request(:patch, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}", body: attributes)
124
+ end
125
+
126
+ def challenge(
127
+ verification_method:,
128
+ action:,
129
+ idempotency_key: nil,
130
+ user_id: nil,
131
+ email: nil,
132
+ phone_number: nil,
133
+ sms_channel: nil,
134
+ locale: nil,
135
+ device_id: nil,
136
+ ip_address: nil,
137
+ user_agent: nil,
138
+ custom: nil,
139
+ scope: nil
140
+ )
141
+ body = {
142
+ verification_method: verification_method,
143
+ action: action,
144
+ idempotency_key: idempotency_key,
145
+ user_id: user_id,
146
+ email: email,
147
+ phone_number: phone_number,
148
+ sms_channel: sms_channel,
149
+ locale: locale,
150
+ device_id: device_id,
151
+ ip_address: ip_address,
152
+ user_agent: user_agent,
153
+ custom: custom,
154
+ scope: scope
155
+ }
156
+ make_request(:post, 'challenge', body: body)
157
+ end
158
+
159
+ def verify(challenge_id:, verification_code:)
160
+ body = {
161
+ challenge_id: challenge_id,
162
+ verification_code: verification_code
163
+ }
164
+ make_request(:post, 'verify', body: body)
165
+ end
166
+
167
+ def claim_challenge(
168
+ challenge_id:,
169
+ user_id:,
170
+ skip_verification_check: nil
171
+ )
172
+ body = {
173
+ challenge_id: challenge_id,
174
+ user_id: user_id,
175
+ skip_verification_check: skip_verification_check
176
+ }
177
+ make_request(:post, 'claim', body: body)
178
+ end
179
+
180
+ def get_challenge(
181
+ challenge_id: nil,
182
+ user_id: nil,
183
+ action: nil,
184
+ verification_method: nil
185
+ )
186
+ params = {}
187
+ params[:challengeId] = challenge_id if challenge_id
188
+ params[:userId] = user_id if user_id
189
+ params[:action] = action if action
190
+ params[:verificationMethod] = verification_method if verification_method
191
+
192
+ query_string = URI.encode_www_form(params) unless params.empty?
193
+ path = query_string ? "challenges?#{query_string}" : 'challenges'
194
+
195
+ make_request(:get, path)
196
+ end
197
+
198
+ def create_session(client_id:, token:, action: nil)
199
+ body = {
200
+ client_id: client_id,
201
+ token: token,
202
+ action: action
203
+ }.compact
204
+ make_request(:post, 'sessions', body: body)
205
+ end
206
+
207
+ def validate_session(access_token:, client_ids: nil)
208
+ body = {
209
+ access_token: access_token,
210
+ client_ids: client_ids
211
+ }.compact
212
+ make_request(:post, 'sessions/validate', body: body)
213
+ end
214
+
215
+ def refresh_session(refresh_token:)
216
+ body = { refresh_token: refresh_token }
217
+ make_request(:post, 'sessions/refresh', body: body)
218
+ end
219
+
220
+ def revoke_session(access_token:)
221
+ body = { access_token: access_token }
222
+ make_request(:post, 'sessions/revoke', body: body)
223
+ end
224
+
225
+ def revoke_user_sessions(user_id:)
226
+ body = { user_id: user_id }
227
+ make_request(:post, 'sessions/user/revoke', body: body)
228
+ end
229
+
230
+ ##
231
+ # TODO: delete identify?
232
+ def identify(user_id, user_payload)
233
+ make_request(:post, "users/#{url_encode(user_id)}", body: user_payload)
234
+ end
235
+
236
+ private
237
+
238
+ def url_encode(str)
239
+ ERB::Util.url_encode(str)
240
+ end
241
+
242
+ def version
243
+ Authsignal.configuration.version
244
+ end
245
+
246
+ def print_api_key_warning
247
+ warn(NO_API_KEY_MESSAGE)
248
+ end
249
+
250
+ def require_api_key
251
+ Authsignal.configuration.api_secret_key || print_api_key_warning
252
+ end
253
+
254
+ def make_request(method, path, body: nil, headers: nil)
255
+ body = body.compact if body.is_a?(Hash)
256
+ @client.public_send(method, path, body, headers)
116
257
  end
258
+ end
117
259
  end
@@ -1,52 +1,55 @@
1
- require "ostruct"
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Authsignal
4
4
  class Configuration
5
- def self.config_option(name)
6
- define_method(name) do
7
- read_value(name)
8
- end
9
-
10
- define_method("#{name}=") do |value|
11
- set_value(name, value)
12
- end
13
- end
14
-
15
- config_option :api_secret_key
16
- config_option :api_url
17
- config_option :debug
18
- config_option :retry
19
-
20
- def initialize
21
- @config_values = {}
22
-
23
- # set default attribute values
24
- @defaults = OpenStruct.new({
25
- api_url: 'https://signal.authsignal.com/v1/',
26
- retry: false,
27
- debug: false
28
- })
29
- end
30
-
31
- def [](key)
32
- read_value(key)
33
- end
34
-
35
- def []=(key, value)
36
- set_value(key, value)
37
- end
38
-
39
- private
40
- def read_value(name)
41
- if @config_values.has_key?(name)
42
- @config_values[name]
43
- else
44
- @defaults.send(name)
45
- end
46
- end
47
-
48
- def set_value(name, value)
49
- @config_values[name] = value
50
- end
51
- end
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
@@ -1,7 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Authsignal
2
4
  class InvalidSignatureError < StandardError
3
- def initialize(message)
4
- super(message)
5
- end
6
5
  end
7
6
  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("actionCode") # Remove deprecated actionCode from response
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
- word = camelcased.to_s.gsub("::", "/")
24
- word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
25
- word.tr!("-", "_")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Authsignal
4
- VERSION = "5.2.1"
4
+ VERSION = '5.3.0'
5
5
  end
@@ -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 = "v2"
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 > 0 && parsed_signature[:timestamp] < seconds_since_epoch - (tolerance * 60)
23
- raise InvalidSignatureError, "Timestamp is outside the tolerance zone."
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
- result = {
53
- timestamp: -1,
54
- signatures: []
55
- }
52
+ handle_invalid_signature unless value
56
53
 
57
- return handle_invalid_signature unless value
54
+ result = extract_signature_parts(value)
55
+ handle_invalid_signature if result[:timestamp] == -1 || result[:signatures].empty?
58
56
 
59
- value.split(',').each do |item|
60
- kv = item.split('=')
61
- next unless kv.length == 2
57
+ result
58
+ end
62
59
 
63
- if kv[0] == 't'
64
- result[:timestamp] = kv[1].to_i
65
- elsif kv[0] == VERSION
66
- result[:signatures] << kv[1]
67
- end
68
- end
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
- if result[:timestamp] == -1 || result[:signatures].empty?
71
- handle_invalid_signature
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, "Signature format is invalid."
77
+ raise InvalidSignatureError, 'Signature format is invalid.'
81
78
  end
82
79
  end
83
80
  end
data/lib/authsignal.rb CHANGED
@@ -1,133 +1,202 @@
1
- require "faraday"
2
- require "faraday/retry"
3
- require "authsignal/version"
4
- require "authsignal/client"
5
- require "authsignal/configuration"
6
- require "authsignal/api_error"
7
- require "authsignal/invalid_signature_error"
8
- require "authsignal/webhook"
9
- require "authsignal/middleware/json_response"
10
- require "authsignal/middleware/json_request"
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
- NON_API_METHODS = [:setup, :configuration, :default_configuration, :webhook]
15
+ NON_API_METHODS = %i[setup configuration default_configuration webhook].freeze
14
16
 
15
- class << self
16
- attr_writer :configuration
17
+ class << self
18
+ attr_writer :configuration
17
19
 
18
- def setup
19
- yield(configuration)
20
- end
20
+ def setup
21
+ yield(configuration)
22
+ end
23
+
24
+ def configuration
25
+ @configuration ||= Authsignal::Configuration.new
26
+ end
27
+
28
+ def default_configuration
29
+ configuration.defaults
30
+ end
31
+
32
+ def webhook
33
+ @webhook ||= Webhook.new(configuration.api_secret_key)
34
+ end
35
+
36
+ def get_user(user_id:)
37
+ response = Client.new.get_user(user_id: user_id)
38
+
39
+ handle_response(response)
40
+ end
41
+
42
+ def query_users(**options)
43
+ response = Client.new.query_users(**options)
44
+
45
+ handle_response(response)
46
+ end
47
+
48
+ def update_user(user_id:, attributes:)
49
+ response = Client.new.update_user(user_id: user_id, attributes: attributes)
50
+
51
+ handle_response(response)
52
+ end
53
+
54
+ def delete_user(user_id:)
55
+ response = Client.new.delete_user(user_id: user_id)
56
+
57
+ handle_response(response)
58
+ end
59
+
60
+ def get_authenticators(user_id:)
61
+ response = Client.new.get_authenticators(user_id: user_id)
62
+
63
+ handle_response(response)
64
+ end
65
+
66
+ def enroll_verified_authenticator(user_id:, attributes:)
67
+ response = Client.new.enroll_verified_authenticator(user_id: user_id, attributes: attributes)
68
+
69
+ handle_response(response)
70
+ end
71
+
72
+ def delete_authenticator(user_id:, user_authenticator_id:)
73
+ response = Client.new.delete_authenticator(user_id: user_id, user_authenticator_id: user_authenticator_id)
74
+
75
+ handle_response(response)
76
+ end
77
+
78
+ def track(user_id:, action:, attributes:)
79
+ response = Client.new.track(user_id: user_id, action: action, attributes: attributes)
80
+ handle_response(response)
81
+ end
21
82
 
22
- def configuration
23
- @configuration ||= Authsignal::Configuration.new
24
- end
83
+ def validate_challenge(token:, user_id: nil, action: nil)
84
+ response = Client.new.validate_challenge(token: token, user_id: user_id, action: action)
25
85
 
26
- def default_configuration
27
- configuration.defaults
28
- end
86
+ handle_response(response)
87
+ end
88
+
89
+ def get_action(user_id:, action:, idempotency_key:)
90
+ response = Client.new.get_action(user_id: user_id, action: action, idempotency_key: idempotency_key)
91
+
92
+ handle_response(response)
93
+ end
94
+
95
+ def query_user_actions(user_id:, **options)
96
+ response = Client.new.query_user_actions(user_id: user_id, **options)
29
97
 
30
- def webhook
31
- @webhook ||= Webhook.new(configuration.api_secret_key)
32
- end
98
+ handle_response(response)
99
+ end
33
100
 
34
- def get_user(user_id:)
35
- response = Client.new.get_user(user_id: user_id)
101
+ def update_action(user_id:, action:, idempotency_key:, attributes:)
102
+ response = Client.new.update_action(user_id: user_id, action: action, idempotency_key: idempotency_key, attributes: attributes)
36
103
 
37
- handle_response(response)
38
- end
104
+ handle_response(response)
105
+ end
39
106
 
40
- def update_user(user_id:, attributes:)
41
- response = Client.new.update_user(user_id: user_id, attributes: attributes)
107
+ def challenge(verification_method:, action:, **options)
108
+ response = Client.new.challenge(verification_method: verification_method, action: action, **options)
42
109
 
43
- handle_response(response)
44
- end
110
+ handle_response(response)
111
+ end
45
112
 
46
- def delete_user(user_id:)
47
- response = Client.new.delete_user(user_id: user_id)
113
+ def verify(challenge_id:, verification_code:)
114
+ response = Client.new.verify(challenge_id: challenge_id, verification_code: verification_code)
48
115
 
49
- handle_response(response)
50
- end
116
+ handle_response(response)
117
+ end
51
118
 
52
- def get_authenticators(user_id:)
53
- response = Client.new.get_authenticators(user_id: user_id)
119
+ def claim_challenge(challenge_id:, user_id:, **options)
120
+ response = Client.new.claim_challenge(challenge_id: challenge_id, user_id: user_id, **options)
54
121
 
55
- handle_response(response)
56
- end
122
+ handle_response(response)
123
+ end
57
124
 
58
- def enroll_verified_authenticator(user_id:, attributes:)
59
- response = Client.new.enroll_verified_authenticator(user_id: user_id, attributes: attributes)
125
+ def get_challenge(**options)
126
+ response = Client.new.get_challenge(**options)
60
127
 
61
- handle_response(response)
62
- end
128
+ handle_response(response)
129
+ end
63
130
 
64
- def delete_authenticator(user_id:, user_authenticator_id:)
65
- response = Client.new.delete_authenticator(user_id: user_id, user_authenticator_id: user_authenticator_id)
131
+ def create_session(client_id:, token:, action: nil)
132
+ response = Client.new.create_session(client_id: client_id, token: token, action: action)
66
133
 
67
- handle_response(response)
68
- end
134
+ handle_response(response)
135
+ end
69
136
 
70
- def track(user_id:, action:, attributes:)
71
- response = Client.new.track(user_id: user_id, action: action, attributes: attributes)
72
- handle_response(response)
73
- end
137
+ def validate_session(access_token:, client_ids: nil)
138
+ response = Client.new.validate_session(access_token: access_token, client_ids: client_ids)
74
139
 
75
- def validate_challenge(token:, user_id: nil, action: nil)
76
- response = Client.new.validate_challenge(token: token,user_id: user_id, action: action)
77
-
78
- handle_response(response)
79
- end
140
+ handle_response(response)
141
+ end
80
142
 
81
- def get_action(user_id:, action:, idempotency_key:)
82
- response = Client.new.get_action(user_id: user_id, action: action, idempotency_key: idempotency_key)
143
+ def refresh_session(refresh_token:)
144
+ response = Client.new.refresh_session(refresh_token: refresh_token)
83
145
 
84
- handle_response(response)
85
- end
146
+ handle_response(response)
147
+ end
86
148
 
87
- def update_action(user_id:, action:, idempotency_key:, attributes:)
88
- response = Client.new.update_action(user_id: user_id, action: action, idempotency_key: idempotency_key, attributes: attributes)
149
+ def revoke_session(access_token:)
150
+ response = Client.new.revoke_session(access_token: access_token)
89
151
 
90
- handle_response(response)
91
- end
152
+ handle_response(response)
153
+ end
92
154
 
93
- private
155
+ def revoke_user_sessions(user_id:)
156
+ response = Client.new.revoke_user_sessions(user_id: user_id)
94
157
 
95
- def handle_response(response)
96
- if response.success?
97
- handle_success_response(response)
98
- else
99
- handle_error_response(response)
100
- end
101
- end
158
+ handle_response(response)
159
+ end
102
160
 
103
- def handle_success_response(response)
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
161
+ private
110
162
 
111
- def handle_error_response(response)
112
- case response.body
113
- when Hash
114
- { status_code: response.status, success?: false, error_code: response.body[:error], error_description: response.body[:error_description] }
115
- else
116
- { status_code: response&.status || 500, success?: false }
117
- end
118
- end
163
+ def handle_response(response)
164
+ if response.success?
165
+ handle_success_response(response)
166
+ else
167
+ handle_error_response(response)
168
+ end
119
169
  end
120
170
 
121
- methods = Authsignal.singleton_class.public_instance_methods(false)
122
- (methods - NON_API_METHODS).each do |method|
123
- define_singleton_method("#{method}!") do |*args, **kwargs|
124
- send(method, *args, **kwargs).tap do |response|
125
- status_code = response[:status_code]
126
- error_code = response[:error_code]
127
- error_description = response[:error_description]
171
+ def handle_success_response(response)
172
+ if response.body.is_a?(Array)
173
+ { success?: true, data: response.body }
174
+ else
175
+ response.body.merge(success?: true)
176
+ end
177
+ end
128
178
 
129
- raise ApiError.new(status_code, error_code, error_description) unless response[:success?]
130
- end
131
- end
179
+ def handle_error_response(response)
180
+ case response.body
181
+ when Hash
182
+ { status_code: response.status, success?: false, error_code: response.body[:error],
183
+ error_description: response.body[:error_description] }
184
+ else
185
+ { status_code: response&.status || 500, success?: false }
186
+ end
187
+ end
188
+ end
189
+
190
+ methods = Authsignal.singleton_class.public_instance_methods(false)
191
+ (methods - NON_API_METHODS).each do |method|
192
+ define_singleton_method("#{method}!") do |*args, **kwargs|
193
+ send(method, *args, **kwargs).tap do |response|
194
+ status_code = response[:status_code]
195
+ error_code = response[:error_code]
196
+ error_description = response[:error_description]
197
+
198
+ raise ApiError.new(status_code, error_code, error_description) unless response[:success?]
199
+ end
132
200
  end
201
+ end
133
202
  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.1
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - justinsoong
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-01-08 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: faraday
@@ -101,7 +102,10 @@ executables: []
101
102
  extensions: []
102
103
  extra_rdoc_files: []
103
104
  files:
105
+ - ".editorconfig"
104
106
  - ".rspec"
107
+ - ".rubocop.yml"
108
+ - ".ruby-version"
105
109
  - ".tool-versions"
106
110
  - CHANGELOG.md
107
111
  - Gemfile
@@ -127,6 +131,7 @@ licenses:
127
131
  metadata:
128
132
  homepage_uri: https://www.authsignal.com
129
133
  source_code_uri: https://github.com/authsignal/authsignal-ruby
134
+ post_install_message:
130
135
  rdoc_options: []
131
136
  require_paths:
132
137
  - lib
@@ -141,7 +146,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
146
  - !ruby/object:Gem::Version
142
147
  version: '0'
143
148
  requirements: []
144
- rubygems_version: 3.6.9
149
+ rubygems_version: 3.4.19
150
+ signing_key:
145
151
  specification_version: 4
146
152
  summary: The Authsignal ruby server side signal API.
147
153
  test_files: []