supabase-auth 0.1.1 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b4f7cdcf0814c8ebe968554adfc4d142691b864b3813d58d0ad78c07d100bda
4
- data.tar.gz: 4051b6ea710ac669ed1c0a94c45c31d29e0e9469c3d580e8496d6f4144af3a06
3
+ metadata.gz: 447551cf93e708192099399ba8c3bd2d3256c35534b48766dd93e96d96409c2a
4
+ data.tar.gz: a6579187b098a1e57f94c2cf03a043ba6b513e90233d0d5bf6bc9b2fd09f345b
5
5
  SHA512:
6
- metadata.gz: cebe4dde4a57246f1b5d05541aca98286b118f6f56a161776b1d433cb97818bf45307ed629b902ef497cc4f8c78412784b7a289b94654df8bdc58babea7cec7b
7
- data.tar.gz: bfdd074a5f2a876308ea01240dc32a152e9955da9eae370b1c8d1cb4ddbaa07bd70814d58c88513f1edb3f48a3049b1be509b9d0d8810d97728344d5913b2e8d
6
+ metadata.gz: 79c44b3178e54c53a50f7ea63d1d8d3740f46a5ff41a52fdcd28a123022ca4e09ad1c9728dd6e86416ec2cff12b5c92a604e6657d82b93c3d9c92e4d2561cb18
7
+ data.tar.gz: 50662f169c281937d9f9aae60f5d3f4f7d82171ab48bfb818153360783a68fcd48a3da961a5b977ff749847579eab51814840d9b1751a92c4b98fbc64bf2796e
@@ -10,8 +10,11 @@ module Supabase
10
10
  # @param url [String] The GoTrue API base URL
11
11
  # @param headers [Hash] Headers including Authorization bearer token
12
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)
13
+ # @param verify [Boolean] Verify TLS certificates (default true)
14
+ # @param proxy [String, nil] HTTP proxy URL
15
+ # @param timeout [Numeric, nil] Per-request timeout in seconds
16
+ def initialize(url:, headers: {}, http_client: nil, verify: true, proxy: nil, timeout: nil)
17
+ super(url: url, headers: headers, http_client: http_client, verify: verify, proxy: proxy, timeout: timeout)
15
18
  end
16
19
 
17
20
  # Creates a new user via the admin API.
@@ -118,6 +121,94 @@ module Supabase
118
121
  data = delete("admin/users/#{user_id}/factors/#{factor_id}")
119
122
  Types::AuthMFAAdminDeleteFactorResponse.from_hash(data)
120
123
  end
124
+
125
+ # Lists OAuth clients with optional pagination. Only relevant when the OAuth 2.1
126
+ # server is enabled in Supabase Auth.
127
+ # @param params [Hash, Types::PageParams, nil] optional :page and :per_page
128
+ # @return [Types::OAuthClientListResponse]
129
+ def _list_oauth_clients(params = nil)
130
+ query = {}
131
+ if params
132
+ page = params[:page] || params["page"]
133
+ per_page = params[:per_page] || params["per_page"]
134
+ query[:page] = page if page
135
+ query[:per_page] = per_page if per_page
136
+ end
137
+
138
+ response = _request("GET", "admin/oauth/clients", params: query, no_resolve_json: true)
139
+ body = response.body.is_a?(String) ? JSON.parse(response.body) : (response.body || {})
140
+ result = Types::OAuthClientListResponse.from_hash(body)
141
+
142
+ total = response.headers["x-total-count"] || response.headers["X-Total-Count"]
143
+ result.total = total.to_i if total
144
+
145
+ links = response.headers["link"] || response.headers["Link"]
146
+ if links
147
+ links.split(",").each do |link|
148
+ parts = link.split(";")
149
+ next unless parts.length >= 2
150
+
151
+ page_match = parts[0].split("page=")
152
+ next unless page_match.length >= 2
153
+
154
+ page_num = page_match[1].split("&")[0].sub(/>$/, "").to_i
155
+ rel = parts[1].split("=")[1].to_s.delete('"').strip
156
+ case rel
157
+ when "next" then result.next_page = page_num
158
+ when "last" then result.last_page = page_num
159
+ end
160
+ end
161
+ end
162
+
163
+ result
164
+ end
165
+
166
+ # Creates a new OAuth client. Only relevant when the OAuth 2.1 server is enabled.
167
+ # @param params [Hash] OAuth client attributes (client_name, redirect_uris, etc.)
168
+ # @return [Types::OAuthClientResponse]
169
+ def _create_oauth_client(params)
170
+ data = post("admin/oauth/clients", body: params)
171
+ Types::OAuthClientResponse.new(client: Types::OAuthClient.from_hash(data))
172
+ end
173
+
174
+ # Gets details of a specific OAuth client.
175
+ # @param client_id [String] OAuth client UUID
176
+ # @return [Types::OAuthClientResponse]
177
+ # @raise [ArgumentError] if client_id is not a valid UUID
178
+ def _get_oauth_client(client_id)
179
+ _validate_uuid(client_id)
180
+ data = get("admin/oauth/clients/#{client_id}")
181
+ Types::OAuthClientResponse.new(client: Types::OAuthClient.from_hash(data))
182
+ end
183
+
184
+ # Updates an OAuth client.
185
+ # @param client_id [String] OAuth client UUID
186
+ # @param params [Hash] attributes to update
187
+ # @return [Types::OAuthClientResponse]
188
+ # @raise [ArgumentError] if client_id is not a valid UUID
189
+ def _update_oauth_client(client_id, params)
190
+ _validate_uuid(client_id)
191
+ data = put("admin/oauth/clients/#{client_id}", body: params)
192
+ Types::OAuthClientResponse.new(client: Types::OAuthClient.from_hash(data))
193
+ end
194
+
195
+ # Deletes an OAuth client.
196
+ # @param client_id [String] OAuth client UUID
197
+ # @raise [ArgumentError] if client_id is not a valid UUID
198
+ def _delete_oauth_client(client_id)
199
+ _validate_uuid(client_id)
200
+ _request("DELETE", "admin/oauth/clients/#{client_id}")
201
+ end
202
+
203
+ # Regenerates the secret for an OAuth client.
204
+ # @param client_id [String] OAuth client UUID
205
+ # @return [Types::OAuthClientResponse]
206
+ # @raise [ArgumentError] if client_id is not a valid UUID
207
+ def _regenerate_oauth_client_secret(client_id)
208
+ _validate_uuid(client_id)
209
+ data = post("admin/oauth/clients/#{client_id}/regenerate_secret")
210
+ Types::OAuthClientResponse.new(client: Types::OAuthClient.from_hash(data))
211
+ end
121
212
  end
122
213
  end
123
214
  end
@@ -14,10 +14,16 @@ module Supabase
14
14
  # @param url [String] The GoTrue API base URL
15
15
  # @param headers [Hash] Default headers to include on every request (e.g., apikey)
16
16
  # @param http_client [Faraday::Connection, nil] Optional custom Faraday client
17
- def initialize(url:, headers: {}, http_client: nil)
17
+ # @param verify [Boolean] Verify TLS certificates (default true)
18
+ # @param proxy [String, nil] HTTP proxy URL
19
+ # @param timeout [Numeric, nil] Per-request timeout in seconds
20
+ def initialize(url:, headers: {}, http_client: nil, verify: true, proxy: nil, timeout: nil)
18
21
  @url = url
19
22
  @headers = headers
20
23
  @http_client = http_client
24
+ @verify = verify
25
+ @proxy = proxy
26
+ @timeout = timeout
21
27
  end
22
28
 
23
29
  # Central HTTP dispatch method. Builds URL, merges headers (including API version
@@ -92,8 +98,12 @@ module Supabase
92
98
  end
93
99
 
94
100
  def build_connection
95
- Faraday.new(url: @url) do |f|
101
+ Faraday.new(url: @url, ssl: { verify: @verify }, proxy: @proxy) do |f|
96
102
  f.response :raise_error
103
+ if @timeout
104
+ f.options.timeout = @timeout
105
+ f.options.open_timeout = @timeout
106
+ end
97
107
  f.adapter Faraday.default_adapter
98
108
  end
99
109
  end
@@ -48,6 +48,9 @@ module Supabase
48
48
  @storage_key = opts[:storage_key] || STORAGE_KEY
49
49
  @storage = opts[:storage] || MemoryStorage.new
50
50
  @http_client = opts[:http_client]
51
+ @verify = opts.fetch(:verify, true)
52
+ @proxy = opts[:proxy]
53
+ @timeout = opts[:timeout]
51
54
 
52
55
  @current_session = nil
53
56
  @jwks = { "keys" => [] }
@@ -56,8 +59,10 @@ module Supabase
56
59
  @refresh_token_timer = nil
57
60
  @network_retries = 0
58
61
 
59
- @api = Api.new(url: @url, headers: @headers, http_client: @http_client)
60
- @admin = AdminApi.new(url: @url, headers: @headers, http_client: @http_client)
62
+ @api = Api.new(url: @url, headers: @headers, http_client: @http_client,
63
+ verify: @verify, proxy: @proxy, timeout: @timeout)
64
+ @admin = AdminApi.new(url: @url, headers: @headers, http_client: @http_client,
65
+ verify: @verify, proxy: @proxy, timeout: @timeout)
61
66
  @mfa = MFAApi.new(self)
62
67
  end
63
68
 
@@ -262,7 +267,7 @@ module Supabase
262
267
  end
263
268
  return nil unless current_session
264
269
 
265
- time_now = Time.now.to_i
270
+ time_now = Time.now.round.to_i
266
271
  has_expired = current_session.expires_at ? current_session.expires_at <= time_now + EXPIRY_MARGIN : false
267
272
 
268
273
  if has_expired
@@ -307,7 +312,7 @@ module Supabase
307
312
  # @raise [Errors::AuthInvalidJwtError] if token is malformed
308
313
  # @raise [Errors::AuthSessionMissing] if token expired and no refresh token
309
314
  def set_session(access_token, refresh_token)
310
- time_now = Time.now.to_i
315
+ time_now = Time.now.round.to_i
311
316
  expires_at = time_now
312
317
  has_expired = true
313
318
  session = nil
@@ -583,7 +588,7 @@ module Supabase
583
588
 
584
589
  # Link an OAuth identity to the current user.
585
590
  # @param credentials [Hash] (:provider, optional :options)
586
- # @return [Types::LinkIdentityResponse]
591
+ # @return [Types::OAuthResponse]
587
592
  # @raise [Errors::AuthSessionMissing] if no active session
588
593
  def link_identity(credentials)
589
594
  provider = credentials[:provider] || credentials["provider"]
@@ -601,10 +606,11 @@ module Supabase
601
606
  session = get_session
602
607
  raise Errors::AuthSessionMissing unless session
603
608
 
604
- _request("GET", url,
605
- params: query,
606
- jwt: session.access_token,
607
- xform: ->(data) { Helpers.parse_link_identity_response(data) })
609
+ link_identity = _request("GET", url,
610
+ params: query,
611
+ jwt: session.access_token,
612
+ xform: ->(data) { Helpers.parse_link_identity_response(data) })
613
+ Types::OAuthResponse.new(provider: provider, url: link_identity.url)
608
614
  end
609
615
 
610
616
  # Unlink an identity from the current user.
@@ -813,7 +819,7 @@ module Supabase
813
819
  return
814
820
  end
815
821
 
816
- time_now = Time.now.to_i
822
+ time_now = Time.now.round.to_i
817
823
  expires_at = current_session.expires_at
818
824
  expires_at = expires_at.to_i if expires_at
819
825
 
@@ -888,7 +894,7 @@ module Supabase
888
894
 
889
895
  expire_at = session.expires_at
890
896
  if expire_at
891
- time_now = Time.now.to_i
897
+ time_now = Time.now.round.to_i
892
898
  expire_in = expire_at - time_now
893
899
  refresh_duration_before_expires = expire_in > EXPIRY_MARGIN ? EXPIRY_MARGIN : 0.5
894
900
  value = (expire_in - refresh_duration_before_expires) * 1000
@@ -1011,7 +1017,7 @@ module Supabase
1011
1017
  token_type = params["token_type"]
1012
1018
  raise Errors::AuthImplicitGrantRedirectError.new("No token_type detected.") unless token_type
1013
1019
 
1014
- time_now = Time.now.to_i
1020
+ time_now = Time.now.round.to_i
1015
1021
  expires_at = time_now + expires_in
1016
1022
 
1017
1023
  user_response = get_user(access_token)
@@ -45,7 +45,7 @@ module Supabase
45
45
  end
46
46
 
47
47
  def to_h
48
- { name: @name, message: message, status: @status }
48
+ { name: @name, message: message, status: @status, code: @code }
49
49
  end
50
50
  end
51
51
 
@@ -73,7 +73,7 @@ module Supabase
73
73
  end
74
74
 
75
75
  def to_h
76
- { name: @name, message: message, status: @status, details: @details }
76
+ { name: @name, message: message, status: @status, code: @code, details: @details }
77
77
  end
78
78
  end
79
79
 
@@ -94,7 +94,7 @@ module Supabase
94
94
  end
95
95
 
96
96
  def to_h
97
- { name: @name, message: message, status: @status, reasons: @reasons }
97
+ { name: @name, message: message, status: @status, code: @code, reasons: @reasons }
98
98
  end
99
99
  end
100
100
 
@@ -200,6 +200,7 @@ module Supabase
200
200
  invalid_credentials
201
201
  email_address_not_authorized
202
202
  email_address_invalid
203
+ invalid_jwt
203
204
  ].freeze
204
205
  end
205
206
  end
@@ -12,7 +12,6 @@ module Supabase
12
12
  module Helpers
13
13
  API_VERSION_HEADER_NAME = "X-Supabase-Api-Version"
14
14
  API_VERSION_REGEX = /\A2[0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|1[0-9]|2[0-9]|3[0-1])\z/
15
- BASE64URL_REGEX = /\A([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)\z/i
16
15
  PKCE_CHARSET = (("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + %w[- . _ ~]).freeze
17
16
  API_VERSION_2024_01_01_TIMESTAMP = Time.new(2024, 1, 1).to_f
18
17
 
@@ -23,7 +22,7 @@ module Supabase
23
22
  raise Errors::AuthInvalidJwtError, "Invalid JWT structure" unless parts.length == 3
24
23
 
25
24
  parts.each do |part|
26
- raise Errors::AuthInvalidJwtError, "JWT not in base64url format" unless part.match?(BASE64URL_REGEX)
25
+ raise Errors::AuthInvalidJwtError, "JWT not in base64url format" unless part.match?(Constants::BASE64URL_REGEX)
27
26
  end
28
27
 
29
28
  header = JSON.parse(str_from_base64url(parts[0]))
@@ -101,6 +101,9 @@ module Supabase
101
101
  :new_phone,
102
102
  :invited_at,
103
103
  :is_anonymous,
104
+ :is_sso_user,
105
+ :deleted_at,
106
+ :banned_until,
104
107
  :confirmation_sent_at,
105
108
  :recovery_sent_at,
106
109
  :email_change_sent_at,
@@ -133,6 +136,9 @@ module Supabase
133
136
  new_phone: hash["new_phone"] || hash[:new_phone],
134
137
  invited_at: Types.parse_timestamp(hash["invited_at"] || hash[:invited_at]),
135
138
  is_anonymous: hash.key?("is_anonymous") ? hash["is_anonymous"] : (hash.key?(:is_anonymous) ? hash[:is_anonymous] : false),
139
+ is_sso_user: hash.key?("is_sso_user") ? hash["is_sso_user"] : (hash.key?(:is_sso_user) ? hash[:is_sso_user] : false),
140
+ deleted_at: hash["deleted_at"] || hash[:deleted_at],
141
+ banned_until: hash["banned_until"] || hash[:banned_until],
136
142
  confirmation_sent_at: Types.parse_timestamp(hash["confirmation_sent_at"] || hash[:confirmation_sent_at]),
137
143
  recovery_sent_at: Types.parse_timestamp(hash["recovery_sent_at"] || hash[:recovery_sent_at]),
138
144
  email_change_sent_at: Types.parse_timestamp(hash["email_change_sent_at"] || hash[:email_change_sent_at]),
@@ -158,7 +164,7 @@ module Supabase
158
164
  expires_at = hash["expires_at"] || hash[:expires_at]
159
165
  expires_in = hash["expires_in"] || hash[:expires_in]
160
166
  if expires_in && !expires_at
161
- expires_at = Time.now.to_i + expires_in.to_i
167
+ expires_at = Time.now.round.to_i + expires_in.to_i
162
168
  end
163
169
  expires_at = expires_at.to_i if expires_at
164
170
 
@@ -430,6 +436,82 @@ module Supabase
430
436
  :signature,
431
437
  keyword_init: true
432
438
  )
439
+
440
+ # OAuth 2.1 server: client object returned by admin OAuth endpoints.
441
+ OAuthClient = Struct.new(
442
+ :client_id,
443
+ :client_name,
444
+ :client_secret,
445
+ :client_type,
446
+ :token_endpoint_auth_method,
447
+ :registration_type,
448
+ :client_uri,
449
+ :logo_uri,
450
+ :redirect_uris,
451
+ :grant_types,
452
+ :response_types,
453
+ :scope,
454
+ :created_at,
455
+ :updated_at,
456
+ keyword_init: true
457
+ ) do
458
+ def self.from_hash(hash)
459
+ return nil if hash.nil?
460
+
461
+ new(
462
+ client_id: hash["client_id"] || hash[:client_id],
463
+ client_name: hash["client_name"] || hash[:client_name],
464
+ client_secret: hash["client_secret"] || hash[:client_secret],
465
+ client_type: hash["client_type"] || hash[:client_type],
466
+ token_endpoint_auth_method: hash["token_endpoint_auth_method"] || hash[:token_endpoint_auth_method],
467
+ registration_type: hash["registration_type"] || hash[:registration_type],
468
+ client_uri: hash["client_uri"] || hash[:client_uri],
469
+ logo_uri: hash["logo_uri"] || hash[:logo_uri],
470
+ redirect_uris: hash["redirect_uris"] || hash[:redirect_uris] || [],
471
+ grant_types: hash["grant_types"] || hash[:grant_types] || [],
472
+ response_types: hash["response_types"] || hash[:response_types] || [],
473
+ scope: hash["scope"] || hash[:scope],
474
+ created_at: hash["created_at"] || hash[:created_at],
475
+ updated_at: hash["updated_at"] || hash[:updated_at]
476
+ )
477
+ end
478
+ end
479
+
480
+ # OAuth 2.1 server: response wrapper for single-client operations.
481
+ OAuthClientResponse = Struct.new(
482
+ :client,
483
+ keyword_init: true
484
+ )
485
+
486
+ # OAuth 2.1 server: paginated list response.
487
+ OAuthClientListResponse = Struct.new(
488
+ :clients,
489
+ :aud,
490
+ :next_page,
491
+ :last_page,
492
+ :total,
493
+ keyword_init: true
494
+ ) do
495
+ def self.from_hash(hash)
496
+ return nil if hash.nil?
497
+
498
+ clients_data = hash["clients"] || hash[:clients] || []
499
+ new(
500
+ clients: clients_data.map { |c| OAuthClient.from_hash(c) },
501
+ aud: hash["aud"] || hash[:aud],
502
+ next_page: hash["next_page"] || hash[:next_page],
503
+ last_page: hash["last_page"] || hash[:last_page] || 0,
504
+ total: hash["total"] || hash[:total] || 0
505
+ )
506
+ end
507
+ end
508
+
509
+ # Pagination params accepted by admin list endpoints.
510
+ PageParams = Struct.new(
511
+ :page,
512
+ :per_page,
513
+ keyword_init: true
514
+ )
433
515
  end
434
516
  end
435
517
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Supabase
4
4
  module Auth
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: supabase-auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
- - Bogdan Tarasenko
7
+ - Supabase
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-05-11 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: faraday
@@ -110,7 +111,7 @@ dependencies:
110
111
  description: A Ruby gem implementing a client for Supabase Auth (GoTrue API), with
111
112
  adaptations for Ruby idioms.
112
113
  email:
113
- - bogdantarasenkozp@gmail.com
114
+ - support@supabase.io
114
115
  executables: []
115
116
  extensions: []
116
117
  extra_rdoc_files: []
@@ -129,13 +130,14 @@ files:
129
130
  - lib/supabase/auth/timer.rb
130
131
  - lib/supabase/auth/types.rb
131
132
  - lib/supabase/auth/version.rb
132
- homepage: https://github.com/bogdantarasenko/supabase-rb
133
+ homepage: https://github.com/suparails/supabase-auth
133
134
  licenses:
134
135
  - MIT
135
136
  metadata:
136
- homepage_uri: https://github.com/bogdantarasenko/supabase-rb
137
- source_code_uri: https://github.com/bogdantarasenko/supabase-rb
138
- changelog_uri: https://github.com/bogdantarasenko/supabase-rb/blob/main/CHANGELOG.md
137
+ homepage_uri: https://github.com/suparails/supabase-auth
138
+ source_code_uri: https://github.com/suparails/supabase-auth
139
+ changelog_uri: https://github.com/suparails/supabase-auth/blob/main/CHANGELOG.md
140
+ post_install_message:
139
141
  rdoc_options: []
140
142
  require_paths:
141
143
  - lib
@@ -150,7 +152,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
152
  - !ruby/object:Gem::Version
151
153
  version: '0'
152
154
  requirements: []
153
- rubygems_version: 3.6.9
155
+ rubygems_version: 3.0.3.1
156
+ signing_key:
154
157
  specification_version: 4
155
158
  summary: Ruby client for Supabase Auth (GoTrue API)
156
159
  test_files: []