supabase-auth 0.1.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/README.md +233 -0
- data/lib/supabase/auth/admin_api.rb +123 -0
- data/lib/supabase/auth/api.rb +115 -0
- data/lib/supabase/auth/client.rb +1209 -0
- data/lib/supabase/auth/constants.rb +32 -0
- data/lib/supabase/auth/errors.rb +206 -0
- data/lib/supabase/auth/helpers.rb +223 -0
- data/lib/supabase/auth/memory_storage.rb +25 -0
- data/lib/supabase/auth/storage.rb +19 -0
- data/lib/supabase/auth/timer.rb +40 -0
- data/lib/supabase/auth/types.rb +435 -0
- data/lib/supabase/auth/version.rb +7 -0
- data/lib/supabase/auth.rb +18 -0
- data/lib/supabase-auth.rb +3 -0
- metadata +159 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4f59194c543746f17fee64c695a67376d979b29d84cef81927f2e0ee28b2c3c0
|
|
4
|
+
data.tar.gz: 0645f3fa4daca0abd64124e61735f2b95e04fca4b2a9395e519882fd15b50150
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 95dec921b37d44b44a94f36dcc9ab5e6466a2f9fc774657f2b526c003cba2acf867ed2103093ff5b2bd4b01064f8db271b5298f07b6dab5621cc83e511679d67
|
|
7
|
+
data.tar.gz: 10a3996b51836f65cbabc33514ae57c1a11f997b35885144bd818a208f8625e65138231fe335b9b5cf65c89cf7872702927600b4abd779c571ed9bb30dd392a5
|
data/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# supabase-auth
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/supabase-auth)
|
|
4
|
+
[](https://github.com/supabase/supabase-rb/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.ruby-lang.org)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Ruby client for [Supabase Auth](https://supabase.com/docs/guides/auth) (GoTrue API).
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- Email/password, phone, and anonymous sign-in
|
|
13
|
+
- OAuth and SSO provider support
|
|
14
|
+
- Magic link and OTP verification
|
|
15
|
+
- PKCE authentication flow
|
|
16
|
+
- MFA (TOTP and phone)
|
|
17
|
+
- JWT verification with JWKS support
|
|
18
|
+
- Session management with auto-refresh
|
|
19
|
+
- Admin API for user management
|
|
20
|
+
- Auth state change subscriptions
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Add to your Gemfile:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
gem "supabase-auth"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then run:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "supabase/auth"
|
|
40
|
+
|
|
41
|
+
client = Supabase::Auth::Client.new(
|
|
42
|
+
url: "https://your-project.supabase.co/auth/v1",
|
|
43
|
+
headers: { "apiKey" => "your-anon-key" }
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Sign up
|
|
47
|
+
response = client.sign_up(email: "user@example.com", password: "secure-password")
|
|
48
|
+
|
|
49
|
+
# Sign in
|
|
50
|
+
response = client.sign_in_with_password(email: "user@example.com", password: "secure-password")
|
|
51
|
+
|
|
52
|
+
# Get session and user
|
|
53
|
+
session = client.get_session
|
|
54
|
+
user = client.get_user
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
### Authentication
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# Email/password
|
|
63
|
+
client.sign_in_with_password(email: "user@example.com", password: "password")
|
|
64
|
+
|
|
65
|
+
# Magic link (OTP)
|
|
66
|
+
client.sign_in_with_otp(email: "user@example.com")
|
|
67
|
+
|
|
68
|
+
# Phone OTP
|
|
69
|
+
client.sign_in_with_otp(phone: "+1234567890")
|
|
70
|
+
|
|
71
|
+
# OAuth
|
|
72
|
+
response = client.sign_in_with_oauth(provider: "google")
|
|
73
|
+
|
|
74
|
+
# SSO
|
|
75
|
+
response = client.sign_in_with_sso(domain: "company.com")
|
|
76
|
+
|
|
77
|
+
# ID token (e.g. from Google Sign-In)
|
|
78
|
+
response = client.sign_in_with_id_token(provider: "google", token: "id-token")
|
|
79
|
+
|
|
80
|
+
# Anonymous
|
|
81
|
+
response = client.sign_in_anonymously
|
|
82
|
+
|
|
83
|
+
# Sign out
|
|
84
|
+
client.sign_out
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Session Management
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
# Set session from existing tokens
|
|
91
|
+
client.set_session("access_token", "refresh_token")
|
|
92
|
+
|
|
93
|
+
# Refresh session
|
|
94
|
+
client.refresh_session
|
|
95
|
+
|
|
96
|
+
# Exchange code for session (PKCE)
|
|
97
|
+
client.exchange_code_for_session(auth_code: "code")
|
|
98
|
+
|
|
99
|
+
# Listen for auth state changes
|
|
100
|
+
subscription = client.on_auth_state_change do |event, session|
|
|
101
|
+
puts "Auth event: #{event}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Unsubscribe
|
|
105
|
+
subscription.unsubscribe.call
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Auth events: `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`, `USER_UPDATED`, `MFA_CHALLENGE_VERIFIED`, `PASSWORD_RECOVERY`
|
|
109
|
+
|
|
110
|
+
### User Management
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
# Get current user
|
|
114
|
+
user = client.get_user
|
|
115
|
+
|
|
116
|
+
# Update user
|
|
117
|
+
client.update_user(data: { name: "Jane Doe" })
|
|
118
|
+
|
|
119
|
+
# Reset password
|
|
120
|
+
client.reset_password_for_email("user@example.com")
|
|
121
|
+
|
|
122
|
+
# Verify OTP
|
|
123
|
+
client.verify_otp(type: "email", email: "user@example.com", token: "123456")
|
|
124
|
+
|
|
125
|
+
# Identity linking
|
|
126
|
+
client.link_identity(provider: "github")
|
|
127
|
+
client.unlink_identity(identity)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### MFA (Multi-Factor Authentication)
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
# Enroll a TOTP factor
|
|
134
|
+
enrolled = client.mfa.enroll(factor_type: "totp")
|
|
135
|
+
|
|
136
|
+
# Challenge
|
|
137
|
+
challenge = client.mfa.challenge(factor_id: enrolled["id"])
|
|
138
|
+
|
|
139
|
+
# Verify
|
|
140
|
+
client.mfa.verify(factor_id: enrolled["id"], challenge_id: challenge.id, code: "123456")
|
|
141
|
+
|
|
142
|
+
# List factors
|
|
143
|
+
factors = client.mfa.list_factors
|
|
144
|
+
|
|
145
|
+
# Get assurance level
|
|
146
|
+
aal = client.mfa.get_authenticator_assurance_level
|
|
147
|
+
|
|
148
|
+
# Unenroll
|
|
149
|
+
client.mfa.unenroll(factor_id: enrolled["id"])
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### JWT Verification
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
# Verify and extract JWT claims (supports HS256, RS256, ES256, PS256+)
|
|
156
|
+
claims = client.get_claims(jwt: "eyJhbG...")
|
|
157
|
+
claims.claims # decoded payload
|
|
158
|
+
claims.headers # JWT headers
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Admin API
|
|
162
|
+
|
|
163
|
+
Requires a service role key.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
admin = Supabase::Auth::AdminApi.new(
|
|
167
|
+
url: "https://your-project.supabase.co/auth/v1",
|
|
168
|
+
headers: { "Authorization" => "Bearer #{service_role_key}", "apiKey" => service_role_key }
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# CRUD operations
|
|
172
|
+
user = admin.create_user(email: "new@example.com", password: "password")
|
|
173
|
+
users = admin.list_users(page: 1, per_page: 50)
|
|
174
|
+
user = admin.get_user_by_id("uuid")
|
|
175
|
+
admin.update_user_by_id("uuid", email: "updated@example.com")
|
|
176
|
+
admin.delete_user("uuid")
|
|
177
|
+
|
|
178
|
+
# Invite user
|
|
179
|
+
admin.invite_user_by_email("user@example.com")
|
|
180
|
+
|
|
181
|
+
# Generate links
|
|
182
|
+
admin.generate_link(type: "signup", email: "user@example.com", password: "password")
|
|
183
|
+
|
|
184
|
+
# Sign out a user
|
|
185
|
+
admin.sign_out("access_token")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Configuration Options
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
client = Supabase::Auth::Client.new(
|
|
192
|
+
url: "https://your-project.supabase.co/auth/v1",
|
|
193
|
+
headers: { "apiKey" => "your-anon-key" },
|
|
194
|
+
auto_refresh_token: true, # Auto-refresh expiring tokens (default: true)
|
|
195
|
+
persist_session: true, # Persist session to storage (default: true)
|
|
196
|
+
detect_session_in_url: true, # Detect OAuth callback in URL (default: true)
|
|
197
|
+
flow_type: "implicit", # "implicit" or "pkce" (default: "implicit")
|
|
198
|
+
storage: custom_storage # Custom storage backend (default: in-memory)
|
|
199
|
+
)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Development
|
|
203
|
+
|
|
204
|
+
### Prerequisites
|
|
205
|
+
|
|
206
|
+
- Ruby >= 3.0
|
|
207
|
+
- Docker & Docker Compose (for integration tests)
|
|
208
|
+
|
|
209
|
+
### Setup
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
bundle install
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Running Tests
|
|
216
|
+
|
|
217
|
+
Start the GoTrue infrastructure:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
docker compose -f infra/docker-compose.yml up -d
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Run the test suite:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
bundle exec rspec
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Coverage reports are generated automatically via SimpleCov. After running tests, open `coverage/index.html`.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Supabase
|
|
6
|
+
module Auth
|
|
7
|
+
# Admin API for managing users with a service role key.
|
|
8
|
+
# Provides CRUD operations on users, link generation, and MFA management.
|
|
9
|
+
class AdminApi < Api
|
|
10
|
+
# @param url [String] The GoTrue API base URL
|
|
11
|
+
# @param headers [Hash] Headers including Authorization bearer token
|
|
12
|
+
# @param http_client [Faraday::Connection, nil] Optional custom Faraday client
|
|
13
|
+
def initialize(url:, headers: {}, http_client: nil)
|
|
14
|
+
super(url: url, headers: headers, http_client: http_client)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Creates a new user via the admin API.
|
|
18
|
+
# @param attributes [Hash] user attributes (email, password, user_metadata, app_metadata, etc.)
|
|
19
|
+
# @return [Types::UserResponse]
|
|
20
|
+
def create_user(attributes)
|
|
21
|
+
data = post("admin/users", body: attributes)
|
|
22
|
+
Helpers.parse_user_response(data)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Lists all users.
|
|
26
|
+
# @param page [Integer, nil] page number
|
|
27
|
+
# @param per_page [Integer, nil] users per page
|
|
28
|
+
# @return [Array<Types::User>]
|
|
29
|
+
def list_users(page: nil, per_page: nil)
|
|
30
|
+
params = {}
|
|
31
|
+
params[:page] = page if page
|
|
32
|
+
params[:per_page] = per_page if per_page
|
|
33
|
+
data = get("admin/users", params: params)
|
|
34
|
+
users = data["users"] || []
|
|
35
|
+
users.map { |u| Types::User.from_hash(u) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Gets a user by their ID.
|
|
39
|
+
# @param uid [String] user UUID
|
|
40
|
+
# @return [Types::UserResponse]
|
|
41
|
+
# @raise [ArgumentError] if uid is not a valid UUID
|
|
42
|
+
def get_user_by_id(uid)
|
|
43
|
+
_validate_uuid(uid)
|
|
44
|
+
data = get("admin/users/#{uid}")
|
|
45
|
+
Helpers.parse_user_response(data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Updates a user by their ID.
|
|
49
|
+
# @param uid [String] user UUID
|
|
50
|
+
# @param attributes [Hash] attributes to update
|
|
51
|
+
# @return [Types::UserResponse]
|
|
52
|
+
# @raise [ArgumentError] if uid is not a valid UUID
|
|
53
|
+
def update_user_by_id(uid, attributes)
|
|
54
|
+
_validate_uuid(uid)
|
|
55
|
+
data = put("admin/users/#{uid}", body: attributes)
|
|
56
|
+
Helpers.parse_user_response(data)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Deletes a user by their ID.
|
|
60
|
+
# @param uid [String] user UUID
|
|
61
|
+
# @param should_soft_delete [Boolean] soft delete instead of hard delete
|
|
62
|
+
# @raise [ArgumentError] if uid is not a valid UUID
|
|
63
|
+
def delete_user(uid, should_soft_delete: false)
|
|
64
|
+
_validate_uuid(uid)
|
|
65
|
+
_request("DELETE", "admin/users/#{uid}", body: { should_soft_delete: should_soft_delete })
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Generates email links and OTPs.
|
|
69
|
+
def generate_link(params)
|
|
70
|
+
options = params[:options] || params["options"] || {}
|
|
71
|
+
body = {
|
|
72
|
+
type: params[:type] || params["type"],
|
|
73
|
+
email: params[:email] || params["email"],
|
|
74
|
+
password: params[:password] || params["password"],
|
|
75
|
+
new_email: params[:new_email] || params["new_email"],
|
|
76
|
+
data: options[:data] || options["data"]
|
|
77
|
+
}
|
|
78
|
+
redirect_to = options[:redirect_to] || options["redirect_to"]
|
|
79
|
+
query = {}
|
|
80
|
+
query["redirect_to"] = redirect_to if redirect_to
|
|
81
|
+
data = post("admin/generate_link", body: body, params: query)
|
|
82
|
+
Helpers.parse_link_response(data)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Invites a user by email.
|
|
86
|
+
def invite_user_by_email(email, options = {})
|
|
87
|
+
body = { email: email, data: options[:data] || options["data"] }
|
|
88
|
+
redirect_to = options[:redirect_to] || options["redirect_to"]
|
|
89
|
+
query = {}
|
|
90
|
+
query["redirect_to"] = redirect_to if redirect_to
|
|
91
|
+
data = post("invite", body: body, params: query)
|
|
92
|
+
Helpers.parse_user_response(data)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Signs out a user by revoking their session via the admin API.
|
|
96
|
+
def sign_out(access_token, scope = "global")
|
|
97
|
+
_request("POST", "logout", jwt: access_token, params: { "scope" => scope }, no_resolve_json: true)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Lists MFA factors for a user (admin).
|
|
101
|
+
# @param params [Hash] :user_id (required)
|
|
102
|
+
# @return [Types::AuthMFAAdminListFactorsResponse]
|
|
103
|
+
def _list_factors(params)
|
|
104
|
+
user_id = params[:user_id] || params["user_id"]
|
|
105
|
+
_validate_uuid(user_id)
|
|
106
|
+
data = get("admin/users/#{user_id}/factors")
|
|
107
|
+
Types::AuthMFAAdminListFactorsResponse.from_hash(data)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Deletes an MFA factor for a user (admin).
|
|
111
|
+
# @param params [Hash] :user_id and :id (both required)
|
|
112
|
+
# @return [Types::AuthMFAAdminDeleteFactorResponse]
|
|
113
|
+
def _delete_factor(params)
|
|
114
|
+
user_id = params[:user_id] || params["user_id"]
|
|
115
|
+
factor_id = params[:id] || params["id"]
|
|
116
|
+
_validate_uuid(user_id)
|
|
117
|
+
_validate_uuid(factor_id)
|
|
118
|
+
data = delete("admin/users/#{user_id}/factors/#{factor_id}")
|
|
119
|
+
Types::AuthMFAAdminDeleteFactorResponse.from_hash(data)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Supabase
|
|
7
|
+
module Auth
|
|
8
|
+
class Api
|
|
9
|
+
CONTENT_TYPE = "application/json;charset=UTF-8"
|
|
10
|
+
UUID_REGEX = /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i
|
|
11
|
+
|
|
12
|
+
attr_reader :url, :headers
|
|
13
|
+
|
|
14
|
+
# @param url [String] The GoTrue API base URL
|
|
15
|
+
# @param headers [Hash] Default headers to include on every request (e.g., apikey)
|
|
16
|
+
# @param http_client [Faraday::Connection, nil] Optional custom Faraday client
|
|
17
|
+
def initialize(url:, headers: {}, http_client: nil)
|
|
18
|
+
@url = url
|
|
19
|
+
@headers = headers
|
|
20
|
+
@http_client = http_client
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Central HTTP dispatch method. Builds URL, merges headers (including API version
|
|
24
|
+
# and Authorization), handles redirect_to as query param, parses JSON, applies
|
|
25
|
+
# optional transform, and maps errors via Helpers.handle_exception.
|
|
26
|
+
#
|
|
27
|
+
# @param method [String, Symbol] HTTP method (GET, POST, PUT, DELETE)
|
|
28
|
+
# @param path [String] Request path (relative to base URL)
|
|
29
|
+
# @param jwt [String, nil] Bearer token for Authorization header
|
|
30
|
+
# @param body [Hash, nil] Request body (serialized to JSON)
|
|
31
|
+
# @param params [Hash] Query parameters
|
|
32
|
+
# @param headers [Hash] Additional headers for this request
|
|
33
|
+
# @param redirect_to [String, nil] If present, added as redirect_to query param
|
|
34
|
+
# @param xform [Proc, nil] Optional transform function applied to parsed response
|
|
35
|
+
# @param no_resolve_json [Boolean] If true, return raw Faraday::Response
|
|
36
|
+
# @return [Hash, Object] Parsed JSON response, transformed result, or raw response
|
|
37
|
+
def _request(method, path, jwt: nil, body: nil, params: {}, headers: {}, redirect_to: nil, xform: nil, no_resolve_json: false)
|
|
38
|
+
merged_headers = @headers.merge(headers)
|
|
39
|
+
merged_headers["Content-Type"] ||= CONTENT_TYPE
|
|
40
|
+
merged_headers[Constants::API_VERSION_HEADER_NAME] ||= Constants::API_VERSIONS.keys.last
|
|
41
|
+
merged_headers["Authorization"] = "Bearer #{jwt}" if jwt
|
|
42
|
+
|
|
43
|
+
query = params.dup
|
|
44
|
+
query["redirect_to"] = redirect_to if redirect_to
|
|
45
|
+
|
|
46
|
+
full_path = build_path(path)
|
|
47
|
+
json_body = body ? JSON.generate(body) : nil
|
|
48
|
+
|
|
49
|
+
response = connection.run_request(method.to_s.downcase.to_sym, full_path, json_body, merged_headers) do |req|
|
|
50
|
+
req.params.update(query) unless query.empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
result = no_resolve_json ? response : parse_response(response)
|
|
54
|
+
|
|
55
|
+
xform ? xform.call(result) : result
|
|
56
|
+
rescue Faraday::Error => e
|
|
57
|
+
raise Helpers.handle_exception(e)
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
raise Helpers.handle_exception(e)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param id [String] UUID to validate
|
|
63
|
+
# @raise [ArgumentError] if not a valid UUID format
|
|
64
|
+
def _validate_uuid(id)
|
|
65
|
+
unless id.is_a?(String) && id.match?(UUID_REGEX)
|
|
66
|
+
raise ArgumentError, "Invalid id, '#{id}' is not a valid uuid"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Convenience methods that delegate to _request
|
|
71
|
+
|
|
72
|
+
def get(path, headers: {}, params: {})
|
|
73
|
+
_request(:get, path, headers: headers, params: params)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def post(path, body: {}, headers: {}, params: {})
|
|
77
|
+
_request(:post, path, body: body, headers: headers, params: params)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def put(path, body: {}, headers: {}, params: {})
|
|
81
|
+
_request(:put, path, body: body, headers: headers, params: params)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete(path, headers: {}, params: {})
|
|
85
|
+
_request(:delete, path, headers: headers, params: params)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def connection
|
|
91
|
+
@connection ||= @http_client || build_connection
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_connection
|
|
95
|
+
Faraday.new(url: @url) do |f|
|
|
96
|
+
f.response :raise_error
|
|
97
|
+
f.adapter Faraday.default_adapter
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def build_path(path)
|
|
102
|
+
base_path = URI.parse(@url).path.chomp("/")
|
|
103
|
+
"#{base_path}/#{path.sub(%r{^/}, '')}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def parse_response(response)
|
|
107
|
+
return {} if response.body.nil? || response.body.empty?
|
|
108
|
+
|
|
109
|
+
JSON.parse(response.body)
|
|
110
|
+
rescue JSON::ParserError
|
|
111
|
+
{}
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|