Module: BetterAuth::URLHelpers
- Defined in:
- lib/better_auth/url_helpers.rb
Class Method Summary collapse
- .dynamic_config?(config) ⇒ Boolean
- .env_base_url(base_path) ⇒ Object
- .header_value(headers, key) ⇒ Object
- .headers_from_source(source) ⇒ Object
- .host_from_source(source, trusted_proxy_headers: false) ⇒ Object
- .loopback_for_dev_scheme?(host) ⇒ Boolean
- .matches_host_pattern?(host, pattern) ⇒ Boolean
- .normalize_host_pattern_value(value) ⇒ Object
- .origin(url) ⇒ Object
- .protocol_from_source(source, config_protocol: nil, trusted_proxy_headers: false) ⇒ Object
- .resolve_base_url(config, base_path, source = nil, load_env: true, trusted_proxy_headers: false) ⇒ Object
- .resolve_dynamic_base_url(config, source, base_path, trusted_proxy_headers: false) ⇒ Object
- .source_url(source) ⇒ Object
- .uri_host(url) ⇒ Object
- .uri_scheme(url) ⇒ Object
- .valid_port?(host) ⇒ Boolean
- .valid_proxy_header?(header, type) ⇒ Boolean
- .with_path(url, path = "/api/auth") ⇒ Object
Class Method Details
.dynamic_config?(config) ⇒ Boolean
173 174 175 |
# File 'lib/better_auth/url_helpers.rb', line 173 def dynamic_config?(config) config.is_a?(Hash) && (config.key?(:allowed_hosts) || config.key?("allowed_hosts") || config.key?(:allowedHosts) || config.key?("allowedHosts")) end |
.env_base_url(base_path) ⇒ Object
177 178 179 180 181 |
# File 'lib/better_auth/url_helpers.rb', line 177 def env_base_url(base_path) 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"] url ||= ENV["BASE_URL"] if ENV["BASE_URL"] && ENV["BASE_URL"] != "/" url ? with_path(url, base_path) : nil end |
.header_value(headers, key) ⇒ Object
161 162 163 164 165 166 167 |
# File 'lib/better_auth/url_helpers.rb', line 161 def header_value(headers, key) if headers.respond_to?(:get) headers.get(key) else headers[key] || headers[key.to_s] || headers[key.to_s.downcase] || headers[key.to_s.upcase] || headers[key.tr("-", "_").upcase] end end |
.headers_from_source(source) ⇒ Object
153 154 155 156 157 158 159 |
# File 'lib/better_auth/url_helpers.rb', line 153 def headers_from_source(source) return {} unless source return source.headers if source.respond_to?(:headers) return source if source.is_a?(Hash) {} end |
.host_from_source(source, trusted_proxy_headers: false) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/better_auth/url_helpers.rb', line 43 def host_from_source(source, trusted_proxy_headers: false) headers = headers_from_source(source) if trusted_proxy_headers forwarded_host = header_value(headers, "x-forwarded-host") return forwarded_host if forwarded_host && valid_proxy_header?(forwarded_host, :host) end host = header_value(headers, "host") return host if host && valid_proxy_header?(host, :host) uri_host(source_url(source)) end |
.loopback_for_dev_scheme?(host) ⇒ Boolean
183 184 185 186 |
# File 'lib/better_auth/url_helpers.rb', line 183 def loopback_for_dev_scheme?(host) hostname = host.to_s.sub(/:\d+\z/, "").sub(/\A\[/, "").sub(/\]\z/, "").downcase hostname == "localhost" || hostname.end_with?(".localhost") || hostname == "::1" || hostname.start_with?("127.") end |
.matches_host_pattern?(host, pattern) ⇒ Boolean
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/better_auth/url_helpers.rb', line 32 def matches_host_pattern?(host, pattern) return false if host.to_s.empty? || pattern.to_s.empty? normalized_host = normalize_host_pattern_value(host) normalized_pattern = normalize_host_pattern_value(pattern) regex = Regexp.escape(normalized_pattern) .gsub("\\*", ".*") .gsub("\\?", ".") !!normalized_host.match?(/\A#{regex}\z/i) end |
.normalize_host_pattern_value(value) ⇒ Object
149 150 151 |
# File 'lib/better_auth/url_helpers.rb', line 149 def normalize_host_pattern_value(value) value.to_s.sub(%r{\Ahttps?://}i, "").split("/").first.to_s.downcase end |
.origin(url) ⇒ Object
122 123 124 125 126 127 128 129 130 131 |
# File 'lib/better_auth/url_helpers.rb', line 122 def origin(url) parsed = URI.parse(url.to_s) return nil unless ["http", "https"].include?(parsed.scheme) port = parsed.port default_port = (parsed.scheme == "http" && port == 80) || (parsed.scheme == "https" && port == 443) default_port ? "#{parsed.scheme}://#{parsed.host}" : "#{parsed.scheme}://#{parsed.host}:#{port}" rescue URI::InvalidURIError nil end |
.protocol_from_source(source, config_protocol: nil, trusted_proxy_headers: false) ⇒ Object
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/better_auth/url_helpers.rb', line 56 def protocol_from_source(source, config_protocol: nil, trusted_proxy_headers: false) return config_protocol if ["http", "https"].include?(config_protocol) headers = headers_from_source(source) if trusted_proxy_headers forwarded_proto = header_value(headers, "x-forwarded-proto") return forwarded_proto if forwarded_proto && valid_proxy_header?(forwarded_proto, :proto) end protocol = uri_scheme(source_url(source)) return protocol if ["http", "https"].include?(protocol) host = host_from_source(source, trusted_proxy_headers: trusted_proxy_headers) return "http" if host && loopback_for_dev_scheme?(host) "https" end |
.resolve_base_url(config, base_path, source = nil, load_env: true, trusted_proxy_headers: false) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/better_auth/url_helpers.rb', line 74 def resolve_base_url(config, base_path, source = nil, load_env: true, trusted_proxy_headers: false) if dynamic_config?(config) return resolve_dynamic_base_url(config, source, base_path, trusted_proxy_headers: trusted_proxy_headers) if source return with_path(config[:fallback] || config["fallback"], base_path) if config[:fallback] || config["fallback"] return env_base_url(base_path) if load_env return nil end return with_path(config, base_path) if config.is_a?(String) return env_base_url(base_path) if load_env return with_path(origin(source_url(source)), base_path) if source nil end |
.resolve_dynamic_base_url(config, source, base_path, trusted_proxy_headers: false) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/better_auth/url_helpers.rb', line 90 def resolve_dynamic_base_url(config, source, base_path, trusted_proxy_headers: false) host = host_from_source(source, trusted_proxy_headers: trusted_proxy_headers) fallback = config[:fallback] || config["fallback"] raise Error, "Could not determine host from request headers. Please provide a fallback URL in your baseURL config." unless host || fallback allowed_hosts = config[:allowed_hosts] || config["allowed_hosts"] || config[:allowedHosts] || config["allowedHosts"] || [] if host && allowed_hosts.any? { |pattern| matches_host_pattern?(host, pattern) } protocol = protocol_from_source(source, config_protocol: config[:protocol] || config["protocol"], trusted_proxy_headers: trusted_proxy_headers) return with_path("#{protocol}://#{host}", base_path) end return with_path(fallback, base_path) if fallback raise Error, "Host \"#{host}\" is not in the allowed hosts list." end |
.source_url(source) ⇒ Object
169 170 171 |
# File 'lib/better_auth/url_helpers.rb', line 169 def source_url(source) source.url if source.respond_to?(:url) end |
.uri_host(url) ⇒ Object
133 134 135 136 137 138 139 140 141 |
# File 'lib/better_auth/url_helpers.rb', line 133 def uri_host(url) parsed = URI.parse(url.to_s) return nil unless parsed.host default_port = (parsed.scheme == "http" && parsed.port == 80) || (parsed.scheme == "https" && parsed.port == 443) default_port ? parsed.host : "#{parsed.host}:#{parsed.port}" rescue URI::InvalidURIError nil end |
.uri_scheme(url) ⇒ Object
143 144 145 146 147 |
# File 'lib/better_auth/url_helpers.rb', line 143 def uri_scheme(url) URI.parse(url.to_s).scheme rescue URI::InvalidURIError nil end |
.valid_port?(host) ⇒ Boolean
188 189 190 191 192 193 |
# File 'lib/better_auth/url_helpers.rb', line 188 def valid_port?(host) port = host[/:(\d{1,5})\z/, 1] return true unless port port.to_i.between?(1, 65_535) end |
.valid_proxy_header?(header, type) ⇒ Boolean
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/better_auth/url_helpers.rb', line 9 def valid_proxy_header?(header, type) value = header.to_s return false if value.strip.empty? case type.to_sym when :proto ["http", "https"].include?(value) when :host return false if value.match?(/\.\.|\0|\s|\A[.]|[<>'"]|javascript:|file:|data:/i) return false if value.match?(%r{[/\\]}) patterns = [ /\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/, /\A(\d{1,3}\.){3}\d{1,3}(:[0-9]{1,5})?\z/, /\A\[[0-9a-fA-F:]+\](:[0-9]{1,5})?\z/, /\Alocalhost(:[0-9]{1,5})?\z/i ] patterns.any? { |pattern| value.match?(pattern) } && valid_port?(value) else false end end |
.with_path(url, path = "/api/auth") ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/better_auth/url_helpers.rb', line 106 def with_path(url, path = "/api/auth") parsed = URI.parse(url.to_s) raise Error, "Invalid base URL: #{url}. URL must include 'http://' or 'https://'" unless ["http", "https"].include?(parsed.scheme) current_path = parsed.path.to_s.gsub(%r{/+\z}, "") return url.to_s if !current_path.empty? && current_path != "/" trimmed = url.to_s.gsub(%r{/+\z}, "") return trimmed if path.to_s.empty? || path == "/" suffix = path.start_with?("/") ? path : "/#{path}" "#{trimmed}#{suffix}" rescue URI::InvalidURIError raise Error, "Invalid base URL: #{url}. Please provide a valid base URL." end |