tutorial functions example node.js firebase firebase-storage google-cloud-functions

node.js - example - firebase functions http request



Obtenga la URL de descarga del archivo cargado con Cloud Functions para Firebase (17)

Después de cargar un archivo en Firebase Storage con funciones para Firebase, me gustaría obtener la URL de descarga del archivo.

Tengo esto :

... return bucket .upload(fromFilePath, {destination: toFilePath}) .then((err, file) => { // Get the download url of file });

El archivo objeto tiene muchos parámetros. Incluso uno llamado mediaLink . Sin embargo, si intento acceder a este enlace, aparece este error:

Los usuarios anónimos no tienen acceso storage.objects.get al objeto ...

¿Alguien puede decirme cómo obtener la URL de descarga pública?

Gracias


A partir de firebase 6.0.0 pude acceder al almacenamiento directamente con el administrador de esta manera:

const bucket = admin.storage().bucket();

Entonces no necesitaba agregar una cuenta de servicio. Luego, configurar el UUID como se mencionó anteriormente funcionó para obtener la URL de Firebase.


Aquí hay un ejemplo sobre cómo especificar el token de descarga al cargar:

const UUID = require("uuid-v4"); const fbId = "<YOUR APP ID>"; const fbKeyFile = "./YOUR_AUTH_FIlE.json"; const gcs = require(''@google-cloud/storage'')({keyFilename: fbKeyFile}); const bucket = gcs.bucket(`${fbId}.appspot.com`); var upload = (localFile, remoteFile) => { let uuid = UUID(); return bucket.upload(localFile, { destination: remoteFile, uploadType: "media", metadata: { contentType: ''image/png'', metadata: { firebaseStorageDownloadTokens: uuid } } }) .then((data) => { let file = data[0]; return Promise.resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(file.name) + "?alt=media&token=" + uuid); }); }

luego llame con

upload(localPath, remotePath).then( downloadURL => { console.log(downloadURL); });

La clave aquí es que hay un objeto de metadata anidado dentro de la propiedad de opción de metadata . Establecer firebaseStorageDownloadTokens en un valor firebaseStorageDownloadTokens -v4 le indicará a Cloud Storage que lo use como su token de autenticación público.

Muchas gracias a @martemorfosis


Con los cambios recientes en la respuesta del objeto de funciones, puede obtener todo lo que necesita para "unir" la URL de descarga de esta manera:

const img_url = ''https://firebasestorage.googleapis.com/v0/b/[YOUR BUCKET]/o/'' + encodeURIComponent(object.name) + ''?alt=media&token='' + object.metadata.firebaseStorageDownloadTokens; console.log(''URL'',img_url);


Deberá generar una URL firmada usando getSignedURL través del módulo @google-cloud/storage NPM.

Ejemplo:

const gcs = require(''@google-cloud/storage'')({keyFilename: ''service-account.json''}); // ... const bucket = gcs.bucket(bucket); const file = bucket.file(fileName); return file.getSignedUrl({ action: ''read'', expires: ''03-09-2491'' }).then(signedUrls => { // signedUrls[0] contains the file''s public URL });

Deberá inicializar @google-cloud/storage con las credenciales de su cuenta de servicio, ya que las credenciales predeterminadas de la aplicación no serán suficientes.

ACTUALIZACIÓN : ahora se puede acceder al SDK de Cloud Storage a través de Firebase Admin SDK, que actúa como un contenedor alrededor de @ google-cloud / storage. La única forma en que lo hará es si usted:

  1. Inicie el SDK con una cuenta de servicio especial, generalmente a través de una segunda instancia no predeterminada.
  2. O, sin una cuenta de servicio, otorgando a la cuenta de servicio predeterminada de App Engine el permiso "signBlob".

Esta respuesta resumirá las opciones para obtener la URL de descarga al cargar un archivo en Google / Firebase Cloud Storage. Existen tres tipos de URL de descarga:

  1. URL de descarga firmadas, que son temporales y tienen características de seguridad
  2. URL de descarga de token, que son persistentes y tienen características de seguridad
  3. URL de descarga pública, que son persistentes y carecen de seguridad

Hay tres formas de obtener una URL de descarga de token. Las otras dos URL de descarga solo tienen una forma de obtenerlas.

Desde la consola de almacenamiento de Firebase

Puede obtener la URL de descarga desde la consola de Firebase Storage:

La URL de descarga se ve así:

https://firebasestorage.googleapis.com/v0/b/languagetwo-cd94d.appspot.com/o/Audio%2FEnglish%2FUnited_States-OED-0%2Fabout.mp3?alt=media&token=489c48b3-23fb-4270-bd85-0a328d2808e5

La primera parte es una ruta estándar a su archivo. Al final está el token. Esta URL de descarga es permanente, es decir, no caducará, aunque puede revocarla.

getDownloadURL () desde el frente

La documentation nos dice que usemos getDownloadURL() :

let url = await firebase.storage().ref(''Audio/English/United_States-OED-'' + i +''/'' + $scope.word.word + ".mp3").getDownloadURL();

Esto obtiene la misma URL de descarga que puede obtener de su consola de Firebase Storage. Este método es fácil pero requiere que conozca la ruta a su archivo, que en mi aplicación tiene aproximadamente 300 líneas de código, para una estructura de base de datos relativamente simple. Si su base de datos es compleja, esto sería una pesadilla. Y podría cargar archivos desde el front-end, pero esto expondría sus credenciales a cualquiera que descargue su aplicación. Entonces, para la mayoría de los proyectos, querrá cargar sus archivos desde su back-end Node o Google Cloud Functions, luego obtenga la URL de descarga y guárdela en su base de datos junto con otros datos sobre su archivo.

getSignedUrl () para URL de descarga temporal

getSignedURL es fácil de usar desde un getSignedURL Node o Google Cloud Functions:

function oedPromise() { return new Promise(function(resolve, reject) { http.get(oedAudioURL, function(response) { response.pipe(file.createWriteStream(options)) .on(''error'', function(error) { console.error(error); reject(error); }) .on(''finish'', function() { file.getSignedUrl(config, function(err, url) { if (err) { console.error(err); return; } else { resolve(url); } }); }); }); }); }

Una URL de descarga firmada se ve así:

https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio%2FSpanish%2FLatin_America-Sofia-Female-IBM%2Faqu%C3%AD.mp3?GoogleAccessId=languagetwo-cd94d%40appspot.gserviceaccount.com&Expires=4711305600&Signature=WUmABCZIlUp6eg7dKaBFycuO%2Baz5vOGTl29Je%2BNpselq8JSl7%2BIGG1LnCl0AlrHpxVZLxhk0iiqIejj4Qa6pSMx%2FhuBfZLT2Z%2FQhIzEAoyiZFn8xy%2FrhtymjDcpbDKGZYjmWNONFezMgYekNYHi05EPMoHtiUDsP47xHm3XwW9BcbuW6DaWh2UKrCxERy6cJTJ01H9NK1wCUZSMT0%2BUeNpwTvbRwc4aIqSD3UbXSMQlFMxxWbPvf%2B8Q0nEcaAB1qMKwNhw1ofAxSSaJvUdXeLFNVxsjm2V9HX4Y7OIuWwAxtGedLhgSleOP4ErByvGQCZsoO4nljjF97veil62ilaQ%3D%3D

La URL firmada tiene una fecha de vencimiento y una firma larga. La documentación de la línea de comando gsutil signurl -d dice que las URL firmadas son temporales: la caducidad predeterminada es de una hora y la caducidad máxima es de siete días.

Voy a decir aquí que getSignedUrl nunca dice que su URL firmada caducará en una semana. El código de documentación tiene el 3-17-2025 como fecha de vencimiento, lo que sugiere que puede establecer los años de vencimiento en el futuro. Mi aplicación funcionó perfectamente y luego se bloqueó una semana después. El mensaje de error decía que las firmas no coincidían, no que la URL de descarga había expirado. Hice varios cambios en mi código, y todo funcionó ... hasta que se estrelló una semana después. Esto continuó durante más de un mes de frustración.

Haga que su archivo esté disponible públicamente

Puede configurar los permisos de su archivo para lectura pública, como se explica en la documentation . Esto se puede hacer desde el Navegador de almacenamiento en la nube o desde su servidor Node. Puede hacer público un archivo o un directorio o toda su base de datos de almacenamiento. Aquí está el código de nodo:

var webmPromise = new Promise(function(resolve, reject) { var options = { destination: (''Audio/'' + longLanguage + ''/'' + pronunciation + ''/'' + word + ''.mp3''), predefinedAcl: ''publicRead'', contentType: ''audio/'' + audioType, }; synthesizeParams.accept = ''audio/webm''; var file = bucket.file(''Audio/'' + longLanguage + ''/'' + pronunciation + ''/'' + word + ''.webm''); textToSpeech.synthesize(synthesizeParams) .then(function(audio) { audio.pipe(file.createWriteStream(options)); }) .then(function() { console.log("webm audio file written."); resolve(); }) .catch(error => console.error(error)); });

El resultado se verá así en su navegador de almacenamiento en la nube:

Cualquiera puede usar la ruta estándar para descargar su archivo:

https://storage.googleapis.com/languagetwo-cd94d.appspot.com/Audio/English/United_States-OED-0/system.mp3

Otra forma de hacer público un archivo es usar el método makePublic() . No he podido hacer que esto funcione, es complicado obtener el bucket y las rutas de archivo correctas.

Una alternativa interesante es usar las listas de control de acceso . Puede hacer que un archivo esté disponible solo para los usuarios que haya incluido en una lista, o usar authenticatedRead para que el archivo esté disponible para cualquier persona que haya iniciado sesión desde una cuenta de Google. Si hubiera una opción "cualquiera que haya iniciado sesión en mi aplicación usando Firebase Auth", la usaría, ya que limitaría el acceso solo a mis usuarios.

Cree su propia URL de descarga con firebaseStorageDownloadTokens

Varias respuestas describen una propiedad de objeto de Google Storage no documentada firebaseStorageDownloadTokens . Con esto puede decirle a Storage el token que desea usar. Puede generar un token con el módulo Nodo uuid . Cuatro líneas de código y puede crear su propia URL de descarga, la misma URL de descarga que obtiene de la consola o getDownloadURL() . Las cuatro líneas de código son:

const uuidv4 = require(''uuid/v4''); const uuid = uuidv4();

metadata: { firebaseStorageDownloadTokens: uuid }

https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(''Audio/'' + longLanguage + ''/'' + pronunciation + ''/'' + word + ''.webm'') + "?alt=media&token=" + uuid);

Aquí está el código en contexto:

var webmPromise = new Promise(function(resolve, reject) { var options = { destination: (''Audio/'' + longLanguage + ''/'' + pronunciation + ''/'' + word + ''.mp3''), contentType: ''audio/'' + audioType, metadata: { metadata: { firebaseStorageDownloadTokens: uuid, } } }; synthesizeParams.accept = ''audio/webm''; var file = bucket.file(''Audio/'' + longLanguage + ''/'' + pronunciation + ''/'' + word + ''.webm''); textToSpeech.synthesize(synthesizeParams) .then(function(audio) { audio.pipe(file.createWriteStream(options)); }) .then(function() { resolve("https://firebasestorage.googleapis.com/v0/b/" + bucket.name + "/o/" + encodeURIComponent(''Audio/'' + longLanguage + ''/'' + pronunciation + ''/'' + word + ''.webm'') + "?alt=media&token=" + uuid); }) .catch(error => console.error(error)); });

Eso no es un error tipográfico: debe anidar firebaseStorageDownloadTokens en capas dobles de metadata: firebaseStorageDownloadTokens

Doug Stevenson señaló que firebaseStorageDownloadTokens no es una función oficial de Google Cloud Storage. No lo encontrará en ninguna documentación de Google, y no hay promesa de que esté en una versión futura de @google-cloud . Me gusta firebaseStorageDownloadTokens porque es la única forma de obtener lo que quiero, pero tiene un "olor" que no es seguro de usar.

¿Por qué no getDownloadURL () de Node?

Como escribió @Clinton, Google debería hacer un file.getDownloadURL() un método en @google-cloud/storage (es decir, su backend Node). Quiero subir un archivo de Google Cloud Functions y obtener la URL de descarga del token.


Esto es lo mejor que se me ocurrió. Es redundante, pero la única solución razonable que funcionó para mí.

await bucket.upload(localFilePath, {destination: uploadPath, public: true}); const f = await bucket.file(uploadPath) const meta = await f.getMetadata() console.log(meta[0].mediaLink)


Esto es lo que uso actualmente, es simple y funciona perfectamente.

No necesitas hacer nada con Google Cloud. Funciona fuera de la caja con Firebase.

// Save the base64 to storage. const file = admin.storage().bucket(''url found on the storage part of firebase'').file(`profile_photos/${uid}`); await file.save(base64Image, { metadata: { contentType: ''image/jpeg'', }, predefinedAcl: ''publicRead'' }); const metaData = await file.getMetadata() const url = metaData[0].mediaLinkenter


Esto funciona si solo necesita un archivo público con una URL simple. Tenga en cuenta que esto puede anular sus reglas de almacenamiento de Firebase.

bucket.upload(file, function(err, file) { if (!err) { //Make the file public file.acl.add({ entity: ''allUsers'', role: gcs.acl.READER_ROLE }, function(err, aclObject) { if (!err) { var URL = "https://storage.googleapis.com/[your bucket name]/" + file.id; console.log(URL); } else { console.log("Failed to set permissions: " + err); } }); } else { console.log("Upload failed: " + err); } });


Lo sentimos, pero no puedo publicar un comentario a su pregunta anterior debido a la falta de reputación, por lo que lo incluiré en esta respuesta.

Haga lo indicado anteriormente generando una URL firmada, pero en lugar de usar service-account.json, creo que debe usar serviceAccountKey.json, que puede generar en (reemplace YOURPROJECTID en consecuencia)

https://console.firebase.google.com/project/YOURPROJECTID/settings/serviceaccounts/adminsdk

Ejemplo:

const gcs = require(''@google-cloud/storage'')({keyFilename: ''serviceAccountKey.json''}); // ... const bucket = gcs.bucket(bucket); // ... return bucket.upload(tempLocalFile, { destination: filePath, metadata: { contentType: ''image/jpeg'' } }) .then((data) => { let file = data[0] file.getSignedUrl({ action: ''read'', expires: ''03-17-2025'' }, function(err, url) { if (err) { console.error(err); return; } // handle url })


No puedo comentar sobre la respuesta que dio James Daniels, pero creo que es muy importante leerlo.

Dar una URL firmada como lo hizo parece en muchos casos bastante malo y posible peligroso . De acuerdo con la documentación de Firebase, la URL firmada caduca después de un tiempo, por lo que agregar eso a su base de datos dará lugar a una URL vacía después de un cierto período de tiempo

Es posible que haya entendido mal la documentación allí y que la URL firmada no caduque, lo que tendría algunos problemas de seguridad como resultado. La clave parece ser la misma para cada archivo cargado. Esto significa que una vez que obtiene la url de un archivo, alguien podría acceder fácilmente a los archivos a los que no se supone que debe acceder, simplemente conociendo sus nombres.

Si entendí mal eso, me gustaría que me corrigieran. De lo contrario, alguien probablemente debería actualizar la solución mencionada anteriormente. Si puedo estar equivocado allí


Para aquellos que se preguntan dónde debe ir el archivo Firebase Admin SDK serviceAccountKey.json. Simplemente colóquelo en la carpeta de funciones e implemente como de costumbre.

Todavía me desconcierta por qué no podemos obtener la URL de descarga de los metadatos como lo hacemos en el SDK de Javascript. No es deseable generar una URL que finalmente caduque y guardarla en la base de datos.


Para aquellos que usan Firebase SDK y admin.initializeApp :

1 - Genere una clave privada y colóquela en la carpeta / funciones.

2 - Configure su código de la siguiente manera:

const serviceAccount = require(''../../serviceAccountKey.json''); try { admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) })); } catch (e) {}

Documentation

El intento / captura es porque estoy usando un index.js que importa otros archivos y crea una función para cada archivo. Si está utilizando un solo archivo index.js con todas las funciones, debería estar bien con admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) })); .


Si está trabajando en un proyecto de Firebase, puede crear URL firmadas en una función en la nube sin incluir otras bibliotecas o descargar un archivo de credenciales. Solo necesita habilitar la API de IAM y agregar un rol a su cuenta de servicio existente (ver más abajo).

Inicialice la biblioteca de administración y obtenga una referencia de archivo como lo haría normalmente:

import * as functions from ''firebase-functions'' import * as admin from ''firebase-admin'' admin.initializeApp(functions.config().firebase) const myFile = admin.storage().bucket().file(''path/to/my/file'')

Luego genera una URL firmada con

myFile.getSignedUrl({action: ''read'', expires: someDateObj}).then(urls => { const signedUrl = urls[0] })

Asegúrese de que su cuenta de servicio de Firebase tenga permisos suficientes para ejecutar esto

  1. Vaya a la consola API de Google y habilite la API IAM ( https://console.developers.google.com/apis/api/iam.googleapis.com/overview )
  2. Aún en la consola API, vaya al menú principal, "IAM y admin" -> "IAM"
  3. Haga clic en editar para el rol "Cuenta de servicio predeterminada de App Engine"
  4. Haga clic en "Agregar otro rol" y agregue el llamado "Creador de tokens de cuenta de servicio"
  5. Ahorre y espere un minuto para que los cambios se propaguen

Con una configuración de Firebase de vainilla, la primera vez que ejecute el código anterior obtendrá un error La API de administración de acceso e identidad (IAM) no se ha utilizado en el proyecto XXXXXX antes o está deshabilitada. . Si sigue el enlace en el mensaje de error y habilita la API de IAM, obtendrá otro error: se requiere permiso iam.serviceAccounts.signBlob para realizar esta operación en la cuenta de servicio my-service-account . Agregar el rol Creador de tokens soluciona este segundo problema de permisos.


Si utiliza el valor de listas de control de acceso predefinido de ''publicRead'', puede cargar el archivo y acceder a él con una estructura de URL muy simple:

// Upload to GCS const opts: UploadOptions = { gzip: true, destination: dest, // ''someFolder/image.jpg'' predefinedAcl: ''publicRead'', public: true }; return bucket.upload(imagePath, opts);

Luego puede construir la url de esta manera:

const storageRoot = ''https://storage.googleapis.com/''; const bucketName = ''myapp.appspot.com/''; // CHANGE TO YOUR BUCKET NAME const downloadUrl = storageRoot + bucketName + encodeURIComponent(dest);


Sugiero usar la opción predefinedAcl: ''publicRead'' al cargar un archivo con Cloud Storage NodeJS 1.6.xo +:

const options = { destination: yourFileDestination, predefinedAcl: ''publicRead'' }; bucket.upload(attachment, options);

Luego, obtener la URL pública es tan simple como:

bucket.upload(attachment, options).then(result => { const file = result[0]; return file.getMetadata(); }).then(results => { const metadata = results[0]; console.log(''metadata='', metadata.mediaLink); }).catch(error => { console.error(error); });


Tuve el mismo problema, sin embargo, estaba mirando el código del ejemplo de la función firebase en lugar del README. Y las respuestas en este hilo tampoco ayudaron ...

Puede evitar pasar el archivo de configuración haciendo lo siguiente:

Vaya a la Consola en la nube de su proyecto > IAM y administrador> IAM , encuentre la cuenta de servicio predeterminada de App Engine y agregue la función Creador de tokens de cuenta de servicio a ese miembro. Esto permitirá que su aplicación cree URL públicas firmadas para las imágenes.

fuente: Generar automáticamente la función de miniaturas README

Su rol para el motor de aplicaciones debería verse así:


Un método que estoy usando con éxito es establecer un valor de UUID v4 en una clave llamada firebaseStorageDownloadTokens en los metadatos del archivo después de que termine de cargarse y luego ensamblar la URL de descarga siguiendo la estructura que Firebase usa para generar estas URL, por ejemplo:

https://firebasestorage.googleapis.com/v0/b/[BUCKET_NAME]/o/[FILE_PATH]?alt=media&token=[THE_TOKEN_YOU_CREATED]

No sé cuánto "seguro" es usar este método (dado que Firebase podría cambiar la forma en que genera las URL de descarga en el futuro), pero es fácil de implementar.