Skip to content

Instantly share code, notes, and snippets.

@breathe
Last active October 26, 2023 18:20
Show Gist options
  • Save breathe/5b73c82f3ef3564b8b8f0641bf3e0a11 to your computer and use it in GitHub Desktop.
Save breathe/5b73c82f3ef3564b8b8f0641bf3e0a11 to your computer and use it in GitHub Desktop.
// this workaround is needed for this issue ...
// https://github.com/firebase/firebase-admin-node/issues/1377
// workaround from here: https://gist.github.com/k2wanko/289f5cf231ca80da099c7414dceb465d
// This allows us to use federated identity when running build in ci and otherwise uses normal auth mechanism
import { GoogleOAuthAccessToken } from "firebase-admin"; // version 11.10.1
import { getAuth } from "firebase-admin/auth";
import {
getApps,
Credential,
applicationDefault,
initializeApp,
} from "firebase-admin/app";
import { ComputeEngineCredential } from "../../../node_modules/firebase-admin/lib/app/credential-internal.js";
import { ExternalAccountClient } from "google-auth-library"; // version 9.0.0
import fs from "fs/promises";
import { Agent } from "http";
import path from "path";
import os from "os";
const configDir = (() => {
// Windows has a dedicated low-rights location for apps at ~/Application Data
const sys = os.platform();
if (sys && sys.length >= 3 && sys.substring(0, 3).toLowerCase() === "win") {
return process.env.APPDATA;
}
// On *nix the gcloud cli creates a . dir.
return process.env.HOME && path.resolve(process.env.HOME, ".config");
})();
const GCLOUD_CREDENTIAL_SUFFIX = "gcloud/application_default_credentials.json";
const GCLOUD_CREDENTIAL_PATH =
configDir && path.resolve(configDir, GCLOUD_CREDENTIAL_SUFFIX);
export class ExternalAccountCredential
extends ComputeEngineCredential // Inherits this class because it is verified to be an internal class at Firestore initialization.
implements Credential
{
async getAccessToken(): Promise<GoogleOAuthAccessToken> {
const json = JSON.parse(
await fs.readFile(GCLOUD_CREDENTIAL_PATH as string, "utf-8")
);
const client = ExternalAccountClient.fromJSON(json);
if (!client) {
throw new Error("client is empty");
}
const res = await client.getAccessToken();
return {
access_token: res.res?.data?.accessToken || "",
expires_in: new Date(res.res?.data?.expireTime ?? 1000).getTime() / 1000,
};
}
}
let app;
const alreadyCreatedApps = getApps();
if (alreadyCreatedApps.length !== 0) {
app = alreadyCreatedApps[0];
} else if (process.env["GITHUB_ACTION"] != null) {
// detect if running inside github action -- which uses external account auth ...
// external account auth is not supported by firebase cli yet hence this horrible workaround ...
// note that the _app_ itself has to launch in ci because that's how nextjs app bundling works ...
// bad incidental complexity ...
// see: https://github.com/firebase/firebase-admin-node/issues/1377
console.log("using external account credential from", GCLOUD_CREDENTIAL_PATH);
const credential = new ExternalAccountCredential();
app = initializeApp(
{
credential,
},
"WebAppName"
);
} else {
app = initializeApp(
{
credential: applicationDefault(),
},
"WebAppName"
);
}
const auth = getAuth(app);
export { app, auth };
- name: Authenticate with GC
# https://cloud.google.com/blog/products/identity-security/enabling-keyless-authentication-from-github-actions
id: auth
uses: 'google-github-actions/auth@v1'
with:
project_id: "${{env.GOOGLE_CLOUD_FIREBASE_PROJECT}}"
workload_identity_provider: "..."
service_account: "deployment@project.iam.gserviceaccount.com"
create_credentials_file: true
token_format: access_token
access_token_lifetime: 900s
- name: "Adapt federated auth to support firebase cli"
run: |
echo "SERVICE_ACCOUNT_KEY=$(cat "${{ steps.auth.outputs.credentials_file_path }}" | tr -d '\n')" >> $GITHUB_ENV
- uses: actions/setup-node@v3
with:
node-version: 20.8.0
- run: npm install
- name: Deploy per PR preview of firebase site
if: github.ref != 'refs/heads/main'
uses: FirebaseExtended/action-hosting-deploy@v0
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
with:
projectId: "${{env.GOOGLE_CLOUD_FIREBASE_PROJECT}}"
firebaseServiceAccount: "${{ env.SERVICE_ACCOUNT_KEY }}"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
# workaround issue when project name is too long
firebaseToolsVersion: git@github.com:breathe/firebase-tools.git
expires: 7d
- name: Deploy new 'live' version of firebase site
if: github.ref == 'refs/heads/main'
uses: FirebaseExtended/action-hosting-deploy@v0
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
with:
projectId: "${{env.GOOGLE_CLOUD_FIREBASE_PROJECT}}"
firebaseServiceAccount: "${{ env.SERVICE_ACCOUNT_KEY }}"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
# workaround issue when project name is too long
firebaseToolsVersion: git@github.com:breathe/firebase-tools.git
channelId: live
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment