truelayer-signing 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b85aabeca0591f5c775275d9b0b338583ca1d62483ed0f97be215b112cfefc68
4
- data.tar.gz: 89c46174ff39b02a4353184f0dd4fc46c5ff8cc519386efba30fa7ef6e789a23
3
+ metadata.gz: 8cc4b5beb4b79ccfafd7350d4882927d45abea2580d4d4d869d39faae157498d
4
+ data.tar.gz: ab43241ee2475b2797ff43fc5c142b778ff4e4ef28d042a123b9ac8179d913aa
5
5
  SHA512:
6
- metadata.gz: f79271f810bc3638a73743489a689cf8dc31f886588450815124e4ee2f7632006a54152d371aaab4ea46b4bac8279dc42bb706e92c5639be505c7459c60e547f
7
- data.tar.gz: 2546aa36fb8126be0e2f666aa38fe9a4062916b886eec87f00d91df56d1ad7ba1c7e43974a3ca8b62c1fd05e0b1b621a03d522e02e37b1c6d23691fdd583bc64
6
+ metadata.gz: 6e208e6b3ed16190946cdeceae856ebcddc42b472458b86c527e0dfe52a67fe0d33e954c018f63fcb8502b659081aa35522516f9dacfb12f492b36a7830fe6b6
7
+ data.tar.gz: 3282b084e0907c8c709dfa2f3d2bef71527bca7181c342a24de63262c70ffa3a9e41f4437eb77f667a1e9c6a45776b8db6549ce709b244128b5fb58006b4afea
data/CHANGELOG.md CHANGED
@@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - ...
11
11
 
12
+ ## [0.1.2] – 2023-05-19
13
+
14
+ - Fix conflict with JWT library
15
+
16
+ ## [0.1.1] – 2023-05-17
17
+
18
+ - Fix webhook server example
19
+
12
20
  ## [0.1.0] – 2023-01-09
13
21
 
14
22
  - Add `TrueLayerSigning.sign_with_pem` and `TrueLayerSigning.verify_with_pem(pem)`.
data/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # truelayer-signing
2
2
 
3
+ ![GitHub Workflow Status (with branch)](https://img.shields.io/github/actions/workflow/status/TrueLayer/truelayer-signing/ruby.yml?branch=main)
4
+ ![Gem](https://img.shields.io/gem/v/truelayer-signing)
5
+ ![Gem](https://img.shields.io/gem/dt/truelayer-signing)
6
+
3
7
  Ruby gem to produce and verify TrueLayer API requests signatures.
4
8
 
5
9
  ## Installation
@@ -1,7 +1,7 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- addressable (2.8.1)
4
+ addressable (2.8.4)
5
5
  public_suffix (>= 2.0.2, < 6.0)
6
6
  domain_name (0.5.20190701)
7
7
  unf (>= 0.0.5, < 1.0.0)
@@ -23,7 +23,7 @@ GEM
23
23
  rake (~> 13.0)
24
24
  public_suffix (5.0.1)
25
25
  rake (13.0.6)
26
- truelayer-signing (0.1.0)
26
+ truelayer-signing (0.1.2)
27
27
  jwt (= 2.6)
28
28
  unf (0.1.4)
29
29
  unf_ext
@@ -31,11 +31,10 @@ GEM
31
31
 
32
32
  PLATFORMS
33
33
  arm64-darwin-21
34
- ruby
35
34
 
36
35
  DEPENDENCIES
37
36
  http
38
37
  truelayer-signing
39
38
 
40
39
  BUNDLED WITH
41
- 2.4.1
40
+ 2.4.13
@@ -1,3 +1,4 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "truelayer-signing"
4
+ gem "webrick"
@@ -2,14 +2,16 @@ GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
4
  jwt (2.6.0)
5
- truelayer-signing (0.1.0)
5
+ truelayer-signing (0.1.1)
6
6
  jwt (= 2.6)
7
+ webrick (1.8.1)
7
8
 
8
9
  PLATFORMS
9
10
  arm64-darwin-21
10
11
 
11
12
  DEPENDENCIES
12
13
  truelayer-signing
14
+ webrick
13
15
 
14
16
  BUNDLED WITH
15
- 2.4.1
17
+ 2.4.13
@@ -1,57 +1,54 @@
1
- require "socket"
2
- require "truelayer-signing"
1
+ require 'securerandom'
2
+ require 'truelayer-signing'
3
+ require 'webrick'
3
4
 
4
5
  class TrueLayerSigningExamples
5
6
  # Note: the webhook path can be whatever is configured for your application.
6
7
  # Here a unique path is used, matching the example signature in the README.
7
8
  WEBHOOK_PATH = "/hook/d7a2c49d-110a-4ed2-a07d-8fdb3ea6424b".freeze
8
- PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\n" +
9
- "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBJ6ET9XeVCyMy+yOetZaNNCXPhwr5\n" +
10
- "BlyDDg1CLmyNM5SvqOs8RveL6dYl4lpPur4xrPQl04ggYlVd9wnHkZnp3jcBlXw8\n" +
11
- "Lc5phyYF1q2/QV/5wp2WHIhKDqUiXC0TvlE8d7MdTAN9yolcwrh6aWZ3kesTMZif\n" +
12
- "BgItyT6PXUab8mMdI8k=\n-----END PUBLIC KEY-----"
9
+ PUBLIC_KEY_PEM = <<~TXT.freeze
10
+ -----BEGIN PUBLIC KEY-----
11
+ MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBJ6ET9XeVCyMy+yOetZaNNCXPhwr5
12
+ BlyDDg1CLmyNM5SvqOs8RveL6dYl4lpPur4xrPQl04ggYlVd9wnHkZnp3jcBlXw8
13
+ Lc5phyYF1q2/QV/5wp2WHIhKDqUiXC0TvlE8d7MdTAN9yolcwrh6aWZ3kesTMZif
14
+ BgItyT6PXUab8mMdI8k=
15
+ -----END PUBLIC KEY-----
16
+ TXT
13
17
 
14
18
  class << self
15
19
  def run_webhook_server
16
- server = TCPServer.new(4567)
20
+ TrueLayerSigning.certificate_id ||= SecureRandom.uuid
21
+ server = WEBrick::HTTPServer.new(Port: 4567)
17
22
 
18
23
  puts "Server running at http://localhost:4567"
19
24
 
20
- loop do
21
- Thread.start(server.accept) do |client|
22
- begin
23
- request = parse_request(client)
24
- status, body = handle_request.call(request)
25
- headers = { "Content-Type" => "text/plain" }
26
-
27
- send_response(client, status, headers, body)
28
- rescue => error
29
- puts error
30
- ensure
31
- client.close
32
- end
33
- end
25
+ server.mount_proc('/') do |req, res|
26
+ request = parse_request(req)
27
+ status, body = handle_request.call(request)
28
+ headers = { "Content-Type" => "text/plain" }
29
+
30
+ send_response(res, status, headers, body)
31
+ rescue => error
32
+ puts error
34
33
  end
35
- end
36
34
 
37
- private def parse_request(client)
38
- request = client.gets
39
- headers, body = client.readpartial(2048).split("\r\n\r\n", 2)
40
- method, remainder = request.split(" ", 2)
41
- url = remainder.split(" ").first
42
- path, _query_strings = url.split("?", 2)
35
+ server.start
36
+ ensure
37
+ server.shutdown
38
+ end
43
39
 
40
+ private def parse_request(request)
44
41
  {
45
- method: method,
46
- path: path,
47
- headers: headers_to_hash(headers),
48
- body: body
42
+ method: request.request_method,
43
+ path: request.path,
44
+ headers: headers_to_hash(request.header),
45
+ body: request.body
49
46
  }
50
47
  end
51
48
 
52
49
  private def handle_request
53
50
  Proc.new do |request|
54
- if request[:method] == "POST" and request[:path] == WEBHOOK_PATH
51
+ if request[:method] == "POST" && request[:path] == WEBHOOK_PATH
55
52
  verify_webhook(request[:path], request[:headers], request[:body])
56
53
  else
57
54
  ["403", "Forbidden"]
@@ -65,7 +62,8 @@ class TrueLayerSigningExamples
65
62
  return ["400", "Bad Request – Header `Tl-Signature` missing"] unless tl_signature
66
63
 
67
64
  begin
68
- TrueLayerSigning.verify_with_pem(PUBLIC_KEY_PEM)
65
+ TrueLayerSigning
66
+ .verify_with_pem(PUBLIC_KEY_PEM)
69
67
  .set_method(:post)
70
68
  .set_path(path)
71
69
  .set_headers(headers)
@@ -73,24 +71,22 @@ class TrueLayerSigningExamples
73
71
  .verify(tl_signature)
74
72
 
75
73
  ["202", "Accepted"]
76
- rescue TrueLayerSigning::Error
74
+ rescue TrueLayerSigning::Error => error
75
+ puts error
76
+
77
77
  ["401", "Unauthorized"]
78
78
  end
79
79
  end
80
80
 
81
- private def send_response(client, status, headers, body)
82
- client.print("HTTP/1.1 #{status}\r\n")
83
- headers.each { |key, value| client.print("#{key}: #{value}\r\n") }
84
- client.print("\r\n#{body}")
81
+ private def headers_to_hash(headers)
82
+ headers.transform_keys { |key| key.to_s.strip.downcase }.transform_values(&:first)
85
83
  end
86
84
 
87
- private def headers_to_hash(headers_string, hash = Hash.new)
88
- headers_string.split("\r\n").each do |line|
89
- pair = line.split(":")
90
- hash[pair.first.to_s.strip.downcase] = pair.last.to_s.strip
91
- end
92
-
93
- hash
85
+ private def send_response(response, status, headers, body)
86
+ response.status = status
87
+ response.header.merge!(headers)
88
+ response.body = body
89
+ response
94
90
  end
95
91
  end
96
92
  end
@@ -2,14 +2,16 @@
2
2
  # It prevents the payload from being systematically converted to and from JSON.
3
3
  # To be changed in the 'jwt' gem directly, or hard-coded in this library.
4
4
  module JWT
5
- class Encode
5
+ module_function
6
+
7
+ class TrueLayerEncode < Encode
6
8
  # See https://github.com/jwt/ruby-jwt/blob/main/lib/jwt/encode.rb#L53-L55
7
9
  private def encode_payload
8
10
  ::JWT::Base64.url_encode(@payload)
9
11
  end
10
12
  end
11
13
 
12
- class Decode
14
+ class TrueLayerDecode < Decode
13
15
  # See https://github.com/jwt/ruby-jwt/blob/main/lib/jwt/decode.rb#L154-L156
14
16
  private def payload
15
17
  @payload ||= ::JWT::Base64.url_decode(@segments[1])
@@ -17,4 +19,23 @@ module JWT
17
19
  raise JWT::DecodeError, 'Invalid segment encoding'
18
20
  end
19
21
  end
22
+
23
+ def truelayer_encode(payload, key, algorithm, headers)
24
+ TrueLayerEncode.new(
25
+ payload: payload,
26
+ key: key,
27
+ algorithm: algorithm,
28
+ headers: headers
29
+ ).segments
30
+ end
31
+
32
+ def truelayer_decode(jwt, key, verify, options, &keyfinder)
33
+ TrueLayerDecode.new(
34
+ jwt,
35
+ key,
36
+ verify,
37
+ configuration.decode.to_h.merge(options),
38
+ &keyfinder
39
+ ).decode_segments
40
+ end
20
41
  end
@@ -9,7 +9,8 @@ module TrueLayerSigning
9
9
  jws_header_args = { tl_headers: headers }
10
10
  jws_header_args[:jku] = jws_jku if jws_jku
11
11
  jws_header = TrueLayerSigning::JwsHeader.new(jws_header_args).to_h
12
- jwt = JWT.encode(build_signing_payload, private_key, TrueLayerSigning.algorithm, jws_header)
12
+ jwt = JWT.truelayer_encode(build_signing_payload, private_key, TrueLayerSigning.algorithm,
13
+ jws_header)
13
14
  header, _, signature = jwt.split(".")
14
15
 
15
16
  "#{header}..#{signature}"
@@ -27,7 +27,7 @@ module TrueLayerSigning
27
27
  jwt_options = { algorithm: TrueLayerSigning.algorithm }
28
28
 
29
29
  begin
30
- JWT.decode(full_signature, public_key, true, jwt_options)
30
+ JWT.truelayer_decode(full_signature, public_key, true, jwt_options)
31
31
  rescue JWT::VerificationError
32
32
  @path = path.end_with?("/") && path[0...-1] || path + "/"
33
33
  payload_b64 = Base64.urlsafe_encode64(build_signing_payload(ordered_headers),
@@ -35,7 +35,7 @@ module TrueLayerSigning
35
35
  full_signature = [jws_header_b64, payload_b64, signature_b64].join(".")
36
36
 
37
37
  begin
38
- JWT.decode(full_signature, public_key, true, jwt_options)
38
+ JWT.truelayer_decode(full_signature, public_key, true, jwt_options)
39
39
  rescue
40
40
  raise(Error, "Signature verification failed")
41
41
  end
@@ -369,4 +369,58 @@ class TrueLayerSigningTest < Minitest::Test
369
369
  assert(result.first
370
370
  .start_with?("POST /merchant_accounts/a61acaef-ee05-4077-92f3-25543a11bd8d/sweeping\n"))
371
371
  end
372
+
373
+ # TODO: remove if/when we get rid of `lib/truelayer-signing/jwt.rb`
374
+ def test_jwt_encode_and_decode_should_succeed
375
+ payload_object = { currency: "GBP", max_amount_in_minor: 50_000_00 }
376
+ token_when_object = "eyJhbGciOiJIUzI1NiJ9.eyJjdXJyZW5jeSI6IkdCUCIsIm1heF9hbW" +
377
+ "91bnRfaW5fbWlub3IiOjUwMDAwMDB9.SjbwZCqTl6G7LQNs_M6oQhwl3a9rbqO7p3cVncLtgZY"
378
+ token_when_json = "eyJhbGciOiJIUzI1NiJ9.IntcImN1cnJlbmN5XCI6XCJHQlBcIixcIm1h" +
379
+ "eF9hbW91bnRfaW5fbWlub3JcIjo1MDAwMDAwfSI.rvCcgu-JevsNxbjUwJiFOuTd0hzVKvPK5RvGmaoDc7E"
380
+
381
+ # succeeds with a hash object
382
+ assert_equal(token_when_object, JWT.encode(payload_object, "12345", "HS256", {}))
383
+ assert_equal(
384
+ [{ "currency" => "GBP", "max_amount_in_minor" => 50_000_00 }, { "alg" => "HS256" }],
385
+ JWT.decode(token_when_object, "12345", true, algorithm: "HS256")
386
+ )
387
+
388
+ # succeeds with a JSON string
389
+ assert_equal(token_when_json, JWT.encode(payload_object.to_json, "12345", "HS256", {}))
390
+ assert_equal(
391
+ ["{\"currency\":\"GBP\",\"max_amount_in_minor\":5000000}", { "alg" => "HS256" }],
392
+ JWT.decode(token_when_json, "12345", true, algorithm: "HS256")
393
+ )
394
+ end
395
+
396
+ # TODO: remove if/when we get rid of `lib/truelayer-signing/jwt.rb`
397
+ def test_jwt_truelayer_encode_and_decode_when_given_json_should_succeed
398
+ payload_json = { currency: "GBP", max_amount_in_minor: 50_000_00 }.to_json
399
+ token_when_json = "eyJhbGciOiJIUzI1NiJ9.eyJjdXJyZW5jeSI6IkdCUCIsIm1heF9hbW9" +
400
+ "1bnRfaW5fbWlub3IiOjUwMDAwMDB9.SjbwZCqTl6G7LQNs_M6oQhwl3a9rbqO7p3cVncLtgZY"
401
+
402
+ assert_equal(token_when_json, JWT.truelayer_encode(payload_json, "12345", "HS256", {}))
403
+ assert_equal(
404
+ ["{\"currency\":\"GBP\",\"max_amount_in_minor\":5000000}", { "alg" => "HS256" }],
405
+ JWT.truelayer_decode(token_when_json, "12345", true, algorithm: "HS256")
406
+ )
407
+ end
408
+
409
+ # TODO: remove if/when we get rid of `lib/truelayer-signing/jwt.rb`
410
+ def test_jwt_truelayer_encode_when_given_a_hash_should_not_succeed
411
+ payload_object = { currency: "GBP", max_amount_in_minor: 50_000_00 }
412
+ error = assert_raises(TypeError) { JWT.truelayer_encode(payload_object, "12345", "HS256", {}) }
413
+ assert_equal("no implicit conversion of Hash into String", error.message)
414
+ end
415
+
416
+ # TODO: remove if/when we get rid of `lib/truelayer-signing/jwt.rb`
417
+ def test_jwt_truelayer_decode_when_given_a_hash_should_succeed
418
+ token_when_object = "eyJhbGciOiJIUzI1NiJ9.eyJjdXJyZW5jeSI6IkdCUCIsIm1heF9hbW" +
419
+ "91bnRfaW5fbWlub3IiOjUwMDAwMDB9.SjbwZCqTl6G7LQNs_M6oQhwl3a9rbqO7p3cVncLtgZY"
420
+
421
+ assert_equal(
422
+ ["{\"currency\":\"GBP\",\"max_amount_in_minor\":5000000}", { "alg" => "HS256" }],
423
+ JWT.truelayer_decode(token_when_object, "12345", true, algorithm: "HS256")
424
+ )
425
+ end
372
426
  end
@@ -2,19 +2,19 @@ $LOAD_PATH.unshift(::File.join(::File.dirname(__FILE__), "lib"))
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "truelayer-signing"
5
- s.version = "0.1.0"
5
+ s.version = "0.1.2"
6
6
  s.summary = "Ruby gem to produce and verify TrueLayer API requests signatures"
7
7
  s.description = "TrueLayer provides instant access to open banking to " \
8
8
  "easily integrate next-generation payments and financial data into any app." \
9
9
  "This helps easily sign TrueLayer API requests using a JSON web signature."
10
10
  s.author = "Kevin Plattret"
11
11
  s.email = "kevin@truelayer.com"
12
- s.homepage = "https://github.com/truelayer/truelayer-signing/ruby"
12
+ s.homepage = "https://github.com/TrueLayer/truelayer-signing/tree/main/ruby"
13
13
  s.licenses = ["Apache-2.0", "MIT"]
14
14
 
15
15
  s.metadata = {
16
- "bug_tracker_uri" => "https://github.com/truelayer/truelayer-signing/issues",
17
- "changelog_uri" => "https://github.com/truelayer/truelayer-signing/tree/ruby/CHANGELOG.md",
16
+ "bug_tracker_uri" => "https://github.com/TrueLayer/truelayer-signing/issues",
17
+ "changelog_uri" => "https://github.com/TrueLayer/truelayer-signing/blob/main/ruby/CHANGELOG.md",
18
18
  }
19
19
 
20
20
  s.files = Dir["./**/*"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: truelayer-signing
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Plattret
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-09 00:00:00.000000000 Z
11
+ date: 2023-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -122,13 +122,13 @@ files:
122
122
  - "./lib/truelayer-signing/verifier.rb"
123
123
  - "./test/test-truelayer-signing.rb"
124
124
  - "./truelayer-signing.gemspec"
125
- homepage: https://github.com/truelayer/truelayer-signing/ruby
125
+ homepage: https://github.com/TrueLayer/truelayer-signing/tree/main/ruby
126
126
  licenses:
127
127
  - Apache-2.0
128
128
  - MIT
129
129
  metadata:
130
- bug_tracker_uri: https://github.com/truelayer/truelayer-signing/issues
131
- changelog_uri: https://github.com/truelayer/truelayer-signing/tree/ruby/CHANGELOG.md
130
+ bug_tracker_uri: https://github.com/TrueLayer/truelayer-signing/issues
131
+ changelog_uri: https://github.com/TrueLayer/truelayer-signing/blob/main/ruby/CHANGELOG.md
132
132
  post_install_message:
133
133
  rdoc_options: []
134
134
  require_paths:
@@ -144,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  requirements: []
147
- rubygems_version: 3.4.1
147
+ rubygems_version: 3.4.13
148
148
  signing_key:
149
149
  specification_version: 4
150
150
  summary: Ruby gem to produce and verify TrueLayer API requests signatures