functions-samples icon indicating copy to clipboard operation
functions-samples copied to clipboard

Firebase functions: Error SignatureDoesNotMatch

Open rubsilvah opened this issue 6 years ago • 15 comments

Hi, there!

I'm facing the same problem but I've already removed the content type while creating the signed URL. The interesting fact is that this problem only happens suddenly a few days after the signed URL is created..

The code I'm following is the one below: https://github.com/firebase/functions-samples/blob/master/generate-thumbnail/functions/index.js

Ps.: I have already followed the tips in the link below but the problem remains: https://github.com/GoogleCloudPlatform/google-cloud-node/issues/1976

Thanks in advance!

rubsilvah avatar Apr 04 '18 23:04 rubsilvah

Unless you changed the expiery date to a few days in the future (instead of 400 years in the future like the sample does) then I'm not sure what could trigger this. Maybe a but with the Cloud Storage service itself...

nicolasgarnier avatar Apr 05 '18 00:04 nicolasgarnier

I am facing the similar problem.

ravirajdarisi avatar Apr 16 '18 07:04 ravirajdarisi

I am facing this issue as well. Similar to @rubsilvah, my URL works for a few days, and then seems to expire with "SignatureDoesNotMatch" 403 response.

The URL seems valid too, and includes this query parameter: '...&Expires=16730323200&Signature=...'

For reference, here is a repo of relevant code:

https://github.com/colinjstief/getSignedUrl-example

Same issue reported here https://github.com/GoogleCloudPlatform/google-cloud-node/issues/1976 and here https://github.com/googleapis/nodejs-storage/issues/144

colinjstief avatar Jun 04 '18 14:06 colinjstief

Same problem here! Can't ship to production until this is solved...

mqln avatar Jun 05 '18 17:06 mqln

The Firebase Admin SDK just wraps the Cloud Storage SDK, so you might be better off filing a report in the repo for the @google-cloud/storage module. https://github.com/googleapis/nodejs-storage/

CodingDoug avatar Jun 07 '18 19:06 CodingDoug

Same here, the links work for a couple of days and then the following error happens. "The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method."

amitkukadia avatar Jun 14 '18 03:06 amitkukadia

Same here, the links work for a couple of days and then the following error happens.

zhuixinjian avatar Apr 24 '19 11:04 zhuixinjian

Any solution? Same here, the links work for 7 days.

douglasulmer avatar Sep 28 '19 23:09 douglasulmer

While the issues referenced above provide alternatives to getting a signed url on a google managed key, the tutorial should not be misleading and indicate that the url won't expire until the year 2500

DecentGradient avatar Oct 03 '19 20:10 DecentGradient

This is also happening to me, works for a few days and stops. Any ideas?

royherma avatar Nov 15 '19 15:11 royherma

Same here, it works for a week and then it doesnt.

damofer avatar Dec 08 '19 22:12 damofer

This appears to be a known error and is linked to this issue https://github.com/googleapis/nodejs-storage/issues/244

The eventual solution is to set your service account explicitly within your cloud functions https://github.com/googleapis/nodejs-storage/issues/244#issuecomment-441202047

I've just updated my code and will just have to wait and see if this solves it.

tljesse avatar Feb 07 '20 22:02 tljesse

Also seems sketchy that it's possible to set an expiry beyond the key rotation duration, should be some kind of validation checking for default configuration and warn during development if the API call with a really long duration is used. This is a tricky bug to catch!

otri avatar Apr 14 '20 14:04 otri

I actually had to come back to this as the images started to expire again for no apparent reason. I ended up creating my own access tokens from within the function and bypassing getSignedUrl completely. Hopefully this helps anyone down the line

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

const mkdirp = require('mkdirp-promise');
const spawn = require('child-process-promise').spawn;

import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';

import { uuid } from 'uuidv4';

// Max height and width of the thumbnail in pixels.
const THUMB_MAX_HEIGHT = 200;
const THUMB_MAX_WIDTH = 400;
// Thumbnail prefix added to file names.
const THUMB_PREFIX = 'thumb_';

/**
 * When an image is uploaded in the Storage bucket We generate a thumbnail automatically using
 * ImageMagick.
 * After the thumbnail has been generated and uploaded to Cloud Storage,
 * we write the public URL to the Firebase Realtime Database.
 */
export const generateThumbnail = functions.storage.object().onFinalize(async (object: any) => {
  // File and directory paths.
  const filePath = object.name;
  const contentType = object.contentType; // This is the image MIME type
  const fileDir = path.dirname(filePath);
  const fileName = path.basename(filePath);
  const thumbFilePath = path.normalize(path.join(fileDir, `${THUMB_PREFIX}${fileName}`));
  const tempLocalFile = path.join(os.tmpdir(), filePath);
  const tempLocalDir = path.dirname(tempLocalFile);
  const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath);

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith('image/')) {
    return console.log('This is not an image.');
  }

  // Exit if the image is already a thumbnail.
  if (fileName.startsWith(THUMB_PREFIX)) {
    return console.log('Already a Thumbnail.');
  }

  // Cloud Storage files.
  const bucket = admin.storage().bucket(object.bucket);
  const file = bucket.file(filePath);

  const fileUUID = uuid();
  const thumbUUID = uuid();
  //const thumbFile = bucket.file(thumbFilePath);

  const metadata = {
    contentType: contentType,
    metadata: {
      firebaseStorageDownloadTokens: fileUUID
    }
  };
  const thumbMetadata = {
    contentType: contentType,
    metadata: {
      firebaseStorageDownloadTokens: thumbUUID
    }
    // To enable Client-side caching you can set the Cache-Control headers here. Uncomment below.
    // 'Cache-Control': 'public,max-age=3600',
  };
  
  // Create the temp directory where the storage file will be downloaded.
  await mkdirp(tempLocalDir)

  // Download file from bucket.
  await file.download({destination: tempLocalFile});
  // console.log('The file has been downloaded to', tempLocalFile);

  // Set the new token on the file
  await file.setMetadata(metadata);

  // Generate a thumbnail using ImageMagick.
  await spawn('convert', [tempLocalFile, '-thumbnail', `${THUMB_MAX_WIDTH}x${THUMB_MAX_HEIGHT}>`, tempLocalThumbFile], {capture: ['stdout', 'stderr']});
  // console.log('Thumbnail created at', tempLocalThumbFile);

  // Uploading the Thumbnail.
  await bucket.upload(tempLocalThumbFile, {destination: thumbFilePath, metadata: thumbMetadata});

  //  console.log('Thumbnail uploaded to Storage at', thumbFilePath);

  // Once the image has been uploaded delete the local files to free up disk space.
  fs.unlinkSync(tempLocalFile);
  fs.unlinkSync(tempLocalThumbFile);
  // Get the Signed URLs for the thumbnail and original image.
  // const config: any = {
  //   action: 'read',
  //   expires: '03-01-2500',
  //   contentType: contentType
  // };
  // console.log('Config:', config);
  // const results = await Promise.all([
  //   thumbFile.getSignedUrl(config),
  //   file.getSignedUrl(config),
  // ]);

  // console.log('Results:', results);
  // // console.log('Got Signed URLs.');
  // const thumbResult = results[0];
  // const originalResult = results[1];
  // const thumbFileUrl = thumbResult[0];
  // const fileUrl = originalResult[0];

  const fileUrl = "https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(YOUR_UPLOAD_PATH + '/' + fileName) + "?alt=media&token=" + fileUUID;
  const thumbFileUrl = "https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(YOUR_UPLOAD_PATH + '/thumb_' + fileName) + "?alt=media&token=" + thumbUUID;

  const dbData = {
    name: filePath.replace(YOUR_UPLOAD_PATH + '/',''),
    path: fileUrl,
    thumbnail: thumbFileUrl,
    dateCreated: admin.firestore.FieldValue.serverTimestamp(),
    contentType: contentType
  };
  
  // Add the URLs to the Database
  let docRef = await admin.firestore().collection('mediaData').add(dbData);
  //console.log('Doc created with ID: ', docRef.id);

  await admin.firestore().collection('mediaData').doc(docRef.id).update({_id: docRef.id});
  return console.log('Thumbnail URLs saved to database.');
});

I haven't had any issues since implementing this. Good luck!

tljesse avatar Apr 14 '20 17:04 tljesse

I have the same issue index.js

const { onRequest } = require('firebase-functions/v2/https');
const webhookHandler = require('./webhook');
const getTransactionDetails = require('./getTransactionDetails');

exports.webhookHandler = onRequest(webhookHandler);
exports.getTransactionDetails = onRequest(getTransactionDetails);

webhook.js

const getFirebaseApp = require('./firebaseConfig');
const { getFirestore } = require('firebase-admin/firestore');
const { onRequest } = require('firebase-functions/v2/https');

const firebaseApp = getFirebaseApp();

exports.saveTransactionDetails = onRequest(async (req, res) => {
  try {
    console.log(req);
    console.log('req body is', req.body);
    const total = parseFloat(req.body.total) || 0;
    const counter = parseInt(req.body.counter) || 0;

    const db = getFirestore();
    const totalsRef = db.collection('totals').doc('total');
    const totalsDoc = await totalsRef.get();

    if (totalsDoc.exists) {
      const totalsData = totalsDoc.data();
      const newTotal = totalsData.total + total;
      const newCounter = totalsData.counter + counter;

      await totalsRef.update({
        total: newTotal,
        counter: newCounter
      });
    } else {
      await totalsRef.set({
        total,
        counter
      });
    }

    res.status(200).json({ message: 'Transaction details saved successfully' });
  } catch (error) {
    res.status(500).json({ message: 'Failed to save transaction details' });
  }
});

getTransactionDetails.js

const getFirebaseApp = require('./firebaseConfig');
const { getFirestore } = require('firebase-admin/firestore');
const { onRequest } = require('firebase-functions/v2/https');

const firebaseApp = getFirebaseApp();

exports.getTransactionDetails = onRequest(async (req, res) => {
  try {
    const db = getFirestore();
    const totalsRef = db.collection('totals').doc('total');
    const totalsDoc = await totalsRef.get();

    if (totalsDoc.exists) {
      const totalsData = totalsDoc.data();
      res.status(200).json({ total: totalsData.total, counter: totalsData.counter });
    } else {
      res.status(404).json({ message: 'Transaction details not found' });
    }
  } catch (error) {
    res.status(500).json({ message: 'Failed to retrieve transaction details' });
  }
});


Firebaseconfig.js

const { initializeApp } = require('firebase-admin/app');

let firebaseApp;

function getFirebaseApp() {
  if (!firebaseApp) {
    firebaseApp = initializeApp();
  }
  return firebaseApp;
}

module.exports = getFirebaseApp;

adirzoari avatar Mar 28 '24 08:03 adirzoari