Now that the infrastructure is ready, let's turn our focus to the implementation of the file downloads. This is only needed for the deliverable files, as the promo images are accessible publicly via the CloudFront distribution. That means the only operation we'll implement is what happens when the user clicks on the "Download" button.
The file download is an API operation that returns a URL in the response:
/product/{id}/download:
get:
# ...
security:
- cognito: []
summary: Download a product
responses:
'200':
description: return the download url
content:
application/json:
schema:
type: object
properties:
url:
type: string
format: url
example: https://bucket.com/key2
'403':
description: If the current user did not buy the product
Notice that the operation is authenticated (security: - cognito: []
) and it can return two different responses. The one with the 200
status code is when the user is allowed to download the product in which case the resulting URL grants access to the file. Then the 403
status code means the download is denied.
This distinction is important as access control happens at this point of the download. When the result URL is used, S3 only checks if the signature is not forged and that the Lambda execution role has read access.
In the API handler the first thing is to decide whether the download is allowed or not:
const productToDownload = await (async () => {
// fetch product
const product = await ddbClient.send(new GetItemCommand({
// ...
}));
// fetch order
const order = await ddbClient.send(new GetItemCommand({
// ...
}));
if (order.Item) {
// user bought the product, allow download
return unmarshall(product.Item);
}else {
if (product.Item.author.S === c.security.cognito.sub) {
// user is the author, allow download
return unmarshall(product.Item);
}
}
// deny download
return null;
})();
if (!productToDownload) {
return {
statusCode: 403,
};
}else {
// ... sign URL
}