better_auth 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df93cdae06059d52fa2c3d35c5b173aba9025d5ac46bda0b93a7a8abc5045f40
4
- data.tar.gz: adec802dade2610da15329266c91dcd46aecb8642165c29e364ce7db2829d217
3
+ metadata.gz: 38179f5800613263ca30525a3d878b91c2a5f9057f104b1f24cecbf72a0cc030
4
+ data.tar.gz: 9883d339ce2f1ab8f5a618da3708f4ab5bdde69a09fe98b48889b6069351c7bb
5
5
  SHA512:
6
- metadata.gz: f46ac8a5cf79a859a417e2cbe60f000c290d014975fb3ce910c49b6c1cd455b2123e436a42a323dd530b38096a4ebc18a1f206c97f0ef6624378c9eb051d37e3
7
- data.tar.gz: '0469ddcdcad97a7b1b58e3ddc0ef8d54c7469a1e0411546367f829291ab5664fc0b4685d460d8d328fe6ef67543908060d33eb961a638121930154ca31a4cfaf'
6
+ metadata.gz: b0bdd97ab9d677df601b0eed5d387a09543743aee41d0243f7ed3981935c3391f3ae10dfc110fc41312a5a4b188f29581563f2c99154a8808ed3158cb47663ad
7
+ data.tar.gz: 6b9b76c6969e601e01506a2cd91a28abf13eeccf0446023fad7c58e8c95476f6110b7ac6f3979fc18705687589cff74748f839c685a8ccb791392d0d2e729c15
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-04-30
11
+
12
+ ### Added
13
+
14
+ - Added upstream-parity helpers for async execution, host resolution, instrumentation, request state, URL handling, OAuth2, deprecation warnings, and expanded route behavior.
15
+ - Added two-factor, OAuth protocol, social route, organization, admin, adapter, schema, and session parity coverage.
16
+
17
+ ### Changed
18
+
19
+ - Aligned core auth, email OTP, generic OAuth, organization, two-factor, OAuth protocol, adapter, router, rate-limiter, logger, and middleware behavior more closely with upstream Better Auth.
20
+
21
+ ### Fixed
22
+
23
+ - Fixed upstream parity gaps in organization handling, generic OAuth user info, email OTP sign-up, database schema behavior, and route/session edge cases.
24
+
10
25
  ## [0.3.0] - 2026-04-29
11
26
 
12
27
  ### Added
@@ -59,9 +59,12 @@ module BetterAuth
59
59
  end
60
60
 
61
61
  def delete_user(user_id)
62
- delete_sessions(user_id) if !secondary_storage || options.session[:store_session_in_database]
62
+ deleted = hooks.delete([{field: "id", value: user_id}], "user")
63
+ return false if deleted == false
64
+
63
65
  hooks.delete_many([{field: "userId", value: user_id}], "account")
64
- hooks.delete([{field: "id", value: user_id}], "user")
66
+ delete_sessions(user_id) if !secondary_storage || options.session[:store_session_in_database]
67
+ deleted
65
68
  end
66
69
 
67
70
  def create_session(user_id, dont_remember_me = false, override = nil, override_all = false, context = nil)
@@ -156,33 +156,79 @@ module BetterAuth
156
156
  value = fetch_key(clause, :value)
157
157
  operator = (fetch_key(clause, :operator) || "eq").to_s
158
158
  current = record[field]
159
+ comparable = coerce_where_value(record, field, value, operator)
159
160
 
160
161
  case operator
161
162
  when "in"
162
- Array(value).include?(current)
163
+ Array(comparable).include?(current)
163
164
  when "not_in"
164
- !Array(value).include?(current)
165
+ !Array(comparable).include?(current)
165
166
  when "contains"
166
- current.to_s.include?(value.to_s)
167
+ current.to_s.include?(comparable.to_s)
167
168
  when "starts_with"
168
- current.to_s.start_with?(value.to_s)
169
+ current.to_s.start_with?(comparable.to_s)
169
170
  when "ends_with"
170
- current.to_s.end_with?(value.to_s)
171
+ current.to_s.end_with?(comparable.to_s)
171
172
  when "ne"
172
- current != value
173
+ current != comparable
173
174
  when "gt"
174
- !value.nil? && current > value
175
+ !comparable.nil? && current > comparable
175
176
  when "gte"
176
- !value.nil? && current >= value
177
+ !comparable.nil? && current >= comparable
177
178
  when "lt"
178
- !value.nil? && current < value
179
+ !comparable.nil? && current < comparable
179
180
  when "lte"
180
- !value.nil? && current <= value
181
+ !comparable.nil? && current <= comparable
181
182
  else
182
- current == value
183
+ current == comparable
183
184
  end
184
185
  end
185
186
 
187
+ def coerce_where_value(record, field, value, operator)
188
+ attributes = schema_for_record_field(record, field)
189
+ return value unless attributes
190
+ return Array(value).map { |entry| coerce_scalar_where_value(entry, attributes) } if %w[in not_in].include?(operator)
191
+
192
+ coerce_scalar_where_value(value, attributes)
193
+ end
194
+
195
+ def schema_for_record_field(record, field)
196
+ db.each_key do |model|
197
+ fields = Schema.auth_tables(options)[model]&.fetch(:fields, nil)
198
+ next unless fields&.key?(field)
199
+ return fields[field] if table_for(model).include?(record)
200
+ end
201
+ nil
202
+ end
203
+
204
+ def coerce_scalar_where_value(value, attributes)
205
+ return value if value.nil?
206
+
207
+ case attributes[:type]
208
+ when "boolean"
209
+ return false if value == false || value == 0 || value.to_s.downcase == "false" || value.to_s == "0"
210
+ return true if value == true || value == 1 || value.to_s.downcase == "true" || value.to_s == "1"
211
+ when "number"
212
+ return coerce_number(value)
213
+ when "date"
214
+ return Time.parse(value) if value.is_a?(String)
215
+ when "number[]"
216
+ return Array(value).map { |entry| coerce_number(entry) }
217
+ when "string[]"
218
+ return Array(value).map(&:to_s)
219
+ end
220
+
221
+ value
222
+ end
223
+
224
+ def coerce_number(value)
225
+ return value unless value.is_a?(String)
226
+ return value.to_i if /\A-?\d+\z/.match?(value)
227
+ return value.to_f if /\A-?\d+\.\d+\z/.match?(value)
228
+
229
+ value
230
+ end
231
+
186
232
  def sort_records(model, records, sort_by)
187
233
  field = Schema.storage_key(fetch_key(sort_by, :field))
188
234
  direction = fetch_key(sort_by, :direction).to_s
@@ -204,10 +204,11 @@ module BetterAuth
204
204
  column = "#{quote(table_for(model))}.#{quote(storage_field(model, field))}"
205
205
  operator = (fetch_key(clause, :operator) || "eq").to_s
206
206
  value = fetch_key(clause, :value)
207
+ attributes = schema_for(model).fetch(:fields).fetch(field)
207
208
 
208
209
  expression = case operator
209
210
  when "in", "not_in"
210
- values = Array(value)
211
+ values = Array(value).map { |entry| coerce_where_value(entry, attributes) }
211
212
  placeholders = values.map do |entry|
212
213
  params << entry
213
214
  placeholder(params.length)
@@ -223,7 +224,7 @@ module BetterAuth
223
224
  params << pattern
224
225
  "#{column} LIKE #{placeholder(params.length)}"
225
226
  else
226
- params << value
227
+ params << coerce_where_value(value, attributes)
227
228
  "#{column} #{sql_operator(operator)} #{placeholder(params.length)}"
228
229
  end
229
230
 
@@ -385,6 +386,22 @@ module BetterAuth
385
386
  value
386
387
  end
387
388
 
389
+ def coerce_where_value(value, attributes)
390
+ return value if value.nil?
391
+
392
+ case attributes[:type]
393
+ when "boolean"
394
+ return coerce_value(false, attributes) if value == false || value == 0 || value.to_s.downcase == "false" || value.to_s == "0"
395
+ return coerce_value(true, attributes) if value == true || value == 1 || value.to_s.downcase == "true" || value.to_s == "1"
396
+ when "number"
397
+ return coerce_number(value)
398
+ when "date"
399
+ return Time.parse(value) if value.is_a?(String)
400
+ end
401
+
402
+ coerce_value(value, attributes)
403
+ end
404
+
388
405
  def coerce_output_value(value, attributes)
389
406
  return value if value.nil?
390
407
  return coerce_boolean(value) if attributes[:type] == "boolean"
@@ -412,6 +429,14 @@ module BetterAuth
412
429
  value
413
430
  end
414
431
 
432
+ def coerce_number(value)
433
+ return value unless value.is_a?(String)
434
+ return value.to_i if /\A-?\d+\z/.match?(value)
435
+ return value.to_f if /\A-?\d+\.\d+\z/.match?(value)
436
+
437
+ value
438
+ end
439
+
415
440
  def stringify_keys(data)
416
441
  data.each_with_object({}) do |(key, value), result|
417
442
  result[storage_key(key)] = value
@@ -211,7 +211,12 @@ module BetterAuth
211
211
  return value unless value.is_a?(Hash)
212
212
 
213
213
  value.each_with_object({}) do |(key, object_value), result|
214
- result[normalize_key(key)] = object_value.is_a?(Hash) ? symbolize_keys(object_value) : object_value
214
+ normalized_key = normalize_key(key)
215
+ result[normalized_key] = if normalized_key == :metadata
216
+ object_value
217
+ else
218
+ object_value.is_a?(Hash) ? symbolize_keys(object_value) : object_value
219
+ end
215
220
  end
216
221
  end
217
222
 
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module Async
5
+ module_function
6
+
7
+ def map_concurrent(items, concurrency:, &mapper)
8
+ list = items.to_a
9
+ return [] if list.empty?
10
+
11
+ width = normalized_concurrency(concurrency, list.length)
12
+ results = Array.new(list.length)
13
+ next_index = 0
14
+ first_error = nil
15
+ mutex = Mutex.new
16
+ status = Queue.new
17
+
18
+ workers = Array.new(width) do
19
+ Thread.new do
20
+ loop do
21
+ index = mutex.synchronize do
22
+ break if first_error || next_index >= list.length
23
+
24
+ current = next_index
25
+ next_index += 1
26
+ current
27
+ end
28
+ break unless index
29
+
30
+ begin
31
+ results[index] = mapper.call(list[index], index)
32
+ rescue => error
33
+ mutex.synchronize { first_error ||= error }
34
+ status << [:error, error]
35
+ break
36
+ end
37
+ end
38
+ ensure
39
+ status << [:done, nil]
40
+ end
41
+ end
42
+
43
+ done = 0
44
+ while done < workers.length
45
+ type, error = status.pop
46
+ if type == :error
47
+ workers.each { |worker| worker.kill if worker.alive? }
48
+ workers.each(&:join)
49
+ raise error
50
+ end
51
+
52
+ done += 1
53
+ end
54
+
55
+ raise first_error if first_error
56
+
57
+ results
58
+ end
59
+
60
+ def normalized_concurrency(concurrency, item_count)
61
+ raw = begin
62
+ Float(concurrency).floor
63
+ rescue ArgumentError, TypeError
64
+ 1
65
+ end
66
+ raw = 1 if raw < 1
67
+ [raw, item_count].min
68
+ end
69
+ end
70
+ end
@@ -35,9 +35,9 @@ module BetterAuth
35
35
 
36
36
  def delete(where, model, custom: nil, context: nil)
37
37
  entity = adapter.find_one(model: model, where: where)
38
- return custom ? custom.call(where) : adapter.delete(model: model, where: where) unless entity
38
+ return nil unless entity
39
39
 
40
- return nil if before_hooks(model, :delete).any? { |hook| hook.call(entity, context) == false }
40
+ return false if before_hooks(model, :delete).any? { |hook| hook.call(entity, context) == false }
41
41
 
42
42
  deleted = custom ? custom.call(where) : adapter.delete(model: model, where: where)
43
43
  after_hooks(model, :delete).each { |hook| hook.call(entity, context) }
@@ -47,7 +47,7 @@ module BetterAuth
47
47
  def delete_many(where, model, custom: nil, context: nil)
48
48
  entities = adapter.find_many(model: model, where: where)
49
49
  entities.each do |entity|
50
- return nil if before_hooks(model, :delete).any? { |hook| hook.call(entity, context) == false }
50
+ return false if before_hooks(model, :delete).any? { |hook| hook.call(entity, context) == false }
51
51
  end
52
52
  deleted = custom ? custom.call(where) : adapter.delete_many(model: model, where: where)
53
53
  entities.each { |entity| after_hooks(model, :delete).each { |hook| hook.call(entity, context) } }
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module Deprecate
5
+ module_function
6
+
7
+ def wrap(message, logger: nil, &block)
8
+ warned = false
9
+ proc do |*args, **kwargs|
10
+ unless warned
11
+ warn_once("[Deprecation] #{message}", logger)
12
+ warned = true
13
+ end
14
+ kwargs.empty? ? block.call(*args) : block.call(*args, **kwargs)
15
+ end
16
+ end
17
+
18
+ def warn_once(message, logger)
19
+ if logger.respond_to?(:call)
20
+ logger.call(message)
21
+ elsif logger.respond_to?(:warn)
22
+ logger.warn(message)
23
+ else
24
+ warn(message)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -7,16 +7,18 @@ module BetterAuth
7
7
  attr_reader :path,
8
8
  :body_schema,
9
9
  :query_schema,
10
+ :params_schema,
10
11
  :headers_schema,
11
12
  :metadata,
12
13
  :use,
13
14
  :handler
14
15
 
15
- def initialize(path: nil, method: nil, body_schema: nil, query_schema: nil, headers_schema: nil, metadata: {}, use: [], &handler)
16
+ def initialize(path: nil, method: nil, body_schema: nil, query_schema: nil, params_schema: nil, headers_schema: nil, metadata: {}, use: [], &handler)
16
17
  @path = path
17
18
  @methods = Array(method || "*").map { |value| value.to_s.upcase }
18
19
  @body_schema = body_schema
19
20
  @query_schema = query_schema
21
+ @params_schema = params_schema
20
22
  @headers_schema = headers_schema
21
23
  @metadata = metadata || {}
22
24
  @use = Array(use)
@@ -47,6 +49,7 @@ module BetterAuth
47
49
  def apply_schemas!(context)
48
50
  context.body = validate_schema(:body, body_schema, context.body)
49
51
  context.query = validate_schema(:query, query_schema, context.query)
52
+ context.params = validate_schema(:params, params_schema, context.params)
50
53
  context.headers = context.send(:normalize_headers, validate_schema(:headers, headers_schema, context.headers))
51
54
  end
52
55
 
@@ -136,7 +139,7 @@ module BetterAuth
136
139
  return @raw_response if raw_response?
137
140
 
138
141
  body = if response.nil?
139
- [""]
142
+ [JSON.generate(nil)]
140
143
  elsif response.is_a?(String)
141
144
  [response]
142
145
  else
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module BetterAuth
6
+ module Host
7
+ CLOUD_METADATA_HOSTS = [
8
+ "metadata.google.internal",
9
+ "metadata.goog",
10
+ "metadata",
11
+ "instance-data",
12
+ "instance-data.ec2.internal"
13
+ ].freeze
14
+
15
+ module_function
16
+
17
+ def classify_host(host)
18
+ canonical_input = normalize_input(host)
19
+ lowered = canonical_input.downcase
20
+ return {kind: :reserved, literal: :fqdn, canonical: ""} if lowered.empty?
21
+
22
+ address = parse_ip(lowered)
23
+ unless address
24
+ return {kind: :localhost, literal: :fqdn, canonical: lowered} if lowered == "localhost" || lowered.end_with?(".localhost")
25
+ return {kind: :cloud_metadata, literal: :fqdn, canonical: lowered} if CLOUD_METADATA_HOSTS.include?(lowered)
26
+
27
+ return {kind: :public, literal: :fqdn, canonical: lowered}
28
+ end
29
+
30
+ native = address.respond_to?(:native) ? address.native : address
31
+ if native.ipv4?
32
+ canonical = native.to_s
33
+ return {kind: classify_ipv4(canonical), literal: :ipv4, canonical: canonical}
34
+ end
35
+
36
+ canonical = expanded_ipv6(native)
37
+ {kind: classify_ipv6(canonical), literal: :ipv6, canonical: canonical}
38
+ end
39
+
40
+ def loopback_ip?(host)
41
+ classify_host(host)[:kind] == :loopback
42
+ end
43
+
44
+ def loopback_host?(host)
45
+ [:loopback, :localhost].include?(classify_host(host)[:kind])
46
+ end
47
+
48
+ def public_routable_host?(host)
49
+ classify_host(host)[:kind] == :public
50
+ end
51
+
52
+ def normalize_input(host)
53
+ value = host.to_s.strip
54
+ value = strip_port(value)
55
+ value = value[1...-1] if value.start_with?("[") && value.end_with?("]")
56
+ value = value.split("%", 2).first || ""
57
+ value.gsub(/\.+\z/, "")
58
+ end
59
+
60
+ def strip_port(host)
61
+ if host.start_with?("[")
62
+ closing = host.index("]")
63
+ return host unless closing
64
+
65
+ return host[0..closing] if host[(closing + 1)..]&.match?(/\A:\d+\z/)
66
+ return host
67
+ end
68
+
69
+ first_colon = host.index(":")
70
+ return host unless first_colon
71
+ return host if host.index(":", first_colon + 1)
72
+
73
+ host[0...first_colon]
74
+ end
75
+
76
+ def parse_ip(host)
77
+ IPAddr.new(host)
78
+ rescue ArgumentError
79
+ nil
80
+ end
81
+
82
+ def classify_ipv4(ip)
83
+ return :unspecified if ip == "0.0.0.0"
84
+ return :broadcast if ip == "255.255.255.255"
85
+
86
+ value = ipv4_to_i(ip)
87
+ return :loopback if ipv4_range?(value, "127.0.0.0", 8)
88
+ return :private if ipv4_range?(value, "10.0.0.0", 8)
89
+ return :private if ipv4_range?(value, "172.16.0.0", 12)
90
+ return :private if ipv4_range?(value, "192.168.0.0", 16)
91
+ return :link_local if ipv4_range?(value, "169.254.0.0", 16)
92
+ return :shared_address_space if ipv4_range?(value, "100.64.0.0", 10)
93
+ return :documentation if ipv4_range?(value, "192.0.2.0", 24)
94
+ return :documentation if ipv4_range?(value, "198.51.100.0", 24)
95
+ return :documentation if ipv4_range?(value, "203.0.113.0", 24)
96
+ return :benchmarking if ipv4_range?(value, "198.18.0.0", 15)
97
+ return :multicast if ipv4_range?(value, "224.0.0.0", 4)
98
+ return :reserved if ipv4_range?(value, "0.0.0.0", 8)
99
+ return :reserved if ipv4_range?(value, "192.0.0.0", 24)
100
+ return :reserved if ipv4_range?(value, "240.0.0.0", 4)
101
+
102
+ :public
103
+ end
104
+
105
+ def ipv4_to_i(ip)
106
+ ip.split(".").map(&:to_i).reduce(0) { |sum, part| (sum << 8) + part }
107
+ end
108
+
109
+ def ipv4_range?(value, prefix, length)
110
+ mask = (length == 32) ? 0xffffffff : ((0xffffffff << (32 - length)) & 0xffffffff)
111
+ (value & mask) == (ipv4_to_i(prefix) & mask)
112
+ end
113
+
114
+ def classify_ipv6(expanded)
115
+ return :unspecified if expanded == "0000:0000:0000:0000:0000:0000:0000:0000"
116
+ return :loopback if expanded == "0000:0000:0000:0000:0000:0000:0000:0001"
117
+
118
+ first_byte = expanded[0, 2].to_i(16)
119
+ second_byte = expanded[2, 2].to_i(16)
120
+
121
+ return :multicast if first_byte == 0xff
122
+ return :link_local if first_byte == 0xfe && (second_byte & 0xc0) == 0x80
123
+ return :private if (first_byte & 0xfe) == 0xfc
124
+ return :documentation if expanded.start_with?("2001:0db8:")
125
+
126
+ if expanded.start_with?("2002:")
127
+ embedded = embedded_ipv4(expanded, 1)
128
+ return (classify_ipv4(embedded) == :public) ? :public : :reserved if embedded
129
+ end
130
+
131
+ if expanded.start_with?("0064:ff9b:0000:0000:0000:0000:")
132
+ embedded = embedded_ipv4(expanded, 6)
133
+ return :reserved if embedded
134
+ end
135
+
136
+ if expanded.start_with?("2001:0000:")
137
+ embedded = embedded_ipv4(expanded, 6, xor: true)
138
+ return :reserved if embedded
139
+ end
140
+
141
+ return :reserved if expanded.start_with?("0100:0000:0000:0000:")
142
+
143
+ :public
144
+ end
145
+
146
+ def embedded_ipv4(expanded, start_group, xor: false)
147
+ groups = expanded.split(":")
148
+ combined = (groups.fetch(start_group).to_i(16) << 16) | groups.fetch(start_group + 1).to_i(16)
149
+ combined ^= 0xffffffff if xor
150
+ [
151
+ (combined >> 24) & 0xff,
152
+ (combined >> 16) & 0xff,
153
+ (combined >> 8) & 0xff,
154
+ combined & 0xff
155
+ ].join(".")
156
+ rescue IndexError
157
+ nil
158
+ end
159
+
160
+ def expanded_ipv6(address)
161
+ address.hton.bytes.each_slice(2).map do |high, low|
162
+ ((high << 8) + low).to_s(16).rjust(4, "0")
163
+ end.join(":")
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module Instrumentation
5
+ module SpanStatusCode
6
+ UNSET = 0
7
+ OK = 1
8
+ ERROR = 2
9
+ end
10
+
11
+ class NoopSpan
12
+ def set_attribute(_key, _value)
13
+ self
14
+ end
15
+
16
+ def set_attributes(_attributes)
17
+ self
18
+ end
19
+
20
+ def record_exception(_error)
21
+ self
22
+ end
23
+
24
+ def set_status(_status)
25
+ self
26
+ end
27
+
28
+ def add_event(_name, _attributes = nil)
29
+ self
30
+ end
31
+
32
+ def end
33
+ self
34
+ end
35
+ end
36
+
37
+ class NoopTracer
38
+ def start_active_span(_name, attributes: {}, &block)
39
+ span = NoopSpan.new
40
+ return span unless block
41
+
42
+ block.call(span)
43
+ ensure
44
+ span&.end
45
+ end
46
+ end
47
+
48
+ class Trace
49
+ def get_tracer(_name = "better-auth")
50
+ NoopTracer.new
51
+ end
52
+
53
+ def get_active_span
54
+ NoopSpan.new
55
+ end
56
+ end
57
+
58
+ module_function
59
+
60
+ def trace
61
+ @trace ||= Trace.new
62
+ end
63
+
64
+ def with_span(name, attributes: {}, &block)
65
+ trace.get_tracer("better-auth").start_active_span(name, attributes: attributes) do |span|
66
+ block.call(span)
67
+ rescue => error
68
+ span.record_exception(error)
69
+ span.set_status(SpanStatusCode::ERROR)
70
+ raise
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BetterAuth
4
+ module Logger
5
+ LEVELS = [:debug, :info, :success, :warn, :error].freeze
6
+
7
+ Internal = Struct.new(:level, :disabled, :handler, keyword_init: true) do
8
+ LEVELS.each do |log_level|
9
+ define_method(log_level) do |message, *args|
10
+ return if disabled || !Logger.should_publish?(level, log_level)
11
+
12
+ if handler
13
+ handler.call((log_level == :success) ? :info : log_level, message, *args)
14
+ else
15
+ Kernel.warn("#{log_level.upcase} [Better Auth]: #{message}")
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ module_function
22
+
23
+ def should_publish?(current_log_level, log_level)
24
+ LEVELS.index(log_level.to_sym).to_i >= LEVELS.index(current_log_level.to_sym).to_i
25
+ end
26
+
27
+ def create(level: :warn, disabled: false, log: nil, **)
28
+ Internal.new(level: level.to_sym, disabled: disabled, handler: log)
29
+ end
30
+ end
31
+ end
@@ -14,7 +14,7 @@ module BetterAuth
14
14
 
15
15
  validate_origin(endpoint_context)
16
16
  validate_fetch_metadata(endpoint_context)
17
- return if skip_origin_check?(endpoint_context)
17
+ return if skip_origin_check?(endpoint_context) || skip_origin_path?(endpoint_context)
18
18
 
19
19
  validate_callback_urls(endpoint_context)
20
20
  nil
@@ -87,7 +87,7 @@ module BetterAuth
87
87
  end
88
88
 
89
89
  def skip_origin_check?(endpoint_context)
90
- !!endpoint_context.context.options.advanced[:disable_origin_check]
90
+ endpoint_context.context.options.advanced[:disable_origin_check] == true
91
91
  end
92
92
 
93
93
  def skip_csrf_for_backward_compat?(endpoint_context)