From ea115683549a40423253f977553ce2e4dd8727d7 Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Wed, 17 Jun 2026 18:33:05 +0000 Subject: [PATCH 1/3] feat: Add service mapping and defensive global region check for v2 Auth Eventarc triggers --- .../functions/services/authEventarc.spec.ts | 47 +++++++++++++++++++ src/deploy/functions/services/authEventarc.ts | 18 +++++++ src/deploy/functions/services/index.ts | 18 ++++++- src/functions/events/v2.ts | 8 +++- 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/deploy/functions/services/authEventarc.spec.ts create mode 100644 src/deploy/functions/services/authEventarc.ts diff --git a/src/deploy/functions/services/authEventarc.spec.ts b/src/deploy/functions/services/authEventarc.spec.ts new file mode 100644 index 00000000000..5217d0741fa --- /dev/null +++ b/src/deploy/functions/services/authEventarc.spec.ts @@ -0,0 +1,47 @@ +import { expect } from "chai"; +import { Endpoint } from "../backend"; +import * as authEventarc from "./authEventarc"; + +const projectNumber = "123456789"; + +const endpoint: Endpoint = { + id: "endpoint", + region: "us-central1", + project: projectNumber, + eventTrigger: { + retry: false, + eventType: "google.firebase.auth.user.v2.created", + eventFilters: {}, + }, + entryPoint: "endpoint", + platform: "gcfv2", + runtime: "nodejs16", +}; + +describe("ensureAuthEventarcTriggerRegion", () => { + it("should set the trigger location to global", async () => { + const ep = { ...endpoint }; + + await authEventarc.ensureAuthEventarcTriggerRegion(ep); + + expect(ep.eventTrigger.region).to.eq("global"); + }); + + it("should not error if the trigger location is global", async () => { + const ep = { ...endpoint }; + ep.eventTrigger.region = "global"; + + await authEventarc.ensureAuthEventarcTriggerRegion(ep); + + expect(ep.eventTrigger.region).to.eq("global"); + }); + + it("should error if the trigger location is not global", () => { + const ep = { ...endpoint }; + ep.eventTrigger.region = "us-west1"; + + expect(() => authEventarc.ensureAuthEventarcTriggerRegion(ep)).to.throw( + "A Firebase Auth Eventarc trigger must specify 'global' trigger location", + ); + }); +}); diff --git a/src/deploy/functions/services/authEventarc.ts b/src/deploy/functions/services/authEventarc.ts new file mode 100644 index 00000000000..995626a54d8 --- /dev/null +++ b/src/deploy/functions/services/authEventarc.ts @@ -0,0 +1,18 @@ +import * as backend from "../backend"; +import { FirebaseError } from "../../../error"; + +/** + * Sets a Firebase Auth Eventarc event trigger's region to 'global' since the service is global. + * @param endpoint the auth eventarc endpoint + */ +export function ensureAuthEventarcTriggerRegion( + endpoint: backend.Endpoint & backend.EventTriggered, +): Promise { + if (!endpoint.eventTrigger.region) { + endpoint.eventTrigger.region = "global"; + } + if (endpoint.eventTrigger.region !== "global") { + throw new FirebaseError("A Firebase Auth Eventarc trigger must specify 'global' trigger location"); + } + return Promise.resolve(); +} diff --git a/src/deploy/functions/services/index.ts b/src/deploy/functions/services/index.ts index c79971daa34..c5ba4ff2f92 100644 --- a/src/deploy/functions/services/index.ts +++ b/src/deploy/functions/services/index.ts @@ -24,6 +24,7 @@ import { getDefaultRegion as ensureDataConnectDefaultRegion, } from "./dataconnect"; import { AILogicService } from "./ailogic"; +import { ensureAuthEventarcTriggerRegion } from "./authEventarc"; /** A standard void No Op */ export const noop = (): Promise => Promise.resolve(); @@ -49,7 +50,8 @@ export type Name = | "testlab" | "firestore" | "dataconnect" - | "ailogic"; + | "ailogic" + | "autheventarc"; /** A service interface for the underlying GCP event services */ export interface Service { @@ -177,6 +179,18 @@ const dataconnectService: Service = { getDefaultRegion: ensureDataConnectDefaultRegion, }; +/** A Firebase Auth Eventarc service object */ +const authEventarcService: Service = { + name: "autheventarc", + api: "eventarc.googleapis.com", + requiredProjectBindings: noopProjectBindings, + ensureTriggerRegion: ensureAuthEventarcTriggerRegion, + validateTrigger: noop, + registerTrigger: noop, + unregisterTrigger: noop, + getDefaultRegion: () => Promise.resolve(DEFAULT_GLOBAL_TRIGGER_REGION), +}; + /** Mapping from event type string to service object */ // TODO: See if there's a way to deduplicate these consts while still ensuring type safety and exhaustion const EVENT_SERVICE_MAPPING: Record = { @@ -207,6 +221,8 @@ const EVENT_SERVICE_MAPPING: Record = { "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": authEventarcService, + "google.firebase.auth.user.v2.deleted": authEventarcService, }; export function serviceForEndpoint(endpoint: backend.Endpoint | build.Endpoint): Service { diff --git a/src/functions/events/v2.ts b/src/functions/events/v2.ts index 8dd1fe9d691..177e9a0d251 100644 --- a/src/functions/events/v2.ts +++ b/src/functions/events/v2.ts @@ -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] @@ -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 From 37753a926c9a435502ac28a62bbd6422370c865c Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Wed, 17 Jun 2026 18:35:22 +0000 Subject: [PATCH 2/3] lint --- src/deploy/functions/services/authEventarc.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/deploy/functions/services/authEventarc.ts b/src/deploy/functions/services/authEventarc.ts index 995626a54d8..a28119e2784 100644 --- a/src/deploy/functions/services/authEventarc.ts +++ b/src/deploy/functions/services/authEventarc.ts @@ -12,7 +12,9 @@ export function ensureAuthEventarcTriggerRegion( endpoint.eventTrigger.region = "global"; } if (endpoint.eventTrigger.region !== "global") { - throw new FirebaseError("A Firebase Auth Eventarc trigger must specify 'global' trigger location"); + throw new FirebaseError( + "A Firebase Auth Eventarc trigger must specify 'global' trigger location", + ); } return Promise.resolve(); } From e5d49cfd3850fa996e00ab01df2802a8fecfd4a2 Mon Sep 17 00:00:00 2001 From: Varun Shetty Date: Wed, 17 Jun 2026 18:55:19 +0000 Subject: [PATCH 3/3] addressing gemini comments --- src/deploy/functions/services/authEventarc.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/deploy/functions/services/authEventarc.spec.ts b/src/deploy/functions/services/authEventarc.spec.ts index 5217d0741fa..dabce2aa9e4 100644 --- a/src/deploy/functions/services/authEventarc.spec.ts +++ b/src/deploy/functions/services/authEventarc.spec.ts @@ -20,7 +20,7 @@ const endpoint: Endpoint = { describe("ensureAuthEventarcTriggerRegion", () => { it("should set the trigger location to global", async () => { - const ep = { ...endpoint }; + const ep = { ...endpoint, eventTrigger: { ...endpoint.eventTrigger } }; await authEventarc.ensureAuthEventarcTriggerRegion(ep); @@ -28,7 +28,7 @@ describe("ensureAuthEventarcTriggerRegion", () => { }); it("should not error if the trigger location is global", async () => { - const ep = { ...endpoint }; + const ep = { ...endpoint, eventTrigger: { ...endpoint.eventTrigger } }; ep.eventTrigger.region = "global"; await authEventarc.ensureAuthEventarcTriggerRegion(ep); @@ -37,7 +37,7 @@ describe("ensureAuthEventarcTriggerRegion", () => { }); it("should error if the trigger location is not global", () => { - const ep = { ...endpoint }; + const ep = { ...endpoint, eventTrigger: { ...endpoint.eventTrigger } }; ep.eventTrigger.region = "us-west1"; expect(() => authEventarc.ensureAuthEventarcTriggerRegion(ep)).to.throw(