diff --git a/frappe/app.py b/frappe/app.py index 2ba6432a89..2176a1bc5a 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -157,35 +157,45 @@ def process_response(response): response.headers.extend(frappe.local.rate_limiter.headers()) # CORS headers - if hasattr(frappe.local, "conf") and frappe.conf.allow_cors: + if hasattr(frappe.local, "conf"): set_cors_headers(response) def set_cors_headers(response): - origin = frappe.request.headers.get("Origin") - allow_cors = frappe.conf.allow_cors - if not (origin and allow_cors): + if not ( + (allowed_origins := frappe.conf.allow_cors) + and (request := frappe.local.request) + and (origin := request.headers.get("Origin")) + ): return - if allow_cors != "*": - if not isinstance(allow_cors, list): - allow_cors = [allow_cors] + if allowed_origins != "*": + if not isinstance(allowed_origins, list): + allowed_origins = [allowed_origins] - if origin not in allow_cors: + if origin not in allowed_origins: return - response.headers.extend( - { - "Access-Control-Allow-Origin": origin, - "Access-Control-Allow-Credentials": "true", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", - "Access-Control-Allow-Headers": ( - "Authorization,DNT,X-Mx-ReqToken," - "Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since," - "Cache-Control,Content-Type" - ), - } - ) + cors_headers = { + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": origin, + "Vary": "Origin", + } + + # only required for preflight requests + if request.method == "OPTIONS": + cors_headers["Access-Control-Allow-Methods"] = request.headers.get( + "Access-Control-Request-Method" + ) + + if allowed_headers := request.headers.get("Access-Control-Request-Headers"): + cors_headers["Access-Control-Allow-Headers"] = allowed_headers + + # allow browsers to cache preflight requests for upto a day + if not frappe.conf.developer_mode: + cors_headers["Access-Control-Max-Age"] = "86400" + + response.headers.extend(cors_headers) def make_form_dict(request): diff --git a/frappe/tests/test_cors.py b/frappe/tests/test_cors.py index 0712c8d24a..1974c174db 100644 --- a/frappe/tests/test_cors.py +++ b/frappe/tests/test_cors.py @@ -11,6 +11,7 @@ HEADERS = ( "Access-Control-Allow-Credentials", "Access-Control-Allow-Methods", "Access-Control-Allow-Headers", + "Vary", ) @@ -20,9 +21,13 @@ class TestCORS(FrappeTestCase): headers = {} if origin: - headers = {"Origin": origin} + headers = { + "Origin": origin, + "Access-Control-Request-Method": "POST", + "Access-Control-Request-Headers": "X-Test-Header", + } - frappe.utils.set_request(headers=headers) + frappe.utils.set_request(method="OPTIONS", headers=headers) self.response = Response() process_response(self.response)