Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: firebase/firebase-functions
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.2.0
Choose a base ref
...
head repository: firebase/firebase-functions
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.2.1
Choose a head ref
  • 6 commits
  • 9 files changed
  • 3 contributors

Commits on Jan 19, 2023

  1. Copy the full SHA
    dc4f917 View commit details

Commits on Jan 30, 2023

  1. Fixes an emulator issue where snapshot.ref could not point to multipl…

    …e databases (#1339)
    
    * yank first condition
    
    * update changelog
    
    * add emulator instance creation
    
    * run prettier
    colerogers authored Jan 30, 2023
    Copy the full SHA
    bcf41c6 View commit details

Commits on Feb 1, 2023

  1. Copy the full SHA
    6d4c0a5 View commit details
  2. Patch that fixes emulated v1 callable functions that are in a monorep…

    …o project (#1345)
    
    * quick patch to re-create the context.auth using emulator supplied headers
    
    * add changelog entry
    
    * Add TODO and use isDebugFeatureEnabled function
    colerogers authored Feb 1, 2023
    Copy the full SHA
    233104c View commit details

Commits on Feb 2, 2023

  1. adding test (#1347)

    colerogers authored Feb 2, 2023
    Copy the full SHA
    e7c4b49 View commit details
  2. 4.2.1

    google-oss-bot committed Feb 2, 2023
    Copy the full SHA
    23fadce View commit details
Showing with 102 additions and 16 deletions.
  1. +2 −4 CHANGELOG.md
  2. +2 −2 package-lock.json
  3. +1 −1 package.json
  4. +3 −1 spec/fixtures/mockrequest.ts
  5. +52 −1 spec/v1/providers/https.spec.ts
  6. +1 −4 src/common/providers/database.ts
  7. +31 −0 src/common/providers/https.ts
  8. +1 −1 src/v1/providers/testLab.ts
  9. +9 −2 src/v2/providers/database.ts
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
- Allow Functions to specify vpcConnector as a string parameter (#1329)
- Upgrade jsonwebtoken to version 9 (#1336)
- Adds 'eventarcpublishing' as required API to custom event function
- Hide type information of defineFloat in params module - it's not supported yet (#1341)
- Fixes an emulator issue where snapshot.ref could not point to multiple databases (#1339).
- Fixes emulated v1 callable functions that use a monorepo setup (#1345).
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebase-functions",
"version": "4.2.0",
"version": "4.2.1",
"description": "Firebase SDK for Cloud Functions",
"keywords": [
"firebase",
4 changes: 3 additions & 1 deletion spec/fixtures/mockrequest.ts
Original file line number Diff line number Diff line change
@@ -28,7 +28,8 @@ export function mockRequest(
authorization?: string;
instanceIdToken?: string;
appCheckToken?: string;
} = {}
} = {},
reqHeaders?: Record<string, string>,
) {
const body: any = {};
if (typeof data !== 'undefined') {
@@ -41,6 +42,7 @@ export function mockRequest(
'firebase-instance-id-token': context.instanceIdToken,
'x-firebase-appcheck': context.appCheckToken,
origin: 'example.com',
...reqHeaders,
};

return new MockRequest(body, headers);
53 changes: 52 additions & 1 deletion spec/v1/providers/https.spec.ts
Original file line number Diff line number Diff line change
@@ -24,9 +24,17 @@ import { expect } from "chai";

import * as functions from "../../../src/v1";
import * as https from "../../../src/v1/providers/https";
import { expectedResponseHeaders, MockRequest } from "../../fixtures/mockrequest";
import * as debug from "../../../src/common/debug";
import * as sinon from "sinon";
import {
expectedResponseHeaders,
generateUnsignedIdToken,
MockRequest,
mockRequest,
} from "../../fixtures/mockrequest";
import { runHandler } from "../../helper";
import { MINIMAL_V1_ENDPOINT } from "../../fixtures";
import { CALLABLE_AUTH_HEADER, ORIGINAL_AUTH_HEADER } from "../../../src/common/providers/https";

describe("CloudHttpsBuilder", () => {
describe("#onRequest", () => {
@@ -66,6 +74,10 @@ describe("CloudHttpsBuilder", () => {
});

describe("#onCall", () => {
afterEach(() => {
sinon.verifyAndRestore();
});

it("should return a trigger/endpoint with appropriate values", () => {
const result = https.onCall(() => {
return "response";
@@ -139,6 +151,45 @@ describe("#onCall", () => {
expect(response.status).to.equal(200);
expect(gotData).to.deep.equal({ foo: "bar" });
});

// Test for firebase-tools#5210
it("should create context.auth for v1 emulated functions", async () => {
sinon.stub(debug, "isDebugFeatureEnabled").withArgs("skipTokenVerification").returns(true);

let gotData: Record<string, any>;
let gotContext: Record<string, any>;
const reqData = { hello: "world" };
const authContext = {
uid: "SomeUID",
token: {
aud: "123456",
sub: "SomeUID",
uid: "SomeUID",
},
};
const originalAuth = "Bearer " + generateUnsignedIdToken("123456");
const func = https.onCall((data, context) => {
gotData = data;
gotContext = context;
});
const mockReq = mockRequest(
reqData,
"application/json",
{},
{
[CALLABLE_AUTH_HEADER]: encodeURIComponent(JSON.stringify(authContext)),
[ORIGINAL_AUTH_HEADER]: originalAuth,
}
);

const response = await runHandler(func, mockReq as any);

expect(response.status).to.equal(200);
expect(gotData).to.deep.eq(reqData);
expect(gotContext.rawRequest).to.deep.eq(mockReq);
expect(gotContext.rawRequest.headers["authorization"]).to.eq(originalAuth);
expect(gotContext.auth).to.deep.eq(authContext);
});
});

describe("callable CORS", () => {
5 changes: 1 addition & 4 deletions src/common/providers/database.ts
Original file line number Diff line number Diff line change
@@ -50,10 +50,7 @@ export class DataSnapshot implements database.DataSnapshot {
instance?: string
) {
const config = firebaseConfig();
if (app?.options?.databaseURL?.startsWith("http:")) {
// In this case we're dealing with an emulator
this.instance = app.options.databaseURL;
} else if (instance) {
if (instance) {
// SDK always supplies instance, but user's unit tests may not
this.instance = instance;
} else if (app) {
31 changes: 31 additions & 0 deletions src/common/providers/https.ts
Original file line number Diff line number Diff line change
@@ -36,6 +36,11 @@ import { TaskContext } from "./tasks";

const JWT_REGEX = /^[a-zA-Z0-9\-_=]+?\.[a-zA-Z0-9\-_=]+?\.([a-zA-Z0-9\-_=]+)?$/;

/** @internal */
export const CALLABLE_AUTH_HEADER = "x-callable-context-auth";
/** @internal */
export const ORIGINAL_AUTH_HEADER = "x-original-auth";

/** An express request with the wire format representation of the request body. */
export interface Request extends express.Request {
/** The wire format representation of the request body. */
@@ -661,6 +666,32 @@ function wrapOnCallHandler<Req = any, Res = any>(
}

const context: CallableContext = { rawRequest: req };

// TODO(colerogers): yank this when we release a breaking change of the CLI that removes
// our monkey-patching code referenced below and increases the minimum supported SDK version.
//
// Note: This code is needed to fix v1 callable functions in the emulator with a monorepo setup.
// The original monkey-patched code lived in the functionsEmulatorRuntime
// (link: https://github.com/firebase/firebase-tools/blob/accea7abda3cc9fa6bb91368e4895faf95281c60/src/emulator/functionsEmulatorRuntime.ts#L480)
// and was not compatible with how monorepos separate out packages (see https://github.com/firebase/firebase-tools/issues/5210).
if (isDebugFeatureEnabled("skipTokenVerification") && handler.length === 2) {
const authContext = context.rawRequest.header(CALLABLE_AUTH_HEADER);
if (authContext) {
logger.debug("Callable functions auth override", {
key: CALLABLE_AUTH_HEADER,
value: authContext,
});
context.auth = JSON.parse(decodeURIComponent(authContext));
delete context.rawRequest.headers[CALLABLE_AUTH_HEADER];
}

const originalAuth = context.rawRequest.header(ORIGINAL_AUTH_HEADER);
if (originalAuth) {
context.rawRequest.headers["authorization"] = originalAuth;
delete context.rawRequest.headers[ORIGINAL_AUTH_HEADER];
}
}

const tokenStatus = await checkTokens(req, context);
if (tokenStatus.auth === "INVALID") {
throw new HttpsError("unauthenticated", "Unauthenticated");
2 changes: 1 addition & 1 deletion src/v1/providers/testLab.ts
Original file line number Diff line number Diff line change
@@ -221,7 +221,7 @@ export class ResultStorage {
* - `USE_DESTINATION_ARTIFACTS`: One or more of the test targets defined in the
* .xctestrun file specifies "UseDestinationArtifacts", which is disallowed.
*
* - `TEST_NON_APP_HOSTED`: XC tests which run on physical devices must have
* - `TEST_NOT_APP_HOSTED`: XC tests which run on physical devices must have
* "IsAppHostedTestBundle" == "true" in the xctestrun file.
*
* - `PLIST_CANNOT_BE_PARSED`: An Info.plist file in the XCTest zip could not be
11 changes: 9 additions & 2 deletions src/v2/providers/database.ts
Original file line number Diff line number Diff line change
@@ -465,7 +465,7 @@ export function onChangedOperation<Ref extends string>(
// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawRTDBCloudEvent;
const instanceUrl = `https://${event.instance}.${event.firebasedatabasehost}`;
const instanceUrl = getInstance(event);
const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf<Ref>;
const databaseEvent = makeChangedDatabaseEvent(event, instanceUrl, params);
return wrapTraceContext(handler)(databaseEvent);
@@ -492,7 +492,7 @@ export function onOperation<Ref extends string>(
// wrap the handler
const func = (raw: CloudEvent<unknown>) => {
const event = raw as RawRTDBCloudEvent;
const instanceUrl = `https://${event.instance}.${event.firebasedatabasehost}`;
const instanceUrl = getInstance(event);
const params = makeParams(event, pathPattern, instancePattern) as unknown as ParamsOf<Ref>;
const data = eventType === deletedEventType ? event.data.data : event.data.delta;
const databaseEvent = makeDatabaseEvent(event, data, instanceUrl, params);
@@ -505,3 +505,10 @@ export function onOperation<Ref extends string>(

return func;
}

function getInstance(event: RawRTDBCloudEvent) {
const emuHost = process.env.FIREBASE_DATABASE_EMULATOR_HOST;
return emuHost
? `http://${emuHost}/?ns=${event.instance}`
: `https://${event.instance}.${event.firebasedatabasehost}`;
}