Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/deploy/functions/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
name: "noop",
api: "",
ensureTriggerRegion: noop,
validateTrigger: noop,

Check warning on line 74 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: () => Promise.resolve(FALLBACK_DEPLOYMENT_REGION),
Expand All @@ -83,7 +83,7 @@
api: "pubsub.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: noop,
validateTrigger: noop,

Check warning on line 86 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: () => Promise.resolve(DEFAULT_GLOBAL_TRIGGER_REGION),
Expand All @@ -95,7 +95,7 @@
api: "storage.googleapis.com",
requiredProjectBindings: obtainStorageBindings,
ensureTriggerRegion: ensureStorageTriggerRegion,
validateTrigger: noop,

Check warning on line 98 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: ensureStorageDefaultRegion,
Expand All @@ -107,7 +107,7 @@
api: "firebasealerts.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureFirebaseAlertsTriggerRegion,
validateTrigger: noop,

Check warning on line 110 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: () => Promise.resolve(DEFAULT_GLOBAL_TRIGGER_REGION),
Expand All @@ -123,7 +123,7 @@
api: "firebasedatabase.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureDatabaseTriggerRegion,
validateTrigger: noop,

Check warning on line 126 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: ensureDatabaseDefaultRegion,
Expand All @@ -135,7 +135,7 @@
api: "firebaseremoteconfig.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureRemoteConfigTriggerRegion,
validateTrigger: noop,

Check warning on line 138 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: () => Promise.resolve(DEFAULT_GLOBAL_TRIGGER_REGION),
Expand All @@ -147,7 +147,7 @@
api: "testing.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureTestLabTriggerRegion,
validateTrigger: noop,

Check warning on line 150 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: () => Promise.resolve(DEFAULT_GLOBAL_TRIGGER_REGION),
Expand All @@ -159,7 +159,7 @@
api: "firestore.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureFirestoreTriggerRegion,
validateTrigger: noop,

Check warning on line 162 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: ensureFirestoreDefaultRegion,
Expand All @@ -171,7 +171,7 @@
api: "firebasedataconnect.googleapis.com",
requiredProjectBindings: noopProjectBindings,
ensureTriggerRegion: ensureDataConnectTriggerRegion,
validateTrigger: noop,

Check warning on line 174 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Promise-returning function provided to property where a void return was expected
registerTrigger: noop,
unregisterTrigger: noop,
getDefaultRegion: ensureDataConnectDefaultRegion,
Expand Down Expand Up @@ -207,9 +207,11 @@
"google.firebase.dataconnect.connector.v1.mutationExecuted": dataconnectService,
"google.firebase.ailogic.v1.beforeGenerate": aiLogicService,
"google.firebase.ailogic.v1.afterGenerate": aiLogicService,
"google.firebase.auth.user.v2.created": noOpService,
"google.firebase.auth.user.v2.deleted": noOpService,
};

export function serviceForEndpoint(endpoint: backend.Endpoint | build.Endpoint): Service {

Check warning on line 214 in src/deploy/functions/services/index.ts

View workflow job for this annotation

GitHub Actions / lint (24)

Missing JSDoc comment
let eventType: string | undefined;
if ("eventTrigger" in endpoint && endpoint.eventTrigger?.eventType) {
eventType = endpoint.eventTrigger.eventType;
Expand Down
41 changes: 41 additions & 0 deletions src/deploy/functions/validate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as secretManager from "../../gcp/secretManager";
import * as backend from "./backend";
import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../functions/events/v1";
import { resolveCpuAndConcurrency } from "./prepare";
import * as experiments from "../../experiments";

describe("validate", () => {
describe("functionsDirectoryExists", () => {
Expand Down Expand Up @@ -494,6 +495,46 @@ describe("validate", () => {
"The following functions have timeouts that exceed the maximum allowed for their trigger typ",
);
});

it("disallows v2 Auth Eventarc triggers if autheventarc experiment is not enabled", () => {
experiments.setEnabled("autheventarc", false);
const ep: backend.Endpoint = {
...ENDPOINT_BASE,
platform: "gcfv2",
eventTrigger: {
eventType: "google.firebase.auth.user.v2.created",
eventFilters: {},
retry: false,
},
};

try {
expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw(
/Cannot deploy Auth Eventarc triggers because the experiment.*autheventarc.*is not enabled/,
);
} finally {
experiments.setEnabled("autheventarc", null);
}
});

it("allows v2 Auth Eventarc triggers if autheventarc experiment is enabled", () => {
experiments.setEnabled("autheventarc", true);
const ep: backend.Endpoint = {
...ENDPOINT_BASE,
platform: "gcfv2",
eventTrigger: {
eventType: "google.firebase.auth.user.v2.created",
eventFilters: {},
retry: false,
},
};

try {
expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw();
} finally {
experiments.setEnabled("autheventarc", null);
}
});
Comment on lines +499 to +537

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of stubbing experiments.assertEnabled with Sinon, we can use the public experiments.setEnabled API to toggle the experiment state. This tests the actual integration with the experiments module, verifies the real error messages, and avoids stubbing module exports which can be brittle and problematic in some environments.

    it("disallows v2 Auth Eventarc triggers if autheventarc experiment is not enabled", () => {
      experiments.setEnabled("autheventarc", false);
      const ep: backend.Endpoint = {
        ...ENDPOINT_BASE,
        platform: "gcfv2",
        eventTrigger: {
          eventType: "google.firebase.auth.user.v2.created",
          eventFilters: {},
          retry: false,
        },
      };

      try {
        expect(() => validate.endpointsAreValid(backend.of(ep))).to.throw(
          /Cannot deploy Auth Eventarc triggers because the experiment.*autheventarc.*is not enabled/
        );
      } finally {
        experiments.setEnabled("autheventarc", null);
      }
    });

    it("allows v2 Auth Eventarc triggers if autheventarc experiment is enabled", () => {
      experiments.setEnabled("autheventarc", true);
      const ep: backend.Endpoint = {
        ...ENDPOINT_BASE,
        platform: "gcfv2",
        eventTrigger: {
          eventType: "google.firebase.auth.user.v2.created",
          eventFilters: {},
          retry: false,
        },
      };

      try {
        expect(() => validate.endpointsAreValid(backend.of(ep))).to.not.throw();
      } finally {
        experiments.setEnabled("autheventarc", null);
      }
    });

});

describe("endpointsAreUnqiue", () => {
Expand Down
9 changes: 9 additions & 0 deletions src/deploy/functions/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import * as fsutils from "../../fsutils";
import * as backend from "./backend";
import * as utils from "../../utils";
import * as secrets from "../../functions/secrets";
import * as experiments from "../../experiments";
import { AUTH_EVENTS } from "../../functions/events/v2";
Comment on lines +13 to +14

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid inline type assertions and optimize lookup performance, we can define a Set at the module level for AUTH_EVENTS.

import * as experiments from "../../experiments";
import { AUTH_EVENTS } from "../../functions/events/v2";

const AUTH_EVENTS_SET = new Set<string>(AUTH_EVENTS);


const AUTH_EVENTS_SET = new Set<string>(AUTH_EVENTS);

/**
* GCF Gen 1 has a max timeout of 540s.
Expand Down Expand Up @@ -89,6 +93,11 @@ export function endpointsAreValid(wantBackend: backend.Backend): void {
validateTimeoutConfig(endpoints);
for (const ep of endpoints) {
validateScheduledTimeout(ep);
if (backend.isEventTriggered(ep)) {
if (AUTH_EVENTS_SET.has(ep.eventTrigger.eventType)) {
experiments.assertEnabled("autheventarc", "deploy Auth Eventarc triggers");
}
}
Comment on lines +96 to +100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the module-level AUTH_EVENTS_SET to check if the event type is an Auth Eventarc trigger. This avoids the inline type assertion as readonly string[] and provides $O(1)$ lookup complexity.

Suggested change
if (backend.isEventTriggered(ep)) {
if ((AUTH_EVENTS as readonly string[]).includes(ep.eventTrigger.eventType)) {
experiments.assertEnabled("autheventarc", "deploy Auth Eventarc triggers");
}
}
if (backend.isEventTriggered(ep)) {
if (AUTH_EVENTS_SET.has(ep.eventTrigger.eventType)) {
experiments.assertEnabled("autheventarc", "deploy Auth Eventarc triggers");
}
}

const service = serviceForEndpoint(ep);
if (backend.isBlockingTriggered(ep)) {
if (service.name === "noop") {
Expand Down
7 changes: 7 additions & 0 deletions src/experiments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@ export const ALL_EXPERIMENTS = experiments({
default: false,
public: true,
},
autheventarc: {
shortDescription: "Enable experimental Eventarc-based Auth triggers",
fullDescription:
"Enable support for v2 Eventarc-based Auth triggers (onUserCreated and onUserDeleted)",
default: false,
public: true,
},
});

export type ExperimentName = keyof typeof ALL_EXPERIMENTS;
Expand Down
8 changes: 7 additions & 1 deletion src/functions/events/v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export const FIREALERTS_EVENT = "google.firebase.firebasealerts.alerts.v1.publis

export const DATACONNECT_EVENT = "google.firebase.dataconnect.connector.v1.mutationExecuted";

export const AUTH_EVENTS = [
"google.firebase.auth.user.v2.created",
"google.firebase.auth.user.v2.deleted",
] as const;

export type Event =
| typeof PUBSUB_PUBLISH_EVENT
| (typeof STORAGE_EVENTS)[number]
Expand All @@ -51,7 +56,8 @@ export type Event =
| (typeof FIRESTORE_EVENTS)[number]
| typeof FIREALERTS_EVENT
| typeof DATACONNECT_EVENT
| (typeof AI_LOGIC_EVENTS)[number];
| (typeof AI_LOGIC_EVENTS)[number]
| (typeof AUTH_EVENTS)[number];

// Why can't auth context be removed? This is map was added to correct a bug where a regex
// allowed any non-auth type to be converted to any auth type, but we should follow up for why
Expand Down
Loading