From e15835bda935db32165da20c2e6ba11159bc6ce6 Mon Sep 17 00:00:00 2001 From: Runar Ingebrigtsen Date: Thu, 5 Feb 2026 23:59:11 +0100 Subject: [PATCH] support browser cors request --- lib/middleware/sanasto_cors.rb | 59 +++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/lib/middleware/sanasto_cors.rb b/lib/middleware/sanasto_cors.rb index 67f3475..ecffa85 100644 --- a/lib/middleware/sanasto_cors.rb +++ b/lib/middleware/sanasto_cors.rb @@ -9,14 +9,16 @@ module Middleware def call(env) if allow_cors_for?(env) - return preflight_response(env["HTTP_ORIGIN"]) if env["REQUEST_METHOD"] == "OPTIONS" + if env["REQUEST_METHOD"] == "OPTIONS" + return preflight_response(env["HTTP_ORIGIN"], allowed_request_headers(env)) + end end status, headers, body = @app.call(env) if allow_cors_for?(env) apply_cors_headers(headers, env["HTTP_ORIGIN"]) end - [status, headers, body] + [ status, headers, body ] end private @@ -25,25 +27,60 @@ module Middleware origin = env["HTTP_ORIGIN"].to_s return false if origin.empty? + if env["REQUEST_METHOD"] == "OPTIONS" + return preflight_includes_app_id_header?(env) + end + app_id = env[APP_ID_HEADER].to_s return false if app_id.empty? app_id == ALLOWED_APP_ID end - def preflight_response(origin) - headers = {} - apply_cors_headers(headers, origin) - headers["Access-Control-Max-Age"] = "86400" - [204, headers, []] + def preflight_includes_app_id_header?(env) + access_control_headers = env["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"].to_s + return false if access_control_headers.empty? + + access_control_headers + .split(",") + .map { |header_name| header_name.strip.downcase } + .include?("x-sanasto-app") end - def apply_cors_headers(headers, origin) + def allowed_request_headers(env) + access_control_headers = env["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"].to_s + return default_allowed_headers if access_control_headers.empty? + + sanitized = access_control_headers + .split(",") + .map { |header_name| header_name.strip } + .reject(&:empty?) + .join(", ") + + sanitized.empty? ? default_allowed_headers : sanitized + end + + def preflight_response(origin, allowed_headers) + headers = {} + apply_cors_headers(headers, origin, allowed_headers) + headers["Access-Control-Max-Age"] = "86400" + headers["Vary"] = [ + headers["Vary"], + "Access-Control-Request-Headers", + "Access-Control-Request-Method" + ].compact.join(", ") + [ 204, headers, [] ] + end + + def apply_cors_headers(headers, origin, allowed_headers = default_allowed_headers) headers["Access-Control-Allow-Origin"] = origin headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS" - headers["Access-Control-Allow-Headers"] = - "Origin, Content-Type, Accept, Authorization, X-Sanasto-App" - headers["Vary"] = [headers["Vary"], "Origin, X-Sanasto-App"].compact.join(", ") + headers["Access-Control-Allow-Headers"] = allowed_headers + headers["Vary"] = [ headers["Vary"], "Origin, X-Sanasto-App" ].compact.join(", ") + end + + def default_allowed_headers + "Origin, Content-Type, Accept, Authorization, X-Sanasto-App" end end end