authsignal-ruby 2.1.3 → 3.0.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: 261ad3aa9e626f62f4b8ec843b0ec4516a157b083980cae0ed7c8641f16b5088
4
- data.tar.gz: 56d2f9ad45e5f6330999034ee7ac4667f5864bd0a2a745941f11edca0e840395
3
+ metadata.gz: b0204e93c2f7fa2b3bf20379b5837243a62960ce0b35c39d69e41f6c98b02125
4
+ data.tar.gz: 4624f0d600ac6377b4d2ef3945121e7a8dc628852786ca8ecccb2b6c7b589d07
5
5
  SHA512:
6
- metadata.gz: eac78f4ddab0dbb2d042c2d77ce424b512524cb0c1b31c5c956793e651441a4b2d93c1611a195eadfe53b4d377c1ad404a2190ee2b88add9a5867c40ba5c396e
7
- data.tar.gz: a0178eda449c5707522d441d680fd7ea7586fa21694a4ca7ea14a24b83543b9cfbcdf0613a00c2ad7d5f9cc10465ac3d767c8449dad6a18aaa44e85298fe96cd
6
+ metadata.gz: 6c9592f43a421e9bd5032b3d81aa44a10e30e03553e293667b5ac1ca7b196d4cad3a12e544e41f70392a54649ed584a8ef4a1a9259363e5de4a4104e50f20601
7
+ data.tar.gz: 1409c27d455564263fb596e1ac920f5867540711ea07a8bd2b85a47a07c0843dded453c3648557e81ac8d6586539bb531d551c2d09c19178b728c6720d005944
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.3.5
data/Gemfile.lock CHANGED
@@ -1,26 +1,36 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- authsignal-ruby (2.1.3)
5
- httparty (~> 0.21.0)
4
+ authsignal-ruby (3.0.0)
5
+ faraday (>= 2)
6
+ faraday-retry (~> 2.2)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
- addressable (2.8.0)
11
- public_suffix (>= 2.0.2, < 5.0)
12
- crack (0.4.5)
11
+ addressable (2.8.7)
12
+ public_suffix (>= 2.0.2, < 7.0)
13
+ bigdecimal (3.1.8)
14
+ crack (1.0.0)
15
+ bigdecimal
13
16
  rexml
14
17
  diff-lcs (1.5.0)
15
- hashdiff (1.0.1)
16
- httparty (0.21.0)
17
- mini_mime (>= 1.0.0)
18
- multi_xml (>= 0.5.2)
19
- mini_mime (1.1.5)
20
- multi_xml (0.6.0)
21
- public_suffix (4.0.7)
18
+ faraday (2.12.0)
19
+ faraday-net_http (>= 2.0, < 3.4)
20
+ json
21
+ logger
22
+ faraday-net_http (3.3.0)
23
+ net-http
24
+ faraday-retry (2.2.1)
25
+ faraday (~> 2.0)
26
+ hashdiff (1.1.1)
27
+ json (2.7.2)
28
+ logger (1.6.1)
29
+ net-http (0.4.1)
30
+ uri
31
+ public_suffix (6.0.1)
22
32
  rake (13.0.6)
23
- rexml (3.2.5)
33
+ rexml (3.3.8)
24
34
  rspec (3.11.0)
25
35
  rspec-core (~> 3.11.0)
26
36
  rspec-expectations (~> 3.11.0)
@@ -34,7 +44,8 @@ GEM
34
44
  diff-lcs (>= 1.2.0, < 2.0)
35
45
  rspec-support (~> 3.11.0)
36
46
  rspec-support (3.11.0)
37
- webmock (3.14.0)
47
+ uri (0.13.1)
48
+ webmock (3.24.0)
38
49
  addressable (>= 2.8.0)
39
50
  crack (>= 0.3.2)
40
51
  hashdiff (>= 0.4.0, < 2.0.0)
@@ -46,7 +57,7 @@ DEPENDENCIES
46
57
  authsignal-ruby!
47
58
  rake (~> 13.0)
48
59
  rspec (~> 3.2)
49
- webmock (~> 3.14.0)
60
+ webmock (~> 3.14)
50
61
 
51
62
  BUNDLED WITH
52
63
  2.3.21
data/README.md CHANGED
@@ -48,6 +48,12 @@ require 'authsignal'
48
48
  Authsignal.setup do |config|
49
49
  config.api_secret_key = ENV["AUTHSIGNAL_SECRET_KEY"]
50
50
  config.base_uri = "https://au.signal.authsignal.com/v1"
51
+
52
+ # If you would like the Authsignal client to retry requests due to network issues
53
+ config.retry = true # default value: false
54
+
55
+ # If you would like to inspect raw request/response in development
56
+ config.debug = true # default value: false
51
57
  end
52
58
  ```
53
59
 
@@ -63,6 +69,8 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
63
69
 
64
70
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
71
 
72
+ Log request/response against test server: `Authsignal.configuration.debug = true`
73
+
66
74
  ## License
67
75
 
68
76
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,95 +1,102 @@
1
+ require 'erb'
2
+
1
3
  module Authsignal
2
4
  class Client
3
5
  USER_AGENT = "Authsignal Ruby v#{Authsignal::VERSION}"
4
6
  NO_API_KEY_MESSAGE = "No Authsignal API Secret Key Set"
5
- include HTTParty
6
7
 
7
- def handle_response(response)
8
- unless response.success?
9
- raise HTTParty::ResponseError, "Failed with status code #{response.code}"
10
- end
11
- response
12
- end
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
13
15
 
14
- def initialize
15
- self.class.base_uri Authsignal.configuration.base_uri
16
+ def initialize(retry_options: RETRY_OPTIONS)
16
17
  @api_key = require_api_key
17
18
 
18
- @headers = {
19
- "User-Agent" => USER_AGENT,
20
- 'Content-Type' => 'application/json'
21
- }
22
- end
19
+ @client = Faraday.new do |builder|
20
+ builder.url_prefix = Authsignal.configuration.base_uri
21
+ builder.adapter :net_http
22
+ builder.request :authorization, :basic, @api_key, nil
23
23
 
24
- def require_api_key
25
- Authsignal.configuration.api_secret_key || print_api_key_warning
24
+ builder.headers['Accept'] = 'application/json'
25
+ builder.headers['Content-Type'] = 'application/json'
26
+ builder.headers['User-Agent'] = USER_AGENT
27
+
28
+ builder.request :json
29
+ builder.response :json, parser_options: { symbolize_names: true }
30
+
31
+ builder.use Middleware::JsonRequest
32
+ builder.use Middleware::JsonResponse
33
+
34
+ builder.request :retry, retry_options if Authsignal.configuration.retry
35
+ builder.response :logger, ::Logger.new(STDOUT), bodies: true if Authsignal.configuration.debug
36
+ end
26
37
  end
27
38
 
28
- def track(action, options = {})
29
- actionCode = action[:action]
30
- idempotencyKey = ERB::Util.url_encode(action[:idempotencyKey])
31
- userId = ERB::Util.url_encode(action[:userId])
32
- body = action.except(:userId, :actionCode)
33
- path = "/users/#{userId}/actions/#{actionCode}"
39
+ def track(event)
40
+ user_id = url_encode(event[:user_id])
41
+ action = event[:action]
34
42
 
35
- post(path, query: options, body: JSON.generate(body))
43
+ path = "users/#{user_id}/actions/#{action}"
44
+ body = event.except(:user_id)
45
+
46
+ make_request(:post, path, body: body)
36
47
  end
37
48
 
38
49
  def get_user(user_id:, redirect_url: nil)
39
50
  if(redirect_url)
40
- path = "/users/#{ERB::Util.url_encode(user_id)}?redirectUrl=#{redirect_url}"
51
+ path = "users/#{url_encode(user_id)}?redirectUrl=#{redirect_url}"
41
52
  else
42
- path = "/users/#{ERB::Util.url_encode(user_id)}"
53
+ path = "users/#{url_encode(user_id)}"
43
54
  end
44
- get(path)
55
+ make_request(:get, path)
45
56
  end
46
57
 
47
58
  def update_user(user_id:, user:)
48
- post("/users/#{ERB::Util.url_encode(user_id)}", body: JSON.generate(user))
59
+ make_request(:post, "users/#{url_encode(user_id)}", body: user)
49
60
  end
50
61
 
51
62
  def delete_user(user_id:)
52
- delete("/users/#{ERB::Util.url_encode(user_id)}")
63
+ make_request(:delete, "users/#{url_encode(user_id)}")
53
64
  end
54
65
 
55
66
  def validate_challenge(user_id: nil, token:)
56
- path = "/validate"
57
-
58
- response = post(path, query: {}, body: { userId: user_id, token: token }.to_json)
67
+ path = "validate"
59
68
 
60
- handle_response(response)
69
+ make_request(:post, path, body: { user_id: user_id, token: token })
61
70
  end
62
71
 
63
72
  def get_action(user_id, action, idempotency_key)
64
- get("/users/#{ERB::Util.url_encode(user_id)}/actions/#{action}/#{ERB::Util.url_encode(idempotency_key)}")
73
+ make_request(:get, "users/#{url_encode(user_id)}/actions/#{action}/#{url_encode(idempotency_key)}")
65
74
  end
66
75
 
76
+ ##
77
+ # TODO: delete identify?
67
78
  def identify(user_id, user_payload)
68
- post("/users/#{ERB::Util.url_encode(user_id)}", body: JSON.generate(user_payload))
79
+ make_request(:post , "users/#{url_encode(user_id)}", body: user_payload)
69
80
  end
70
81
 
71
82
  def enroll_verified_authenticator(user_id, authenticator)
72
- post("/users/#{ERB::Util.url_encode(user_id)}/authenticators", body: JSON.generate(authenticator))
83
+ make_request(:post, "users/#{url_encode(user_id)}/authenticators", body: authenticator)
73
84
  end
74
85
 
75
86
  def delete_user_authenticator(user_id:, user_authenticator_id:)
76
- delete("/users/#{ERB::Util.url_encode(user_id)}/authenticators/#{ERB::Util.url_encode(user_authenticator_id)}")
87
+ make_request(:delete, "users/#{url_encode(user_id)}/authenticators/#{url_encode(user_authenticator_id)}")
77
88
  end
78
89
 
79
- def get(path, query: {})
80
- self.class.get(path, headers: @headers, basic_auth: {username: @api_key})
81
- end
90
+ private
82
91
 
83
- def post(path, query: {}, body: {})
84
- self.class.post(path, headers: @headers, body: body, basic_auth: {username: @api_key})
92
+ def make_request(method, path, body: nil, headers: nil)
93
+ @client.public_send(method, path, body, headers)
85
94
  end
86
95
 
87
- def delete(path, query: {}, body: {})
88
- self.class.delete(path, headers: @headers, body: body, basic_auth: {username: @api_key})
96
+ def url_encode(s)
97
+ ERB::Util.url_encode(s)
89
98
  end
90
99
 
91
- private
92
-
93
100
  def version
94
101
  Authsignal.configuration.version
95
102
  end
@@ -97,5 +104,9 @@ module Authsignal
97
104
  def print_api_key_warning
98
105
  $stderr.puts(NO_API_KEY_MESSAGE)
99
106
  end
107
+
108
+ def require_api_key
109
+ Authsignal.configuration.api_secret_key || print_api_key_warning
110
+ end
100
111
  end
101
112
  end
@@ -13,15 +13,18 @@ module Authsignal
13
13
  end
14
14
 
15
15
  config_option :api_secret_key
16
-
17
16
  config_option :base_uri
17
+ config_option :debug
18
+ config_option :retry
18
19
 
19
20
  def initialize
20
21
  @config_values = {}
21
22
 
22
23
  # set default attribute values
23
24
  @defaults = OpenStruct.new({
24
- base_uri: 'https://signal.authsignal.com/v1/'
25
+ base_uri: 'https://signal.authsignal.com/v1/',
26
+ retry: false,
27
+ debug: false
25
28
  })
26
29
  end
27
30
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authsignal
4
+ module Middleware
5
+ class JsonRequest < Faraday::Middleware
6
+ def on_request(env)
7
+ return if env.body.nil?
8
+
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
13
+ rescue JSON::ParserError
14
+ # noop
15
+ end
16
+
17
+ private
18
+
19
+ def camelcase_keys(hash)
20
+ hash.transform_keys { |key| snake_to_camel(key.to_s).to_sym }
21
+ end
22
+
23
+ def snake_to_camel(str)
24
+ str.split('_').inject([]) do |buffer, e|
25
+ buffer.push(buffer.empty? ? e : e.capitalize)
26
+ end.join
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Authsignal
4
+ module Middleware
5
+ class JsonResponse < Faraday::Middleware
6
+ def on_complete(env)
7
+ ##
8
+ # NOTE: Response header has "content-type: text/plain" atm
9
+ # Otherwise, we can safe guard with: env.response_headers['content-type'] =~ /application\/json/
10
+ parsed_body = JSON.parse(env.body)
11
+ if parsed_body.is_a?(Hash)
12
+ env.body = transform_to_snake_case(parsed_body)
13
+ end
14
+ rescue JSON::ParserError
15
+ # noop
16
+ end
17
+
18
+ private
19
+
20
+ def underscore(camelcased)
21
+ return camelcased.to_s unless /[A-Z-]|::/.match?(camelcased)
22
+ word = camelcased.to_s.gsub("::", "/")
23
+ word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" }
24
+ word.tr!("-", "_")
25
+ word.downcase!
26
+ word
27
+ end
28
+
29
+ def transform_to_snake_case(value)
30
+ case value
31
+ when Array
32
+ value.map { |v| transform_to_snake_case(v) }
33
+ when Hash
34
+ value.transform_keys! { |k| underscore(k).to_sym }
35
+ value.each do |key, val|
36
+ value[key] = transform_to_snake_case(val)
37
+ end
38
+ else
39
+ value
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Authsignal
4
- VERSION = "2.1.3"
4
+ VERSION = "3.0.0"
5
5
  end
data/lib/authsignal.rb CHANGED
@@ -1,8 +1,10 @@
1
- require "httparty"
2
-
1
+ require "faraday"
2
+ require "faraday/retry"
3
3
  require "authsignal/version"
4
4
  require "authsignal/client"
5
5
  require "authsignal/configuration"
6
+ require "authsignal/middleware/json_response"
7
+ require "authsignal/middleware/json_request"
6
8
 
7
9
  module Authsignal
8
10
  class << self
@@ -16,88 +18,77 @@ module Authsignal
16
18
  @configuration ||= Authsignal::Configuration.new
17
19
  end
18
20
 
19
-
20
21
  def default_configuration
21
22
  configuration.defaults
22
23
  end
23
24
 
24
25
  def get_user(user_id:, redirect_url: nil)
25
26
  response = Client.new.get_user(user_id: user_id, redirect_url: redirect_url)
26
- response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
27
+
28
+ handle_response(response)
27
29
  end
28
30
 
29
31
  def update_user(user_id:, user:)
30
32
  response = Client.new.update_user(user_id: user_id, user: user)
31
- response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
33
+
34
+ handle_response(response)
32
35
  end
33
36
 
34
37
  def delete_user(user_id:)
35
38
  response = Client.new.delete_user(user_id: user_id)
36
- response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
39
+
40
+ handle_response(response)
37
41
  end
38
42
 
39
43
  def get_action(user_id:, action:, idempotency_key:)
40
44
  response = Client.new.get_action(user_id, action, idempotency_key)
41
- response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
45
+
46
+ handle_response(response)
42
47
  end
43
48
 
44
49
  def enroll_verified_authenticator(user_id:, authenticator:)
45
- authenticator = authenticator.transform_keys { |key| camelize(key) }
46
50
  response = Client.new.enroll_verified_authenticator(user_id, authenticator)
47
-
48
- if response["authenticator"]
49
- response["authenticator"] = response["authenticator"].transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
50
- end
51
51
 
52
- response = response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
53
-
54
- response
52
+ handle_response(response)
55
53
  end
56
54
 
57
55
  def delete_user_authenticator(user_id:, user_authenticator_id: )
58
56
  response = Client.new.delete_user_authenticator(user_id: user_id, user_authenticator_id: user_authenticator_id)
59
- response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
57
+
58
+ handle_response(response)
60
59
  end
61
60
 
62
61
  def track(event, options={})
63
62
  raise ArgumentError, "Action Code is required" unless event[:action].to_s.length > 0
64
63
  raise ArgumentError, "User ID value" unless event[:user_id].to_s.length > 0
65
64
 
66
- event = event.transform_keys { |key| camelize(key) }
67
-
68
- response = Client.new.track(event, options)
69
- success = response && response.success?
70
- if success
71
- puts("Tracked event! #{response.response.inspect}")
72
- else
73
- puts("Track failure! #{response.response.inspect} #{response.body}")
74
- end
75
-
76
- response.transform_keys { |key| underscore(key) }.transform_keys(&:to_sym)
77
- rescue => e
78
- RuntimeError.new("Failed to track action")
79
- false
65
+ response = Client.new.track(event)
66
+ handle_response(response)
80
67
  end
81
68
 
82
69
  def validate_challenge(token:, user_id: nil)
83
70
  response = Client.new.validate_challenge(user_id: user_id, token: token)
84
71
 
85
- return { user_id: response["userId"], is_valid: response["isValid"], state: response["state"], action: response["actionCode"] }
72
+ handle_response(response)
86
73
  end
87
74
 
88
75
  private
89
- def underscore(string)
90
- string.gsub(/::/, '/').
91
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
92
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
93
- tr("-", "_").
94
- downcase
76
+
77
+ def handle_response(response)
78
+ if response.success?
79
+ response.body
80
+ else
81
+ handle_error_response(response)
82
+ end
95
83
  end
96
84
 
97
- def camelize(symbol)
98
- string = symbol.to_s
99
- string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
100
- string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub("/", "::").to_sym
85
+ def handle_error_response(response)
86
+ case response.body
87
+ when Hash
88
+ response.body.merge({ status: response.status })
89
+ else
90
+ { status: response.status }
91
+ end
101
92
  end
102
93
  end
103
94
  end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authsignal-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.3
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - justinsoong
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-02 00:00:00.000000000 Z
11
+ date: 2024-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: httparty
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-retry
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
31
  - - "~>"
18
32
  - !ruby/object:Gem::Version
19
- version: 0.21.0
33
+ version: '2.2'
20
34
  type: :runtime
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
- version: 0.21.0
40
+ version: '2.2'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +72,14 @@ dependencies:
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: 3.14.0
75
+ version: '3.14'
62
76
  type: :development
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: 3.14.0
82
+ version: '3.14'
69
83
  description: Authsignal is a passwordless authentication/multifactor authentication
70
84
  with modern factors like passkeys
71
85
  email:
@@ -75,6 +89,7 @@ extensions: []
75
89
  extra_rdoc_files: []
76
90
  files:
77
91
  - ".rspec"
92
+ - ".tool-versions"
78
93
  - CHANGELOG.md
79
94
  - Gemfile
80
95
  - Gemfile.lock
@@ -87,6 +102,8 @@ files:
87
102
  - lib/authsignal.rb
88
103
  - lib/authsignal/client.rb
89
104
  - lib/authsignal/configuration.rb
105
+ - lib/authsignal/middleware/json_request.rb
106
+ - lib/authsignal/middleware/json_response.rb
90
107
  - lib/authsignal/version.rb
91
108
  homepage: https://github.com/authsignal/authsignal-ruby
92
109
  licenses: