module Middleware class SanastoCors ALLOWED_APP_ID = ENV.fetch("SANASTO_APP_ID", "app.sanasto").freeze APP_ID_HEADER = "HTTP_X_SANASTO_APP" def initialize(app) @app = app end def call(env) if allow_cors_for?(env) 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 ] end private def allow_cors_for?(env) 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_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 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"] = 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