Class: Spree::AllowedOrigin

Inherits:
Object
  • Object
show all
Includes:
SingleStoreResource
Defined in:
app/models/spree/allowed_origin.rb

Constant Summary collapse

LOOPBACK_HOSTS =

Loopback/development hosts that match any port, so storing http://localhost keeps matching http://localhost:3000, :4000, etc.

%w[localhost 127.0.0.1 ::1 0.0.0.0].freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.normalize_origin(raw) ⇒ String?

Normalizes free-form input (possibly a bare host like my-shop.vercel.app) to a canonical origin string (scheme://host[:port], the scheme's default port stripped), or nil when it's not a valid http(s) URL.

Parameters:

  • raw (String, nil)

Returns:

  • (String, nil)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/models/spree/allowed_origin.rb', line 41

def self.normalize_origin(raw)
  raw = raw.to_s.strip
  return if raw.blank?

  candidate = raw.match?(%r{\Ahttps?://}i) ? raw : "https://#{raw}"
  parsed = parse_origin(candidate)
  return if parsed.nil?

  origin = "#{parsed[:scheme]}://#{parsed[:host]}"
  # Strip only the scheme's own default — https://host:80 must keep its
  # port, or the normalized origin would silently mean a different one.
  default_port = parsed[:scheme] == 'https' ? 443 : 80
  origin += ":#{parsed[:port]}" unless parsed[:port] == default_port
  origin
end

.parse_origin(url) ⇒ Hash?

Parses a URL into its comparable origin components, or nil when the URL is invalid or not http(s). The host is downcased and has a single trailing dot stripped, and the port is the URI default (80/443) when not explicitly given.

Parameters:

  • url (String)

    the URL to parse

Returns:

  • (Hash, nil)

    { scheme:, host:, port: } or nil



25
26
27
28
29
30
31
32
33
# File 'app/models/spree/allowed_origin.rb', line 25

def self.parse_origin(url)
  uri = URI.parse(url.to_s)
  return nil unless uri.is_a?(URI::HTTP)
  return nil if uri.host.blank?

  { scheme: uri.scheme.downcase, host: uri.host.downcase.chomp('.'), port: uri.port }
rescue URI::InvalidURIError
  nil
end

Instance Method Details

#loopback?Boolean

Whether this origin points at a loopback/development host (LOOPBACK_HOSTS), e.g. the http://localhost origin seeded on install. Loopback origins are ignored when deciding if a real storefront has been connected to the store.

Returns:

  • (Boolean)


62
63
64
# File 'app/models/spree/allowed_origin.rb', line 62

def loopback?
  LOOPBACK_HOSTS.include?(self.class.parse_origin(origin)&.dig(:host))
end

#matches?(url) ⇒ Boolean

Returns true if the given URL's origin matches this stored origin.

Scheme and host must match exactly (host comparison is case- and trailing-dot- insensitive). Port must also match, with the scheme default applied, so storing https://shop.com matches https://shop.com:443. Loopback/development hosts (LOOPBACK_HOSTS) are exempt from the port check, so http://localhost still matches http://localhost:3000, :4000, etc.

Parameters:

  • url (String)

    the candidate URL to check

Returns:

  • (Boolean)


76
77
78
79
80
81
82
83
84
# File 'app/models/spree/allowed_origin.rb', line 76

def matches?(url)
  candidate = self.class.parse_origin(url)
  allowed = self.class.parse_origin(origin)
  return false if candidate.nil? || allowed.nil?
  return false unless allowed[:scheme] == candidate[:scheme]
  return false unless allowed[:host] == candidate[:host]

  LOOPBACK_HOSTS.include?(allowed[:host]) || allowed[:port] == candidate[:port]
end