web-dev-qa-db-ja.com

Cloud Functions for FirebaseでアップロードされたファイルからダウンロードURLを取得する

Functions for Firebaseを使用してFirebase Storageにファイルをアップロードした後、ファイルのダウンロードURLを取得したいと思います。

私はこれを持っています:

...

return bucket
    .upload(fromFilePath, {destination: toFilePath})
    .then((err, file) => {

        // Get the download url of file

    });

オブジェクトファイルには多くのパラメーターがあります。名前がmediaLinkであっても。ただし、このリンクにアクセスしようとすると、次のエラーが表示されます。

匿名ユーザーには、オブジェクトへのstorage.objects.getアクセス権がありません...

誰かがパブリックダウンロードURLを取得する方法を教えてもらえますか?

ありがとうございました

67
Valentin

@ google-cloud/storage NPMモジュール経由で getSignedURL を使用して署名付きURLを生成する必要があります。

例:

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
});

アプリケーションのデフォルトの認証情報では十分ではないため、@google-cloud/storageサービスアカウントの認証情報 で初期化する必要があります。

UPDATE:Cloud Storage SDKにFirebase Admin SDKからアクセスできるようになりました。これは ラッパーとして機能 @の周りgoogle-cloud/storage。唯一の方法は、次のいずれかの場合です。

  1. 通常、2番目のデフォルト以外のインスタンスを介して、特別なサービスアカウントでSDKを初期化します。
  2. または、サービスアカウントなしで、デフォルトのApp Engineサービスアカウントに「signBlob」権限を付与します。
86
James Daniels

アップロード時にダウンロードトークンを指定する方法の例を次に示します。

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);
      });
}

その後で呼び出す

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

ここで重要なことは、metadataオプションプロパティ内にmetadataオブジェクトがネストされていることです。 firebaseStorageDownloadTokensをuuid-v4値に設定すると、Cloud Storageはそれをパブリック認証トークンとして使用します。

@martemorfosisに感謝します

45
Drew Beaupre

関数object responseの最近の変更により、ダウンロードURLを次のように「ステッチ」するために必要なすべてを取得できます。

 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);
15
Demian S

Firebaseプロジェクトで作業している場合は、他のライブラリを含めたり資格情報ファイルをダウンロードしたりせずに、Cloud Functionで署名付きURLを作成できます。 IAM APIを有効にして、既存のサービスアカウントにロールを追加するだけです(以下を参照)。

Adminライブラリを初期化し、通常どおりにファイル参照を取得します。

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')

次に、署名付きURLを生成します

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

Firebaseサービスアカウントに、これを実行するための十分な権限があることを確認してください

  1. Google APIコンソールに移動し、IAM APIを有効にします( https://console.developers.google.com/apis/api/iam.googleapis.com/overview
  2. APIコンソールで、メインメニューの[IAMと管理者]-> [IAM]に移動します
  3. 「App Engineのデフォルトサービスアカウント」ロールの編集をクリックします
  4. [別のロールを追加]をクリックし、「サービスアカウントトークンクリエーター」というロールを追加します
  5. 保存して、変更が反映されるまで1分間待ちます

Vanilla Firebase configを使用すると、上記のコードを初めて実行するとエラーが発生しますIDおよびアクセス管理(IAM)APIはプロジェクトXXXXXXで使用されていないか、無効になっています。。エラーメッセージ内のリンクをたどってIAM APIを有効にすると、別のエラーが表示されます。Permission iam.serviceAccounts.signBlobは、サービスアカウントmy-service-accountでこの操作を実行するために必要です。 Token Creatorロールを追加すると、この2番目の権限の問題が修正されます。

14
SMX

私が成功して使用している方法の1つは、ファイルのメタデータ内のfirebaseStorageDownloadTokensという名前のキーにUUID v4値を設定し、FirebaseがこれらのURLを生成するために使用する構造に従って、ダウンロードURLを自分で組み立てることです:

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

Firebaseが将来ダウンロードURLを生成する方法を変更する可能性があることを考えると、このメソッドを使用することでどれだけ「安全」なのかわかりませんが、実装は簡単です。

11
martemorfosis

Firebase Admin SDKのserviceAccountKey.jsonファイルはどこに行くべきか疑問に思う人のために。関数フォルダーに配置し、通常どおりに展開します。

Javascript SDKのようにメタデータからダウンロードURLを取得できないのは、まだ私を困惑させます。最終的に期限切れになるURLを生成し、データベースに保存することは望ましくありません。

10
Clinton

Cloud Storage NodeJS 1.6.x または+を使用してファイルをアップロードするときに、オプションpredefinedAcl: 'publicRead'を使用することをお勧めします。

const options = {
    destination: yourFileDestination,
    predefinedAcl: 'publicRead'
};

bucket.upload(attachment, options);

次に、パブリックURLの取得は次のように簡単です。

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);
});
9
Laurent

申し訳ありませんが、上記の質問に評判が足りないためコメントを投稿できませんので、この回答に含めます。

上記のように署名付きUrlを生成しますが、service-account.jsonを使用する代わりに、生成できるserviceAccountKey.jsonを使用する必要があります(それに応じてYOURPROJECTIDを置き換えます)

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

例:

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 
        })
8
NiVeK92

ジェームス・ダニエルズが出した答えについてコメントすることはできませんが、これは読むことが非常に重要だと思います。

彼がやったように、署名済みURLを提供することは、多くの場合かなりbadおよび可能性Dangerousと思われます。 Firebaseのドキュメントによると、署名されたURLはしばらくすると期限切れになるため、それをデータベースに追加すると、特定の期間後に空のURLになります

そこのドキュメントを誤解している可能性があり、署名されたURLの有効期限が切れていないため、セキュリティ上の問題が発生する可能性があります。キーは、アップロードされたすべてのファイルで同じようです。これは、1つのファイルのURLを取得すると、名前を知っているだけで、誰かがアクセスする可能性のないファイルに簡単にアクセスできることを意味します。

私がそれを誤解した場合、私は修正することができます。そうでなければ、おそらく上記の名前のソリューションを更新する必要があります。私がそこに間違っている可能性がある場合

4
Renji

これは、単純なURLを持つパブリックファイルだけが必要な場合に機能します。これにより、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);
    }
});
2
Dakine

これは私が現在使用しているものであり、シンプルで完璧に機能します。

Google Cloudで何もする必要はありません。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
2
Oliver Dixon

私は同じ問題を抱えていましたが、READMEではなくfirebase関数の例のコードを見ていました。そして、このスレッドの回答も助けにはなりませんでした...

以下を実行することにより、構成ファイルを渡すことを回避できます。

プロジェクトの Cloud Console> IAM&admin> IAM に移動し、App Engineのデフォルトのサービスアカウントを見つけて、そのメンバーにService Account Token Creatorロールを追加します。これにより、アプリは画像への署名付きパブリックURLを作成できます。

ソース: サムネイル関数の自動生成README

アプリエンジンの役割は次のようになります。

Cloud Console

2

Firebase SDK andadmin.initializeAppを使用している場合:

1- 秘密鍵の生成 を/ functionsフォルダーに配置します。

2-次のようにコードを構成します。

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

ドキュメント

Try/catchは、他のファイルをインポートし、各ファイルに1つの関数を作成するindex.jsを使用しているためです。すべての関数で単一のindex.jsファイルを使用している場合は、admin.initializeApp(Object.assign(functions.config().firebase, { credential: admin.credential.cert(serviceAccount) }));で大丈夫です。

1
Allan Poppe

この回答では、ファイルをGoogle/Firebase Cloud StorageにアップロードするときにダウンロードURLを取得するためのオプションを要約します。ダウンロードURLには3つのタイプがあります。

  1. 署名済みのダウンロードURL。一時的なものであり、セキュリティ機能があります。
  2. 永続的でセキュリティ機能を備えたトークンダウンロードURL
  3. 永続的でセキュリティに欠けるパブリックダウンロードURL

トークンのダウンロードURLを取得するには、3つの方法があります。他の2つのダウンロードURLには、それらを取得する1つの方法しかありません。

Firebase Storageコンソールから

Firebase StorageコンソールからダウンロードURLを取得できます。

enter image description here

ダウンロードURLは次のようになります。

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

最初の部分は、ファイルへの標準パスです。最後はトークンです。このダウンロードURLは永続的です。つまり、失効することはできますが、失効することはできません。

フロントエンドからgetDownloadURL()

ドキュメント は、getDownloadURL()を使用するように指示します。

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

これにより、Firebase Storageコンソールから取得できるものと同じダウンロードURLが取得されます。この方法は簡単ですが、ファイルのパスを知っている必要があります。私のアプリでは、比較的単純なデータベース構造のために約300行のコードです。データベースが複雑な場合、これは悪夢です。また、フロントエンドからファイルをアップロードできますが、これにより、アプリをダウンロードするすべてのユーザーに資格情報が公開されます。そのため、ほとんどのプロジェクトでは、NodeバックエンドまたはGoogle Cloud Functionsからファイルをアップロードし、ダウンロードURLを取得して、ファイルに関する他のデータとともにデータベースに保存します。

一時ダウンロードURLのgetSignedUrl()

getSignedUrl() は、Nodeバックエンドまたは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);
            }
          });
        });
      });
    });
  }

署名されたダウンロードURLは次のようになります。

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

署名付きURLには有効期限と長い署名があります。コマンドラインのドキュメント gsutil signurl -d は、署名付きURLは一時的なものであると述べています。デフォルトの有効期限は1時間で、最大有効期限は7日間です。

ここでは、 getSignedUrl が署名されたURLが1週間で期限切れになることを決して言っていないことを言おうとしています。ドキュメントコードの有効期限は3-17-2025であり、将来の有効期限を設定できることを示唆しています。私のアプリは完全に機能し、1週間後にクラッシュしました。このエラーメッセージは、ダウンロードURLの有効期限が切れたことではなく、署名が一致しなかったことを示しています。コードにさまざまな変更を加え、すべてが機能しました... 1週間後にすべてがクラッシュするまで。これは1か月以上のフラストレーションが続いた。

ファイルを公開する

documentation で説明されているように、ファイルのパーミッションをpublic readに設定できます。これは、Cloud Storage BrowserまたはNodeサーバーから実行できます。 1つのファイルを公開するか、ディレクトリまたはストレージデータベース全体を作成できます。 Nodeコードは次のとおりです。

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));
    });

結果は、Cloud Storageブラウザで次のようになります。

enter image description here

その後、誰でも標準パスを使用してファイルをダウンロードできます。

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

ファイルを公開する別の方法は、メソッド makePublic() を使用することです。私はこれを機能させることができませんでした。バケットとファイルのパスを正しく取得するのは難しいです。

興味深い代替方法は、 アクセス制御リスト を使用することです。リストに登録したユーザーのみがファイルを使用できるようにするか、authenticatedReadを使用して、Googleアカウントからログインしているすべてのユーザーがファイルを使用できるようにします。 「Firebase Authを使用してアプリにログインした人」というオプションがあれば、ユーザーのみにアクセスを制限するため、これを使用します。

firebaseStorageDownloadTokensを使用して独自のダウンロードURLを構築する

文書化されていないGoogle StorageオブジェクトプロパティfirebaseStorageDownloadTokensについていくつかの回答があります。これにより、使用するトークンをStorageに伝えることができます。 uuid Nodeモジュールを使用してトークンを生成できます。 4行のコードで、コンソールまたはgetDownloadURL()から取得したものと同じダウンロードURLを独自のダウンロードURLを作成できます。コードの4行は次のとおりです。

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);

コンテキスト内のコードは次のとおりです。

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));
});

これはタイプミスではありません。metadata:の二重層にfirebaseStorageDownloadTokensをネストする必要があります。

Doug Stevensonは、firebaseStorageDownloadTokensは公式のGoogle Cloud Storage機能ではないことを指摘しました。 Googleのドキュメントにはありません。また、@google-cloudの将来のバージョンで提供される見込みはありません。 firebaseStorageDownloadTokensが好きなのは、それが必要なものを取得する唯一の方法だからです。

ノードからgetDownloadURL()がないのはなぜですか?

@Clintonが書いたように、Googleはfile.getDownloadURL()@google-cloud/storage(つまり、Nodeバックエンド)のメソッドにする必要があります。 Google Cloud Functionsからファイルをアップロードし、トークンダウンロードURLを取得したい。

1

事前定義されたアクセス制御リストの値「publicRead」を使用する場合、ファイルをアップロードして、非常に単純なURL構造でアクセスできます。

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

その後、次のようにURLを作成できます。

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

これは私が思いついた最高のものです。それは冗長ですが、私のために働いた唯一の合理的なソリューションです。

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

Firebase 6.0.0の時点で、次のように管理者から直接ストレージにアクセスできました。

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

したがって、サービスアカウントを追加する必要はありませんでした。次に、上記で参照したようにUUIDを設定すると、firebaseのURLを取得できます。

0
NickJ