Skip to content

Commit

Permalink
feat(asset-server-plugin): Implement ImageTransformStrategy for impro…
Browse files Browse the repository at this point in the history
…ved control over image transformations (#3240)

Closes #3040
  • Loading branch information
michaelbromley authored Nov 27, 2024
1 parent 1167102 commit dde738d
Show file tree
Hide file tree
Showing 30 changed files with 1,137 additions and 367 deletions.
4 changes: 4 additions & 0 deletions docs/docs/guides/deployment/production-configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,7 @@ In **Postgres**, you can execute:
show timezone;
```
and you should expect to see `UTC` or `Etc/UTC`.

## Security Considerations

Please read over the [Security](/guides/developer-guide/security) section of the Developer Guide for more information on how to secure your Vendure application.
29 changes: 29 additions & 0 deletions docs/docs/guides/developer-guide/security/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,35 @@ export const config: VendureConfig = {
For a detailed explanation of how to best configure this plugin, see the [HardenPlugin docs](/reference/core-plugins/harden-plugin/).
:::

### Harden the AssetServerPlugin

If you are using the [AssetServerPlugin](/reference/core-plugins/asset-server-plugin/), it is possible by default to use the dynamic
image transform feature to overload the server with requests for new image sizes & formats. To prevent this, you can
configure the plugin to only allow transformations for the preset sizes, and limited quality levels and formats.
Since v3.1 we ship the [PresetOnlyStrategy](/reference/core-plugins/asset-server-plugin/preset-only-strategy/) for this purpose, and
you can also create your own strategies.

```ts
import { VendureConfig } from '@vendure/core';
import { AssetServerPlugin, PresetOnlyStrategy } from '@vendure/asset-server-plugin';

export const config: VendureConfig = {
// ...
plugins: [
AssetServerPlugin.init({
// ...
// highlight-start
imageTransformStrategy: new PresetOnlyStrategy({
defaultPreset: 'large',
permittedQuality: [0, 50, 75, 85, 95],
permittedFormats: ['jpg', 'webp', 'avif'],
allowFocalPoint: false,
}),
// highlight-end
}),
]
};
```

## OWASP Top Ten Security Assessment

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## AssetServerOptions

<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="72" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="74" packageName="@vendure/asset-server-plugin" />

The configuration options for the AssetServerPlugin.

Expand All @@ -23,10 +23,11 @@ interface AssetServerOptions {
previewMaxWidth?: number;
previewMaxHeight?: number;
presets?: ImageTransformPreset[];
imageTransformStrategy?: ImageTransformStrategy | ImageTransformStrategy[];
namingStrategy?: AssetNamingStrategy;
previewStrategy?: AssetPreviewStrategy;
storageStrategyFactory?: (
options: AssetServerOptions,
storageStrategyFactory?: (
options: AssetServerOptions,
) => AssetStorageStrategy | Promise<AssetStorageStrategy>;
cacheHeader?: CacheConfig | string;
}
Expand All @@ -48,12 +49,12 @@ The local directory to which assets will be uploaded when using the <a href='/re

<MemberInfo kind="property" type={`string | ((ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, identifier: string) =&#62; string)`} />

The complete URL prefix of the asset files. For example, "https://demo.vendure.io/assets/". A
function can also be provided to handle more complex cases, such as serving multiple domains
from a single server. In this case, the function should return a string url prefix.

If not provided, the plugin will attempt to guess based off the incoming
request and the configured route. However, in all but the simplest cases,
The complete URL prefix of the asset files. For example, "https://demo.vendure.io/assets/". A
function can also be provided to handle more complex cases, such as serving multiple domains
from a single server. In this case, the function should return a string url prefix.

If not provided, the plugin will attempt to guess based off the incoming
request and the configured route. However, in all but the simplest cases,
this guess may not yield correct results.
### previewMaxWidth

Expand All @@ -70,6 +71,17 @@ The max height in pixels of a generated preview image.
<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-preset#imagetransformpreset'>ImageTransformPreset</a>[]`} />

An array of additional <a href='/reference/core-plugins/asset-server-plugin/image-transform-preset#imagetransformpreset'>ImageTransformPreset</a> objects.
### imageTransformStrategy

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a> | <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a>[]`} default={`[]`} since="3.1.0" />

The strategy or strategies to use to determine the parameters for transforming an image.
This can be used to implement custom image transformation logic, for example to
limit transform parameters to a known set of presets.

If multiple strategies are provided, they will be executed in the order in which they are defined.
If a strategy throws an error, the image transformation will be aborted and the error
will be logged, with an HTTP 400 response sent to the client.
### namingStrategy

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/assets/asset-naming-strategy#assetnamingstrategy'>AssetNamingStrategy</a>`} default={`<a href='/reference/core-plugins/asset-server-plugin/hashed-asset-naming-strategy#hashedassetnamingstrategy'>HashedAssetNamingStrategy</a>`} />
Expand All @@ -79,19 +91,19 @@ Defines how asset files and preview images are named before being saved.

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/assets/asset-preview-strategy#assetpreviewstrategy'>AssetPreviewStrategy</a>`} since="1.7.0" />

Defines how previews are generated for a given Asset binary. By default, this uses
Defines how previews are generated for a given Asset binary. By default, this uses
the <a href='/reference/core-plugins/asset-server-plugin/sharp-asset-preview-strategy#sharpassetpreviewstrategy'>SharpAssetPreviewStrategy</a>
### storageStrategyFactory

<MemberInfo kind="property" type={`( options: <a href='/reference/core-plugins/asset-server-plugin/asset-server-options#assetserveroptions'>AssetServerOptions</a>, ) =&#62; <a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a> | Promise&#60;<a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a>&#62;`} default={`() =&#62; <a href='/reference/core-plugins/asset-server-plugin/local-asset-storage-strategy#localassetstoragestrategy'>LocalAssetStorageStrategy</a>`} />
<MemberInfo kind="property" type={`( options: <a href='/reference/core-plugins/asset-server-plugin/asset-server-options#assetserveroptions'>AssetServerOptions</a>, ) =&#62; <a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a> | Promise&#60;<a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a>&#62;`} default={`() =&#62; <a href='/reference/core-plugins/asset-server-plugin/local-asset-storage-strategy#localassetstoragestrategy'>LocalAssetStorageStrategy</a>`} />

A function which can be used to configure an <a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a>. This is useful e.g. if you wish to store your assets
A function which can be used to configure an <a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a>. This is useful e.g. if you wish to store your assets
using a cloud storage provider. By default, the <a href='/reference/core-plugins/asset-server-plugin/local-asset-storage-strategy#localassetstoragestrategy'>LocalAssetStorageStrategy</a> is used.
### cacheHeader

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/cache-config#cacheconfig'>CacheConfig</a> | string`} default={`'public, max-age=15552000'`} since="1.9.3" />

Configures the `Cache-Control` directive for response to control caching in browsers and shared caches (e.g. Proxies, CDNs).
Configures the `Cache-Control` directive for response to control caching in browsers and shared caches (e.g. Proxies, CDNs).
Defaults to publicly cached for 6 months.


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## CacheConfig

<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="52" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="54" packageName="@vendure/asset-server-plugin" />

A configuration option for the Cache-Control header in the AssetServerPlugin asset response.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## HashedAssetNamingStrategy

<GenerationInfo sourceFile="packages/asset-server-plugin/src/hashed-asset-naming-strategy.ts" sourceLine="20" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/hashed-asset-naming-strategy.ts" sourceLine="20" packageName="@vendure/asset-server-plugin" />

An extension of the <a href='/reference/typescript-api/assets/default-asset-naming-strategy#defaultassetnamingstrategy'>DefaultAssetNamingStrategy</a> which prefixes file names with
the type (`'source'` or `'preview'`) as well as a 2-character sub-directory based on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## ImageTransformMode

<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="21" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="23" packageName="@vendure/asset-server-plugin" />

Specifies the way in which an asset preview image will be resized to fit in the
proscribed dimensions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## ImageTransformPreset

<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="39" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="41" packageName="@vendure/asset-server-plugin" />

A configuration option for an image size preset for the AssetServerPlugin.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: "ImageTransformStrategy"
isDefaultIndex: false
generated: true
---
<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
import MemberInfo from '@site/src/components/MemberInfo';
import GenerationInfo from '@site/src/components/GenerationInfo';
import MemberDescription from '@site/src/components/MemberDescription';


## ImageTransformStrategy

<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/image-transform-strategy.ts" sourceLine="56" packageName="@vendure/asset-server-plugin" since="3.1.0" />

An injectable strategy which is used to determine the parameters for transforming an image.
This can be used to implement custom image transformation logic, for example to
limit transform parameters to a known set of presets.

This is set via the `imageTransformStrategy` option in the AssetServerOptions. Multiple
strategies can be defined and will be executed in the order in which they are defined.

If a strategy throws an error, the image transformation will be aborted and the error
will be logged, with an HTTP 400 response sent to the client.

```ts title="Signature"
interface ImageTransformStrategy extends InjectableStrategy {
getImageTransformParameters(
args: GetImageTransformParametersArgs,
): Promise<ImageTransformParameters> | ImageTransformParameters;
}
```
* Extends: <code><a href='/reference/typescript-api/common/injectable-strategy#injectablestrategy'>InjectableStrategy</a></code>



<div className="members-wrapper">

### getImageTransformParameters

<MemberInfo kind="method" type={`(args: <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#getimagetransformparametersargs'>GetImageTransformParametersArgs</a>) => Promise&#60;<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>&#62; | <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>`} />

Given the input parameters, return the parameters which should be used to transform the image.


</div>


## ImageTransformParameters

<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/image-transform-strategy.ts" sourceLine="14" packageName="@vendure/asset-server-plugin" since="3.1.0" />

Parameters which are used to transform the image.

```ts title="Signature"
interface ImageTransformParameters {
width: number | undefined;
height: number | undefined;
mode: ImageTransformMode | undefined;
quality: number | undefined;
format: ImageTransformFormat | undefined;
fpx: number | undefined;
fpy: number | undefined;
preset: string | undefined;
}
```

<div className="members-wrapper">

### width

<MemberInfo kind="property" type={`number | undefined`} />


### height

<MemberInfo kind="property" type={`number | undefined`} />


### mode

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-mode#imagetransformmode'>ImageTransformMode</a> | undefined`} />


### quality

<MemberInfo kind="property" type={`number | undefined`} />


### format

<MemberInfo kind="property" type={`ImageTransformFormat | undefined`} />


### fpx

<MemberInfo kind="property" type={`number | undefined`} />


### fpy

<MemberInfo kind="property" type={`number | undefined`} />


### preset

<MemberInfo kind="property" type={`string | undefined`} />




</div>


## GetImageTransformParametersArgs

<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/image-transform-strategy.ts" sourceLine="33" packageName="@vendure/asset-server-plugin" since="3.1.0" />

The arguments passed to the `getImageTransformParameters` method of an ImageTransformStrategy.

```ts title="Signature"
interface GetImageTransformParametersArgs {
req: Request;
availablePresets: ImageTransformPreset[];
input: ImageTransformParameters;
}
```

<div className="members-wrapper">

### req

<MemberInfo kind="property" type={`Request`} />


### availablePresets

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-preset#imagetransformpreset'>ImageTransformPreset</a>[]`} />


### input

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>`} />




</div>
37 changes: 32 additions & 5 deletions docs/docs/reference/core-plugins/asset-server-plugin/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## AssetServerPlugin

<GenerationInfo sourceFile="packages/asset-server-plugin/src/plugin.ts" sourceLine="153" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/plugin.ts" sourceLine="176" packageName="@vendure/asset-server-plugin" />

The `AssetServerPlugin` serves assets (images and other files) from the local file system, and can also be configured to use
other storage strategies (e.g. <a href='/reference/core-plugins/asset-server-plugin/s3asset-storage-strategy#s3assetstoragestrategy'>S3AssetStorageStrategy</a>. It can also perform on-the-fly image transformations
Expand Down Expand Up @@ -133,14 +133,41 @@ large | 800px | 800px | resize
By default, the AssetServerPlugin will cache every transformed image, so that the transformation only needs to be performed a single time for
a given configuration. Caching can be disabled per-request by setting the `?cache=false` query parameter.

### Limiting transformations

By default, the AssetServerPlugin will allow any transformation to be performed on an image. However, it is possible to restrict the transformations
which can be performed by using an <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a>. This can be used to limit the transformations to a known set of presets, for example.

This is advisable in order to prevent abuse of the image transformation feature, as it can be computationally expensive.

Since v3.1.0 we ship with a <a href='/reference/core-plugins/asset-server-plugin/preset-only-strategy#presetonlystrategy'>PresetOnlyStrategy</a> which allows only transformations using a known set of presets.

*Example*

```ts
import { AssetServerPlugin, PresetOnlyStrategy } from '@vendure/core';

// ...

AssetServerPlugin.init({
//...
imageTransformStrategy: new PresetOnlyStrategy({
defaultPreset: 'thumbnail',
permittedQuality: [0, 50, 75, 85, 95],
permittedFormats: ['jpg', 'webp', 'avif'],
allowFocalPoint: false,
}),
});
```

```ts title="Signature"
class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
class AssetServerPlugin implements NestModule, OnApplicationBootstrap, OnApplicationShutdown {
init(options: AssetServerOptions) => Type<AssetServerPlugin>;
constructor(processContext: ProcessContext)
constructor(options: AssetServerOptions, processContext: ProcessContext, moduleRef: ModuleRef, assetServer: AssetServer)
configure(consumer: MiddlewareConsumer) => ;
}
```
* Implements: <code>NestModule</code>, <code>OnApplicationBootstrap</code>
* Implements: <code>NestModule</code>, <code>OnApplicationBootstrap</code>, <code>OnApplicationShutdown</code>



Expand All @@ -153,7 +180,7 @@ class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
Set the plugin options.
### constructor

<MemberInfo kind="method" type={`(processContext: <a href='/reference/typescript-api/common/process-context#processcontext'>ProcessContext</a>) => AssetServerPlugin`} />
<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/asset-server-plugin/asset-server-options#assetserveroptions'>AssetServerOptions</a>, processContext: <a href='/reference/typescript-api/common/process-context#processcontext'>ProcessContext</a>, moduleRef: ModuleRef, assetServer: AssetServer) => AssetServerPlugin`} />


### configure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';

## LocalAssetStorageStrategy

<GenerationInfo sourceFile="packages/asset-server-plugin/src/local-asset-storage-strategy.ts" sourceLine="15" packageName="@vendure/asset-server-plugin" />
<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/local-asset-storage-strategy.ts" sourceLine="15" packageName="@vendure/asset-server-plugin" />

A persistence strategy which saves files to the local file system.

Expand Down
Loading

0 comments on commit dde738d

Please sign in to comment.