Class: AnzBankClient::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/anz_bank_client.rb

Instance Method Summary collapse

Constructor Details

#initializeSession

Returns a new instance of Session.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/anz_bank_client.rb', line 24

def initialize
  @logger = Logger.new $stderr
  @logger.level = Logger::INFO

  @cookie_jar = HTTP::CookieJar.new
  @user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36"

  # Set IBCookieDetect cookie so it knows we have cookies enabled
  @cookie_jar.add(HTTP::Cookie.new(name: "IBCookieDetect", value: "1", domain: "digital.anz.co.nz", path: "/"))
  @client = Faraday.new(
    headers: { "User-Agent" => @user_agent },
    # proxy: "http://Thinkbook.local:8080",
    # :ssl => {:verify => false}
  ) do |builder|
    builder.response :follow_redirects
    builder.use Faraday::CookieJar, jar: @cookie_jar
    # builder.response :logger
    builder.adapter Faraday.default_adapter
  end
end

Instance Method Details

#exportObject

Write the cookie jar out to a string so that the session can be restored later



46
47
48
49
50
51
52
53
# File 'lib/anz_bank_client.rb', line 46

def export
  cookies = StringIO.new
  @cookie_jar.save(cookies, format: :yaml, session: true)
  {
    cookies: cookies.string,
    initialise_response: @initialise_response,
  }.to_json
end

#list_accountsObject



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/anz_bank_client.rb', line 120

def list_accounts
  @initialise_response["viewableAccounts"].map do ||
    balance = .dig("accountBalance", "amount")
    overdrawn = balance && (
      .dig("accountBalance", "indicator") == "overdrawn" || ["isLoan"] || ["isCreditCard"]
    )
    normalised_balance = if balance
                           overdrawn ? -balance : balance
                         end

    {
      accountNo: ["accountNo"],
      nickname: ["nicknameEscaped"],
      accountType: ["productDescription"],
      customerName: ["accountOwnerName"],
      accountBalance: normalised_balance,
      availableFunds: .dig("availableFunds", "amount"),
      isLiabilityType: ["isLoan"] || ["isCreditCard"],
      supportsTransactions: true,
      dynamicBalance: ["isInvestment"],
    }
  end
end

#list_transactions(account_no, start_date, end_date) ⇒ Object

Fetches transactions for an account

Parameters:

  • account_no

    the account number (e.g. 01-1234-1234567-00)

  • start_date

    in iso8601 format

  • end_date

    in iso8601 format



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/anz_bank_client.rb', line 149

def list_transactions(, start_date, end_date)
  @logger.info "Getting transactions for account #{} from #{start_date} to #{end_date}"
   = @initialise_response["viewableAccounts"]
                 .find { || ["accountNo"] ==  }
  if .nil?
    raise "Could not find account #{}"
  end
   = ["accountUuid"]
  raise "Could not find account #{}" unless 

  response = @client.get("https://secure.anz.co.nz/IBCS/service/api/transactions?account=#{}&ascending=false&from=#{start_date}&order=postdate&to=#{end_date}")
  raise "Error getting transactions: #{response.status}\n\n#{response.body}" unless response.status == 200

  response_json = JSON.parse(response.body)
  transactions = response_json["transactions"]
  # Ignore transactions without an amount - these can be informational messages like "Finance Charge Rate Changed Today"
  transactions = transactions.filter{ |transaction| transaction["amount"] }
  transactions.map do |transaction|
    {
      date: transaction["date"],
      postedDate: transaction["postedDate"],
      details: transaction["details"],
      amount: transaction["amount"]["amount"],
      currencyCode: transaction["amount"]["currencyCode"],
      type: transaction["type"],
      balance: transaction.dig("balance", "amount"),
      createdDateTime: transaction["createdDateTime"],
    }
  end
end

#load(str) ⇒ Object

Load the cookie jar from a string to restore a session



56
57
58
59
60
61
62
63
# File 'lib/anz_bank_client.rb', line 56

def load(str)
  json = JSON.parse(str)
  @initialise_response = json["initialise_response"]
  cookies = StringIO.new
  cookies.write(json["cookies"])
  cookies.rewind
  @cookie_jar.load(cookies, format: :yaml)
end

#login(username, password) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/anz_bank_client.rb', line 65

def (username, password)
  @logger.info "Fetching login page"

   = @client.get("https://digital.anz.co.nz/preauth/web/service/login")
  if .status != 200
    raise "Error getting login page: #{.status} #{.body}"
  end

  @encryption_key = .body.match(/encryptionKey: "(.*)"/)[1]
  @encryption_key_id = .body.match(/encryptionKeyId: "(.*)"/)[1]

  # Encrypt password using encryption key
  encrypted_password = encrypt_password(password).strip

  # Log in
  @logger.info "Logging in as #{username}"
  response = @client.post("https://digital.anz.co.nz/preauth/web/service/login") do |req|
    req.headers = {
      "User-Agent" => @user_agent,
      "Accept" => "application/json",
      "Content-Type" => "application/json",
    }
    req.body = {
      userId: username,
      password: encrypted_password,
      "referrer": "",
      "firstPage": "",
      "publicKeyId": @encryption_key_id,
    }.to_json
  end
  if JSON.parse(response.body)["code"] != "success"
    raise "Error logging in: #{response.status}\n\n#{response.body}"
  end

  @logger.info "Fetching session details"
  response = @client.get("https://secure.anz.co.nz/IBCS/service/session?referrer=https%3A%2F%2Fsecure.anz.co.nz%2F")
  if response.status != 200
    raise "Session setup failed with status #{response.status}\n\n#{response.body}"
  end

  csrf_token_match = response.body.match(/sessionCsrfToken *= *"(.*)";/)
  unless csrf_token_match
    raise "Could not find CSRF token in page body:\n\n#{response.body}"
  end

  csrf_token = csrf_token_match[1]
  @logger.info "CSRF Token: #{csrf_token}"

  @logger.info "Getting initial details"
  response = @client.get("https://secure.anz.co.nz/IBCS/service/home/initialise")
  raise "Error getting initial details: #{response.status}\n\n#{response.body}" unless response.status == 200

  @initialise_response = JSON.parse(response.body)
end

#logoutObject



180
181
182
183
184
185
186
# File 'lib/anz_bank_client.rb', line 180

def logout
  @logger.info "Logging out"
  response = @client.get("https://secure.anz.co.nz/IBCS/service/goodbye")
  if response.status != 200
    raise "Error logging out: #{response.status}\n\n#{response.body}"
  end
end