Skip to content

Commit

Permalink
Merge pull request #49 from ResultadosDigitais/retryable-client
Browse files Browse the repository at this point in the history
[Hackathon 2019 Q4] Refresh access tokens after expiration and retry requests
  • Loading branch information
Antônio H. N. Muniz authored Dec 16, 2019
2 parents e56a75d + 40cb1f3 commit fba83df
Show file tree
Hide file tree
Showing 23 changed files with 575 additions and 95 deletions.
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,44 @@
## 2.2.0

### Additions

#### Configuration

Now it is possible to configure global params like client_id and client_secret only once, so you don't need to provide them to `RDStation::Authentication` every time.

This can be done in the following way:

```ruby
RDStation.configure do |config|
config.client_id = YOUR_CLIENT_ID
config.client_secret = YOUR_CLIENT_SECRET
end
```

If you're using Rails, this can be done in `config/initializers`.

#### Automatic refresh of access_tokens

When an access_token expires, a new one will be obtained automatically and the request will be made again.

For this to work, you have to use `RDStation.configure` as described above, and provide the refresh token when instantiating `RDStation::Client` (ex: RDStation::Client.new(access_token: MY_ACCESS_TOKEN, refresh_token: MY_REFRESH_TOKEN).

You can keep track of access_token changes, by providing a callback block inconfiguration. This block will be called with an `RDStation::Authorization` object, which contains the updated `access_token` and `refresh_token`. For example:

```ruby
RDStation.configure do |config|
config.on_access_token_refresh do |authorization|
MyStoredAuth.where(refresh_token: authorization.refresh_token).update_all(access_token: authorization.access_token)
end
end
```

### Deprecations

Providing `client_id` and `client_secret` directly to `RDStation::Authentication.new` is deprecated and will be removed in future versions. Use `RDStation.configure` instead.

Specifying refresh_token in `RDStation::Client.new(access_token: 'at', refresh_token: 'rt')` is optional right now, but will be mandatory in future versions.

## 2.1.1

- Fixed a bug in error handling (issue [#47](https://github.com/ResultadosDigitais/rdstation-ruby-client/issues/47))
Expand Down
74 changes: 53 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ Upgrading? Check the [migration guide](#Migration-guide) before bumping to a new

1. [Installation](#Installation)
2. [Usage](#Usage)
1. [Authentication](#Authentication)
2. [Contacts](#Contacts)
3. [Events](#Events)
4. [Fields](#Fields)
5. [Webhooks](#Webhooks)
6. [Errors](#Errors)
1. [Configuration](#Configuration)
2. [Authentication](#Authentication)
3. [Contacts](#Contacts)
4. [Events](#Events)
5. [Fields](#Fields)
6. [Webhooks](#Webhooks)
7. [Errors](#Errors)
3. [Changelog](#Changelog)
4. [Migration guide](#Migration-guide)
1. [Upgrading from 1.2.x to 2.0.0](#Upgrading-from-1.2.x-to-2.0.0)
Expand All @@ -39,14 +40,27 @@ Or install it yourself as:

## Usage

### Configuration

Before getting youre credentials, you need to configure client_id and client_secret as following:

```ruby
RDStation.configure do |config|
config.client_id = YOUR_CLIENT_ID
config.client_secret = YOUR_CLIENT_SECRET
end
```

For details on what `client_id` and `client_secret` are, check the [developers portal](https://developers.rdstation.com/en/authentication).

### Authentication

For more details, check the [developers portal](https://developers.rdstation.com/en/authentication).

#### Getting authentication URL

```ruby
rdstation_authentication = RDStation::Authentication.new('client_id', 'client_secret')
rdstation_authentication = RDStation::Authentication.new

redirect_url = 'https://yourapp.org/auth/callback'
rdstation_authentication.auth_url(redirect_url)
Expand All @@ -57,17 +71,35 @@ rdstation_authentication.auth_url(redirect_url)
You will need the code param that is returned from RD Station to your application after the user confirms the access at the authorization dialog.

```ruby
rdstation_authentication = RDStation::Authentication.new('client_id', 'client_secret')
rdstation_authentication = RDStation::Authentication.new
rdstation_authentication.authenticate(code_returned_from_rdstation)
# => { 'access_token' => '54321', 'expires_in' => 86_400, 'refresh_token' => 'refresh' }
```

#### Updating access_token
#### Updating an expired access_token

```ruby
rdstation_authentication = RDStation::Authentication.new('client_id', 'client_secret')
rdstation_authentication = RDStation::Authentication.new
rdstation_authentication.update_access_token('refresh_token')
```

**NOTE**: This is done automatically when a request fails due to access_token expiration. To keep track of the new token, you have to provide a callback block in configuration. For example:

```ruby
RDStation.configure do |config|
config.client_id = YOUR_CLIENT_ID
config.client_secret = YOUR_CLIENT_SECRET
config.on_access_token_refresh do |authorization|
# authorization.access_token_expires_in is the time (in seconds for with the token is valid)
# authorization.access_token is the new token
# authorization.refresh_token is the existing refresh_token
#
# If you are using ActiveRecord, you may want to update the stored access_token, like in the following code:
MyStoredAuth.where(refresh_token: authorization.refresh_token).update_all(access_token: authorization.access_token)
end
end
```

#### Revoking an access_token

```ruby
Expand All @@ -83,7 +115,7 @@ Note: this will completely remove your credentials from RD Station (`update_acce
Returns data about a specific Contact

```ruby
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.contacts.by_uuid('uuid')
```

Expand All @@ -94,7 +126,7 @@ More info: https://developers.rdstation.com/pt-BR/reference/contacts#methodGetDe
Returns data about a specific Contact

```ruby
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.contacts.by_email('email')
```

Expand All @@ -109,7 +141,7 @@ contact_info = {
name: "Joe Foo"
}

client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.contacts.update('uuid', contact_info)
```
Contact Default Parameters
Expand Down Expand Up @@ -139,7 +171,7 @@ contact_info = {
identifier = "email"
identifier_value = "[email protected]"

client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.contacts.upsert(identifier, identifier_value, contact_info)
```

Expand All @@ -159,7 +191,7 @@ This creates a new event on RDSM:

```ruby
payload = {} # hash representing the payload
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.events.create(payload)
```

Expand All @@ -170,7 +202,7 @@ Endpoints to [manage Contact Fields](https://developers.rdstation.com/en/referen
#### List all fields

```ruby
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.fields.all
```

Expand All @@ -183,22 +215,22 @@ Choose to receive data based on certain actions, re-cast or marked as an opportu
#### List all webhooks

```ruby
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.webhooks.all
```

#### Getting a webhook by UUID

```ruby
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.webhooks.by_uuid('WEBHOOK_UUID')
```

#### Creating a webhook

```ruby
payload = {} # payload representing a webhook
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.webhooks.create(payload)
```

Expand All @@ -208,7 +240,7 @@ The required strucutre of the payload is [described here](https://developers.rds

```ruby
payload = {} # payload representing a webhook
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.webhooks.create('WEBHOOK_UUID', payload)
```

Expand All @@ -217,7 +249,7 @@ The required strucutre of the payload is [described here](https://developers.rds
#### Deleting a webhook

```ruby
client = RDStation::Client.new(access_token: 'access_token')
client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token')
client.webhooks.delete('WEBHOOK_UUID')
```

Expand Down
4 changes: 3 additions & 1 deletion lib/rdstation-ruby-client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
require 'rdstation/api_response'

# API requests
require 'rdstation'
require 'rdstation/retryable_request'
require 'rdstation/authentication'
require 'rdstation/authorization_header'
require 'rdstation/authorization'
require 'rdstation/client'
require 'rdstation/contacts'
require 'rdstation/events'
Expand Down
19 changes: 19 additions & 0 deletions lib/rdstation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module RDStation
class << self
attr_accessor :configuration

def configure
self.configuration ||= Configuration.new
yield(configuration)
end
end

class Configuration
attr_accessor :client_id, :client_secret
attr_reader :access_token_refresh_callback

def on_access_token_refresh(&block)
@access_token_refresh_callback = block
end
end
end
11 changes: 8 additions & 3 deletions lib/rdstation/authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ class Authentication
DEFAULT_HEADERS = { 'Content-Type' => 'application/json' }.freeze
REVOKE_URL = 'https://api.rd.services/auth/revoke'.freeze

def initialize(client_id, client_secret)
@client_id = client_id
@client_secret = client_secret
def initialize(client_id = nil, client_secret = nil)
warn_deprecation if client_id || client_secret
@client_id = client_id || RDStation.configuration&.client_id
@client_secret = client_secret || RDStation.configuration&.client_secret
end

#
Expand Down Expand Up @@ -83,5 +84,9 @@ def post_to_auth_endpoint(params)
headers: DEFAULT_HEADERS
)
end

def warn_deprecation
warn "DEPRECATION WARNING: Providing client_id and client_secret directly to RDStation::Authentication.new is deprecated and will be removed in future versions. Use RDStation.configure instead."
end
end
end
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
module RDStation
class AuthorizationHeader

def initialize(access_token:)
class Authorization
attr_reader :refresh_token
attr_accessor :access_token, :access_token_expires_in
def initialize(access_token:, refresh_token: nil, access_token_expires_in: nil)
@access_token = access_token
@refresh_token = refresh_token
@access_token_expires_in = access_token_expires_in
validate_access_token access_token
end
def to_h

def headers
{ "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" }
end

private

def validate_access_token(access_token)
access_token_msg = ':access_token is required'
raise ArgumentError, access_token_msg unless access_token
end

end
end
24 changes: 17 additions & 7 deletions lib/rdstation/client.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
module RDStation
class Client
def initialize(access_token:)
@authorization_header = AuthorizationHeader.new(access_token: access_token)
def initialize(access_token:, refresh_token: nil)
warn_deprecation unless refresh_token
@authorization = Authorization.new(
access_token: access_token,
refresh_token: refresh_token
)
end

def contacts
@contacts ||= RDStation::Contacts.new(authorization_header: @authorization_header)
@contacts ||= RDStation::Contacts.new(authorization: @authorization)
end

def events
@events ||= RDStation::Events.new(authorization_header: @authorization_header)
@events ||= RDStation::Events.new(authorization: @authorization)
end

def fields
@fields ||= RDStation::Fields.new(authorization_header: @authorization_header)
@fields ||= RDStation::Fields.new(authorization: @authorization)
end

def webhooks
@webhooks ||= RDStation::Webhooks.new(authorization_header: @authorization_header)
@webhooks ||= RDStation::Webhooks.new(authorization: @authorization)
end

private

def warn_deprecation
warn "DEPRECATION WARNING: Specifying refresh_token in RDStation::Client.new(access_token: 'at', refresh_token: 'rt') is optional right now, but will be mandatory in future versions. "
end
end
end
Loading

0 comments on commit fba83df

Please sign in to comment.