Skip to content

Commit

Permalink
feat(sdk)!: cloud.Bucket supports CORS by default (#7104)
Browse files Browse the repository at this point in the history
Closes #7058 
Closes #7048

Currently, signed URLs created with `cloud.Bucket` can't be used by web applications in Wing since CORS options aren't configurable. CORS (Cross-Origin Resource Sharing) affects how the browser can access other domains. To solve this, we add two new fields, `cors` and `corsOptions` to the props of `cloud.Bucket`.

We've configured `cors: true` as the default so that these kinds of use cases for using signed URLs will work out of the box. If the bucket is private, CORS settings still apply, but they only come into effect when the bucket's objects are accessed through a signed URL or by authenticated users.

BREAKING CHANGE: `cloud.Bucket` now is synthesized with CORS configuration by default (currently only supported on AWS). If your bucket is public or you are using signed URLs extensively, you may want to consider configuring the `corsOptions` on the bucket or disabling CORS based on your application's needs.

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [x] Tests added (always)
- [x] 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)*.
  • Loading branch information
Chriscbr committed Sep 11, 2024
1 parent 0242cf9 commit 3528054
Show file tree
Hide file tree
Showing 100 changed files with 4,967 additions and 67 deletions.
217 changes: 217 additions & 0 deletions docs/api/04-standard-library/cloud/bucket.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,41 @@ store.onDelete(inflight (key: str) => {
});
```

### Configuring CORS

By default, buckets are configured with CORS for any origin. When a bucket is private (the default), CORS options only come into play when the bucket's objects are accessed through a signed URL.

```js playground example
bring cloud;

let uploads = new cloud.Bucket(
// these are the default values
public: false,
cors: true,
corsOptions: {
allowedMethods: [http.HttpMethod.GET, http.HttpMethod.POST, http.HttpMethod.PUT, http.HttpMethod.DELETE, http.HttpMethod.HEAD]
allowedOrigins: ["*"],
allowedHeaders: ["*"],
exposeHeaders: [],
maxAge: 0s
},
)
```

The CORS configuration can be disabled by passing `cors: false` to the constructor. CORS rules can also be configured after the bucket is created by calling the `addCorsRule` method:

```js playground example
bring cloud;

let bucket = new cloud.Bucket(
cors: false, // disable any default CORS rules
);

bucket.addCorsRule({
allowedOrigins: ["https://example.com"],
});
```

## Target-specific details

### Simulator (`sim`)
Expand Down Expand Up @@ -150,6 +185,7 @@ new cloud.Bucket(props?: BucketProps);

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@winglang/sdk.cloud.Bucket.addCorsRule">addCorsRule</a></code> | Add cors configuration to the bucket. |
| <code><a href="#@winglang/sdk.cloud.Bucket.addFile">addFile</a></code> | Add a file to the bucket from system folder. |
| <code><a href="#@winglang/sdk.cloud.Bucket.addObject">addObject</a></code> | Add a file to the bucket that is uploaded when the app is deployed. |
| <code><a href="#@winglang/sdk.cloud.Bucket.onCreate">onCreate</a></code> | Run an inflight whenever a file is uploaded to the bucket. |
Expand Down Expand Up @@ -179,6 +215,22 @@ new cloud.Bucket(props?: BucketProps);

---

##### `addCorsRule` <a name="addCorsRule" id="@winglang/sdk.cloud.Bucket.addCorsRule"></a>

```wing
addCorsRule(value: BucketCorsOptions): void
```

Add cors configuration to the bucket.

###### `value`<sup>Required</sup> <a name="value" id="@winglang/sdk.cloud.Bucket.addCorsRule.parameter.value"></a>

- *Type:* <a href="#@winglang/sdk.cloud.BucketCorsOptions">BucketCorsOptions</a>

The cors configuration.

---

##### `addFile` <a name="addFile" id="@winglang/sdk.cloud.Bucket.addFile"></a>

```wing
Expand Down Expand Up @@ -713,6 +765,123 @@ The tree node.

## Structs <a name="Structs" id="Structs"></a>

### BucketCorsOptions <a name="BucketCorsOptions" id="@winglang/sdk.cloud.BucketCorsOptions"></a>

Cors Options for `Bucket`.

#### Initializer <a name="Initializer" id="@winglang/sdk.cloud.BucketCorsOptions.Initializer"></a>

```wing
bring cloud;
let BucketCorsOptions = cloud.BucketCorsOptions{ ... };
```

#### Properties <a name="Properties" id="Properties"></a>

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@winglang/sdk.cloud.BucketCorsOptions.property.allowedMethods">allowedMethods</a></code> | <code>MutArray&lt;<a href="#@winglang/sdk.http.HttpMethod">HttpMethod</a>&gt;</code> | The list of allowed methods. |
| <code><a href="#@winglang/sdk.cloud.BucketCorsOptions.property.allowedOrigins">allowedOrigins</a></code> | <code>MutArray&lt;str&gt;</code> | The allowed origin. |
| <code><a href="#@winglang/sdk.cloud.BucketCorsOptions.property.allowedHeaders">allowedHeaders</a></code> | <code>MutArray&lt;str&gt;</code> | The list of allowed headers. |
| <code><a href="#@winglang/sdk.cloud.BucketCorsOptions.property.exposeHeaders">exposeHeaders</a></code> | <code>MutArray&lt;str&gt;</code> | The list of exposed headers. |
| <code><a href="#@winglang/sdk.cloud.BucketCorsOptions.property.maxAge">maxAge</a></code> | <code><a href="#@winglang/sdk.std.Duration">duration</a></code> | How long the browser should cache preflight request results. |

---

##### `allowedMethods`<sup>Required</sup> <a name="allowedMethods" id="@winglang/sdk.cloud.BucketCorsOptions.property.allowedMethods"></a>

```wing
allowedMethods: MutArray<HttpMethod>;
```

- *Type:* MutArray&lt;<a href="#@winglang/sdk.http.HttpMethod">HttpMethod</a>&gt;
- *Default:* [HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS]

The list of allowed methods.

---

*Example*

```wing
[HttpMethod.GET, HttpMethod.POST]
```


##### `allowedOrigins`<sup>Required</sup> <a name="allowedOrigins" id="@winglang/sdk.cloud.BucketCorsOptions.property.allowedOrigins"></a>

```wing
allowedOrigins: MutArray<str>;
```

- *Type:* MutArray&lt;str&gt;
- *Default:* ["*"]

The allowed origin.

---

*Example*

```wing
"https://example.com"
```


##### `allowedHeaders`<sup>Optional</sup> <a name="allowedHeaders" id="@winglang/sdk.cloud.BucketCorsOptions.property.allowedHeaders"></a>

```wing
allowedHeaders: MutArray<str>;
```

- *Type:* MutArray&lt;str&gt;
- *Default:* ["Content-Type", "Authorization"]

The list of allowed headers.

---

*Example*

```wing
["Content-Type"]
```


##### `exposeHeaders`<sup>Optional</sup> <a name="exposeHeaders" id="@winglang/sdk.cloud.BucketCorsOptions.property.exposeHeaders"></a>

```wing
exposeHeaders: MutArray<str>;
```

- *Type:* MutArray&lt;str&gt;
- *Default:* []

The list of exposed headers.

---

*Example*

```wing
["Content-Type"]
```


##### `maxAge`<sup>Optional</sup> <a name="maxAge" id="@winglang/sdk.cloud.BucketCorsOptions.property.maxAge"></a>

```wing
maxAge: duration;
```

- *Type:* <a href="#@winglang/sdk.std.Duration">duration</a>
- *Default:* 300 seconds

How long the browser should cache preflight request results.

---

### BucketDeleteOptions <a name="BucketDeleteOptions" id="@winglang/sdk.cloud.BucketDeleteOptions"></a>

Options for `Bucket.delete()`.
Expand Down Expand Up @@ -906,10 +1075,58 @@ let BucketProps = cloud.BucketProps{ ... };

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@winglang/sdk.cloud.BucketProps.property.cors">cors</a></code> | <code>bool</code> | Whether to add default cors configuration. |
| <code><a href="#@winglang/sdk.cloud.BucketProps.property.corsOptions">corsOptions</a></code> | <code><a href="#@winglang/sdk.cloud.BucketCorsOptions">BucketCorsOptions</a></code> | Custom cors configuration for the bucket. |
| <code><a href="#@winglang/sdk.cloud.BucketProps.property.public">public</a></code> | <code>bool</code> | Whether the bucket's objects should be publicly accessible. |

---

##### `cors`<sup>Optional</sup> <a name="cors" id="@winglang/sdk.cloud.BucketProps.property.cors"></a>

```wing
cors: bool;
```

- *Type:* bool
- *Default:* true

Whether to add default cors configuration.

The default cors configuration is equivalent to calling `addCorsRule`
with the following options:
{
allowHeaders: ["*"],
allowOrigins: ["*"],
allowMethods: ["DELETE", "GET", "HEAD", "POST", "PUT"],
exposeHeaders: [],
maxAge: 0s
}

---

##### `corsOptions`<sup>Optional</sup> <a name="corsOptions" id="@winglang/sdk.cloud.BucketProps.property.corsOptions"></a>

```wing
corsOptions: BucketCorsOptions;
```

- *Type:* <a href="#@winglang/sdk.cloud.BucketCorsOptions">BucketCorsOptions</a>
- *Default:* All origins, methods, headers are allowed.

Custom cors configuration for the bucket.

The default cors configuration is equivalent to calling `addCorsRule`
with the following options:
{
allowHeaders: ["*"],
allowOrigins: ["*"],
allowMethods: ["DELETE", "GET", "HEAD", "POST", "PUT"],
exposeHeaders: [],
maxAge: 0s
}

---

##### `public`<sup>Optional</sup> <a name="public" id="@winglang/sdk.cloud.BucketProps.property.public"></a>

```wing
Expand Down
16 changes: 16 additions & 0 deletions packages/@winglang/platform-awscdk/src/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,22 @@ export class Bucket extends cloud.Bucket implements IAwsBucket {
this.public = props.public ?? false;

this.bucket = createEncryptedBucket(this, this.public);

if (props.cors ?? true) {
this.addCorsRule(
props.corsOptions ?? cloud.DEFAULT_BUCKET_CORS_CONFIGURATION
);
}
}

public addCorsRule(value: cloud.BucketCorsOptions): void {
this.bucket.addCorsRule({
allowedHeaders: value.allowedHeaders,
allowedMethods: value.allowedMethods as any,
allowedOrigins: value.allowedOrigins,
maxAge: value.maxAge?.seconds,
exposedHeaders: value.exposeHeaders,
});
}

public addObject(key: string, body: string): void {
Expand Down
Loading

0 comments on commit 3528054

Please sign in to comment.