twitch_oauth2 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db75a943539bebe359931ac1b78fd29b6d55add3922c1dd56c3ff457c190ebbd
4
- data.tar.gz: d171153225fac0312176a923848b6c9f3be048946e23fbec72f5b7413801665d
3
+ metadata.gz: 723edb026a4d7f2d80c2689f2b29f97d94eb4089714f0b73fecbc29328fa1ff0
4
+ data.tar.gz: 95d95090fe825074a893525db1191e1764736fc0f3c431b74837ce59aee8ad7a
5
5
  SHA512:
6
- metadata.gz: 0d191956b126deec94d160e85af9d147ac1e920db8b5031d68602c6459bb2c172a8025b5205be596d32fb8b1f2040c29f401afb533853ac2f2e813899b36d276
7
- data.tar.gz: 75625c8ef174b8115ae4b6fdac56d3fd6e9ab6239854b92619f3e04c978a3ef252bf5255a87ae3685a389f203365739d705a9d588f47f3cb4597c09685b00d38
6
+ metadata.gz: 4ca9e069fef10a36f1cbe922c52bfdba5a091afc13c8356219361c5475129ccc23e56cdd37f626b4b036bf7134a2b61a218ac6fe7d290520b3d142c6728d820d
7
+ data.tar.gz: 96543dd82cd0f3eeb646a3e0ae55c21df4d702cc37f6a8d308a62fbe345b5135e6be2865d8a1afe5deff281d0f40235e694f99f90a076c905e68335b277262c2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.5.0 (2023-07-25)
6
+
7
+ * Introduce `Tokens` object.
8
+ It stores access and refresh tokens, also calls `client` for interactions with them,
9
+ like refreshing.
10
+ This approach allows to share this object with the same tokens between different libraries
11
+ and storing (and refreshing) tokens in (temporary, secret) file.
12
+ * Add Ruby 3.2 to CI.
13
+ * Drop Ruby 2.6 support.
14
+ * Update development dependencies.
15
+ * Improve CI.
16
+
5
17
  ## 0.4.0 (2022-07-23)
6
18
 
7
19
  * Support Ruby 3.
data/README.md CHANGED
@@ -35,70 +35,97 @@ gem install twitch_oauth2
35
35
 
36
36
  ## Usage
37
37
 
38
+ Since version `0.5.0`, the main object here is `TwitchOAuth2::Tokens` which receives
39
+ and internally uses `client` for necessary requests. This approach allows:
40
+
41
+ * get an actual `access_token` with validations, refreshing and other things inside;
42
+ * share and reuse an instance of this class, for example between API and IRC clients;
43
+ * initialize 2 instances for user token and application token, but with the same `client` object.
44
+
38
45
  ### Initialization
39
46
 
47
+ Client, for requests:
48
+
40
49
  ```ruby
41
50
  require 'twitch_oauth2'
42
51
 
43
52
  client = TwitchOAuth2::Client.new(
44
53
  client_id: 'application_client_id',
45
54
  client_secret: 'application_client_secret',
46
- scopes: %w[user:read:email bits:read] # default is `nil`
47
55
  redirect_uri: 'application_redirect_uri' # default is `http://localhost`
48
56
  )
49
57
  ```
50
58
 
51
- ### Check tokens
59
+ Tokens, for their storage and refreshing:
52
60
 
53
61
  ```ruby
54
- tokens = previously_saved_tokens
55
- # => { access_token: 'abcdef', refresh_token: 'ghikjl' }
56
- # Can be empty.
57
-
58
- client.check_tokens **tokens, token_type: :user
62
+ tokens = TwitchOAuth2::Tokens.new(
63
+ client: client, # initialized above, or can be a `Hash` with values for `Client` initialization
64
+ # all other arguments are optional
65
+ access_token: 'somewhere_received_access_token', # default is `nil`
66
+ refresh_token: 'refresh_token_from_the_same_place', # default is `nil`
67
+ token_type: :user, # default is `:application`
68
+ scopes: %w[user:read:email bits:read] # default is `nil`, but it's not so useful
69
+ )
59
70
  ```
60
71
 
61
- `:token_type` is optional and is `:application` by default,
62
- but a number of available API end-points is limited in this case.
72
+ ### Check tokens
63
73
 
64
- Also, Application Access Token has no `refresh_token`, but this gem just receive a new one
65
- if a given one is invalid.
74
+ Please, use `Tokens#valid?` method after initialization, especially for empty initial tokens,
75
+ especially for User Access Token.
66
76
 
67
- #### The first run
77
+ If method returned `false` — direct user to `tokens#authorize_link`.
68
78
 
69
- You can pass nothing to `#check_tokens`, then client will generate new ones.
79
+ For a web-application with N users, you can use `:redirect_uri` argument for `Client`
80
+ initialization as your application callback and redirect them to `#authorize_link`.
70
81
 
71
- If you've specified `:token_type` as `:application` or have not specify it at all (default),
72
- there will be an Application Access Token (without refresh token).
82
+ For something like a CLI tool you can print instructions for user with received link.
73
83
 
74
- Otherwise, for User Access Token here will be raised a `TwitchOAuth2::Error` with Twitch link
75
- inside `#metadata[:link]`.
84
+ #### Application Access Token
76
85
 
77
- If you have a web-application with N users, you can redirect them to this link
78
- and use `redirect_uri` to your application for callbacks.
86
+ It's simpler, has less permissions, and it's the default `:token_type`.
79
87
 
80
- Otherwise, if you have something like CLI tool, you can print instructions with a link for user.
88
+ Application Access Tokens have no `refresh_token` right now and have longer life time,
89
+ so the logic here is simpler: you can pass nothing to `Tokens.new` — the client will generate
90
+ new `access_token`, and regenerate when it's will be outdated.
81
91
 
82
- Then you can use `#token(token_type: :user, code: 'a code from params in redirect uri')`
83
- and get your `:access_token` and `:refresh_token`.
92
+ #### User Access Token
84
93
 
85
- #### Reusing tokens
94
+ If you need for `:user` token and you have no actual `access_token` or `refresh_token`
95
+ (checkable by `Tokens#valid?`), **you have to direct user** to `tokens#authorize_link`.
86
96
 
87
- Then, if you pass tokens, client will validate them and return themselves
88
- or refresh and return new ones.
97
+ After successful user login there will be redirect from Twitch to the `:redirect_uri`
98
+ (by default is `localhost`) with the `code` query argument.
99
+ **You have to pass it** to the `Tokens#code=` for `access_token` and `refresh_token` generation,
100
+ they will be available right after it.
89
101
 
90
- ### Explicitly refresh tokens
102
+ It's one-time manual operation for User Access Token, further the gem will give you actual tokens
103
+ and refresh them as necessary (right now `refresh_token`, getting after `code`, has no life time).
91
104
 
92
- You can refresh tokens manually:
105
+ Without checking tokens the `TwitchOAuth2::AuthorizeError` will be raised on `#access_token` call,
106
+ and it can interrupt some operations, like API library initialization.
93
107
 
94
- ```ruby
95
- client.refreshed_tokens refresh_token: 'ghikjl'
96
- ```
108
+ The reference for such behavior is [the official Google API gem](https://github.com/googleapis/google-api-ruby-client/blob/39ae3527722a003b389a2f7f5275ab9c6e93bb5e/samples/cli/lib/base_cli.rb`).
109
+
110
+ Another reference, [`twitch` package for JavaScript](https://d-fischer.github.io/twitch/),
111
+ has refreshing logic, but [requires initial correct tokens from you](https://d-fischer.github.io/twitch-chat-client/docs/examples/basic-bot.html),
112
+ and doesn't care how you'll get them.
113
+
114
+
115
+ ### Get tokens
116
+
117
+ The main method is `Tokens#access_token`: it's used in API libraries, in chat libraries, etc.
97
118
 
98
- This is used internally in `#check_tokens`, and can be used separately
99
- for failed requests to API.
119
+ It has refreshing logic inside for cases when it's outdated.
120
+ But if there is no initial `refresh_token` — be aware and read the documentation below.
100
121
 
101
- And, because Application Access Tokens have no refresh tokens this method will not work for them.
122
+ There is also `#refresh_token` getter, just for storing it or something else,
123
+ it's more important internally.
124
+
125
+ #### Reusing tokens
126
+
127
+ Then, or if you pass tokens to initialization, client will validate them and return themselves
128
+ or refresh and return new ones.
102
129
 
103
130
  ## Development
104
131
 
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TwitchOAuth2
4
+ ## Error for `#authorize_link`
5
+ class AuthorizeError < Error
6
+ attr_reader :link
7
+
8
+ def initialize(link)
9
+ @link = link
10
+ super 'Direct user to `error.link` and assign `code`'
11
+ end
12
+ end
13
+ end
@@ -19,31 +19,27 @@ module TwitchOAuth2
19
19
  parser_options: { symbolize_names: true }
20
20
  end.freeze
21
21
 
22
- def initialize(
23
- client_id:, client_secret:, redirect_uri: 'http://localhost', scopes: nil
24
- )
22
+ attr_reader :client_id
23
+
24
+ def initialize(client_id:, client_secret:, redirect_uri: 'http://localhost')
25
25
  @client_id = client_id
26
26
  @client_secret = client_secret
27
27
  @redirect_uri = redirect_uri
28
- @scopes = scopes
29
28
  end
30
29
 
31
- def check_tokens(access_token: nil, refresh_token: nil, token_type: :user)
32
- if access_token
33
- validate_result = validate access_token: access_token
34
-
35
- if validate_result[:status] == 401
36
- return refreshed_tokens(refresh_token: refresh_token) if token_type == :user
37
- elsif validate_result[:expires_in].positive?
38
- return { access_token: access_token, refresh_token: refresh_token }
39
- end
40
- end
30
+ def authorize(scopes:)
31
+ response = CONNECTION.get(
32
+ 'authorize',
33
+ client_id: @client_id,
34
+ redirect_uri: @redirect_uri,
35
+ scope: Array(scopes).join(' '),
36
+ response_type: :code
37
+ )
41
38
 
42
- flow token_type: token_type
43
- end
39
+ location = response.headers[:location]
40
+ return location if location
44
41
 
45
- def refreshed_tokens(refresh_token:)
46
- refresh(refresh_token: refresh_token).slice(:access_token, :refresh_token)
42
+ raise Error, response.body[:message]
47
43
  end
48
44
 
49
45
  def token(token_type:, code: nil)
@@ -61,39 +57,6 @@ module TwitchOAuth2
61
57
  raise Error, response.body[:message]
62
58
  end
63
59
 
64
- private
65
-
66
- def flow(token_type:)
67
- if token_type == :user
68
- raise Error.new('Use `error.metadata[:link]` for getting new tokens', link: authorize)
69
- end
70
-
71
- token(token_type: token_type).slice(:access_token, :refresh_token)
72
- end
73
-
74
- def authorize
75
- response = CONNECTION.get(
76
- 'authorize',
77
- client_id: @client_id,
78
- redirect_uri: @redirect_uri,
79
- scope: Array(@scopes).join(' '),
80
- response_type: :code
81
- )
82
-
83
- location = response.headers[:location]
84
- return location if location
85
-
86
- raise Error, response.body[:message]
87
- end
88
-
89
- def grant_type_by_token_type(token_type)
90
- case token_type
91
- when :user then :authorization_code
92
- when :application then :client_credentials
93
- else raise Error, 'unsupported token type'
94
- end
95
- end
96
-
97
60
  def validate(access_token:)
98
61
  response = CONNECTION.get(
99
62
  'validate', {}, { 'Authorization' => "OAuth #{access_token}" }
@@ -115,5 +78,15 @@ module TwitchOAuth2
115
78
 
116
79
  raise Error, response.body[:message]
117
80
  end
81
+
82
+ private
83
+
84
+ def grant_type_by_token_type(token_type)
85
+ case token_type
86
+ when :user then :authorization_code
87
+ when :application then :client_credentials
88
+ else raise UnsupportedTokenTypeError, token_type
89
+ end
90
+ end
118
91
  end
119
92
  end
@@ -3,12 +3,5 @@
3
3
  module TwitchOAuth2
4
4
  ## Error during Twitch OAuth2 operations
5
5
  class Error < StandardError
6
- attr_reader :metadata
7
-
8
- def initialize(message, metadata = {})
9
- super message
10
-
11
- @metadata = metadata
12
- end
13
6
  end
14
7
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'authorize_error'
4
+
5
+ module TwitchOAuth2
6
+ ## Class for tokens and their refreshing, using provided client
7
+ class Tokens
8
+ ## `refresh_token` for `on_update`, but it can be better to make getter with logic
9
+ ## like for `access_token`, but there can be troubles:
10
+ ## * right now `refresh_token` is kind of constant, but it can have TTL in the future;
11
+ ## * right now there is no `refresh_token` for `:application` tokens, but it can appears
12
+ ## in the future.
13
+ attr_reader :client, :token_type, :refresh_token
14
+
15
+ ## I don't know how to make it shorter
16
+ # rubocop:disable Metrics/ParameterLists
17
+ def initialize(
18
+ client:, access_token: nil, refresh_token: nil, token_type: :application, scopes: nil,
19
+ on_update: nil
20
+ )
21
+ # rubocop:enable Metrics/ParameterLists
22
+ @client = client.is_a?(Hash) ? Client.new(**client) : client
23
+ @access_token = access_token
24
+ @refresh_token = refresh_token
25
+ @token_type = token_type
26
+ @scopes = scopes
27
+ @on_update = on_update
28
+
29
+ @expires_at = nil
30
+ end
31
+
32
+ def valid?
33
+ if @access_token
34
+ validate_access_token if @expires_at.nil? || Time.now >= @expires_at
35
+ else
36
+ return false if @token_type == :user
37
+
38
+ request_new_tokens
39
+ end
40
+
41
+ true
42
+ end
43
+
44
+ def authorize_link
45
+ @client.authorize(scopes: @scopes)
46
+ end
47
+
48
+ def access_token
49
+ raise AuthorizeError, authorize_link if !valid? && @token_type == :user
50
+
51
+ @access_token
52
+ end
53
+
54
+ def code=(value)
55
+ assign_tokens @client.token(token_type: @token_type, code: value)
56
+ end
57
+
58
+ private
59
+
60
+ def validate_access_token
61
+ validate_result = @client.validate access_token: @access_token
62
+
63
+ if validate_result[:status] == 401
64
+ refresh_tokens
65
+ elsif (expires_in = validate_result[:expires_in]).positive?
66
+ @expires_at = Time.now + expires_in
67
+ else
68
+ raise "Unexpected validate result: #{validate_result}"
69
+ end
70
+ end
71
+
72
+ def refresh_tokens
73
+ case @token_type
74
+ when :user
75
+ assign_tokens @client.refresh(refresh_token: @refresh_token)
76
+ when :application
77
+ request_new_tokens
78
+ else
79
+ raise UnsupportedTokenTypeError, @token_type
80
+ end
81
+ end
82
+
83
+ def request_new_tokens
84
+ assign_tokens @client.token(token_type: @token_type)
85
+ end
86
+
87
+ def assign_tokens(data)
88
+ @access_token = data[:access_token]
89
+ @refresh_token = data[:refresh_token]
90
+ @expires_at = Time.now + data[:expires_in]
91
+
92
+ @on_update&.call(self)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TwitchOAuth2
4
+ ## Error with `:token_type` recognition
5
+ class UnsupportedTokenTypeError < Error
6
+ def initialize(token_type)
7
+ super "Unsupported token type: `#{token_type}`"
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TwitchOAuth2
4
- VERSION = '0.4.0'
4
+ VERSION = '0.5.0'
5
5
  end
data/lib/twitch_oauth2.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'twitch_oauth2/client'
4
3
  require_relative 'twitch_oauth2/error'
4
+ require_relative 'twitch_oauth2/unsupported_token_type_error'
5
+
6
+ require_relative 'twitch_oauth2/client'
7
+ require_relative 'twitch_oauth2/tokens'
8
+
5
9
  require_relative 'twitch_oauth2/version'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twitch_oauth2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Popov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-23 00:00:00.000000000 Z
11
+ date: 2023-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -38,160 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.1.0
41
- - !ruby/object:Gem::Dependency
42
- name: pry-byebug
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.9'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.9'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '2.0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '2.0'
69
- - !ruby/object:Gem::Dependency
70
- name: gem_toys
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: 0.12.0
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: 0.12.0
83
- - !ruby/object:Gem::Dependency
84
- name: toys
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: 0.13.0
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: 0.13.0
97
- - !ruby/object:Gem::Dependency
98
- name: codecov
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: 0.6.0
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: 0.6.0
111
- - !ruby/object:Gem::Dependency
112
- name: rspec
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '3.9'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '3.9'
125
- - !ruby/object:Gem::Dependency
126
- name: simplecov
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: 0.21.2
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: 0.21.2
139
- - !ruby/object:Gem::Dependency
140
- name: vcr
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '6.0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '6.0'
153
- - !ruby/object:Gem::Dependency
154
- name: rubocop
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - "~>"
158
- - !ruby/object:Gem::Version
159
- version: 1.32.0
160
- type: :development
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - "~>"
165
- - !ruby/object:Gem::Version
166
- version: 1.32.0
167
- - !ruby/object:Gem::Dependency
168
- name: rubocop-performance
169
- requirement: !ruby/object:Gem::Requirement
170
- requirements:
171
- - - "~>"
172
- - !ruby/object:Gem::Version
173
- version: '1.0'
174
- type: :development
175
- prerelease: false
176
- version_requirements: !ruby/object:Gem::Requirement
177
- requirements:
178
- - - "~>"
179
- - !ruby/object:Gem::Version
180
- version: '1.0'
181
- - !ruby/object:Gem::Dependency
182
- name: rubocop-rspec
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - "~>"
186
- - !ruby/object:Gem::Version
187
- version: '2.0'
188
- type: :development
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - "~>"
193
- - !ruby/object:Gem::Version
194
- version: '2.0'
195
41
  description: |
196
42
  Twitch authentication with OAuth 2.
197
43
  Result tokens can be used for API libraries, chat libraries
@@ -206,8 +52,11 @@ files:
206
52
  - LICENSE.txt
207
53
  - README.md
208
54
  - lib/twitch_oauth2.rb
55
+ - lib/twitch_oauth2/authorize_error.rb
209
56
  - lib/twitch_oauth2/client.rb
210
57
  - lib/twitch_oauth2/error.rb
58
+ - lib/twitch_oauth2/tokens.rb
59
+ - lib/twitch_oauth2/unsupported_token_type_error.rb
211
60
  - lib/twitch_oauth2/version.rb
212
61
  homepage: https://github.com/AlexWayfer/twitch_oauth2
213
62
  licenses:
@@ -225,7 +74,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
225
74
  requirements:
226
75
  - - ">="
227
76
  - !ruby/object:Gem::Version
228
- version: '2.6'
77
+ version: '2.7'
229
78
  - - "<"
230
79
  - !ruby/object:Gem::Version
231
80
  version: '4'
@@ -235,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
235
84
  - !ruby/object:Gem::Version
236
85
  version: '0'
237
86
  requirements: []
238
- rubygems_version: 3.3.7
87
+ rubygems_version: 3.4.17
239
88
  signing_key:
240
89
  specification_version: 4
241
90
  summary: Twitch authentication with OAuth 2.