omniauth-clover-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 +7 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +258 -0
- data/lib/omniauth/clover_oauth2/version.rb +7 -0
- data/lib/omniauth/strategies/clover_oauth2.rb +230 -0
- data/lib/omniauth-clover-oauth2.rb +5 -0
- metadata +211 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 718583b3f5599a91cb7c93b32f01af9ac8a658e3e351b4c601b9e5319a1345a3
|
|
4
|
+
data.tar.gz: 152836810576c0efa3c17d4da5c56bfe6cda667f9a25c40e80fddd2ef1235081
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 536d7e5a16bc73d381686f44ea96eeac9bf1225d6830a1a42aab054aeaf49552fcb48790cccb7473d4eed00f33b9521a71d9aab2906aed10a98a747c8b29d78e
|
|
7
|
+
data.tar.gz: a6f5190005c3f2ec1e1cdec91991728bd2b3ba11fda0f67e121bf05016ff10703d00e0e1e14aef83fd2f704c80e09ed3abef7dfb0d4d9c8cb29d8f112a80313b
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2024-01-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release of omniauth-clover-oauth2
|
|
13
|
+
- OmniAuth OAuth2 strategy for Clover POS
|
|
14
|
+
- Support for sandbox and production environments
|
|
15
|
+
- Automatic endpoint switching based on environment
|
|
16
|
+
- Merchant and employee info fetching
|
|
17
|
+
- Comprehensive RSpec test suite
|
|
18
|
+
- GitHub Actions CI/CD workflow
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# OmniAuth Clover OAuth2 Strategy
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/omniauth-clover-oauth2)
|
|
4
|
+
[](https://github.com/dan1d/omniauth-clover-oauth2/actions/workflows/ci.yml)
|
|
5
|
+
|
|
6
|
+
An OmniAuth strategy for authenticating with [Clover POS](https://www.clover.com/) using OAuth 2.0.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'omniauth-clover-oauth2'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then execute:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
$ bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install it yourself:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
$ gem install omniauth-clover-oauth2
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Clover Developer Setup
|
|
29
|
+
|
|
30
|
+
1. Go to the [Clover Developer Dashboard](https://sandbox.dev.clover.com/developer-home/create-account)
|
|
31
|
+
2. Create a new app or select an existing one
|
|
32
|
+
3. Navigate to **App Settings** > **REST Configuration**
|
|
33
|
+
4. Note your **App ID** (Client ID) and **App Secret** (Client Secret)
|
|
34
|
+
5. Configure your **Site URL** and **Callback URL** (e.g., `https://yourapp.com/auth/clover_oauth2/callback`)
|
|
35
|
+
|
|
36
|
+
For more details, read the [Clover OAuth 2.0 documentation](https://docs.clover.com/docs/oauth-flows-in-clover).
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
### Standalone OmniAuth
|
|
41
|
+
|
|
42
|
+
Add the middleware to your application in `config/initializers/omniauth.rb`:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
|
46
|
+
provider :clover_oauth2,
|
|
47
|
+
ENV['CLOVER_CLIENT_ID'],
|
|
48
|
+
ENV['CLOVER_CLIENT_SECRET'],
|
|
49
|
+
sandbox: Rails.env.development?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Required for OmniAuth 2.0+
|
|
53
|
+
OmniAuth.config.allowed_request_methods = %i[get post]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You can now access the OmniAuth Clover OAuth2 URL at `/auth/clover_oauth2`.
|
|
57
|
+
|
|
58
|
+
### With Devise
|
|
59
|
+
|
|
60
|
+
Add the provider to your Devise configuration in `config/initializers/devise.rb`:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
config.omniauth :clover_oauth2,
|
|
64
|
+
ENV['CLOVER_CLIENT_ID'],
|
|
65
|
+
ENV['CLOVER_CLIENT_SECRET'],
|
|
66
|
+
sandbox: !Rails.env.production?
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Do not** create a separate `config/initializers/omniauth.rb` file when using Devise, as it will conflict.
|
|
70
|
+
|
|
71
|
+
Add to your routes in `config/routes.rb`:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Make your User model omniauthable in `app/models/user.rb`:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
devise :omniauthable, omniauth_providers: [:clover_oauth2]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Create the callbacks controller at `app/controllers/users/omniauth_callbacks_controller.rb`:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
87
|
+
def clover_oauth2
|
|
88
|
+
@user = User.from_omniauth(request.env['omniauth.auth'])
|
|
89
|
+
|
|
90
|
+
if @user.persisted?
|
|
91
|
+
flash[:notice] = I18n.t('devise.omniauth_callbacks.success', kind: 'Clover')
|
|
92
|
+
sign_in_and_redirect @user, event: :authentication
|
|
93
|
+
else
|
|
94
|
+
session['devise.clover_data'] = request.env['omniauth.auth'].except('extra')
|
|
95
|
+
redirect_to new_user_registration_url, alert: @user.errors.full_messages.join("\n")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def failure
|
|
100
|
+
redirect_to root_path, alert: "Authentication failed: #{failure_message}"
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Add the `from_omniauth` method to your User model:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
def self.from_omniauth(auth)
|
|
109
|
+
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
|
|
110
|
+
user.email = auth.info.email
|
|
111
|
+
user.password = Devise.friendly_token[0, 20]
|
|
112
|
+
user.name = auth.info.name
|
|
113
|
+
# Add any other fields you need
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For your views, create a login link:
|
|
119
|
+
|
|
120
|
+
```erb
|
|
121
|
+
<%= link_to "Sign in with Clover", user_clover_oauth2_omniauth_authorize_path, method: :post %>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration Options
|
|
125
|
+
|
|
126
|
+
| Option | Default | Description |
|
|
127
|
+
|--------|---------|-------------|
|
|
128
|
+
| `sandbox` | `true` | Use sandbox environment. Set to `false` for production. |
|
|
129
|
+
| `provider_ignores_state` | `true` | Clover doesn't properly return the OAuth state parameter. |
|
|
130
|
+
|
|
131
|
+
### Example with all options:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
provider :clover_oauth2,
|
|
135
|
+
ENV['CLOVER_CLIENT_ID'],
|
|
136
|
+
ENV['CLOVER_CLIENT_SECRET'],
|
|
137
|
+
sandbox: false # Use production environment
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Auth Hash
|
|
141
|
+
|
|
142
|
+
Here's an example of the authentication hash available in the callback by accessing `request.env['omniauth.auth']`:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
{
|
|
146
|
+
"provider" => "clover_oauth2",
|
|
147
|
+
"uid" => "ABCD1234567890",
|
|
148
|
+
"info" => {
|
|
149
|
+
"email" => "employee@example.com",
|
|
150
|
+
"first_name" => "John",
|
|
151
|
+
"last_name" => "Doe",
|
|
152
|
+
"name" => "John Doe",
|
|
153
|
+
"merchant_id" => "ABCD1234567890",
|
|
154
|
+
"employee_id" => "EFGH0987654321"
|
|
155
|
+
},
|
|
156
|
+
"credentials" => {
|
|
157
|
+
"token" => "ACCESS_TOKEN",
|
|
158
|
+
"refresh_token" => "REFRESH_TOKEN",
|
|
159
|
+
"expires_at" => 1704067200,
|
|
160
|
+
"expires" => true
|
|
161
|
+
},
|
|
162
|
+
"extra" => {
|
|
163
|
+
"merchant_id" => "ABCD1234567890",
|
|
164
|
+
"employee_id" => "EFGH0987654321",
|
|
165
|
+
"business_name" => "Acme Restaurant",
|
|
166
|
+
"raw_info" => {
|
|
167
|
+
"merchant" => {
|
|
168
|
+
"id" => "ABCD1234567890",
|
|
169
|
+
"name" => "Acme Restaurant",
|
|
170
|
+
"address" => { ... }
|
|
171
|
+
},
|
|
172
|
+
"employee" => {
|
|
173
|
+
"id" => "EFGH0987654321",
|
|
174
|
+
"name" => "John Doe",
|
|
175
|
+
"email" => "employee@example.com",
|
|
176
|
+
"role" => "ADMIN"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Sandbox vs Production
|
|
184
|
+
|
|
185
|
+
Clover uses different URLs for sandbox and production environments:
|
|
186
|
+
|
|
187
|
+
| Environment | Authorize URL | API URL |
|
|
188
|
+
|-------------|---------------|---------|
|
|
189
|
+
| Sandbox | `https://sandbox.dev.clover.com` | `https://apisandbox.dev.clover.com` |
|
|
190
|
+
| Production | `https://www.clover.com` | `https://api.clover.com` |
|
|
191
|
+
|
|
192
|
+
The gem automatically handles this based on the `sandbox` option.
|
|
193
|
+
|
|
194
|
+
**Important:** You need separate Clover apps for sandbox and production. Make sure to use the correct credentials for each environment.
|
|
195
|
+
|
|
196
|
+
## Clover OAuth2 Specifics
|
|
197
|
+
|
|
198
|
+
This gem handles several Clover-specific OAuth2 behaviors:
|
|
199
|
+
|
|
200
|
+
1. **JSON Token Exchange**: Clover requires `Content-Type: application/json` for the token exchange endpoint (not the standard `application/x-www-form-urlencoded`).
|
|
201
|
+
|
|
202
|
+
2. **Split Endpoints**: Clover uses different domains for authorization (`www.clover.com`) and token/API endpoints (`api.clover.com`).
|
|
203
|
+
|
|
204
|
+
3. **State Parameter**: Clover doesn't properly return the OAuth state parameter, so this gem sets `provider_ignores_state` to `true` by default.
|
|
205
|
+
|
|
206
|
+
4. **Merchant/Employee Context**: The OAuth callback includes `merchant_id` and `employee_id` query parameters, which are used to fetch additional user information.
|
|
207
|
+
|
|
208
|
+
## Storing Tokens
|
|
209
|
+
|
|
210
|
+
You'll likely want to store the access token and refresh token to make API calls later:
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
def self.from_omniauth(auth)
|
|
214
|
+
user = where(provider: auth.provider, uid: auth.uid).first_or_create do |u|
|
|
215
|
+
u.email = auth.info.email
|
|
216
|
+
u.password = Devise.friendly_token[0, 20]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Update tokens on each login
|
|
220
|
+
user.update(
|
|
221
|
+
clover_access_token: auth.credentials.token,
|
|
222
|
+
clover_refresh_token: auth.credentials.refresh_token,
|
|
223
|
+
clover_token_expires_at: Time.at(auth.credentials.expires_at),
|
|
224
|
+
clover_merchant_id: auth.info.merchant_id,
|
|
225
|
+
clover_employee_id: auth.info.employee_id
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
user
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Token Refresh
|
|
233
|
+
|
|
234
|
+
Clover access tokens expire (typically after 1 year, but check your app settings). To refresh tokens, you'll need to implement token refresh logic using the Clover API directly.
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/dan1d/omniauth-clover-oauth2.
|
|
239
|
+
|
|
240
|
+
1. Fork it
|
|
241
|
+
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
|
242
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
243
|
+
4. Push to the branch (`git push origin feature/my-new-feature`)
|
|
244
|
+
5. Create a new Pull Request
|
|
245
|
+
|
|
246
|
+
## Development
|
|
247
|
+
|
|
248
|
+
After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
bundle install
|
|
252
|
+
bundle exec rspec
|
|
253
|
+
bundle exec rubocop
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'omniauth-oauth2'
|
|
4
|
+
require 'faraday'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'ostruct'
|
|
7
|
+
|
|
8
|
+
module OmniAuth
|
|
9
|
+
module Strategies
|
|
10
|
+
# OmniAuth strategy for Clover POS OAuth 2.0
|
|
11
|
+
#
|
|
12
|
+
# Clover OAuth2 has several non-standard behaviors:
|
|
13
|
+
# - Requires JSON Content-Type for token exchange (not form-urlencoded)
|
|
14
|
+
# - Uses different domains for authorize vs token/API endpoints
|
|
15
|
+
# - Doesn't properly return the state parameter
|
|
16
|
+
# - Includes merchant_id and employee_id in callback params
|
|
17
|
+
#
|
|
18
|
+
# @example Basic usage
|
|
19
|
+
# provider :clover_oauth2, ENV['CLOVER_CLIENT_ID'], ENV['CLOVER_CLIENT_SECRET']
|
|
20
|
+
#
|
|
21
|
+
# @example With sandbox disabled (production)
|
|
22
|
+
# provider :clover_oauth2, ENV['CLOVER_CLIENT_ID'], ENV['CLOVER_CLIENT_SECRET'],
|
|
23
|
+
# sandbox: false
|
|
24
|
+
#
|
|
25
|
+
class CloverOauth2 < OmniAuth::Strategies::OAuth2
|
|
26
|
+
option :name, 'clover_oauth2'
|
|
27
|
+
|
|
28
|
+
# Sandbox mode is enabled by default for safety
|
|
29
|
+
option :sandbox, true
|
|
30
|
+
|
|
31
|
+
# Clover doesn't properly return the OAuth state parameter
|
|
32
|
+
option :provider_ignores_state, true
|
|
33
|
+
|
|
34
|
+
# Default client options - these get merged with environment-specific URLs
|
|
35
|
+
option :client_options, {
|
|
36
|
+
authorize_url: '/oauth/v2/authorize'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# UID is the merchant_id from Clover
|
|
40
|
+
uid { merchant_id }
|
|
41
|
+
|
|
42
|
+
info do
|
|
43
|
+
{
|
|
44
|
+
email: employee_info['email'],
|
|
45
|
+
first_name: extract_first_name,
|
|
46
|
+
last_name: extract_last_name,
|
|
47
|
+
name: employee_info['name'] || merchant_info['name'],
|
|
48
|
+
merchant_id: merchant_id,
|
|
49
|
+
employee_id: employee_id
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
extra do
|
|
54
|
+
{
|
|
55
|
+
merchant_id: merchant_id,
|
|
56
|
+
employee_id: employee_id,
|
|
57
|
+
business_name: merchant_info['name'],
|
|
58
|
+
raw_info: {
|
|
59
|
+
merchant: merchant_info,
|
|
60
|
+
employee: employee_info
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
credentials do
|
|
66
|
+
hash = { 'token' => access_token.token }
|
|
67
|
+
hash['refresh_token'] = access_token.refresh_token if access_token.refresh_token
|
|
68
|
+
hash['expires_at'] = access_token.expires_at if access_token.expires_at
|
|
69
|
+
hash['expires'] = access_token.expires?
|
|
70
|
+
hash
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Override the OAuth2 client to use the correct site URL based on sandbox mode
|
|
74
|
+
def client
|
|
75
|
+
::OAuth2::Client.new(
|
|
76
|
+
options.client_id,
|
|
77
|
+
options.client_secret,
|
|
78
|
+
deep_symbolize(options.client_options.merge(site: authorize_site_url))
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Merchant info fetched from Clover API
|
|
83
|
+
def merchant_info
|
|
84
|
+
@merchant_info ||= fetch_merchant_info
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Employee info fetched from Clover API
|
|
88
|
+
def employee_info
|
|
89
|
+
@employee_info ||= fetch_employee_info
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Merchant ID from callback params
|
|
93
|
+
def merchant_id
|
|
94
|
+
request.params['merchant_id']
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Employee ID from callback params
|
|
98
|
+
def employee_id
|
|
99
|
+
request.params['employee_id']
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
# Authorization site URL (where users are redirected to authorize)
|
|
105
|
+
def authorize_site_url
|
|
106
|
+
if sandbox?
|
|
107
|
+
'https://sandbox.dev.clover.com'
|
|
108
|
+
else
|
|
109
|
+
'https://www.clover.com'
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# API base URL (where token exchange and API calls happen)
|
|
114
|
+
def api_base_url
|
|
115
|
+
if sandbox?
|
|
116
|
+
'https://apisandbox.dev.clover.com'
|
|
117
|
+
else
|
|
118
|
+
'https://api.clover.com'
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def sandbox?
|
|
123
|
+
options.sandbox
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def extract_first_name
|
|
127
|
+
employee_info['name']&.split&.first
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def extract_last_name
|
|
131
|
+
name = employee_info['name']
|
|
132
|
+
return nil unless name
|
|
133
|
+
|
|
134
|
+
parts = name.split
|
|
135
|
+
parts.drop(1).join(' ') if parts.length > 1
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def fetch_merchant_info
|
|
139
|
+
return {} unless merchant_id && access_token
|
|
140
|
+
|
|
141
|
+
api_url = "#{api_base_url}/v3/merchants/#{merchant_id}"
|
|
142
|
+
response = make_api_request(api_url)
|
|
143
|
+
|
|
144
|
+
return {} unless response&.success?
|
|
145
|
+
|
|
146
|
+
JSON.parse(response.body)
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
log(:error, "Failed to fetch merchant info: #{e.message}")
|
|
149
|
+
{}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def fetch_employee_info
|
|
153
|
+
return {} unless merchant_id && employee_id && access_token
|
|
154
|
+
|
|
155
|
+
api_url = "#{api_base_url}/v3/merchants/#{merchant_id}/employees/#{employee_id}"
|
|
156
|
+
response = make_api_request(api_url)
|
|
157
|
+
|
|
158
|
+
return {} unless response&.success?
|
|
159
|
+
|
|
160
|
+
JSON.parse(response.body)
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
log(:error, "Failed to fetch employee info: #{e.message}")
|
|
163
|
+
{}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def make_api_request(url)
|
|
167
|
+
Faraday.get(url) do |req|
|
|
168
|
+
req.headers['Authorization'] = "Bearer #{access_token.token}"
|
|
169
|
+
req.headers['Accept'] = 'application/json'
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Override to handle Clover's non-standard token endpoint
|
|
174
|
+
# Clover requires JSON body for token exchange, not form-urlencoded
|
|
175
|
+
def build_access_token
|
|
176
|
+
code = request.params['code']
|
|
177
|
+
token_url = "#{api_base_url}/oauth/v2/token"
|
|
178
|
+
|
|
179
|
+
# Clover requires JSON body for token exchange
|
|
180
|
+
response = Faraday.post(token_url) do |req|
|
|
181
|
+
req.headers['Content-Type'] = 'application/json'
|
|
182
|
+
req.headers['Accept'] = 'application/json'
|
|
183
|
+
req.body = {
|
|
184
|
+
client_id: options.client_id,
|
|
185
|
+
client_secret: options.client_secret,
|
|
186
|
+
code: code
|
|
187
|
+
}.to_json
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
unless response.success?
|
|
191
|
+
error_body = begin
|
|
192
|
+
JSON.parse(response.body)
|
|
193
|
+
rescue JSON::ParserError
|
|
194
|
+
{ 'error' => response.body }
|
|
195
|
+
end
|
|
196
|
+
raise ::OAuth2::Error, build_error_response(error_body, response)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
token_data = JSON.parse(response.body)
|
|
200
|
+
|
|
201
|
+
# Build OAuth2 access token from response
|
|
202
|
+
::OAuth2::AccessToken.new(
|
|
203
|
+
client,
|
|
204
|
+
token_data['access_token'],
|
|
205
|
+
refresh_token: token_data['refresh_token'],
|
|
206
|
+
expires_in: token_data['access_token_expiration'],
|
|
207
|
+
token_type: 'Bearer'
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def build_error_response(error_body, response)
|
|
212
|
+
OpenStruct.new(
|
|
213
|
+
parsed: error_body,
|
|
214
|
+
body: response.body,
|
|
215
|
+
status: response.status
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def callback_url
|
|
220
|
+
full_host + callback_path
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def log(level, message)
|
|
224
|
+
return unless defined?(OmniAuth.logger) && OmniAuth.logger
|
|
225
|
+
|
|
226
|
+
OmniAuth.logger.send(level, "[CloverOauth2] #{message}")
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: omniauth-clover-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: ostruct
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0.6'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0.6'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: bundler
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '2.0'
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '2.0'
|
|
74
|
+
- !ruby/object:Gem::Dependency
|
|
75
|
+
name: rack-test
|
|
76
|
+
requirement: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '2.1'
|
|
81
|
+
type: :development
|
|
82
|
+
prerelease: false
|
|
83
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - "~>"
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '2.1'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: rake
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - "~>"
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '13.0'
|
|
95
|
+
type: :development
|
|
96
|
+
prerelease: false
|
|
97
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - "~>"
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '13.0'
|
|
102
|
+
- !ruby/object:Gem::Dependency
|
|
103
|
+
name: rspec
|
|
104
|
+
requirement: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '3.12'
|
|
109
|
+
type: :development
|
|
110
|
+
prerelease: false
|
|
111
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '3.12'
|
|
116
|
+
- !ruby/object:Gem::Dependency
|
|
117
|
+
name: rubocop
|
|
118
|
+
requirement: !ruby/object:Gem::Requirement
|
|
119
|
+
requirements:
|
|
120
|
+
- - "~>"
|
|
121
|
+
- !ruby/object:Gem::Version
|
|
122
|
+
version: '1.50'
|
|
123
|
+
type: :development
|
|
124
|
+
prerelease: false
|
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - "~>"
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '1.50'
|
|
130
|
+
- !ruby/object:Gem::Dependency
|
|
131
|
+
name: rubocop-rspec
|
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - "~>"
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '2.20'
|
|
137
|
+
type: :development
|
|
138
|
+
prerelease: false
|
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - "~>"
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '2.20'
|
|
144
|
+
- !ruby/object:Gem::Dependency
|
|
145
|
+
name: simplecov
|
|
146
|
+
requirement: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - "~>"
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: '0.22'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - "~>"
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '0.22'
|
|
158
|
+
- !ruby/object:Gem::Dependency
|
|
159
|
+
name: webmock
|
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
|
161
|
+
requirements:
|
|
162
|
+
- - "~>"
|
|
163
|
+
- !ruby/object:Gem::Version
|
|
164
|
+
version: '3.18'
|
|
165
|
+
type: :development
|
|
166
|
+
prerelease: false
|
|
167
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
168
|
+
requirements:
|
|
169
|
+
- - "~>"
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '3.18'
|
|
172
|
+
description: An OmniAuth strategy for authenticating with Clover POS using OAuth 2.0.
|
|
173
|
+
Supports both sandbox and production environments with automatic endpoint switching.
|
|
174
|
+
email:
|
|
175
|
+
- dan1d@users.noreply.github.com
|
|
176
|
+
executables: []
|
|
177
|
+
extensions: []
|
|
178
|
+
extra_rdoc_files: []
|
|
179
|
+
files:
|
|
180
|
+
- CHANGELOG.md
|
|
181
|
+
- LICENSE.txt
|
|
182
|
+
- README.md
|
|
183
|
+
- lib/omniauth-clover-oauth2.rb
|
|
184
|
+
- lib/omniauth/clover_oauth2/version.rb
|
|
185
|
+
- lib/omniauth/strategies/clover_oauth2.rb
|
|
186
|
+
homepage: https://github.com/dan1d/omniauth-clover-oauth2
|
|
187
|
+
licenses:
|
|
188
|
+
- MIT
|
|
189
|
+
metadata:
|
|
190
|
+
homepage_uri: https://github.com/dan1d/omniauth-clover-oauth2
|
|
191
|
+
source_code_uri: https://github.com/dan1d/omniauth-clover-oauth2
|
|
192
|
+
changelog_uri: https://github.com/dan1d/omniauth-clover-oauth2/blob/main/CHANGELOG.md
|
|
193
|
+
rubygems_mfa_required: 'true'
|
|
194
|
+
rdoc_options: []
|
|
195
|
+
require_paths:
|
|
196
|
+
- lib
|
|
197
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
198
|
+
requirements:
|
|
199
|
+
- - ">="
|
|
200
|
+
- !ruby/object:Gem::Version
|
|
201
|
+
version: 3.0.0
|
|
202
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
|
+
requirements:
|
|
204
|
+
- - ">="
|
|
205
|
+
- !ruby/object:Gem::Version
|
|
206
|
+
version: '0'
|
|
207
|
+
requirements: []
|
|
208
|
+
rubygems_version: 3.6.9
|
|
209
|
+
specification_version: 4
|
|
210
|
+
summary: OmniAuth OAuth2 strategy for Clover POS
|
|
211
|
+
test_files: []
|