1

I'm trying to create a signed policy for users to upload their files on Google Cloud Storage.

The Issue i'm facing here is with the filename, i want the user to provide the filename at the time of the upload, as mentioned in the official GCS documentation, you can provide filename as ${filename} if you want it from the user, this doesn't work as i m getting the following error :

<?xml version='1.0' encoding='UTF-8'?>
<Error>
    <Code>InvalidPolicyDocument</Code>
    <Message>The content of the form does not meet the conditions specified in the policy document.</Message>
    <Details>Failed condition: {"key":"${filename}"}</Details>
</Error>

I've tried doing the same with S3's createPresignedPost method and it works fine.

Reference : https://cloud.google.com/storage/docs/xml-api/post-object-forms

Any help would be appreciated.

My Node.js Code for generating policy from GCS :

const { Storage } = require('@google-cloud/storage');
const storage = new Storage();

generateSignedPolicy = () => {

        const bucket = 'some-bucket';
        const file = storage.bucket(bucket).file("someFolder/${filename}");

        const options = {
          expires: Date.now() + (1000 * 300),
          conditions : [
            { bucket : bucket },
          ]
        };

        return new Promise((resolve, reject) => {
            file.generateSignedPostPolicyV4(options)
            .then(([res]) => resolve(res))
            .catch(error => reject(error))
        })
    } 

Policy generated with above code :

{
  "url": "https://storage.googleapis.com/some-bucket/",
  "fields": {
    "key": "someFolder/${filename}",
    "x-goog-date": "20221015T212358Z",
    "x-goog-credential": "credential",
    "x-goog-algorithm": "GOOG4-RSA-SHA256",
    "policy": "policy",
    "x-goog-signature": "signature"
  }
}

1 Answers1

0

The error details say: Failed condition: {"key":"${filename}"}

I took a look at the code written above and I see that you're trying to place a template variable into a normal string. The use of ${ XXX } can only be used in a template string.

const file = storage.bucket(bucket).file("${filename}");

Should be:

const file = storage.bucket(bucket).file(`${filename}`);

Since you aren't modifying anything about filename, you can simplify it to:

const file = storage.bucket(bucket).file(filename);

This code seemed to work when considering variable file names:

const { Storage } = require('@google-cloud/storage');
const bucket = 'my-bucket';
const storage = new Storage();

async function generateSignedPolicy() {

    const file = storage
        .bucket(bucket)
        .file("someFolder/${filename}");


    const [response] = await file.generateSignedPostPolicyV4({
        expires: Date.now() + (1000 * 300),
        conditions: [],
        fields: {
            bucket,
            acl: 'private',
        }
    });

    // console.log(response);

    // Create an HTML form with the provided policy
    let output = `<form action="${response.url}" method="POST" enctype="multipart/form-data">\n`;
    // Include all fields returned in the HTML form as they're required
    for (const name of Object.keys(response.fields)) {
        const value = response.fields[name];
        output += `  <input name="${name}" value="${value}" type="hidden"/>\n`;
    }
    output += '  <input type="file" name="file"/><br />\n';
    output += '  <input type="submit" value="Upload File"/><br />\n';
    output += '</form>';

    console.log(output);

    // Copy file contents to an HTML file and open that file in your browser, select a file, and upload
    // There will be a 204 response and the file will be uploaded in someFolder
} 

generateSignedPolicy().catch((err) => {
    console.error(err);
})

I removed all of the conditions and set the bucket as a field.

kozan
  • 94
  • 5
  • Hi @kozan, thanks for responding, ${filename} is used because in a presigned post we don't know the filename beforehand. For example in my browser I would see a file picker and I would select "cat.jpg", this would be the filename that is used. This is how it works with AWS S3 with createPresignedPost. This feature is mentioned here, check key in the form fields section https://cloud.google.com/storage/docs/xml-api/post-object-forms "The name of the object that you are uploading. You can also use the ${filename} variable if a user is providing a file name." – Husain Batatawala Oct 18 '22 at 10:24
  • Ah apologies I misunderstood. What if you specify a specific file, then in options.conditions specify "key": "somefolder/${ filename }" ? In my implementation, we select the file then we call the backend with the name of the file that we are requesting being uploaded. Then, we use the FormData API to generate a form and follow that with a post request to the signed url in JS. – kozan Oct 19 '22 at 18:28
  • It works for a specific file name, but what if we want a policy that expires, lets say after 5 mins and in that 5 mins, i wanna upload multiple files using the same policy to specific location( eg : someFolder), it wont work, which is why we provide ${filename} as the placeholder for different filenames.. – Husain Batatawala Oct 19 '22 at 21:20
  • Why can't you get a list of all the files you want to upload then create a signed policy for each file? I wouldn't trust the client to upload whatever they want to the GCP bucket. They could easily upload many gigabytes of data in that period and with standard storage that can get expensive. However, I just modified my answer with some code that worked for me that includes the variable file name – kozan Oct 19 '22 at 22:52