Search code examples
node.jsgoogle-cloud-platformnext.jsgoogle-cloud-storagesigned-url

NodeJS - CORS issue with Google Cloud Storage Signed URLs


I want to enable artist clients to upload images to Cloud Storage using Signed URLs.

The following backend script is used to get the signed url and it is behind a Google Cloud load balancer:

(Does that (load balancer) affect anything?)

exports.getSignedUrl = async (req, res) => {

    if (req.method !== 'POST') {
        // Return a "method not allowed" error
        return res.status(405).end();
    }
    try {
        console.log("Getting signed url..");

        const storage = new Storage();
        const bucketName = 'bucket' in req.body ? req.body.bucket : 'gsk-public';
        // Get a reference to the destination file in GCS
        const file = storage.bucket(bucketName).file(req.body.filename);
        // Create a temporary upload URL
        const expiresAtMs = 'expires' in req.body ? req.body.expires : Date.now() + 100; // Link expires in 1 minute
        const configuration = {
            version: 'v4',
            action: req.body.action,
            expires: expiresAtMs
        };
        if ('type' in req.body) {
            configuration.contentType = req.body.type;
        }
        console.log("Got signed url!");
        const url = await file.getSignedUrl(configuration);
        console.log(url);
        return res.send(url[0]);
    }
    catch (err) {
        console.error(err);
        return res.status(500).end();
    }
};

CORS setting:

[
  {
    "origin": [
      "https://localhost:3000/",
      "https://dreamlouvre-git-development-samuq.vercel.app/"
    ],

    "responseHeader": "*",

    "method": ["GET", "PUT", "POST", "OPTIONS", "HEAD"],

    "maxAgeSeconds": 3600
  }
]

Next.js API endpoint to get the signed url:

import axios from 'axios';
import {config} from '../config';
import { fileTypeToJPG, createQueryString } from '../../../helpers/helpers';
import mime from 'mime';
export default async (req, res) => {
    if(req.method === 'POST'){
        try{
            if(!config.mimeExtensions.some(el => el===mime.getExtension(req.body.type))){
                //throw new Error('The file type of your image is not supported. Following types are supported: jpg, png, bmp and tiff.');
                return res.status(500).json({ statusCode: 500, message: 'Unsupported filetype.' });
            }
            //console.log('file name: ', req.body.filename);
            //console.log('auth header: ', req.headers.authorization);
            const response = await axios.post(`${config.apiBaseUrl}/auth/signedUrl`, req.body, {headers:{"authorization": `Bearer ${req.headers.authorization}`}});
            //console.log("Respdata");
            //console.log(response.data);
            //console.log(response.data[0]);
            const respData = {
                filename: fileTypeToJPG(req.body.filename),
                originalFileName: req.body.filename,
                url: response.data
            };
            return res.status(200).json(respData);
        }catch (err) {
            //console.log(err);
            return res.status('status' in err ? err.status : 500).json({ statusCode: 500, message: err.message });
        }
    }
    else return res.status(403);
};

Next.js Front-end code to put to signed url:

const badi = {
  type: file.type,
  filename: fileName,
  action: "write",
  bucket: config.bucketOriginal,
};
console.log("upload 3");
const resp = await axios.post(`/api/auth/getSignedUrl`, badi, {
  headers: { authorization: token },
});
console.log("upload 4", resp.data);
await axios.put(resp.data.url, file, {
  headers: { "Content-type": file.type },
});
  • Observed behaviour: Access has been blocked by CORS policy. Console logs upload 4 and the signed url, but the signed url doesn't work.

  • Expected behaviour: put request would work correctly.


Solution

  • There was an error in expiry time that probably caused the CORS error. I fixed this:

    const expiresAtMs = 'expires' in req.body ? req.body.expires : Date.now() + 100;
    

    to this:

    const expiresAtMs = 'expires' in req.body ? req.body.expires : Date.now() + 15*60*1000;
    

    EDIT: The code already started to work, but is now throwing 403 erros, claiming there's a malformed content-type header although there is not. Content-type header is present in the request.