omniauth-wave-oauth2 1.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d1d923ae112d2a304e298303b75f9b9d62686fa67f112fa5933a0450b2efd637
4
+ data.tar.gz: 30c55761978e96f322632a9cde4668da910ffa85b4ae2f707b8aad119ca4de87
5
+ SHA512:
6
+ metadata.gz: ebc7827b9e163a3d93c0f429be55eb3ec4384231c57d7be0a36b84c12813242505703ea2854b378804d5d6d89661e5240906da9cb1160db0221980c27f0d9ff5
7
+ data.tar.gz: ef442fb36327f6d5f160f053d432ced163e4f4c2d4a0a3c0ef0c1a5e1d7f66e35f1e71f33301757686735e9985ac7fee714655ebd8e529c8d16e4a5bc8c2d444
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2026-03-13
4
+
5
+ ### Added
6
+ - Initial release
7
+ - OmniAuth OAuth2 strategy for Wave (by H&R Block)
8
+ - GraphQL-based user and business info fetching
9
+ - TokenClient for refreshing tokens outside the OmniAuth flow
10
+ - Support for `account:read`, `business:read`, `user:read` scopes
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dan1d
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # OmniAuth Wave OAuth2 Strategy
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/omniauth-wave-oauth2.svg)](https://badge.fury.io/rb/omniauth-wave-oauth2)
4
+
5
+ An OmniAuth strategy for authenticating with [Wave](https://www.waveapps.com/) (by H&R Block) using OAuth 2.0.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'omniauth-wave-oauth2'
13
+ ```
14
+
15
+ Then execute:
16
+
17
+ ```bash
18
+ $ bundle install
19
+ ```
20
+
21
+ ## Wave Developer Setup
22
+
23
+ 1. Sign up at [Wave](https://www.waveapps.com/) and create a business
24
+ 2. Go to the [Wave Developer Portal](https://developer.waveapps.com/hc/en-us/articles/360019762711-Manage-Applications)
25
+ 3. Create a new application
26
+ 4. Note your **Client ID** and **Client Secret**
27
+ 5. Add your **Redirect URI** (e.g., `https://yourapp.com/auth/wave_oauth2/callback`)
28
+
29
+ **Note:** Wave requires a [Pro Plan](https://www.waveapps.com/pricing) ($19/mo) for third-party API access.
30
+
31
+ ## Usage
32
+
33
+ ### Standalone OmniAuth
34
+
35
+ ```ruby
36
+ Rails.application.config.middleware.use OmniAuth::Builder do
37
+ provider :wave_oauth2, ENV['WAVE_CLIENT_ID'], ENV['WAVE_CLIENT_SECRET']
38
+ end
39
+ ```
40
+
41
+ ### With Devise
42
+
43
+ In `config/initializers/devise.rb`:
44
+
45
+ ```ruby
46
+ config.omniauth :wave_oauth2, ENV['WAVE_CLIENT_ID'], ENV['WAVE_CLIENT_SECRET']
47
+ ```
48
+
49
+ Add to your routes:
50
+
51
+ ```ruby
52
+ devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
53
+ ```
54
+
55
+ Create the callbacks controller:
56
+
57
+ ```ruby
58
+ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
59
+ def wave_oauth2
60
+ @user = User.from_omniauth(request.env['omniauth.auth'])
61
+
62
+ if @user.persisted?
63
+ sign_in_and_redirect @user, event: :authentication
64
+ else
65
+ redirect_to new_user_registration_url
66
+ end
67
+ end
68
+ end
69
+ ```
70
+
71
+ ## Auth Hash
72
+
73
+ Here's an example of the authentication hash available in `request.env['omniauth.auth']`:
74
+
75
+ ```ruby
76
+ {
77
+ "provider" => "wave_oauth2",
78
+ "uid" => "QnVzaW5lc3M6abc123def456",
79
+ "info" => {
80
+ "email" => "owner@example.com",
81
+ "name" => "Jane Doe",
82
+ "first_name" => "Jane",
83
+ "last_name" => "Doe",
84
+ "business_name" => "Jane's Bakery"
85
+ },
86
+ "credentials" => {
87
+ "token" => "ACCESS_TOKEN",
88
+ "refresh_token" => "REFRESH_TOKEN",
89
+ "expires_at" => 1704067200,
90
+ "expires" => true
91
+ },
92
+ "extra" => {
93
+ "raw_info" => {
94
+ "user_id" => "user-abc",
95
+ "email" => "owner@example.com",
96
+ "first_name" => "Jane",
97
+ "last_name" => "Doe",
98
+ "name" => "Jane Doe",
99
+ "business_id" => "QnVzaW5lc3M6abc123def456",
100
+ "business_name" => "Jane's Bakery"
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Wave OAuth2 Specifics
107
+
108
+ - **GraphQL API**: Wave uses a GraphQL API at `gql.waveapps.com/graphql/public` (not REST). User and business info are fetched via GraphQL after token exchange.
109
+ - **Auth Scheme**: Wave requires credentials in the POST body (`auth_scheme: :request_body`), not HTTP Basic Auth.
110
+ - **Token Expiry**: Access tokens expire after ~2 hours. Refresh tokens are long-lived.
111
+ - **UID**: The `uid` is the Wave Business ID (Base64-encoded format like `QnVzaW5lc3M6...`). Falls back to User ID if no business exists.
112
+ - **Scopes**: Requests `account:read business:read user:read` by default.
113
+
114
+ ## Token Refresh
115
+
116
+ Wave access tokens expire after ~2 hours. This gem includes a `TokenClient` for refreshing tokens:
117
+
118
+ ```ruby
119
+ client = OmniAuth::WaveOauth2::TokenClient.new(
120
+ client_id: ENV['WAVE_CLIENT_ID'],
121
+ client_secret: ENV['WAVE_CLIENT_SECRET']
122
+ )
123
+
124
+ result = client.refresh_token(account.refresh_token)
125
+
126
+ if result.success?
127
+ account.update!(
128
+ access_token: result.access_token,
129
+ refresh_token: result.refresh_token,
130
+ token_expires_at: Time.at(result.expires_at)
131
+ )
132
+ else
133
+ Rails.logger.error "Token refresh failed: #{result.error}"
134
+ end
135
+ ```
136
+
137
+ ### Check Token Expiration
138
+
139
+ ```ruby
140
+ # Check if token is expired (with 5-minute buffer by default)
141
+ client.token_expired?(account.token_expires_at)
142
+
143
+ # Custom buffer (e.g., refresh 10 minutes before expiry)
144
+ client.token_expired?(account.token_expires_at, buffer_seconds: 600)
145
+ ```
146
+
147
+ ### TokenResult Object
148
+
149
+ | Method | Description |
150
+ |--------|-------------|
151
+ | `success?` | Returns `true` if refresh succeeded |
152
+ | `failure?` | Returns `true` if refresh failed |
153
+ | `access_token` | The new access token |
154
+ | `refresh_token` | The new refresh token |
155
+ | `expires_at` | Unix timestamp when token expires |
156
+ | `expires_in` | Seconds until token expires |
157
+ | `error` | Error message if failed |
158
+ | `raw_response` | Full response hash from Wave |
159
+
160
+ ## Development
161
+
162
+ ```bash
163
+ bundle install
164
+ bundle exec rspec
165
+ bundle exec rubocop
166
+ ```
167
+
168
+ ## Contributing
169
+
170
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dan1d/omniauth-wave-oauth2.
171
+
172
+ 1. Fork it
173
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
174
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
175
+ 4. Push to the branch (`git push origin feature/my-new-feature`)
176
+ 5. Create a new Pull Request
177
+
178
+ ## License
179
+
180
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth-oauth2'
4
+ require 'faraday'
5
+ require 'json'
6
+
7
+ module OmniAuth
8
+ module Strategies
9
+ # OmniAuth strategy for Wave (by H&R Block) OAuth2.
10
+ #
11
+ # Wave uses standard OAuth2 with ~2-hour access tokens and refresh tokens.
12
+ # The authorize and token endpoints are on api.waveapps.com.
13
+ # API data is fetched via GraphQL at gql.waveapps.com/graphql/public.
14
+ #
15
+ # Note: Requires Wave Pro Plan ($19/mo) for third-party API access.
16
+ #
17
+ # @example Basic usage
18
+ # provider :wave_oauth2, ENV['WAVE_CLIENT_ID'], ENV['WAVE_CLIENT_SECRET']
19
+ #
20
+ # @example With Devise
21
+ # config.omniauth :wave_oauth2, ENV['WAVE_CLIENT_ID'], ENV['WAVE_CLIENT_SECRET']
22
+ #
23
+ class WaveOauth2 < OmniAuth::Strategies::OAuth2
24
+ option :name, 'wave_oauth2'
25
+
26
+ option :client_options, {
27
+ site: 'https://api.waveapps.com',
28
+ authorize_url: '/oauth2/authorize/',
29
+ token_url: '/oauth2/token/',
30
+ auth_scheme: :request_body
31
+ }
32
+
33
+ option :authorize_params, {
34
+ scope: 'account:read business:read user:read'
35
+ }
36
+
37
+ # UID is the Wave business ID (primary identifier for the connected account)
38
+ uid { raw_info['business_id'] || raw_info['user_id'] }
39
+
40
+ info do
41
+ {
42
+ email: raw_info['email'],
43
+ name: raw_info['name'],
44
+ first_name: raw_info['first_name'],
45
+ last_name: raw_info['last_name'],
46
+ business_name: raw_info['business_name']
47
+ }
48
+ end
49
+
50
+ extra do
51
+ { raw_info: raw_info }
52
+ end
53
+
54
+ def raw_info
55
+ @raw_info ||= fetch_user_and_business_info
56
+ end
57
+
58
+ # Override to strip query params from callback_url for redirect_uri matching.
59
+ # Wave requires the redirect_uri to match exactly.
60
+ def build_access_token
61
+ redirect_uri = callback_url.sub(/\?.*/, '')
62
+ log(:info, "Token exchange — site: #{client.site}, redirect_uri: #{redirect_uri}")
63
+ verifier = request.params['code']
64
+ client.auth_code.get_token(
65
+ verifier,
66
+ { redirect_uri: redirect_uri }.merge(token_params.to_hash(symbolize_keys: true)),
67
+ deep_symbolize(options.auth_token_params)
68
+ )
69
+ rescue ::OAuth2::Error => e
70
+ log(:error, "Token exchange FAILED: status=#{e.response&.status} body=#{e.response&.body}")
71
+ raise
72
+ end
73
+
74
+ private
75
+
76
+ GRAPHQL_URL = 'https://gql.waveapps.com/graphql/public'
77
+
78
+ USER_QUERY = <<~GRAPHQL
79
+ query {
80
+ user {
81
+ id
82
+ firstName
83
+ lastName
84
+ defaultEmail
85
+ }
86
+ businesses(page: 1, pageSize: 1) {
87
+ edges {
88
+ node {
89
+ id
90
+ name
91
+ }
92
+ }
93
+ }
94
+ }
95
+ GRAPHQL
96
+
97
+ def fetch_user_and_business_info
98
+ response = access_token.post(GRAPHQL_URL, {
99
+ body: { query: USER_QUERY }.to_json,
100
+ headers: { 'Content-Type' => 'application/json' }
101
+ })
102
+
103
+ data = JSON.parse(response.body)['data'] || {}
104
+ user = data['user'] || {}
105
+ business = data.dig('businesses', 'edges', 0, 'node') || {}
106
+
107
+ {
108
+ 'user_id' => user['id'],
109
+ 'email' => user['defaultEmail'],
110
+ 'first_name' => user['firstName'],
111
+ 'last_name' => user['lastName'],
112
+ 'name' => [user['firstName'], user['lastName']].compact.join(' '),
113
+ 'business_id' => business['id'],
114
+ 'business_name' => business['name']
115
+ }
116
+ rescue StandardError => e
117
+ log(:warn, "Failed to fetch user/business info: #{e.message}")
118
+ { 'user_id' => nil }
119
+ end
120
+
121
+ def log(level, message)
122
+ return unless defined?(OmniAuth.logger) && OmniAuth.logger
123
+
124
+ OmniAuth.logger.send(level, "[WaveOauth2] #{message}")
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'json'
5
+
6
+ module OmniAuth
7
+ module WaveOauth2
8
+ # Client for managing Wave OAuth2 tokens outside the OmniAuth flow.
9
+ #
10
+ # Wave access tokens expire after ~2 hours. Use this client to refresh
11
+ # tokens in background jobs or API services.
12
+ #
13
+ # @example Basic usage
14
+ # client = OmniAuth::WaveOauth2::TokenClient.new(
15
+ # client_id: ENV['WAVE_CLIENT_ID'],
16
+ # client_secret: ENV['WAVE_CLIENT_SECRET']
17
+ # )
18
+ #
19
+ # result = client.refresh_token(account.refresh_token)
20
+ # if result.success?
21
+ # account.update!(
22
+ # access_token: result.access_token,
23
+ # refresh_token: result.refresh_token,
24
+ # token_expires_at: Time.at(result.expires_at)
25
+ # )
26
+ # end
27
+ #
28
+ class TokenClient
29
+ # Result object for token operations
30
+ class TokenResult
31
+ attr_reader :access_token, :refresh_token, :expires_at, :expires_in, :error, :raw_response
32
+
33
+ def initialize(success:, access_token: nil, refresh_token: nil, expires_at: nil, expires_in: nil,
34
+ error: nil, raw_response: nil)
35
+ @success = success
36
+ @access_token = access_token
37
+ @refresh_token = refresh_token
38
+ @expires_at = expires_at
39
+ @expires_in = expires_in
40
+ @error = error
41
+ @raw_response = raw_response
42
+ end
43
+
44
+ def success?
45
+ @success
46
+ end
47
+
48
+ def failure?
49
+ !@success
50
+ end
51
+ end
52
+
53
+ TOKEN_URL = 'https://api.waveapps.com/oauth2/token/'
54
+
55
+ attr_reader :client_id, :client_secret
56
+
57
+ # Initialize a new TokenClient
58
+ #
59
+ # @param client_id [String] Your Wave App Client ID
60
+ # @param client_secret [String] Your Wave App Client Secret
61
+ def initialize(client_id:, client_secret:)
62
+ @client_id = client_id
63
+ @client_secret = client_secret
64
+ end
65
+
66
+ # Refresh an access token using a refresh token
67
+ #
68
+ # @param refresh_token [String] The refresh token to use
69
+ # @return [TokenResult] Result object with new tokens or error
70
+ def refresh_token(refresh_token)
71
+ return TokenResult.new(success: false, error: 'Refresh token is required') if refresh_token.nil? || refresh_token.empty?
72
+
73
+ response = make_refresh_request(refresh_token)
74
+
75
+ if response.success?
76
+ parse_success_response(response)
77
+ else
78
+ parse_error_response(response)
79
+ end
80
+ rescue Faraday::Error => e
81
+ TokenResult.new(success: false, error: "Network error: #{e.message}")
82
+ rescue JSON::ParserError => e
83
+ TokenResult.new(success: false, error: "Invalid JSON response: #{e.message}")
84
+ rescue StandardError => e
85
+ TokenResult.new(success: false, error: "Unexpected error: #{e.message}")
86
+ end
87
+
88
+ # Check if a token is expired or about to expire
89
+ #
90
+ # @param expires_at [Time, Integer] Token expiration time
91
+ # @param buffer_seconds [Integer] Buffer before expiration (default: 300 = 5 minutes)
92
+ # @return [Boolean] True if token is expired or will expire within buffer
93
+ def token_expired?(expires_at, buffer_seconds: 300)
94
+ return true if expires_at.nil?
95
+
96
+ expires_at_time = expires_at.is_a?(Integer) ? Time.at(expires_at) : expires_at
97
+ Time.now >= (expires_at_time - buffer_seconds)
98
+ end
99
+
100
+ private
101
+
102
+ def make_refresh_request(refresh_token)
103
+ Faraday.post(TOKEN_URL) do |req|
104
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
105
+ req.headers['Accept'] = 'application/json'
106
+ req.body = URI.encode_www_form(
107
+ grant_type: 'refresh_token',
108
+ client_id: client_id,
109
+ client_secret: client_secret,
110
+ refresh_token: refresh_token
111
+ )
112
+ end
113
+ end
114
+
115
+ def parse_success_response(response)
116
+ data = JSON.parse(response.body)
117
+
118
+ expires_in = data['expires_in']&.to_i
119
+ expires_at = expires_in ? Time.now.to_i + expires_in : nil
120
+
121
+ TokenResult.new(
122
+ success: true,
123
+ access_token: data['access_token'],
124
+ refresh_token: data['refresh_token'],
125
+ expires_in: expires_in,
126
+ expires_at: expires_at,
127
+ raw_response: data
128
+ )
129
+ end
130
+
131
+ def parse_error_response(response)
132
+ error_data = begin
133
+ JSON.parse(response.body)
134
+ rescue JSON::ParserError
135
+ { 'message' => response.body }
136
+ end
137
+
138
+ error_message = error_data['error_description'] || error_data['error'] || "HTTP #{response.status}"
139
+
140
+ TokenResult.new(
141
+ success: false,
142
+ error: error_message,
143
+ raw_response: error_data
144
+ )
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAuth
4
+ module WaveOauth2
5
+ VERSION = '1.0.0'
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'omniauth-oauth2'
4
+ require 'omniauth/wave_oauth2/version'
5
+ require 'omniauth/wave_oauth2/token_client'
6
+ require 'omniauth/strategies/wave_oauth2'
metadata ADDED
@@ -0,0 +1,198 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: omniauth-wave-oauth2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - dan1d
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: faraday
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '1.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '3.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: omniauth-oauth2
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.8'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.8'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bundler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '2.0'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
60
+ - !ruby/object:Gem::Dependency
61
+ name: rack-test
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '2.1'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '2.1'
74
+ - !ruby/object:Gem::Dependency
75
+ name: rake
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '13.0'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '13.0'
88
+ - !ruby/object:Gem::Dependency
89
+ name: rspec
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '3.12'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3.12'
102
+ - !ruby/object:Gem::Dependency
103
+ name: rubocop
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1.75'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.75'
116
+ - !ruby/object:Gem::Dependency
117
+ name: rubocop-rspec
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '3.5'
123
+ type: :development
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '3.5'
130
+ - !ruby/object:Gem::Dependency
131
+ name: simplecov
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '0.22'
137
+ type: :development
138
+ prerelease: false
139
+ version_requirements: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: '0.22'
144
+ - !ruby/object:Gem::Dependency
145
+ name: webmock
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: '3.18'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - "~>"
156
+ - !ruby/object:Gem::Version
157
+ version: '3.18'
158
+ description: An OmniAuth strategy for authenticating with Wave accounting (by H&R
159
+ Block) using OAuth 2.0. Fetches user and business info via Wave's GraphQL API.
160
+ email:
161
+ - dan1d@users.noreply.github.com
162
+ executables: []
163
+ extensions: []
164
+ extra_rdoc_files: []
165
+ files:
166
+ - CHANGELOG.md
167
+ - LICENSE.txt
168
+ - README.md
169
+ - lib/omniauth-wave-oauth2.rb
170
+ - lib/omniauth/strategies/wave_oauth2.rb
171
+ - lib/omniauth/wave_oauth2/token_client.rb
172
+ - lib/omniauth/wave_oauth2/version.rb
173
+ homepage: https://github.com/dan1d/omniauth-wave-oauth2
174
+ licenses:
175
+ - MIT
176
+ metadata:
177
+ homepage_uri: https://github.com/dan1d/omniauth-wave-oauth2
178
+ source_code_uri: https://github.com/dan1d/omniauth-wave-oauth2
179
+ changelog_uri: https://github.com/dan1d/omniauth-wave-oauth2/blob/main/CHANGELOG.md
180
+ rubygems_mfa_required: 'true'
181
+ rdoc_options: []
182
+ require_paths:
183
+ - lib
184
+ required_ruby_version: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: 3.0.0
189
+ required_rubygems_version: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ requirements: []
195
+ rubygems_version: 3.6.9
196
+ specification_version: 4
197
+ summary: OmniAuth OAuth2 strategy for Wave (by H&R Block)
198
+ test_files: []