firebase / firebase/firebase-functions

onInit can only access secrets if they are bound to the calling function - make secrets available for initializing global value?

Open
#1,694 4 comments 0 reactions 1 assignee Claimed by @CorieW View on GitHub
feature request
Dominant language
TypeScript
Stars
1.1k
Forks
232
Avg merge
20h 46m
Merged PRs (30d)
15

Description

### Related issues

### [REQUIRED] Version info

**node:** v20.18.1

**firebase-functions:** 6.3.2

**firebase-tools:** 14.4.0

**firebase-admin:** 13.4.0

### [REQUIRED] Test case

_index.ts_
```ts
import { onInit } from "firebase-functions/v2";
import { onRequest } from "firebase-functions/https";
import { defineSecret } from "firebase-functions/params";

const SECRET = defineSecret("SECRET");

let coolApi: null | CoolApi = null;

onInit(() => {
coolApi = new CoolApi(SECRET.value());
});

/*
Can we do something like this??
onInit(
{ secrets: [SECRET] },
() => {
coolApi = new CoolApi(SECRET.value());
});
*/

export const joke1 = onRequest(
{ secrets: [SECRET] },
async (_req, res) => {
try {
const joke = await coolApi!.tellJoke();
res.status(200).send(joke);
} catch (error) {
console.error("Error: ", error);
res.status(500).send("Error fetching joke");
}
},
);

export const joke2 = onRequest(
async (_req, res) => {
try {
const joke = await coolApi!.tellJoke();
res.status(200).send(joke);
} catch (error) {
console.error("Error: ", error);
res.status(500).send("Error fetching joke");
}
},
);

class CoolApi {
private apiKey: string;

constructor(apiKey: string) {
if (!apiKey) {
throw new Error("API key must be supplied");
}
this.apiKey = apiKey;
}

async tellJoke(): Promise {
if (!this.apiKey) {
throw new Error("API key is not set");
}
const response = await fetch(`https://official-joke-api.appspot.com/random_joke?apiKey=${this.apiKey}`); // doesn't really need apikey
console.log("Response: ", response);
return response.json();
}
}
```

### [REQUIRED] Steps to reproduce

1. Deploy functions
2. Go to link of `joke1` function - joke is returned
3. Go to link of `joke2` function - errors & no joke is returned

### [REQUIRED] Expected behavior

Allow secrets to be bound to `onInit()` (or another initialization callback) so that global value can be initialized without having secrets declared in every Cloud Function consuming the global value.

Let's say we use a library initialized with an API key. It is heavily integrated to our firebase project and used in a lot of Cloud Functions and functions across the codebase. For example:
```ts
import {GoogleGenAI} from '@google/genai';
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;

const ai = new GoogleGenAI({apiKey: GEMINI_API_KEY});
...
// taken from @google/genai

// or in Firebase
import { GoogleGenAI } from "@google/genai";
import { defineSecret } from "firebase-functions/params";

const GEMINI_API_KEY = defineSecret("GEMINI_API_KEY");

let ai;
onInit(() => {
ai = new GoogleGenAI({ apiKey: GEMINI_API_KEY });
});
```

The issue is there is no way to check that the necessary secrets have been bound to all the Cloud Functions consuming the global value. This can be problematic as the project grows, or a Cloud Function is modified to use the API when it was not previously. Even more so when the API is used in shared functions that are called by Cloud Functions. The shared function is removed from the context of the Cloud Function where the secrets would be bound. The only way to surface this issue is at runtime.

Finally in the example above. If I do this
```ts
onInit(() => {
coolApi = new CoolApi(SECRET.value());
});
```
This will error whenever a Cloud Function is called that does not have `SECRET` bound to it. I could check if `SECRET.value()` is falsy before trying to initialize CoolApi - but it would then fail silently if it was ever undefined and should not be. I have no way of differentiating if it should be falsy or not.

I guess making a secret available in the global scope when it was not bound to a function introduces vulnerabilities??

We are migrating from `firebase.config` and having to trace back to the originating Cloud Function for every function that uses a globally declared API

It would be insanely useful to be able to initialize a global value and know it is going to be initialized properly and not wait to find out at runtime.

### [REQUIRED] Actual behavior

Secrets cannot be accessed by `onInit()` unless they were bound to the Cloud Function that will use the global value. There is no way to ensure the Cloud Function has a secret bound until it fails at runtime

### Were you able to successfully deploy your functions?
Yes

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.