AWS S3 generate presigned URL as File Operation

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

yes please!

thanks @barn4k

+1
needed too

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?

on gpt 4 :slight_smile:

+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