Module: Railsmith::CrossDomainGuard

Defined in:
lib/railsmith/cross_domain_guard.rb

Overview

Detects when a service from one bounded context runs under another domain’s request context (context[:current_domain]). Emits non-blocking cross_domain.warning.railsmith instrumentation by default; optional strict hook runs when strict_mode is enabled.

Class Method Summary collapse

Class Method Details

.allowed_crossing?(allowlist, from_domain, to_domain) ⇒ Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/railsmith/cross_domain_guard.rb', line 58

def self.allowed_crossing?(allowlist, from_domain, to_domain)
  Array(allowlist).any? { |entry| pair_matches?(entry, from_domain, to_domain) }
end

.allowlisted?(configuration, mismatch) ⇒ Boolean

Returns:

  • (Boolean)


20
21
22
23
24
25
26
# File 'lib/railsmith/cross_domain_guard.rb', line 20

def self.allowlisted?(configuration, mismatch)
  allowed_crossing?(
    configuration.cross_domain_allowlist,
    mismatch[:context_domain],
    mismatch[:service_domain]
  )
end

.array_pair_matches?(entry, from_domain, to_domain) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
83
84
85
# File 'lib/railsmith/cross_domain_guard.rb', line 80

def self.array_pair_matches?(entry, from_domain, to_domain)
  return false unless entry.size == 2

  Context.normalize_current_domain(entry[0]) == from_domain &&
    Context.normalize_current_domain(entry[1]) == to_domain
end

.build_payload(context_domain:, service_domain:, service:, action:, strict_mode:) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/railsmith/cross_domain_guard.rb', line 87

def self.build_payload(context_domain:, service_domain:, service:, action:, strict_mode:)
  {
    event: "cross_domain.warning",
    context_domain: context_domain,
    service_domain: service_domain,
    service: service,
    action: action,
    strict_mode: strict_mode,
    blocking: false,
    occurred_at: Time.now.utc.iso8601(6)
  }
end

.domain_mismatch(instance) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/railsmith/cross_domain_guard.rb', line 49

def self.domain_mismatch(instance)
  context_domain = Context.normalize_current_domain(instance.context[:current_domain])
  service_domain = instance.class.domain
  return nil if context_domain.nil? || service_domain.nil?
  return nil if context_domain == service_domain

  { context_domain: context_domain, service_domain: service_domain }
end

.emit_if_violation(instance:, action:, configuration: Railsmith.configuration) ⇒ Object



11
12
13
14
15
16
17
18
# File 'lib/railsmith/cross_domain_guard.rb', line 11

def self.emit_if_violation(instance:, action:, configuration: Railsmith.configuration)
  return unless configuration.warn_on_cross_domain_calls

  mismatch = domain_mismatch(instance)
  return if mismatch.nil? || allowlisted?(configuration, mismatch)

  publish_violation(instance:, action:, configuration:, mismatch:)
end

.hash_pair_matches?(entry, from_domain, to_domain) ⇒ Boolean

Returns:

  • (Boolean)


73
74
75
76
77
78
# File 'lib/railsmith/cross_domain_guard.rb', line 73

def self.hash_pair_matches?(entry, from_domain, to_domain)
  from_key = entry[:from] || entry["from"]
  to_key = entry[:to] || entry["to"]
  Context.normalize_current_domain(from_key) == from_domain &&
    Context.normalize_current_domain(to_key) == to_domain
end

.instrument_payload(base) ⇒ Object



42
43
44
45
46
47
# File 'lib/railsmith/cross_domain_guard.rb', line 42

def self.instrument_payload(base)
  base.merge(
    log_json_line: CrossDomainWarningFormatter.as_json_line(base),
    log_kv_line: CrossDomainWarningFormatter.as_key_value_line(base)
  )
end

.pair_matches?(entry, from_domain, to_domain) ⇒ Boolean

Returns:

  • (Boolean)


62
63
64
65
66
67
68
69
70
71
# File 'lib/railsmith/cross_domain_guard.rb', line 62

def self.pair_matches?(entry, from_domain, to_domain)
  case entry
  when Hash
    hash_pair_matches?(entry, from_domain, to_domain)
  when Array
    array_pair_matches?(entry, from_domain, to_domain)
  else
    false
  end
end

.publish_violation(instance:, action:, configuration:, mismatch:) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/railsmith/cross_domain_guard.rb', line 28

def self.publish_violation(instance:, action:, configuration:, mismatch:)
  base = build_payload(
    context_domain: mismatch[:context_domain],
    service_domain: mismatch[:service_domain],
    service: instance.class.name,
    action: action,
    strict_mode: configuration.strict_mode
  )
  payload = instrument_payload(base)

  Instrumentation.instrument("cross_domain.warning", payload)
  configuration.on_cross_domain_violation&.call(payload) if configuration.strict_mode
end