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 +4 -4
- data/CHANGELOG.md +15 -0
- data/lib/better_auth/adapters/internal_adapter.rb +5 -2
- data/lib/better_auth/adapters/memory.rb +57 -11
- data/lib/better_auth/adapters/sql.rb +27 -2
- data/lib/better_auth/api.rb +6 -1
- data/lib/better_auth/async.rb +70 -0
- data/lib/better_auth/database_hooks.rb +3 -3
- data/lib/better_auth/deprecate.rb +28 -0
- data/lib/better_auth/endpoint.rb +5 -2
- data/lib/better_auth/host.rb +166 -0
- data/lib/better_auth/instrumentation.rb +74 -0
- data/lib/better_auth/logger.rb +31 -0
- data/lib/better_auth/middleware/origin_check.rb +2 -2
- data/lib/better_auth/oauth2.rb +94 -0
- data/lib/better_auth/plugins/email_otp.rb +16 -5
- data/lib/better_auth/plugins/generic_oauth.rb +14 -28
- data/lib/better_auth/plugins/oauth_protocol.rb +171 -28
- data/lib/better_auth/plugins/organization/schema.rb +6 -0
- data/lib/better_auth/plugins/organization.rb +51 -20
- data/lib/better_auth/plugins/two_factor.rb +53 -18
- data/lib/better_auth/rate_limiter.rb +18 -0
- data/lib/better_auth/request_state.rb +44 -0
- data/lib/better_auth/routes/account.rb +16 -4
- data/lib/better_auth/routes/password.rb +2 -1
- data/lib/better_auth/routes/sign_in.rb +2 -0
- data/lib/better_auth/routes/sign_up.rb +8 -0
- data/lib/better_auth/routes/social.rb +30 -0
- data/lib/better_auth/routes/user.rb +9 -3
- data/lib/better_auth/session.rb +12 -1
- data/lib/better_auth/url_helpers.rb +195 -0
- data/lib/better_auth/version.rb +1 -1
- data/lib/better_auth.rb +8 -0
- metadata +9 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module BetterAuth
|
|
6
|
+
module URLHelpers
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def valid_proxy_header?(header, type)
|
|
10
|
+
value = header.to_s
|
|
11
|
+
return false if value.strip.empty?
|
|
12
|
+
|
|
13
|
+
case type.to_sym
|
|
14
|
+
when :proto
|
|
15
|
+
["http", "https"].include?(value)
|
|
16
|
+
when :host
|
|
17
|
+
return false if value.match?(/\.\.|\0|\s|\A[.]|[<>'"]|javascript:|file:|data:/i)
|
|
18
|
+
return false if value.match?(%r{[/\\]})
|
|
19
|
+
|
|
20
|
+
patterns = [
|
|
21
|
+
/\A[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*(:[0-9]{1,5})?\z/,
|
|
22
|
+
/\A(\d{1,3}\.){3}\d{1,3}(:[0-9]{1,5})?\z/,
|
|
23
|
+
/\A\[[0-9a-fA-F:]+\](:[0-9]{1,5})?\z/,
|
|
24
|
+
/\Alocalhost(:[0-9]{1,5})?\z/i
|
|
25
|
+
]
|
|
26
|
+
patterns.any? { |pattern| value.match?(pattern) } && valid_port?(value)
|
|
27
|
+
else
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def matches_host_pattern?(host, pattern)
|
|
33
|
+
return false if host.to_s.empty? || pattern.to_s.empty?
|
|
34
|
+
|
|
35
|
+
normalized_host = normalize_host_pattern_value(host)
|
|
36
|
+
normalized_pattern = normalize_host_pattern_value(pattern)
|
|
37
|
+
regex = Regexp.escape(normalized_pattern)
|
|
38
|
+
.gsub("\\*", ".*")
|
|
39
|
+
.gsub("\\?", ".")
|
|
40
|
+
!!normalized_host.match?(/\A#{regex}\z/i)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def host_from_source(source, trusted_proxy_headers: false)
|
|
44
|
+
headers = headers_from_source(source)
|
|
45
|
+
if trusted_proxy_headers
|
|
46
|
+
forwarded_host = header_value(headers, "x-forwarded-host")
|
|
47
|
+
return forwarded_host if forwarded_host && valid_proxy_header?(forwarded_host, :host)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
host = header_value(headers, "host")
|
|
51
|
+
return host if host && valid_proxy_header?(host, :host)
|
|
52
|
+
|
|
53
|
+
uri_host(source_url(source))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def protocol_from_source(source, config_protocol: nil, trusted_proxy_headers: false)
|
|
57
|
+
return config_protocol if ["http", "https"].include?(config_protocol)
|
|
58
|
+
|
|
59
|
+
headers = headers_from_source(source)
|
|
60
|
+
if trusted_proxy_headers
|
|
61
|
+
forwarded_proto = header_value(headers, "x-forwarded-proto")
|
|
62
|
+
return forwarded_proto if forwarded_proto && valid_proxy_header?(forwarded_proto, :proto)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
protocol = uri_scheme(source_url(source))
|
|
66
|
+
return protocol if ["http", "https"].include?(protocol)
|
|
67
|
+
|
|
68
|
+
host = host_from_source(source, trusted_proxy_headers: trusted_proxy_headers)
|
|
69
|
+
return "http" if host && loopback_for_dev_scheme?(host)
|
|
70
|
+
|
|
71
|
+
"https"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def resolve_base_url(config, base_path, source = nil, load_env: true, trusted_proxy_headers: false)
|
|
75
|
+
if dynamic_config?(config)
|
|
76
|
+
return resolve_dynamic_base_url(config, source, base_path, trusted_proxy_headers: trusted_proxy_headers) if source
|
|
77
|
+
return with_path(config[:fallback] || config["fallback"], base_path) if config[:fallback] || config["fallback"]
|
|
78
|
+
|
|
79
|
+
return env_base_url(base_path) if load_env
|
|
80
|
+
return nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
return with_path(config, base_path) if config.is_a?(String)
|
|
84
|
+
return env_base_url(base_path) if load_env
|
|
85
|
+
return with_path(origin(source_url(source)), base_path) if source
|
|
86
|
+
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def resolve_dynamic_base_url(config, source, base_path, trusted_proxy_headers: false)
|
|
91
|
+
host = host_from_source(source, trusted_proxy_headers: trusted_proxy_headers)
|
|
92
|
+
fallback = config[:fallback] || config["fallback"]
|
|
93
|
+
raise Error, "Could not determine host from request headers. Please provide a fallback URL in your baseURL config." unless host || fallback
|
|
94
|
+
|
|
95
|
+
allowed_hosts = config[:allowed_hosts] || config["allowed_hosts"] || config[:allowedHosts] || config["allowedHosts"] || []
|
|
96
|
+
if host && allowed_hosts.any? { |pattern| matches_host_pattern?(host, pattern) }
|
|
97
|
+
protocol = protocol_from_source(source, config_protocol: config[:protocol] || config["protocol"], trusted_proxy_headers: trusted_proxy_headers)
|
|
98
|
+
return with_path("#{protocol}://#{host}", base_path)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
return with_path(fallback, base_path) if fallback
|
|
102
|
+
|
|
103
|
+
raise Error, "Host \"#{host}\" is not in the allowed hosts list."
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def with_path(url, path = "/api/auth")
|
|
107
|
+
parsed = URI.parse(url.to_s)
|
|
108
|
+
raise Error, "Invalid base URL: #{url}. URL must include 'http://' or 'https://'" unless ["http", "https"].include?(parsed.scheme)
|
|
109
|
+
|
|
110
|
+
current_path = parsed.path.to_s.gsub(%r{/+\z}, "")
|
|
111
|
+
return url.to_s if !current_path.empty? && current_path != "/"
|
|
112
|
+
|
|
113
|
+
trimmed = url.to_s.gsub(%r{/+\z}, "")
|
|
114
|
+
return trimmed if path.to_s.empty? || path == "/"
|
|
115
|
+
|
|
116
|
+
suffix = path.start_with?("/") ? path : "/#{path}"
|
|
117
|
+
"#{trimmed}#{suffix}"
|
|
118
|
+
rescue URI::InvalidURIError
|
|
119
|
+
raise Error, "Invalid base URL: #{url}. Please provide a valid base URL."
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def origin(url)
|
|
123
|
+
parsed = URI.parse(url.to_s)
|
|
124
|
+
return nil unless ["http", "https"].include?(parsed.scheme)
|
|
125
|
+
|
|
126
|
+
port = parsed.port
|
|
127
|
+
default_port = (parsed.scheme == "http" && port == 80) || (parsed.scheme == "https" && port == 443)
|
|
128
|
+
default_port ? "#{parsed.scheme}://#{parsed.host}" : "#{parsed.scheme}://#{parsed.host}:#{port}"
|
|
129
|
+
rescue URI::InvalidURIError
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def uri_host(url)
|
|
134
|
+
parsed = URI.parse(url.to_s)
|
|
135
|
+
return nil unless parsed.host
|
|
136
|
+
|
|
137
|
+
default_port = (parsed.scheme == "http" && parsed.port == 80) || (parsed.scheme == "https" && parsed.port == 443)
|
|
138
|
+
default_port ? parsed.host : "#{parsed.host}:#{parsed.port}"
|
|
139
|
+
rescue URI::InvalidURIError
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def uri_scheme(url)
|
|
144
|
+
URI.parse(url.to_s).scheme
|
|
145
|
+
rescue URI::InvalidURIError
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def normalize_host_pattern_value(value)
|
|
150
|
+
value.to_s.sub(%r{\Ahttps?://}i, "").split("/").first.to_s.downcase
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def headers_from_source(source)
|
|
154
|
+
return {} unless source
|
|
155
|
+
return source.headers if source.respond_to?(:headers)
|
|
156
|
+
return source if source.is_a?(Hash)
|
|
157
|
+
|
|
158
|
+
{}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def header_value(headers, key)
|
|
162
|
+
if headers.respond_to?(:get)
|
|
163
|
+
headers.get(key)
|
|
164
|
+
else
|
|
165
|
+
headers[key] || headers[key.to_s] || headers[key.to_s.downcase] || headers[key.to_s.upcase] || headers[key.tr("-", "_").upcase]
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def source_url(source)
|
|
170
|
+
source.url if source.respond_to?(:url)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def dynamic_config?(config)
|
|
174
|
+
config.is_a?(Hash) && (config.key?(:allowed_hosts) || config.key?("allowed_hosts") || config.key?(:allowedHosts) || config.key?("allowedHosts"))
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def env_base_url(base_path)
|
|
178
|
+
url = ENV["BETTER_AUTH_URL"] || ENV["NEXT_PUBLIC_BETTER_AUTH_URL"] || ENV["PUBLIC_BETTER_AUTH_URL"] || ENV["NUXT_PUBLIC_BETTER_AUTH_URL"] || ENV["NUXT_PUBLIC_AUTH_URL"]
|
|
179
|
+
url ||= ENV["BASE_URL"] if ENV["BASE_URL"] && ENV["BASE_URL"] != "/"
|
|
180
|
+
url ? with_path(url, base_path) : nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def loopback_for_dev_scheme?(host)
|
|
184
|
+
hostname = host.to_s.sub(/:\d+\z/, "").sub(/\A\[/, "").sub(/\]\z/, "").downcase
|
|
185
|
+
hostname == "localhost" || hostname.end_with?(".localhost") || hostname == "::1" || hostname.start_with?("127.")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def valid_port?(host)
|
|
189
|
+
port = host[/:(\d{1,5})\z/, 1]
|
|
190
|
+
return true unless port
|
|
191
|
+
|
|
192
|
+
port.to_i.between?(1, 65_535)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
data/lib/better_auth/version.rb
CHANGED
data/lib/better_auth.rb
CHANGED
|
@@ -5,6 +5,14 @@ require_relative "better_auth/core"
|
|
|
5
5
|
require_relative "better_auth/error"
|
|
6
6
|
require_relative "better_auth/api_error"
|
|
7
7
|
require_relative "better_auth/crypto"
|
|
8
|
+
require_relative "better_auth/host"
|
|
9
|
+
require_relative "better_auth/url_helpers"
|
|
10
|
+
require_relative "better_auth/request_state"
|
|
11
|
+
require_relative "better_auth/async"
|
|
12
|
+
require_relative "better_auth/deprecate"
|
|
13
|
+
require_relative "better_auth/logger"
|
|
14
|
+
require_relative "better_auth/instrumentation"
|
|
15
|
+
require_relative "better_auth/oauth2"
|
|
8
16
|
require_relative "better_auth/password"
|
|
9
17
|
require_relative "better_auth/plugin"
|
|
10
18
|
require_relative "better_auth/configuration"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: better_auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sebastian Sala
|
|
@@ -268,6 +268,7 @@ files:
|
|
|
268
268
|
- lib/better_auth/adapters/sqlite.rb
|
|
269
269
|
- lib/better_auth/api.rb
|
|
270
270
|
- lib/better_auth/api_error.rb
|
|
271
|
+
- lib/better_auth/async.rb
|
|
271
272
|
- lib/better_auth/auth.rb
|
|
272
273
|
- lib/better_auth/configuration.rb
|
|
273
274
|
- lib/better_auth/context.rb
|
|
@@ -276,9 +277,14 @@ files:
|
|
|
276
277
|
- lib/better_auth/crypto.rb
|
|
277
278
|
- lib/better_auth/crypto/jwe.rb
|
|
278
279
|
- lib/better_auth/database_hooks.rb
|
|
280
|
+
- lib/better_auth/deprecate.rb
|
|
279
281
|
- lib/better_auth/endpoint.rb
|
|
280
282
|
- lib/better_auth/error.rb
|
|
283
|
+
- lib/better_auth/host.rb
|
|
284
|
+
- lib/better_auth/instrumentation.rb
|
|
285
|
+
- lib/better_auth/logger.rb
|
|
281
286
|
- lib/better_auth/middleware/origin_check.rb
|
|
287
|
+
- lib/better_auth/oauth2.rb
|
|
282
288
|
- lib/better_auth/password.rb
|
|
283
289
|
- lib/better_auth/plugin.rb
|
|
284
290
|
- lib/better_auth/plugin_context.rb
|
|
@@ -322,6 +328,7 @@ files:
|
|
|
322
328
|
- lib/better_auth/plugins/username.rb
|
|
323
329
|
- lib/better_auth/rate_limiter.rb
|
|
324
330
|
- lib/better_auth/request_ip.rb
|
|
331
|
+
- lib/better_auth/request_state.rb
|
|
325
332
|
- lib/better_auth/router.rb
|
|
326
333
|
- lib/better_auth/routes/account.rb
|
|
327
334
|
- lib/better_auth/routes/email_verification.rb
|
|
@@ -375,6 +382,7 @@ files:
|
|
|
375
382
|
- lib/better_auth/social_providers/vk.rb
|
|
376
383
|
- lib/better_auth/social_providers/wechat.rb
|
|
377
384
|
- lib/better_auth/social_providers/zoom.rb
|
|
385
|
+
- lib/better_auth/url_helpers.rb
|
|
378
386
|
- lib/better_auth/version.rb
|
|
379
387
|
homepage: https://github.com/sebasxsala/better-auth
|
|
380
388
|
licenses:
|