Class: OpenBankingIO::Client
- Inherits:
-
Object
- Object
- OpenBankingIO::Client
- Defined in:
- lib/open_banking_io/client.rb
Overview
Server-to-server client for open-banking.io.
Authenticates with an API key (+X-Api-Key+) and decrypts the zero-knowledge data envelopes locally with the exported private key -- the service only ever returns ciphertext it cannot read.
Constant Summary collapse
- DEFAULT_OPEN_TIMEOUT =
15- DEFAULT_READ_TIMEOUT =
60- USER_AGENT =
"open-banking-io/ruby/#{VERSION}".freeze
Class Method Summary collapse
-
.from_credentials(path_or_json, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_client: nil) ⇒ Object
Builds a client from a credentials-bundle JSON string or a path to a bundle file.
Instance Method Summary collapse
-
#get_accounts ⇒ Object
Lists the user's accounts with all sensitive fields decrypted.
-
#get_connections ⇒ Object
Lists the user's bank connections.
-
#get_transactions(account_id, from: nil, to: nil, limit: nil, offset: nil) ⇒ Object
Returns a page of an account's statement, newest first, with decrypted fields.
-
#initialize(api_base_url:, api_key:, private_key_pkcs8:, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_client: nil) ⇒ Client
constructor
+open_timeout+/+read_timeout+ are seconds applied to the internally built Net::HTTP.
-
#sync(account_id) ⇒ Object
Triggers an online sync of one account.
-
#sync_all ⇒ Object
Triggers an online sync of every account that has an active session.
Constructor Details
#initialize(api_base_url:, api_key:, private_key_pkcs8:, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_client: nil) ⇒ Client
+open_timeout+/+read_timeout+ are seconds applied to the internally built Net::HTTP.
http_client, when given, is any object responding to #request (e.g. a preconfigured
Net::HTTP for a proxy, custom CA/mTLS or connection pooling); it is used as-is instead
of building one internally. All three are optional and default to the previous behaviour.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/open_banking_io/client.rb', line 72 def initialize(api_base_url:, api_key:, private_key_pkcs8:, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_client: nil) raise ArgumentError, "api_base_url is required" if blank?(api_base_url) raise ArgumentError, "api_key is required" if blank?(api_key) raise ArgumentError, "private_key_pkcs8 is required" if blank?(private_key_pkcs8) # Strip any trailing slashes without a backtracking regex (avoids ReDoS on # pathological input) then re-append a single one, so the base always ends in "/". base = api_base_url.to_s base = base.chomp("/") while base.end_with?("/") @base_uri = URI.parse(base + "/") @api_key = api_key @private_key = Envelope.load_private_key(private_key_pkcs8) @open_timeout = open_timeout @read_timeout = read_timeout @http_client = http_client end |
Class Method Details
.from_credentials(path_or_json, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_client: nil) ⇒ Object
Builds a client from a credentials-bundle JSON string or a path to a bundle file.
Optional +open_timeout+/+read_timeout+ (seconds) and a caller-supplied http_client
(an object responding to #request, e.g. a preconfigured Net::HTTP) are threaded
straight through to #initialize; the defaults preserve the previous behaviour.
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 |
# File 'lib/open_banking_io/client.rb', line 39 def self.from_credentials(path_or_json, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT, http_client: nil) raw = if File.file?(path_or_json.to_s) File.read(path_or_json) else path_or_json end bundle = JSON.parse(raw) api_base_url = bundle["apiBaseUrl"].to_s api_key = bundle["apiKey"] raise ArgumentError, "The credentials bundle has no apiKey" if api_key.nil? || api_key.empty? enc_key = bundle["encryptionKey"] || {} private_key = enc_key["privateKey"] || enc_key["privateKeyPkcs8B64"] if private_key.nil? || private_key.to_s.empty? raise ArgumentError, "The credentials bundle has no encryption private key" end new( api_base_url: api_base_url, api_key: api_key, private_key_pkcs8: private_key, open_timeout: open_timeout, read_timeout: read_timeout, http_client: http_client ) end |
Instance Method Details
#get_accounts ⇒ Object
Lists the user's accounts with all sensitive fields decrypted.
92 93 94 |
# File 'lib/open_banking_io/client.rb', line 92 def get_accounts account_wires.map { |w| map_account(w) } end |
#get_connections ⇒ Object
Lists the user's bank connections.
110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/open_banking_io/client.rb', line 110 def get_connections get_json("api/connections").map do |c| Connection.new( session_id: c["sessionId"] || "", aspsp_name: c["aspspName"] || "", aspsp_country: c["aspspCountry"] || "", valid_until: c["validUntil"], status: c["status"] || "", account_count: c["accountCount"] || 0, last_synced_at: c["lastSyncedAt"], psu_type: c["psuType"] ) end end |
#get_transactions(account_id, from: nil, to: nil, limit: nil, offset: nil) ⇒ Object
Returns a page of an account's statement, newest first, with decrypted fields.
97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/open_banking_io/client.rb', line 97 def get_transactions(account_id, from: nil, to: nil, limit: nil, offset: nil) params = {} params["from"] = from unless from.nil? params["to"] = to unless to.nil? params["limit"] = limit unless limit.nil? params["offset"] = offset unless offset.nil? page = get_json("api/accounts/#{account_id}/transactions", params) items = (page["items"] || []).map { |t| map_transaction(t) } TransactionPage.new(items: items, total: page["total"] || 0) end |
#sync(account_id) ⇒ Object
Triggers an online sync of one account.
Decrypts that account's Enable Banking uid and posts it, so the service can fetch fresh data without ever holding the uid in plaintext.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/open_banking_io/client.rb', line 129 def sync(account_id) account = account_wires.find { |a| a["id"] == account_id } raise ArgumentError, "Account #{account_id} not found" if account.nil? uid = decrypt_uid(account) if uid.nil? raise ArgumentError, "Account has no active session (reconnect required) -- cannot sync" end result = post_json("api/accounts/#{account_id}/sync", {"uid" => uid}) SyncResult.new( new_transactions: result["newTransactions"] || 0, total_fetched: result["totalFetched"] || 0 ) end |
#sync_all ⇒ Object
Triggers an online sync of every account that has an active session.
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/open_banking_io/client.rb', line 146 def sync_all items = [] account_wires.each do |a| uid = decrypt_uid(a) items << {"accountId" => a["id"], "uid" => uid} unless uid.nil? end result = post_json("api/sync", {"items" => items}) SyncAllResult.new( accounts: result["accounts"] || 0, new_transactions: result["newTransactions"] || 0 ) end |