From 4ac907a26bf93e4ba0d22c3ece0469449cbdd8e9 Mon Sep 17 00:00:00 2001 From: Aadit M Shah Date: Wed, 6 Mar 2024 21:45:48 +0530 Subject: [PATCH] fix: change the type of allowOrigin to string (#5741) (#5825) Fixes #5741 The `Access-Control-Allow-Origin` header must contain only one origin. If we provide an array with more than one origin, then the origins are joined with commas. This causes the CORS preflight to fail because the browser expects only one origin. The proper solution to this problem would be to select one of the origins from the list. However, as a temporary fix, I changed the type of `allowOrigin` from `Array` to simply `string`. The expectation is that the developer should only provide one origin. This prevents the CORS preflight from failing. However, it also disallows developers from having more than one origin. *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md).* ## Checklist - [ ] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [ ] Description explains motivation and solution - [ ] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- docs/docs/04-standard-library/cloud/api.md | 16 +++++++-------- examples/tests/valid/api_cors_custom.test.w | 4 ++-- examples/tests/valid/website_with_api.test.w | 2 +- .../incomplete_inflight_namespace.snap | 2 +- .../completions/namespace_middle_dot.snap | 2 +- .../partial_type_reference_annotation.snap | 2 +- .../variable_type_annotation_namespace.snap | 2 +- libs/wingsdk/src/cloud/api.ts | 20 +++++++++---------- libs/wingsdk/test/target-sim/api.test.ts | 2 +- .../api_cors_custom.test.w_compile_tf-aws.md | 2 +- .../website_with_api.test.w_compile_tf-aws.md | 2 +- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/docs/04-standard-library/cloud/api.md b/docs/docs/04-standard-library/cloud/api.md index eee75ac46aa..42399e40325 100644 --- a/docs/docs/04-standard-library/cloud/api.md +++ b/docs/docs/04-standard-library/cloud/api.md @@ -521,7 +521,7 @@ let ApiCorsOptions = cloud.ApiCorsOptions{ ... }; | allowCredentials | bool | Whether to allow credentials. | | allowHeaders | MutArray<str> | The list of allowed headers. | | allowMethods | MutArray<HttpMethod> | The list of allowed methods. | -| allowOrigin | MutArray<str> | The list of allowed allowOrigin. | +| allowOrigin | str | The allowed origin. | | exposeHeaders | MutArray<str> | The list of exposed headers. | | maxAge | duration | How long the browser should cache preflight request results. | @@ -583,20 +583,20 @@ The list of allowed methods. ##### `allowOrigin`Optional ```wing -allowOrigin: MutArray; +allowOrigin: str; ``` -- *Type:* MutArray<str> -- *Default:* ["*"] +- *Type:* str +- *Default:* "*" -The list of allowed allowOrigin. +The allowed origin. --- *Example* ```wing -["https://example.com"] +"https://example.com" ``` @@ -763,7 +763,7 @@ corsOptions: ApiCorsOptions; ``` - *Type:* ApiCorsOptions -- *Default:* Default CORS options are applied when `cors` is set to `true` allowOrigin: ["*"], allowMethods: [ HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS, ], allowHeaders: ["Content-Type", "Authorization"], exposeHeaders: [], allowCredentials: false, +- *Default:* Default CORS options are applied when `cors` is set to `true` allowOrigin: "*", allowMethods: [ HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS, ], allowHeaders: ["Content-Type", "Authorization"], exposeHeaders: [], allowCredentials: false, Options for configuring the API's CORS behavior across all routes. @@ -774,7 +774,7 @@ Options can also be overridden on a per-route basis. (not yet implemented) *Example* ```wing -{ allowOrigin: ["https://example.com"] } +{ allowOrigin: "https://example.com" } ``` diff --git a/examples/tests/valid/api_cors_custom.test.w b/examples/tests/valid/api_cors_custom.test.w index 6abf47e2b11..20b71b971e5 100644 --- a/examples/tests/valid/api_cors_custom.test.w +++ b/examples/tests/valid/api_cors_custom.test.w @@ -6,7 +6,7 @@ bring expect; let api = new cloud.Api( cors: true, corsOptions: { - allowOrigin: ["winglang.io"], + allowOrigin: "winglang.io", allowMethods: [cloud.HttpMethod.GET, cloud.HttpMethod.POST, cloud.HttpMethod.OPTIONS], allowHeaders: ["Content-Type", "Authorization", "X-Custom-Header"], allowCredentials: true, @@ -70,4 +70,4 @@ test "OPTIONS /users responds with proper headers for requested" { expect.equal(headers.tryGet("access-control-allow-methods"), "GET,POST,OPTIONS"); expect.equal(headers.tryGet("access-control-allow-headers"), "Content-Type,Authorization,X-Custom-Header"); expect.equal(headers.tryGet("access-control-allow-origin"), "winglang.io"); -} \ No newline at end of file +} diff --git a/examples/tests/valid/website_with_api.test.w b/examples/tests/valid/website_with_api.test.w index 4f2223b3363..5590e5bd30e 100644 --- a/examples/tests/valid/website_with_api.test.w +++ b/examples/tests/valid/website_with_api.test.w @@ -7,7 +7,7 @@ bring expect; let api = new cloud.Api( cors: true, corsOptions: cloud.ApiCorsOptions { - allowOrigin: ["*"], + allowOrigin: "*", allowMethods: [cloud.HttpMethod.GET, cloud.HttpMethod.POST, cloud.HttpMethod.OPTIONS], allowHeaders: ["Content-Type"], allowCredentials: false, diff --git a/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap b/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap index d70c0c88493..a121951c269 100644 --- a/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap +++ b/libs/wingc/src/lsp/snapshots/completions/incomplete_inflight_namespace.snap @@ -95,7 +95,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `Array?` — The list of allowed allowOrigin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." + value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `str?` — The allowed origin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." sortText: hh|ApiCorsOptions - label: ApiDeleteOptions kind: 22 diff --git a/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap b/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap index d70c0c88493..a121951c269 100644 --- a/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap +++ b/libs/wingc/src/lsp/snapshots/completions/namespace_middle_dot.snap @@ -95,7 +95,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `Array?` — The list of allowed allowOrigin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." + value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `str?` — The allowed origin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." sortText: hh|ApiCorsOptions - label: ApiDeleteOptions kind: 22 diff --git a/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap b/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap index d70c0c88493..a121951c269 100644 --- a/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap +++ b/libs/wingc/src/lsp/snapshots/completions/partial_type_reference_annotation.snap @@ -95,7 +95,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `Array?` — The list of allowed allowOrigin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." + value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `str?` — The allowed origin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." sortText: hh|ApiCorsOptions - label: ApiDeleteOptions kind: 22 diff --git a/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap b/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap index d70c0c88493..a121951c269 100644 --- a/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap +++ b/libs/wingc/src/lsp/snapshots/completions/variable_type_annotation_namespace.snap @@ -95,7 +95,7 @@ source: libs/wingc/src/lsp/completions.rs kind: 22 documentation: kind: markdown - value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `Array?` — The list of allowed allowOrigin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." + value: "```wing\nstruct ApiCorsOptions\n```\n---\nCors Options for `Api`.\n### Fields\n- `allowCredentials?` — `bool?` — Whether to allow credentials.\n- `allowHeaders?` — `Array?` — The list of allowed headers.\n- `allowMethods?` — `Array?` — The list of allowed methods.\n- `allowOrigin?` — `str?` — The allowed origin.\n- `exposeHeaders?` — `Array?` — The list of exposed headers.\n- `maxAge?` — `duration?` — How long the browser should cache preflight request results." sortText: hh|ApiCorsOptions - label: ApiDeleteOptions kind: 22 diff --git a/libs/wingsdk/src/cloud/api.ts b/libs/wingsdk/src/cloud/api.ts index 2ab8748ed58..8cd2fc62139 100644 --- a/libs/wingsdk/src/cloud/api.ts +++ b/libs/wingsdk/src/cloud/api.ts @@ -16,11 +16,11 @@ export const API_FQN = fqnForType("cloud.Api"); */ export interface ApiCorsOptions { /** - * The list of allowed allowOrigin. - * @example ["https://example.com"] - * @default - ["*"] + * The allowed origin. + * @example "https://example.com" + * @default - "*" */ - readonly allowOrigin?: Array; + readonly allowOrigin?: string; /** * The list of allowed methods. @@ -75,9 +75,9 @@ export interface ApiProps { * Options for configuring the API's CORS behavior across all routes. * Options can also be overridden on a per-route basis. (not yet implemented) * - * @example { allowOrigin: ["https://example.com"] } + * @example { allowOrigin: "https://example.com" } * @default - Default CORS options are applied when `cors` is set to `true` - * allowOrigin: ["*"], + * allowOrigin: "*", * allowMethods: [ * HttpMethod.GET, * HttpMethod.POST, @@ -209,7 +209,7 @@ export class Api extends Resource { }; private corsDefaultValues: ApiCorsOptions = { - allowOrigin: ["*"], + allowOrigin: "*", allowMethods: [ HttpMethod.GET, HttpMethod.POST, @@ -502,7 +502,7 @@ export class Api extends Resource { } const { - allowOrigin = [], + allowOrigin = "*", allowHeaders = [], allowMethods = [], exposeHeaders = [], @@ -511,13 +511,13 @@ export class Api extends Resource { } = corsOptions; const defaultHeaders: CorsDefaultResponseHeaders = { - "Access-Control-Allow-Origin": allowOrigin.join(",") || "", + "Access-Control-Allow-Origin": allowOrigin || "*", "Access-Control-Expose-Headers": exposeHeaders.join(",") || "", "Access-Control-Allow-Credentials": allowCredentials ? "true" : "false", }; const optionsHeaders: CorsOptionsResponseHeaders = { - "Access-Control-Allow-Origin": allowOrigin.join(",") || "", + "Access-Control-Allow-Origin": allowOrigin || "*", "Access-Control-Allow-Headers": allowHeaders.join(",") || "", "Access-Control-Allow-Methods": allowMethods.join(",") || "", "Access-Control-Max-Age": maxAge.seconds.toString(), diff --git a/libs/wingsdk/test/target-sim/api.test.ts b/libs/wingsdk/test/target-sim/api.test.ts index 52e64f20ca9..9474e18578d 100644 --- a/libs/wingsdk/test/target-sim/api.test.ts +++ b/libs/wingsdk/test/target-sim/api.test.ts @@ -669,7 +669,7 @@ test("api with custom CORS settings", async () => { const api = new cloud.Api(app, "my_api", { cors: true, corsOptions: { - allowOrigin: ["https://example.com"], + allowOrigin: "https://example.com", allowCredentials: true, exposeHeaders: ["x-wingnuts"], }, diff --git a/tools/hangar/__snapshots__/test_corpus/valid/api_cors_custom.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/api_cors_custom.test.w_compile_tf-aws.md index fbb8a9e0e3a..a855ebae484 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/api_cors_custom.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/api_cors_custom.test.w_compile_tf-aws.md @@ -484,7 +484,7 @@ class $Root extends $stdlib.std.Resource { }); } } - const api = this.node.root.new("@winglang/sdk.cloud.Api", cloud.Api, this, "cloud.Api", { cors: true, corsOptions: ({"allowOrigin": ["winglang.io"], "allowMethods": [cloud.HttpMethod.GET, cloud.HttpMethod.POST, cloud.HttpMethod.OPTIONS], "allowHeaders": ["Content-Type", "Authorization", "X-Custom-Header"], "allowCredentials": true, "exposeHeaders": ["Content-Type"]}) }); + const api = this.node.root.new("@winglang/sdk.cloud.Api", cloud.Api, this, "cloud.Api", { cors: true, corsOptions: ({"allowOrigin": "winglang.io", "allowMethods": [cloud.HttpMethod.GET, cloud.HttpMethod.POST, cloud.HttpMethod.OPTIONS], "allowHeaders": ["Content-Type", "Authorization", "X-Custom-Header"], "allowCredentials": true, "exposeHeaders": ["Content-Type"]}) }); (api.get("/users", new $Closure1(this, "$Closure1"))); this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:GET /users has cors headers", new $Closure2(this, "$Closure2")); this.node.root.new("@winglang/sdk.std.Test", std.Test, this, "test:OPTIONS /users has cors headers", new $Closure3(this, "$Closure3")); diff --git a/tools/hangar/__snapshots__/test_corpus/valid/website_with_api.test.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/valid/website_with_api.test.w_compile_tf-aws.md index 1a701c26b29..ed042e83088 100644 --- a/tools/hangar/__snapshots__/test_corpus/valid/website_with_api.test.w_compile_tf-aws.md +++ b/tools/hangar/__snapshots__/test_corpus/valid/website_with_api.test.w_compile_tf-aws.md @@ -783,7 +783,7 @@ class $Root extends $stdlib.std.Resource { }); } } - const api = this.node.root.new("@winglang/sdk.cloud.Api", cloud.Api, this, "cloud.Api", { cors: true, corsOptions: ({"allowOrigin": ["*"], "allowMethods": [cloud.HttpMethod.GET, cloud.HttpMethod.POST, cloud.HttpMethod.OPTIONS], "allowHeaders": ["Content-Type"], "allowCredentials": false, "exposeHeaders": ["Content-Type"], "maxAge": (std.Duration.fromSeconds(600))}) }); + const api = this.node.root.new("@winglang/sdk.cloud.Api", cloud.Api, this, "cloud.Api", { cors: true, corsOptions: ({"allowOrigin": "*", "allowMethods": [cloud.HttpMethod.GET, cloud.HttpMethod.POST, cloud.HttpMethod.OPTIONS], "allowHeaders": ["Content-Type"], "allowCredentials": false, "exposeHeaders": ["Content-Type"], "maxAge": (std.Duration.fromSeconds(600))}) }); const website = this.node.root.new("@winglang/sdk.cloud.Website", cloud.Website, this, "cloud.Website", { path: "./website_with_api" }); const usersTable = this.node.root.new("@winglang/sdk.ex.Table", ex.Table, this, "ex.Table", { name: "users-table", primaryKey: "id", columns: ({["id"]: ex.ColumnType.STRING, ["name"]: ex.ColumnType.STRING, ["age"]: ex.ColumnType.NUMBER}) }); const getHandler = new $Closure1(this, "$Closure1");