Class: Otto::Response
- Inherits:
-
Rack::Response
- Object
- Rack::Response
- Otto::Response
- Defined in:
- lib/otto/response.rb
Overview
Otto’s enhanced Rack::Response class with built-in helpers
This class extends Rack::Response with Otto’s framework helpers for HTTP response handling, cookie management, CSP headers, and security. Projects can register additional helpers via Otto#register_response_helpers.
Instance Attribute Summary collapse
-
#request ⇒ Otto::Request
Reference to the request object (needed by some response helpers).
Instance Method Summary collapse
-
#app_path(*paths) ⇒ String
Build application path by joining path segments.
- #cookie_security_headers ⇒ Object
-
#no_cache! ⇒ void
Set cache control headers to prevent caching.
-
#send_csp_headers(content_type, nonce, opts = {}) ⇒ void
Set Content Security Policy (CSP) headers with nonce support.
- #send_secure_cookie(name, value, ttl, opts = {}) ⇒ Object
- #send_session_cookie(name, value, opts = {}) ⇒ Object
Instance Attribute Details
#request ⇒ Otto::Request
Reference to the request object (needed by some response helpers)
25 26 27 |
# File 'lib/otto/response.rb', line 25 def request @request end |
Instance Method Details
#app_path(*paths) ⇒ String
Build application path by joining path segments
This method safely joins multiple path segments, handling duplicate slashes and ensuring proper path formatting. Includes the script name (mount point) as the first segment.
185 186 187 188 189 |
# File 'lib/otto/response.rb', line 185 def app_path(*paths) paths = paths.flatten.compact paths.unshift(request.env['SCRIPT_NAME']) if request&.env&.[]('SCRIPT_NAME') paths.join('/').gsub('//', '/') end |
#cookie_security_headers ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/otto/response.rb', line 81 def # Add security headers that complement cookie security headers = {} # Prevent MIME type sniffing headers['x-content-type-options'] = 'nosniff' # Add referrer policy headers['referrer-policy'] = 'strict-origin-when-cross-origin' # Add frame options headers['x-frame-options'] = 'DENY' # Add XSS protection headers['x-xss-protection'] = '1; mode=block' headers end |
#no_cache! ⇒ void
This method returns an undefined value.
Set cache control headers to prevent caching
This method sets comprehensive cache control headers to ensure that the response is not cached by browsers, proxies, or CDNs. This is particularly useful for sensitive pages or dynamic content that should always be fresh.
163 164 165 166 167 |
# File 'lib/otto/response.rb', line 163 def no_cache! headers['cache-control'] = 'no-store, no-cache, must-revalidate, max-age=0' headers['expires'] = 'Mon, 7 Nov 2011 00:00:00 UTC' headers['pragma'] = 'no-cache' end |
#send_csp_headers(content_type, nonce, opts = {}) ⇒ void
This method returns an undefined value.
Set Content Security Policy (CSP) headers with nonce support
This method generates and sets CSP headers with the provided nonce value, following the same usage pattern as send_cookie methods. The CSP policy is generated dynamically based on the security configuration and environment.
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/otto/response.rb', line 123 def send_csp_headers(content_type, nonce, opts = {}) # Set content type if not already set headers['content-type'] ||= content_type # Warn if CSP header already exists but don't skip warn 'CSP header already set, overriding with nonce-based policy' if headers['content-security-policy'] # Get security configuration security_config = opts[:security_config] || (request&.env && request.env['otto.security_config']) || nil # Skip if CSP nonce support is not enabled return unless security_config&.csp_nonce_enabled? # Generate CSP policy with nonce development_mode = opts[:development_mode] || false csp_policy = security_config.generate_nonce_csp(nonce, development_mode: development_mode) # Debug logging if enabled debug_enabled = opts[:debug] || security_config.debug_csp? if debug_enabled && defined?(Otto.logger) Otto.logger.debug "[CSP] #{csp_policy}" end # Set the CSP header headers['content-security-policy'] = csp_policy end |
#send_secure_cookie(name, value, ttl, opts = {}) ⇒ Object
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/otto/response.rb', line 27 def (name, value, ttl, opts = {}) # Default security options defaults = { secure: true, httponly: true, same_site: :strict, path: '/', } # Merge with provided options = defaults.merge(opts) # Set expiration using max-age (preferred) and expires (fallback) if ttl&.positive? [:max_age] = ttl [:expires] = (Time.now.utc + ttl + 10) elsif ttl&.negative? # For deletion, set both to past date [:max_age] = 0 [:expires] = Time.now.utc - 86_400 end # Set the cookie value [:value] = value # Validate SameSite attribute valid_same_site = [:strict, :lax, :none, 'Strict', 'Lax', 'None'] [:same_site] = :strict unless valid_same_site.include?([:same_site]) # If SameSite=None, Secure must be true [:secure] = true if [:same_site].to_s.downcase == 'none' name, end |
#send_session_cookie(name, value, opts = {}) ⇒ Object
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/otto/response.rb', line 62 def (name, value, opts = {}) # Session cookies don't have expiration session_opts = opts.merge( secure: true, httponly: true, samesite: :strict ) # Remove expiration-related options for session cookies session_opts.delete(:max_age) session_opts.delete(:expires) # Adjust secure flag for local development session_opts[:secure] = false if request.local? session_opts[:value] = value name, session_opts end |