Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## Unreleased

* Allow tokens to be revoked and manually refreshed.

PR #39 - https://github.com/procore/ruby-sdk/pull/39

*Nate Baer*
Comment thread
njbbaer marked this conversation as resolved.

## 1.0.0 (January 5, 2021)

* Adds support for API versioning
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ client = Procore::Client.new(
client.get("me")
```

Expired tokens will automatically be refreshed, but can also be refreshed manually:

```ruby
client.refresh
```

Tokens may also be manually revoked, forcing the client to refresh its token on the next request:

```ruby
client.revoke
```

## Error Handling

The Procore Gem raises errors whenever a request returns a non `2xx` response.
Expand Down
11 changes: 11 additions & 0 deletions lib/procore/auth/access_token_credentials.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ def refresh(token:, refresh:)
end
end

def revoke(token:)
request = {
client_id: @client_id,
client_secret: @client_secret,
token: token.access_token,
}
client.request(:post, "/oauth/revoke", body: request)
rescue RestClient::ExceptionWithResponse
raise OAuthError.new(e.description, response: e.response)
end

private

def client
Expand Down
72 changes: 48 additions & 24 deletions lib/procore/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,48 +38,72 @@ def initialize(client_id:, client_secret:, store:, options: {})
@store = store
end

# @raise [OAuthError] if a token cannot be refreshed.
def refresh
token = fetch_token

begin
new_token = @credentials.refresh(
token: token.access_token,
refresh: token.refresh_token,
)

Util.log_info("Token Refresh Successful", store: store)
store.save(new_token)
rescue RuntimeError
Util.log_error("Token Refresh Failed", store: store)
raise Procore::OAuthError.new(
"Unable to refresh the access token. Perhaps the Procore API is " \
"down or your access token store is misconfigured. Either " \
"way, you should clear the store and prompt the user to sign in " \
"again.",
)
end
end

# @raise [OAuthError] if a token cannot be revoked.
def revoke
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you want to add a comment above def revoke

# @raise [OAuthError] if a token cannot be revoked.

token = fetch_token

begin
@credentials.revoke(token: token)
Util.log_info("Token Revocation Successful", store: store)
rescue RuntimeError
Util.log_error("Token Revocation Failed", store: store)
raise Procore::OAuthError.new(
"Unable to revoke the access token. Perhaps the Procore API is " \
"down or your access token store is misconfigured. Either " \
"way, you should clear the store and prompt the user to sign in " \
"again.",
)
end
end

private

def base_api_path
"#{options[:host]}"
end

# @raise [OAuthError] if the store does not have a token stored in it prior
# to making a request.
# @raise [OAuthError] if a token cannot be refreshed.
# @raise [OAuthError] if incorrect credentials have been supplied.
def access_token
# @raise [OAuthError] if the store does not have a token stored.
def fetch_token
token = store.fetch

if token.nil? || token.invalid?
raise Procore::MissingTokenError.new(
"Unable to retreive an access token from the store. Double check " \
"your store configuration and make sure to correctly store a token " \
"before attempting to make API requests",
)
end
token
end

def access_token
token = fetch_token
if token.expired?
Util.log_info("Token Expired", store: store)
begin
token = @credentials.refresh(
token: token.access_token,
refresh: token.refresh_token,
)

Util.log_info("Token Refresh Successful", store: store)
store.save(token)
rescue RuntimeError
Util.log_error("Token Refresh Failed", store: store)
raise Procore::OAuthError.new(
"Unable to refresh the access token. Perhaps the Procore API is " \
"down or the your access token store is misconfigured. Either " \
"way, you should clear the store and prompt the user to sign in " \
"again.",
)
end
refresh
end

token.access_token
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/procore/requestable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ def full_path(path, version)
elsif /\Av\d+\.\d+\z/.match?(version)
File.join(base_api_path, "rest", version, path)
else
raise ArgumentError.new "'#{version}' is an invalid Procore API version"
raise Procore::InvalidRequestError.new "#{version} is an invalid Procore API version"
end
end
end
Expand Down
42 changes: 42 additions & 0 deletions test/procore/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,46 @@ def test_client_no_token
client.get("me")
end
end

def test_client_token_refresh
stub_refresh_token

user = User.create(
access_token: "token",
refresh_token: "refresh",
expires_at: 2.hours.from_now,
)

store = Procore::Auth::Stores::ActiveRecord.new(object: user)
client = Procore::Client.new(
client_id: "client id",
client_secret: "client secret",
store: store,
)

client.refresh

assert_requested stub_refresh_token
end

def test_client_token_revoke
stub_revoke_token

user = User.create(
access_token: "token",
refresh_token: "refresh",
expires_at: 2.hours.from_now,
)

store = Procore::Auth::Stores::ActiveRecord.new(object: user)
client = Procore::Client.new(
client_id: "client id",
client_secret: "client secret",
store: store,
)

client.revoke

assert_requested stub_revoke_token
end
end
4 changes: 4 additions & 0 deletions test/support/auth_stubs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ def stub_refresh_token
headers: { "Content-Type" => "application/json" },
)
end

def stub_revoke_token
stub_request(:post, "https://procore.example.com/oauth/revoke").to_return(status: 200)
end
end