barn4k
March 25, 2024, 3:36pm
1
The idea is:
Add an option to generate presigned URL for AWS S3 File Operation
My use case:
Need to share the s3 file with someone.
I think it would be beneficial to add this because:
it’s quite a useful option when there is a need to share some files located on the S3 with somebody (I use that method quite a lot to share a 1-day expiration link with users).
Any resources to support this?
Reference: https://community.n8n.io/t/why-am-i-not-able-to-upload-files-to-aws-s3
i try this on Code node but it’s not working:
const crypto = require('crypto');
const accessKeyId = 'YOUR_ACCESS_KEY_ID';
const secretAccessKey = 'YOUR_SECRET_ACCESS_KEY';
const region = 'YOUR_AWS_REGION';
const bucketName = 'YOUR_BUCKET_NAME';
const expiration = 60 * 60;
let outputs = [];
for (const item of $input.all()) {
const objectKey = item.json. YOUR_OBJECT_KEY;
const date = new Date();
const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, '') + 'Z';
const dateStamp = date.toISOString().slice(0, 10).replace(/-/g, '');
const canonicalUri = '/' + objectKey;
const host = `${bucketName}.s3.${region}.amazonaws.com`;
const canonicalQuerystring = `X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=${encodeURIComponent(accessKeyId + '/' + dateStamp + '/' + region + '/s3/aws4_request')}&X-Amz-Date=${amzDate}&X-Amz-Expires=${expiration}&X-Amz-SignedHeaders=host`;
const canonicalHeaders = `host:${host}\n`;
const signedHeaders = 'host';
const payloadHash = crypto.createHash('sha256').update('', 'utf8').digest('hex');
const canonicalRequest = `GET\n${canonicalUri}\n${canonicalQuerystring}\n${canonicalHeaders}\n${signedHeaders}\n${payloadHash}`;
const algorithm = 'AWS4-HMAC-SHA256';
const credentialScope = `${dateStamp}/${region}/s3/aws4_request`;
const stringToSign = `${algorithm}\n${amzDate}\n${credentialScope}\n${crypto.createHash('sha256').update(canonicalRequest, 'utf8').digest('hex')}`;
const getSignatureKey = (key, dateStamp, regionName, serviceName) => {
const kDate = crypto.createHmac('sha256', 'AWS4' + key).update(dateStamp).digest();
const kRegion = crypto.createHmac('sha256', kDate).update(regionName).digest();
const kService = crypto.createHmac('sha256', kRegion).update(serviceName).digest();
const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest();
return kSigning;
};
const signingKey = getSignatureKey(secretAccessKey, dateStamp, region, 's3');
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');
const signedUrl = `https://${host}${canonicalUri}?${canonicalQuerystring}&X-Amz-Signature=${signature}`;
const output = {
json: {
signedUrl: signedUrl,
id_request: item.json.id_request,
file_path: item.json.file_path,
}};
outputs.push(output);
}
return outputs;
1 Like
I’m having the same issue.
Nice implementation with crypto library but where did you find the docs about the signature implementation?
+1 this feature would be required to offload the upload and download of large files
I’ve manage to hack a presigned url for my company cubbit.eu but you can replace your own istance aws very easly!
This works tested by me!
const crypto = require('crypto');
function awsS3PresignDownload(accessKeyId, secretAccessKey, bucketName, region, objectPath, expires = 8400) {
// Ensure the object path starts with a slash and is only encoded once
const canonicalUri = '/' + objectPath.replace(/^\/+/, '');
const encodedUri = encodeURIComponent(canonicalUri).replace(/%2F/g, '/');
const host = `s3.cubbit.eu`; // assuming path-style like: s3.cubbit.eu/bucket/key
const headerString = `host:${host}\n`;
const signedHeaders = 'host';
// Real UTC timestamp
const now = new Date();
const pad = n => n.toString().padStart(2, '0');
const dateText = now.getUTCFullYear().toString() +
pad(now.getUTCMonth() + 1) +
pad(now.getUTCDate());
const timeText = dateText + 'T' +
pad(now.getUTCHours()) +
pad(now.getUTCMinutes()) +
pad(now.getUTCSeconds()) + 'Z';
const algorithm = 'AWS4-HMAC-SHA256';
const scope = `${dateText}/${region}/s3/aws4_request`;
const xAmzParams = {
'X-Amz-Algorithm': algorithm,
'X-Amz-Credential': `${accessKeyId}/${scope}`,
'X-Amz-Date': timeText,
'X-Amz-SignedHeaders': signedHeaders,
};
if (expires > 0) {
xAmzParams['X-Amz-Expires'] = expires.toString();
}
const sortedKeys = Object.keys(xAmzParams).sort();
const queryString = sortedKeys.map(key =>
`${encodeURIComponent(key)}=${encodeURIComponent(xAmzParams[key])}`
).join('&');
// Full canonical URI includes /bucket/key for path-style
const canonicalPath = `/${bucketName}${encodedUri}`;
const canonicalRequest = [
'GET',
canonicalPath,
queryString,
headerString,
signedHeaders,
'UNSIGNED-PAYLOAD'
].join('\n');
const hashedCanonicalRequest = crypto.createHash('sha256').update(canonicalRequest, 'utf8').digest('hex');
const stringToSign = `${algorithm}\n${timeText}\n${scope}\n${hashedCanonicalRequest}`;
const hmac = (key, data) => crypto.createHmac('sha256', key).update(data, 'utf8').digest();
const dateKey = hmac(`AWS4${secretAccessKey}`, dateText);
const dateRegionKey = hmac(dateKey, region);
const dateServiceKey = hmac(dateRegionKey, 's3');
const signingKey = hmac(dateServiceKey, 'aws4_request');
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign, 'utf8').digest('hex');
// Final URL (path-style)
return `https://${host}/${bucketName}${encodedUri}?${queryString}&X-Amz-Signature=${signature}`;
}
api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
secret_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
bucket_name = 'bucketname';
uuu='your_file_name.jpg';
region = 'eu-west-1'
return [{
data: awsS3PresignDownload(api_key, secret_key, bucket_name, region, uuu)
}];
2 Likes