firebase / functions-samples

Collection of sample apps showcasing popular use cases using Cloud Functions for Firebase
https://firebase.google.com/docs/functions
Apache License 2.0
12.08k stars 3.84k forks source link

Firebase functions: Error SignatureDoesNotMatch #360

Open rubsilvah opened 6 years ago

rubsilvah commented 6 years ago

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!

nicolasgarnier commented 6 years ago

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...

ravirajdarisi commented 6 years ago

I am facing the similar problem.

colinjstief commented 6 years ago

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

mqln commented 6 years ago

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

CodingDoug commented 6 years ago

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/

amitkukadia commented 6 years ago

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."

zhuixinjian commented 5 years ago

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

douglasulmer commented 5 years ago

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

DecentGradient commented 5 years ago

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

royherma commented 5 years ago

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

damofer commented 4 years ago

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

tljesse commented 4 years ago

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.

otri commented 4 years ago

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!

tljesse commented 4 years ago

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!

adirzoari commented 8 months ago

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;