jorttbv / bunq-client

Ruby wrapper for the bunq public api
MIT License
12 stars 7 forks source link

Suggestion "registration script" #32

Open ErikDeBruijn opened 4 years ago

ErikDeBruijn commented 4 years ago

To make it easier for people to register I created this registration script:

require "openssl"

def register!

    puts "Input the API key from Bunq and press Enter: "
    api_key = gets.chomp

    puts "OK. Generating an SSL certificate..."
    openssl_key = OpenSSL::PKey::RSA.new(2048)
    public_key = openssl_key.public_key.to_pem
    private_key = openssl_key.to_pem

    Bunq.configure do |config|
        config.api_key = api_key
        config.private_key = private_key
        config.server_public_key = public_key
        config.installation_token = nil
    end

    puts "OK. \nSetting up the installation..."
    installation = Bunq.client.installations.create(public_key)

    # Print the installation token to put in your Bunq::Configuration
    installation_token = installation[1]['Token']['token']
    puts "config.installation_token = #{installation_token}"
    puts "OK. \nSetting up the device..."

    Bunq.configure do |config|
        config.api_key = api_key
        config.private_key = private_key
        config.server_public_key = public_key
        config.installation_token = installation_token
    end

    puts "Input a name or identifer for your device and press Enter: "
    device_name = gets.chomp

    begin
      response = Bunq.client.device_servers.create(device_name)
    rescue StandardError => e
      puts "Rescued: #{e.inspect}"
    end
    # raise "Exiting since we couldn't register the device (response code needs to be 200)." unless response.code == 200
    puts "OK."
    pp response
    # puts "Device server created: #{response[0]['Id']['id']}"

    puts "Find your configuration below:"

    puts <<-CONFIG
Bunq.configure do |config|
    config.api_key = "#{api_key}"
    config.private_key = "#{openssl_key.public_key.to_pem}"
    config.server_public_key = "#{openssl_key.public_key.to_pem}"
    config.installation_token = "#{installation_token}"
end
CONFIG
end

register!

Obviously it could use some refinement. Also, I never got a good response from Bunq.client.device_servers.create, but this should probably not be worked around with a rescue block.

The error I'm now catching is the following:

Rescued: #<Bunq::InvalidResponseSignature: Response error (code: 200, body: {"Response":[{"Id":{"id":123123}}]})>

Since I am abandoning this attempt to automate payments for now, maybe someone likes to take it from here?

alexanderjeurissen commented 4 years ago

The issue with your script is that the server_public_key references the public key portion of the generated SSL certificate instead of the server_public_key that is returned when an installation is created.

I created the following helper class that I use for my own testing

# frozen_string_literal: true

require 'openssl'

class BunqInstaller
  def initialize(api_key: ENV['api_key'])
    @configuration = OpenStruct.new api_key: api_key
  end

  def generate_configuration
    configuration = with_installation_token(
      with_ssl_certificate(@configuration)
    )

    create_device_server(configuration)

    yield configuration
  end

  def with_ssl_certificate(configuration)
    openssl_key = OpenSSL::PKey::RSA.new(2048)
    new_configuration = OpenStruct.new(configuration)

    new_configuration.private_key = openssl_key.to_pem
    new_configuration.public_key = openssl_key.public_key.to_pem

    new_configuration
  end

  def with_installation_token(configuration)
    Bunq.configure do |config|
      config.api_key = configuration.api_key
      config.private_key = configuration.private_key
      config.server_public_key = nil # you don't have this yet
      config.installation_token = nil # this MUST be nil for installations
    end

    response = Bunq.client.installations.create(configuration.public_key)

    # NOTE: we won't need this field anymore after this step
    configuration.delete_field('public_key')

    new_configuration = OpenStruct.new(configuration)

    new_configuration.installation_token = response[1]['Token']['token']
    new_configuration.server_public_key = response[2]['ServerPublicKey']['server_public_key']

    new_configuration
  end

  def create_device_server(configuration)
    Bunq.configure do |config|
      config.api_key = configuration.api_key
      config.private_key = configuration.private_key
      config.server_public_key = configuration.server_public_key
      config.installation_token = configuration.installation_token
    end

    Bunq.client.device_servers.create(Socket.gethostname.to_s)
  end
end

This allows me to create a new configuration and consume the resulting configuration. For local testing I simply write the result to a .env file.

installer = BunqInstaller.new api_key: <api_key>

installer.genererate_configuration do |config|
  File.open('.env', 'a') do |f|
    f.puts "api_key=\"#{config.api_key}\"" unless ENV['api_key']
    f.puts "private_key=\"#{config.private_key}\""
    f.puts "server_public_key=\"#{config.server_public_key}\""
    f.puts "installation_token=\"#{config.installation_token}\""
  end
end

that way I can consume it in an initializer using dot-env-rails gem:

# frozen_string_literal: true

Bunq.configure do |config|
  config.api_key = ENV['api_key']
  config.private_key = ENV['private_key']
  config.server_public_key = ENV['server_public_key']
  config.installation_token = ENV['installation_token']
end

You could also write the configuration to a database if you want to support multiple users, and have a BunqClient wrapper that configures the BunqClient to use the configuration of the current_user.