amorail 0.4.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rspec.yml +23 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +3 -0
  5. data/CHANGELOG.md +19 -0
  6. data/README.md +49 -8
  7. data/RELEASING.md +43 -0
  8. data/amorail.gemspec +5 -3
  9. data/lib/amorail.rb +27 -6
  10. data/lib/amorail/access_token.rb +44 -0
  11. data/lib/amorail/client.rb +84 -23
  12. data/lib/amorail/config.rb +14 -8
  13. data/lib/amorail/entities/company.rb +2 -0
  14. data/lib/amorail/entities/contact.rb +3 -0
  15. data/lib/amorail/entities/contact_link.rb +2 -0
  16. data/lib/amorail/entities/elementable.rb +4 -2
  17. data/lib/amorail/entities/lead.rb +3 -0
  18. data/lib/amorail/entities/leadable.rb +3 -0
  19. data/lib/amorail/entities/note.rb +2 -0
  20. data/lib/amorail/entities/task.rb +2 -0
  21. data/lib/amorail/entities/webhook.rb +44 -0
  22. data/lib/amorail/entity.rb +17 -3
  23. data/lib/amorail/entity/finders.rb +5 -2
  24. data/lib/amorail/entity/params.rb +3 -2
  25. data/lib/amorail/entity/persistence.rb +5 -0
  26. data/lib/amorail/exceptions.rb +2 -0
  27. data/lib/amorail/property.rb +7 -3
  28. data/lib/amorail/railtie.rb +3 -1
  29. data/lib/amorail/store_adapters.rb +15 -0
  30. data/lib/amorail/store_adapters/abstract_store_adapter.rb +23 -0
  31. data/lib/amorail/store_adapters/memory_store_adapter.rb +50 -0
  32. data/lib/amorail/store_adapters/redis_store_adapter.rb +83 -0
  33. data/lib/amorail/version.rb +3 -1
  34. data/lib/tasks/amorail.rake +2 -0
  35. data/spec/access_token_spec.rb +59 -0
  36. data/spec/client_spec.rb +36 -24
  37. data/spec/company_spec.rb +2 -0
  38. data/spec/contact_link_spec.rb +2 -0
  39. data/spec/contact_spec.rb +2 -0
  40. data/spec/entity_spec.rb +2 -0
  41. data/spec/fixtures/amorail_test.yml +5 -3
  42. data/spec/fixtures/authorize.json +6 -0
  43. data/spec/fixtures/webhooks/list.json +24 -0
  44. data/spec/fixtures/webhooks/subscribe.json +17 -0
  45. data/spec/fixtures/webhooks/unsubscribe.json +17 -0
  46. data/spec/helpers/webmock_helpers.rb +80 -13
  47. data/spec/lead_spec.rb +2 -0
  48. data/spec/my_contact_spec.rb +2 -0
  49. data/spec/note_spec.rb +2 -0
  50. data/spec/property_spec.rb +2 -0
  51. data/spec/spec_helper.rb +4 -2
  52. data/spec/store_adapters/memory_store_adapter_spec.rb +56 -0
  53. data/spec/store_adapters/redis_store_adapter_spec.rb +67 -0
  54. data/spec/support/elementable_example.rb +2 -0
  55. data/spec/support/entity_class_example.rb +2 -0
  56. data/spec/support/leadable_example.rb +2 -0
  57. data/spec/support/my_contact.rb +2 -0
  58. data/spec/support/my_entity.rb +2 -0
  59. data/spec/task_spec.rb +2 -0
  60. data/spec/webhook_spec.rb +61 -0
  61. metadata +48 -17
  62. data/.travis.yml +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 42a2851b1c9adb5f49a331d622c3fd276c63761b
4
- data.tar.gz: 8012dce427d24125bfeb3a10059033c577bcbd5b
2
+ SHA256:
3
+ metadata.gz: 3b18605e272eb7a0c72af69e2d017f54753ff715d474d79cc9071791a036cd6d
4
+ data.tar.gz: d77fee259b877786fd18a044aaa52aff432be6936834095b5d8ab2d857d0df11
5
5
  SHA512:
6
- metadata.gz: a102ba6296a1e3ed6ab4f2c4160415429f351398e83240d0d2453a72c6302eefaf83c42624b43f037e00fb7dbc40f669f37ea27bd32d6311fe5d250a5b0dc248
7
- data.tar.gz: b7214099abe616f5902717c4f823cabab5e818e84752cf649330cf70191c8b0b6488495f1b4e26fd7afcc27c5712d2431ef728356daefd5fd53a6d03b6b3af2b
6
+ metadata.gz: ab1e1221b40f406909ed76f4b444b7f7f7805d8f04c1e386030563471172175149ff718438bb4ee97d9bee28f2a3d9b9a78733828f8ab2d7b62edca6f631f56e
7
+ data.tar.gz: 4034fbcc9fea9004e159e62ac910ce9bfbe301ea76013f7d62d05a26cae8df2084c2836a4e4c65707cc4451fc8380fc163c5afe580068bdb8c4a40f79f632ff3
@@ -0,0 +1,23 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Set up Ruby 2.5
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: 2.5
20
+ bundler-cache: true
21
+ bundler: 1.13.6
22
+ - name: Build and test
23
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -12,4 +12,5 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
- *.gem
15
+ *.gem
16
+ .idea
data/.rubocop.yml CHANGED
@@ -56,3 +56,6 @@ Style/SignalException:
56
56
 
57
57
  Layout/MultilineMethodCallBraceLayout:
58
58
  Enabled: false
59
+
60
+ Lint/MissingCopEnableDirective:
61
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # Change log
2
+
3
+ ## master (unreleased)
4
+
5
+ ## 0.7.0 (2021-07-16)
6
+
7
+ ### Features
8
+
9
+ - Introduce [#53](https://github.com/teachbase/amorail/issues/48) Implement OAuth authentication ([@lHydra][])
10
+
11
+ ### Changes
12
+
13
+ - increased ruby version to >= 2.5.0
14
+ - updates dependencies versions
15
+
16
+
17
+ [@palkan]: https://github.com/palkan
18
+ [@AlexanderShvaykin]: https://github.com/AlexanderShvaykin
19
+ [@lHydra]: https://github.com/lHydra
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) [![Build Status](https://travis-ci.org/teachbase/amorail.svg?branch=master)](https://travis-ci.org/teachbase/amorail)
1
+ [![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) ![Build](https://github.com/teachbase/amorail/workflows/Build/badge.svg)
2
2
 
3
3
  # Amorail
4
4
 
@@ -25,12 +25,48 @@ Or install it yourself as:
25
25
  With Amorail you can manipulate the following AmoCRM entities: Companies, Contacts, Leads and Tasks.
26
26
  We're triying to build simple AR-like interface.
27
27
 
28
+ ### Store configuration
29
+
30
+ In order to configure a token store you should set up a store adapter in a following way: `Amorail.token_store = :redis, { redis_url: 'redis://127.0.0.1:6379/0' }` (options can be omitted). Currently supported stores are `:redis` and `:memory`. Memory adapter is used **by default**.
31
+
32
+ Here is a default configuration for Redis:
33
+
34
+ ```ruby
35
+ Amorail.token_store = :redis, {
36
+ redis_host: "127.0.0.1",
37
+ redis_port: "6379",
38
+ redis_db_name: "0"
39
+ }
40
+ ```
41
+
42
+ You can also provide a Redis URL instead:
43
+
44
+ ```ruby
45
+ Amorail.token_store = :redis, { redis_url: "redis://localhost:6397" }
46
+ ```
47
+
48
+ **NOTE**: if `REDIS_URL` environment variable is set it is used automatically.
49
+
50
+ ### Add custom store
51
+
52
+ To add custom store you need declare a class that implements the interface `AbstractStoreAdapter`.
53
+ For example `class FileStoreAdapter < Amorail::StoreAdapters::AbstractStoreAdapter`
54
+
55
+ The class must contain constructor `initialize(**options)` and **4 required methods**:
56
+
57
+ 1. `fetch_access` — method that should return Hash with token data (**required keys:** `token`, `refresh_token` and `expiration`) or empty Hash (`{}`) if no value was received
58
+ 2. `persist_access` — method that stores data in storage
59
+ 3. `update_access` — method that updates existed token data in storage
60
+ 4. `access_expired?` — method that returns `true` if token was expired otherwise `false`
61
+
28
62
  ### Auth configuration
29
63
 
30
64
  Amorail uses [anyway_config](https://github.com/palkan/anyway_config) for configuration, so you
31
65
  can provide configuration parameters through env vars, seperate config file (`config/amorail.yml`) or `secrets.yml`.
32
66
 
33
- Required params: **usermail**, **api_key** and **api_endpoint**.
67
+ Required params: **client_id**, **client_secret** **code**, **redirect_uri** and **api_endpoint**.
68
+
69
+ An authorization **code** is required for the initial obtaining of a pair of access and refresh tokens. You can see it in the interface or through a Redirect URI if the authorization was run from the modal window for permissions. The lifespan of the code is 20 minutes. [More details](https://www.amocrm.com/developers/content/oauth/oauth/)
34
70
 
35
71
  Example:
36
72
 
@@ -39,9 +75,14 @@ Example:
39
75
  development:
40
76
  ...
41
77
  amorail:
42
- usermail: 'amorail@test.com'
43
- api_key: '75742b166417fe32ae132282ce178cf6'
44
- api_endpoint: 'https://test.amocrm.ru'
78
+ client_id: c0df457d-eacc-47cc-behb-3d8f962g4lbf
79
+ client_secret: a36b564b64398d3e53004c12e4997eb340e32b18ee185389ddb409292ebc5ebae297a3eab96be4a9d38ecbf274d90bbb54a7e8f282f40d1b29e5c9b2e2e357a6
80
+ code: a911ff963f58ea6c846901056114d37a14d2efa4d05ffb6ef0a8d60d32e5d6dae785bd317cbc9b0bd04261cb0cf9905af0cc32b5567c1eb84433328d08888f5c613608b822c1928272769ffd284b
81
+ redirect_uri: https://example.ru
82
+ api_endpoint: https://test.amocrm.ru
83
+ redis_host: 127.0.0.1
84
+ redis_port: 6379
85
+ redis_db_name: 0
45
86
  ```
46
87
 
47
88
  ### Running from console
@@ -50,7 +91,7 @@ You can try amorail in action from console ([PRY](https://github.com/pry/pry) is
50
91
 
51
92
  ```shell
52
93
  # amorail gem directory
53
- AMORAIL_USERMAIL=my_mail@test.com AMORAIL_API_KEY=my_key AMORAIL_API_ENDPOINT=my@amo.com bundle exec rake console
94
+ AMORAIL_CLIENT_ID=integration_id AMORAIL_CLIENT_SECRET=secret_key AMORAIL_CODE=my_code AMORAIL_REDIRECT_URI=https://example.com AMORAIL_API_ENDPOINT=https://test.amocrm.ru bundle exec rake console
54
95
  pry> Amorail.properties
55
96
  # ... prints properties (custom_fields) data
56
97
  pry> Amorail::Contact.find_by_query("test_contact")
@@ -223,12 +264,12 @@ It is possible to use Amorail with multiple AmoCRM accounts. To do so use `Amora
223
264
  which receive client params or client instance and a block to execute within custom context:
224
265
 
225
266
  ```ruby
226
- Amorail.with_client(usermail: "custom@mail.com", api_endpoint: "https://my.acmocrm.ru", api_key: "my_secret_key") do
267
+ Amorail.with_client(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com") do
227
268
  # Client specific code here
228
269
  end
229
270
 
230
271
  # or using client instance
231
- my_client = Amorail::Client.new(usermail: "custom@mail.com", api_endpoint: "https://my.acmocrm.ru", api_key: "my_secret_key")
272
+ my_client = Amorail::Client.new(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com")
232
273
 
233
274
  Amorail.with_client(client) do
234
275
  ...
data/RELEASING.md ADDED
@@ -0,0 +1,43 @@
1
+ # How to release a gem
2
+
3
+ This document describes a process of releasing a new version of a gem.
4
+
5
+ 1. Bump version.
6
+
7
+ ```sh
8
+ git commit -m "Bump 1.<x>.<y>"
9
+ ```
10
+
11
+ We're (kinda) using semantic versioning:
12
+
13
+ - Bugfixes should be released as fast as possible as patch versions.
14
+ - New features could be combined and released as minor or patch version upgrades (depending on the _size of the feature_—it's up to maintainers to decide).
15
+ - Breaking API changes should be avoided in minor and patch releases.
16
+ - Breaking dependencies changes (e.g., dropping older Ruby support) could be released in minor versions.
17
+
18
+ How to bump a version:
19
+
20
+ - Change the version number in `lib/amorail/version.rb` file.
21
+ - Update the changelog (add new heading with the version name and date).
22
+ - Update the installation documentation if necessary (e.g., during minor and major updates).
23
+
24
+ 2. Push code to GitHub and make sure CI passes.
25
+
26
+ ```sh
27
+ git push
28
+ ```
29
+
30
+ 3. Release a gem.
31
+
32
+ ```sh
33
+ gem release -t
34
+ git push --tags
35
+ ```
36
+
37
+ We use [gem-release](https://github.com/svenfuchs/gem-release) for publishing gems with a single command:
38
+
39
+ ```sh
40
+ gem release -t
41
+ ```
42
+
43
+ Don't forget to push tags and write release notes on GitHub (if necessary).
data/amorail.gemspec CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = %q{Ruby API client for AmoCRM. You can integrate your system with it.}
13
13
  spec.homepage = ""
14
14
  spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -25,9 +26,10 @@ Gem::Specification.new do |spec|
25
26
  spec.add_development_dependency "pry"
26
27
  spec.add_development_dependency "shoulda-matchers", "~> 2.0"
27
28
  spec.add_development_dependency "rubocop", "~> 0.49"
28
- spec.add_dependency "anyway_config", "~> 0", ">= 0.3"
29
+ spec.add_dependency "anyway_config", ">= 1.0"
29
30
  spec.add_dependency "faraday"
30
31
  spec.add_dependency "faraday_middleware"
31
- spec.add_dependency 'activemodel'
32
- spec.add_dependency 'json'
32
+ spec.add_dependency "activemodel"
33
+ spec.add_dependency "json"
34
+ spec.add_dependency "redis"
33
35
  end
data/lib/amorail.rb CHANGED
@@ -1,37 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/version'
2
4
  require 'amorail/config'
3
5
  require 'amorail/client'
4
6
  require 'amorail/exceptions'
5
7
  require 'amorail/entity'
6
8
  require 'amorail/property'
9
+ require 'amorail/access_token'
10
+ require 'amorail/store_adapters'
7
11
 
8
12
  Gem.find_files('amorail/entities/*.rb').each { |path| require path }
9
13
 
10
14
  # AmoCRM API integration.
11
15
  # https://www.amocrm.com/
12
16
  module Amorail
13
- def self.config
17
+ extend self
18
+
19
+ def config
14
20
  @config ||= Config.new
15
21
  end
16
22
 
17
- def self.properties
23
+ def properties
18
24
  client.properties
19
25
  end
20
26
 
21
- def self.configure
27
+ def configure
22
28
  yield(config) if block_given?
23
29
  end
24
30
 
25
- def self.client
31
+ def client
26
32
  ClientRegistry.client || (@client ||= Client.new)
27
33
  end
28
34
 
29
- def self.reset
35
+ def reset
30
36
  @config = nil
31
37
  @client = nil
32
38
  end
33
39
 
34
- def self.with_client(client)
40
+ def with_client(client)
35
41
  client = Client.new(client) unless client.is_a?(Client)
36
42
  ClientRegistry.client = client
37
43
  yield
@@ -39,6 +45,21 @@ module Amorail
39
45
  ClientRegistry.client = nil
40
46
  end
41
47
 
48
+ def token_store=(args)
49
+ adapter, options = Array(args)
50
+ @token_store = StoreAdapters.build_by_name(adapter, options)
51
+ rescue NameError => e
52
+ raise e.class, "Token store adapter for :#{adapter} haven't been found", e.backtrace
53
+ end
54
+
55
+ def token_store
56
+ unless instance_variable_defined?(:@token_store)
57
+ self.token_store = :memory
58
+ end
59
+
60
+ @token_store
61
+ end
62
+
42
63
  class ClientRegistry # :nodoc:
43
64
  extend ActiveSupport::PerThreadRegistry
44
65
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amorail
4
+ class AccessToken
5
+ attr_reader :token, :secret, :expiration, :refresh_token, :store
6
+
7
+ def initialize(secret, token, refresh_token, expiration, store)
8
+ @secret = secret
9
+ @token = token
10
+ @refresh_token = refresh_token
11
+ @expiration = expiration
12
+ @store = store
13
+ end
14
+
15
+ def expired?
16
+ store.access_expired?(secret)
17
+ end
18
+
19
+ class << self
20
+ def create(secret, token, refresh_token, expiration, store)
21
+ new(secret, token, refresh_token, expiration, store).tap do |access_token|
22
+ store.persist_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
23
+ end
24
+ end
25
+
26
+ def find(secret, store)
27
+ token_attrs = store.fetch_access(secret)
28
+ build_with_token_attrs(store, secret, token_attrs)
29
+ end
30
+
31
+ def refresh(secret, token, refresh_token, expiration, store)
32
+ new(secret, token, refresh_token, expiration, store).tap do |access_token|
33
+ store.update_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def build_with_token_attrs(store, secret, token_attrs)
40
+ new(secret, token_attrs[:token], token_attrs[:refresh_token], token_attrs[:expiration], store)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'faraday_middleware'
3
5
  require 'json'
@@ -8,14 +10,31 @@ module Amorail
8
10
  class Client
9
11
  SUCCESS_STATUS_CODES = [200, 204].freeze
10
12
 
11
- attr_reader :usermail, :api_key, :api_endpoint
13
+ attr_accessor :store
14
+ attr_reader :api_endpoint,
15
+ :access_token,
16
+ :refresh_token,
17
+ :access,
18
+ :client_id,
19
+ :client_secret,
20
+ :code,
21
+ :redirect_uri
12
22
 
13
23
  def initialize(api_endpoint: Amorail.config.api_endpoint,
14
- api_key: Amorail.config.api_key,
15
- usermail: Amorail.config.usermail)
24
+ client_id: Amorail.config.client_id,
25
+ client_secret: Amorail.config.client_secret,
26
+ code: Amorail.config.code,
27
+ redirect_uri: Amorail.config.redirect_uri)
28
+ @store = Amorail.token_store
16
29
  @api_endpoint = api_endpoint
17
- @api_key = api_key
18
- @usermail = usermail
30
+ @client_id = client_id
31
+ @client_secret = client_secret
32
+ @code = code
33
+ @redirect_uri = redirect_uri
34
+ @access = AccessToken.find(@client_secret, store)
35
+ @access_token = @access.token
36
+ @refresh_token = @access.refresh_token
37
+
19
38
  @connect = Faraday.new(url: api_endpoint) do |faraday|
20
39
  faraday.response :json, content_type: /\bjson$/
21
40
  faraday.use :instrumentation
@@ -32,33 +51,53 @@ module Amorail
32
51
  end
33
52
 
34
53
  def authorize
35
- self.cookies = nil
36
- response = post(
37
- Amorail.config.auth_url,
38
- 'USER_LOGIN' => usermail,
39
- 'USER_HASH' => api_key
40
- )
41
- cookie_handler(response)
54
+ response = post(Amorail.config.auth_url, auth_params)
55
+ create_access_token(response)
56
+ response
57
+ end
58
+
59
+ def refresh_token!
60
+ response = post(Amorail.config.auth_url, refresh_params)
61
+ update_access_token(response)
42
62
  response
43
63
  end
44
64
 
65
+ def auth_params
66
+ {
67
+ client_id: client_id,
68
+ client_secret: client_secret,
69
+ grant_type: 'authorization_code',
70
+ code: @code,
71
+ redirect_uri: redirect_uri
72
+ }
73
+ end
74
+
75
+ def refresh_params
76
+ {
77
+ client_id: client_id,
78
+ client_secret: client_secret,
79
+ grant_type: 'refresh_token',
80
+ refresh_token: refresh_token,
81
+ redirect_uri: redirect_uri
82
+ }
83
+ end
84
+
45
85
  def safe_request(method, url, params = {})
46
- public_send(method, url, params)
47
- rescue ::Amorail::AmoUnauthorizedError
48
- authorize
86
+ authorize if access_token.blank?
87
+ refresh_token! if access.expired?
49
88
  public_send(method, url, params)
50
89
  end
51
90
 
52
91
  def get(url, params = {})
53
92
  response = connect.get(url, params) do |request|
54
- request.headers['Cookie'] = cookies if cookies.present?
93
+ request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
55
94
  end
56
95
  handle_response(response)
57
96
  end
58
97
 
59
98
  def post(url, params = {})
60
99
  response = connect.post(url) do |request|
61
- request.headers['Cookie'] = cookies if cookies.present?
100
+ request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
62
101
  request.headers['Content-Type'] = 'application/json'
63
102
  request.body = params.to_json
64
103
  end
@@ -67,12 +106,6 @@ module Amorail
67
106
 
68
107
  private
69
108
 
70
- attr_accessor :cookies
71
-
72
- def cookie_handler(response)
73
- self.cookies = response.headers['set-cookie'].split('; ')[0]
74
- end
75
-
76
109
  def handle_response(response) # rubocop:disable all
77
110
  return response if SUCCESS_STATUS_CODES.include?(response.status)
78
111
 
@@ -97,5 +130,33 @@ module Amorail
97
130
  fail ::Amorail::AmoUnknownError, response.body
98
131
  end
99
132
  end
133
+
134
+ def create_access_token(response)
135
+ _access = AccessToken.create(
136
+ client_secret,
137
+ response.body['access_token'],
138
+ response.body['refresh_token'],
139
+ expiration(response.body['expires_in']),
140
+ store
141
+ )
142
+ @access_token = _access.token
143
+ @refresh_token = _access.refresh_token
144
+ end
145
+
146
+ def update_access_token(response)
147
+ _access = AccessToken.refresh(
148
+ client_secret,
149
+ response.body['access_token'],
150
+ response.body['refresh_token'],
151
+ expiration(response.body['expires_in']),
152
+ store
153
+ )
154
+ @access_token = _access.token
155
+ @refresh_token = _access.refresh_token
156
+ end
157
+
158
+ def expiration(expires_in)
159
+ Time.now.to_i + expires_in.to_i
160
+ end
100
161
  end
101
162
  end