Class: UkrsibAPI::Authentication

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

Constant Summary collapse

BASE_URL =
"https://business.ukrsibbank.com/morpheus"
TOKEN_URL =
"#{BASE_URL}/token"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(private_key: nil, client_params: nil, tokens: nil) ⇒ Authentication

Initializes authentication using provided credentials or defaults from config.

Examples:

Initialize with explicit credentials

auth = Authentication.new(private_key: my_key, client_params: { client_id: "abc", client_secret: "xyz" })

Initialize using stored config

auth = Authentication.new

Initialize with tokens

auth = Authentication.new(tokens: { access_token: "token123", refresh_token: "token456", expires_at: Time.now + 3600 })

Parameters:

  • private_key (String, nil) (defaults to: nil)

    RSA private key in PEM format. Falls back to ‘UkrsibAPI.private_key`.

  • client_params (Hash, nil) (defaults to: nil)

    OAuth2 credentials (‘client_id`, `client_secret`). Defaults to `UkrsibAPI` config.

  • tokens (Hash, nil) (defaults to: nil)

    Previously acquired tokens (‘access_token`, `refresh_token`, `expires_at`).

Raises:

  • (OpenSSL::PKey::RSAError)

    If the private key is invalid.

  • (ArgumentError)

    If tokens are provided without ‘expires_at`.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ukrsib_api/authentication.rb', line 33

def initialize(private_key: nil, client_params: nil, tokens: nil)
  @private_key = OpenSSL::PKey::RSA.new(private_key || UkrsibAPI.private_key)
  @client_params = {
    client_id: client_params&.dig(:client_id) || UkrsibAPI.client_id,
    client_secret: client_params&.dig(:client_secret) || UkrsibAPI.client_secret
  }.compact

  unless tokens
    UkrsibAPI.logger.warn "No tokens provided, authenticate first, or pass UkrsibAPI::Authentication with existing tokens to UkrsibAPI::Client"
    return
  end

  @tokens = tokens&.slice(:access_token, :refresh_token, :expires_at)
  return unless @tokens && !@tokens[:expires_at]

  raise ArgumentError, "Missing :expires_at in tokens, it should be a Time object, e.g. Time.now + expires_in"
end

Instance Attribute Details

#client_paramsObject (readonly)

Returns the value of attribute client_params.



14
15
16
# File 'lib/ukrsib_api/authentication.rb', line 14

def client_params
  @client_params
end

#private_keyObject (readonly)

Returns the value of attribute private_key.



14
15
16
# File 'lib/ukrsib_api/authentication.rb', line 14

def private_key
  @private_key
end

#tokensObject (readonly)

Returns the value of attribute tokens.



14
15
16
# File 'lib/ukrsib_api/authentication.rb', line 14

def tokens
  @tokens
end

Instance Method Details

#access_tokenObject



51
52
53
54
55
56
57
# File 'lib/ukrsib_api/authentication.rb', line 51

def access_token
  unless valid_token?
    UkrsibAPI.logger.warn "Access token is invalid or expired. Refreshing..."
    refresh_access_token
  end
  @tokens[:access_token]
end

#authorize(redirect_url: nil) ⇒ Hash

Initiates the OAuth2 authorization process. This method should be called first to obtain an authorization URL to which the client should be redirected.

after the user has authorized the client, only then must the #fetch_token method be called.

Examples:

Authorization with redirect URI

authorize("https://example.com/callback")

Authorization without redirect URI (using client_code)

authorize

Parameters:

  • redirect_url (String, nil) (defaults to: nil)

    Optional URL to redirect after authorization. If provided, the authorization request includes ‘redirect_uri`. Otherwise, a unique `client_code` is generated.

Returns:

  • (Hash)

    A hash containing:

    • ‘:client_code` [String, nil] The generated client code (if no redirect URL is used).

    • ‘:location` [String] The authorization URL to which the client should be redirected.

Raises:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/ukrsib_api/authentication.rb', line 76

def authorize(redirect_url: nil)
  UkrsibAPI.logger.warn "Already authorized, redundant call to authorize" if tokens

  params = @client_params.clone
  params[:response_type] = "code"

  if redirect_url
    params[:redirect_uri] = redirect_url
  else
    params[:client_code] = SecureRandom.uuid
  end

  url_params = URI.encode_www_form(params)
  response = Faraday.get("#{BASE_URL}/authorize?#{url_params}")
  raise Error, "Authorization request failed with #{response.status}, #{response.body}" if response.status == 400

  { client_code: params[:client_code], location: response.headers["location"] }
end

#fetch_token(client_code: nil, code: nil) ⇒ Hash

Exchanges an authorization code or client code for an access token.

It requires either a ‘client_code` (if no redirect URI was used) or an authorization `code` if the user was redirected back after authorization.

This method is called after the user has authorized the client via #authorize method with the browser. ‘code` is used when #authorize method was called with redirect_uri. It is found in the redirect to your server. `client_code` is used when no redirect_uri is provided for #authorize method. It is returned by #authorize method.

Examples:

Fetch token using client_code

fetch_token(client_code: "123e4567-e89b-12d3-a456-426614174000")

Fetch token using authorization code

fetch_token(code: "AQABAAIAAAAGV_b3")

Parameters:

  • client_code (String, nil) (defaults to: nil)

    The client code returned by the ‘authorize` method if no redirect URI is used.

  • code (String, nil) (defaults to: nil)

    The authorization code obtained from the redirect URI after user authorization.

Returns:

  • (Hash)

    A hash containing the OAuth2 token response. The response typically includes:

    • ‘:access_token` [String] The access token to be used for API requests.

    • ‘:expires_in` [Integer] The token’s expiration time in seconds.

    • ‘:refresh_token` [String] The refresh token for obtaining a new access token.

Raises:

  • (ArgumentError)

    If neither ‘client_code` nor `code` is provided.



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

def fetch_token(client_code: nil, code: nil)
  raise ArgumentError, "Either client_code or code must be provided" if client_code.nil? && code.nil?

  params = @client_params.clone
  if client_code
    params[:client_code] = client_code
    params[:grant_type] = "client_code"
  else
    params[:grant_type] = "authorization_code"
    params[:code] = code
  end

  response = Faraday.post(TOKEN_URL) do |req|
    req.headers["Content-Type"] = "application/x-www-form-urlencoded"
    req.body = URI.encode_www_form(params)
  end

  store_token(response)
end

#generate_signature(data_string) ⇒ Object



164
165
166
167
# File 'lib/ukrsib_api/authentication.rb', line 164

def generate_signature(data_string)
  signature = @private_key.sign(OpenSSL::Digest.new("SHA512"), data_string)
  Base64.strict_encode64(signature)
end

#refresh_access_tokenObject



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/ukrsib_api/authentication.rb', line 141

def refresh_access_token
  return NotAuthorizedError unless @tokens && @tokens[:refresh_token]

  params = client_params.clone.merge({ grant_type: "refresh_token", refresh_token: @tokens[:refresh_token] })
  response = Faraday.post(TOKEN_URL) do |req|
    req.headers["Content-Type"] = "application/x-www-form-urlencoded"
    req.body = URI.encode_www_form(params)
  end

  store_token(response)
end

#refresh_token_if_neededObject



157
158
159
160
161
162
# File 'lib/ukrsib_api/authentication.rb', line 157

def refresh_token_if_needed
  return if valid_token?

  UkrsibAPI.logger.info "Refreshing expired token..."
  refresh_access_token
end

#valid_token?Boolean

Returns:

  • (Boolean)


153
154
155
# File 'lib/ukrsib_api/authentication.rb', line 153

def valid_token?
  @tokens && @tokens[:access_token] && @tokens[:expires_at] && Time.now - 3600 < @tokens[:expires_at]
end