Version not found. Please check the version and try again.

open-banking-io 0.1.0 → 0.2.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: 6d7f5d02a8a4a808faa00f6c78558b2e11c90a7c5b852062682746c1ab2a3da1
4
- data.tar.gz: c24bf4296b70c0b5ffc6af71faae40d4c559301f59654358c6c34e94130bbef3
3
+ metadata.gz: 4b6ab2de9b335f7edc7203e5723066756103876c5f986dfd6ac6ff1ac7bf263f
4
+ data.tar.gz: 4ede77b0da39a399387d49d7b47ec4c76b7c7277c4983b61d928fbca320adf7f
5
5
  SHA512:
6
- metadata.gz: d4870301dfb82caf2456f2b24a7ae8e6b469375da2bf6ff86821196eaf620f3e1b734deae2a391884e7eaa6dd3eea150c6714962d1f77247e950b5e4b2bef8db
7
- data.tar.gz: 24788e31dd5f8deb079a4c3bd2dd411ab6f93e1928eb4de6ed6bf0b398d241b22c8ab8e7009be3f758c286a3131ab3d123dc16cc46ce6c0449f01eb88a4c181c
6
+ metadata.gz: e8af173269a9036665b494976ccb5292754a6984f6f18f356e2e7946edc6cd7387919cf348c73d226f96bed1ae6e65dc6fb35b8881a64dfc5788b6574c53a8b9
7
+ data.tar.gz: 5a91e86f5fdfaf166b1e1fe691efb86fc39cdeb2b9246838af679b2c8693f222100d6dafaa1db92df1ea976ec87b9947072eef93ee553f2e46090c0b34cf4472
data/README.md CHANGED
@@ -49,6 +49,8 @@ client = OpenBankingIO::Client.new(
49
49
 
50
50
  Amounts are exposed as `BigDecimal`. Models are immutable keyword-initialised `Struct`s.
51
51
 
52
+ Every request sets connect/read timeouts (15s/60s) and a `User-Agent: open-banking-io/ruby/<version>` header.
53
+
52
54
  ## Encryption
53
55
 
54
56
  Envelopes use **ECDH P-256 → HKDF-SHA256 → AES-256-GCM**, implemented entirely with Ruby's OpenSSL
@@ -7,6 +7,7 @@ require "bigdecimal"
7
7
 
8
8
  require_relative "envelope"
9
9
  require_relative "models"
10
+ require_relative "version"
10
11
 
11
12
  module OpenBankingIO
12
13
  # Raised when the API returns a non-success HTTP status.
@@ -28,14 +29,15 @@ module OpenBankingIO
28
29
  class Client
29
30
  DEFAULT_OPEN_TIMEOUT = 15
30
31
  DEFAULT_READ_TIMEOUT = 60
32
+ USER_AGENT = "open-banking-io/ruby/#{VERSION}".freeze
31
33
 
32
34
  # Builds a client from a credentials-bundle JSON string or a path to a bundle file.
33
35
  def self.from_credentials(path_or_json)
34
36
  raw = if File.file?(path_or_json.to_s)
35
- File.read(path_or_json)
36
- else
37
- path_or_json
38
- end
37
+ File.read(path_or_json)
38
+ else
39
+ path_or_json
40
+ end
39
41
 
40
42
  bundle = JSON.parse(raw)
41
43
  api_base_url = bundle["apiBaseUrl"].to_s
@@ -108,7 +110,7 @@ module OpenBankingIO
108
110
  raise ArgumentError, "Account has no active session (reconnect required) -- cannot sync"
109
111
  end
110
112
 
111
- result = post_json("api/accounts/#{account_id}/sync", { "uid" => uid })
113
+ result = post_json("api/accounts/#{account_id}/sync", {"uid" => uid})
112
114
  SyncResult.new(
113
115
  new_transactions: result["newTransactions"] || 0,
114
116
  total_fetched: result["totalFetched"] || 0
@@ -120,10 +122,10 @@ module OpenBankingIO
120
122
  items = []
121
123
  account_wires.each do |a|
122
124
  uid = decrypt_uid(a)
123
- items << { "accountId" => a["id"], "uid" => uid } unless uid.nil?
125
+ items << {"accountId" => a["id"], "uid" => uid} unless uid.nil?
124
126
  end
125
127
 
126
- result = post_json("api/sync", { "items" => items })
128
+ result = post_json("api/sync", {"items" => items})
127
129
  SyncAllResult.new(
128
130
  accounts: result["accounts"] || 0,
129
131
  new_transactions: result["newTransactions"] || 0
@@ -228,6 +230,10 @@ module OpenBankingIO
228
230
  uri.query = URI.encode_www_form(params)
229
231
  end
230
232
 
233
+ # `path` is an internal, library-controlled API route resolved against the configured
234
+ # base URI (see #resolve), not user-supplied file/URL input. This is an HTTP API client;
235
+ # issuing the request is its purpose.
236
+ # nosemgrep: ruby.rails.security.audit.avoid-tainted-http-request.avoid-tainted-http-request
231
237
  request = Net::HTTP::Get.new(uri)
232
238
  send_request(uri, request)
233
239
  end
@@ -247,6 +253,7 @@ module OpenBankingIO
247
253
  def send_request(uri, request)
248
254
  request["X-Api-Key"] = @api_key
249
255
  request["Accept"] = "application/json"
256
+ request["User-Agent"] = USER_AGENT
250
257
 
251
258
  http = Net::HTTP.new(uri.host, uri.port)
252
259
  http.use_ssl = (uri.scheme == "https")
@@ -15,7 +15,7 @@ module OpenBankingIO
15
15
  POINT_LEN = 65
16
16
  NONCE_LEN = 12
17
17
  TAG_LEN = 16
18
- HKDF_SALT = ("\x00".b * 32).freeze
18
+ HKDF_SALT = ("\x00".b * 32)
19
19
  HKDF_INFO = "bank.core.ci/zk/v1".b.freeze
20
20
  GROUP = OpenSSL::PKey::EC::Group.new("prime256v1")
21
21
 
@@ -43,7 +43,7 @@ module OpenBankingIO
43
43
  tag = envelope_bytes.byteslice(1 + POINT_LEN + NONCE_LEN, TAG_LEN)
44
44
  ciphertext = envelope_bytes.byteslice((1 + POINT_LEN + NONCE_LEN + TAG_LEN)..) || "".b
45
45
 
46
- pub = OpenSSL::PKey::EC::Point.new(GROUP, OpenSSL::BN.new(eph_pub_bytes, 2))
46
+ pub = decode_public_point(eph_pub_bytes)
47
47
  shared = private_key.dh_compute_key(pub)
48
48
 
49
49
  key = OpenSSL::KDF.hkdf(
@@ -63,6 +63,17 @@ module OpenBankingIO
63
63
  cipher.update(ciphertext) + cipher.final
64
64
  end
65
65
 
66
+ # Parses the 65-byte raw ephemeral public key into a P-256 point.
67
+ #
68
+ # A malformed or off-curve point makes +EC::Point.new+ raise an OpenSSL-internal
69
+ # error; we wrap it in a clean +ArgumentError+ so callers see a consistent envelope
70
+ # error rather than a leaking implementation detail.
71
+ def decode_public_point(eph_pub_bytes)
72
+ OpenSSL::PKey::EC::Point.new(GROUP, OpenSSL::BN.new(eph_pub_bytes, 2))
73
+ rescue OpenSSL::PKey::EC::Point::Error, OpenSSL::BNError => e
74
+ raise ArgumentError, "Invalid ephemeral public key in envelope: #{e.message}"
75
+ end
76
+
66
77
  # Decrypts a base64 envelope and parses its JSON payload. +nil+ in -> +nil+ out.
67
78
  def decrypt_to_json(private_key, envelope_b64)
68
79
  return nil if envelope_b64.nil?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenBankingIO
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: open-banking-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - open-banking.io
@@ -65,6 +65,68 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '3.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: simplecov
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.22'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '0.22'
82
+ - !ruby/object:Gem::Dependency
83
+ name: simplecov-cobertura
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '2.1'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '2.1'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rexml
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3.3'
103
+ - - "<"
104
+ - !ruby/object:Gem::Version
105
+ version: '3.4'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: '3.3'
113
+ - - "<"
114
+ - !ruby/object:Gem::Version
115
+ version: '3.4'
116
+ - !ruby/object:Gem::Dependency
117
+ name: standard
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.0'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '1.0'
68
130
  description: Authenticates with your API key and decrypts open-banking.io's zero-knowledge
69
131
  data envelopes locally with your exported private key (ECDH P-256 -> HKDF-SHA256
70
132
  -> AES-256-GCM). The service only ever returns ciphertext it cannot read.