Skip to content

Commit e71df1d

Browse files
committed
Implement missing onUserCreated/onUserDeleted v2 auth function triggers
1 parent 7f435e2 commit e71df1d

File tree

2 files changed

+241
-2
lines changed

2 files changed

+241
-2
lines changed

spec/v2/providers/identity.spec.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,114 @@ const opts: identity.BlockingOptions = {
6565
};
6666

6767
describe("identity", () => {
68+
describe("onUserCreated", () => {
69+
it("should accept a handler", async () => {
70+
let received: identity.UserRecord | undefined;
71+
const fn = identity.onUserCreated((event) => {
72+
received = event.data;
73+
return null;
74+
});
75+
76+
expect(fn.__endpoint).to.deep.equal({
77+
...MINIMAL_V2_ENDPOINT,
78+
platform: "gcfv2",
79+
labels: {},
80+
eventTrigger: {
81+
eventType: identity.userCreatedEvent,
82+
eventFilters: {},
83+
retry: false,
84+
},
85+
});
86+
87+
const rawEvent = {
88+
specversion: "1.0" as const,
89+
id: "event-id",
90+
source: "//identitytoolkit.googleapis.com/projects/project-id",
91+
type: identity.userCreatedEvent,
92+
time: new Date().toISOString(),
93+
data: {
94+
uid: "abc123",
95+
metadata: {
96+
creationTime: "2016-12-15T19:37:37.059Z",
97+
lastSignInTime: "2017-01-01T00:00:00.000Z",
98+
},
99+
},
100+
};
101+
102+
await fn(rawEvent as any);
103+
expect(received).to.exist;
104+
expect(received!.uid).to.equal("abc123");
105+
expect(received!.metadata).to.be.instanceof(identity.UserRecordMetadata);
106+
});
107+
108+
it("should accept options and a handler", () => {
109+
const fn = identity.onUserCreated(
110+
{
111+
region: "us-central1",
112+
minInstances: 2,
113+
memory: "512MiB",
114+
retry: true,
115+
},
116+
() => null
117+
);
118+
119+
expect(fn.__endpoint).to.deep.equal({
120+
...MINIMAL_V2_ENDPOINT,
121+
platform: "gcfv2",
122+
availableMemoryMb: 512,
123+
region: ["us-central1"],
124+
minInstances: 2,
125+
labels: {},
126+
eventTrigger: {
127+
eventType: identity.userCreatedEvent,
128+
eventFilters: {},
129+
retry: true,
130+
},
131+
});
132+
});
133+
});
134+
135+
describe("onUserDeleted", () => {
136+
it("should accept a handler", () => {
137+
const fn = identity.onUserDeleted(() => null);
138+
139+
expect(fn.__endpoint).to.deep.equal({
140+
...MINIMAL_V2_ENDPOINT,
141+
platform: "gcfv2",
142+
labels: {},
143+
eventTrigger: {
144+
eventType: identity.userDeletedEvent,
145+
eventFilters: {},
146+
retry: false,
147+
},
148+
});
149+
});
150+
151+
it("should accept options and a handler", () => {
152+
const fn = identity.onUserDeleted(
153+
{
154+
region: "europe-west3",
155+
concurrency: 5,
156+
retry: true,
157+
},
158+
() => null
159+
);
160+
161+
expect(fn.__endpoint).to.deep.equal({
162+
...MINIMAL_V2_ENDPOINT,
163+
platform: "gcfv2",
164+
concurrency: 5,
165+
region: ["europe-west3"],
166+
labels: {},
167+
eventTrigger: {
168+
eventType: identity.userDeletedEvent,
169+
eventFilters: {},
170+
retry: true,
171+
},
172+
});
173+
});
174+
});
175+
68176
describe("beforeUserCreated", () => {
69177
it("should accept a handler", () => {
70178
const fn = identity.beforeUserCreated(() => Promise.resolve());

src/v2/providers/identity.ts

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,23 @@ import {
3737
HttpsError,
3838
wrapHandler,
3939
MaybeAsync,
40+
UserInfo,
41+
UserRecord,
42+
UserRecordMetadata,
43+
userRecordConstructor,
4044
} from "../../common/providers/identity";
4145
import { BlockingFunction } from "../../v1/cloud-functions";
4246
import { wrapTraceContext } from "../trace";
4347
import { Expression } from "../../params";
44-
import { initV2Endpoint } from "../../runtime/manifest";
48+
import { CloudEvent, CloudFunction } from "../core";
49+
import { initV2Endpoint, ManifestEndpoint } from "../../runtime/manifest";
4550
import * as options from "../options";
4651
import { SecretParam } from "../../params/types";
4752
import { withInit } from "../../common/onInit";
4853

4954
export { HttpsError };
50-
export type { AuthUserRecord, AuthBlockingEvent };
55+
export { UserRecordMetadata, userRecordConstructor };
56+
export type { AuthUserRecord, AuthBlockingEvent, UserRecord, UserInfo };
5157

5258
/** @hidden Internally used when parsing the options. */
5359
interface InternalOptions {
@@ -166,6 +172,84 @@ export interface BlockingOptions {
166172
secrets?: (string | SecretParam)[];
167173
}
168174

175+
/** @internal */
176+
export const userCreatedEvent = "google.firebase.auth.user.v1.created";
177+
/** @internal */
178+
export const userDeletedEvent = "google.firebase.auth.user.v1.deleted";
179+
180+
/** A CloudEvent that contains a Firebase Auth user record. */
181+
export type UserRecordEvent = CloudEvent<UserRecord>;
182+
183+
type UserEventHandler = (event: UserRecordEvent) => any | Promise<any>;
184+
185+
/**
186+
* Event handler which triggers when a Firebase Auth user is created.
187+
*
188+
* @param handler - Event handler which is run every time a Firebase Auth user is created.
189+
* @returns A function that you can export and deploy.
190+
*/
191+
export function onUserCreated(handler: UserEventHandler): CloudFunction<UserRecordEvent>;
192+
193+
/**
194+
* Event handler which triggers when a Firebase Auth user is created.
195+
*
196+
* @param opts - Options that can be set on an individual event-handling function.
197+
* @param handler - Event handler which is run every time a Firebase Auth user is created.
198+
* @returns A function that you can export and deploy.
199+
*/
200+
export function onUserCreated(
201+
opts: options.EventHandlerOptions,
202+
handler: UserEventHandler
203+
): CloudFunction<UserRecordEvent>;
204+
205+
/**
206+
* Event handler which triggers when a Firebase Auth user is created.
207+
*
208+
* @param optsOrHandler - Options or an event handler.
209+
* @param handler - Event handler which is run every time a Firebase Auth user is created.
210+
* @returns A function that you can export and deploy.
211+
*/
212+
export function onUserCreated(
213+
optsOrHandler: options.EventHandlerOptions | UserEventHandler,
214+
handler?: UserEventHandler
215+
): CloudFunction<UserRecordEvent> {
216+
return onUserOperation(userCreatedEvent, optsOrHandler, handler);
217+
}
218+
219+
/**
220+
* Event handler which triggers when a Firebase Auth user is deleted.
221+
*
222+
* @param handler - Event handler which is run every time a Firebase Auth user is deleted.
223+
* @returns A function that you can export and deploy.
224+
*/
225+
export function onUserDeleted(handler: UserEventHandler): CloudFunction<UserRecordEvent>;
226+
227+
/**
228+
* Event handler which triggers when a Firebase Auth user is deleted.
229+
*
230+
* @param opts - Options that can be set on an individual event-handling function.
231+
* @param handler - Event handler which is run every time a Firebase Auth user is deleted.
232+
* @returns A function that you can export and deploy.
233+
*/
234+
export function onUserDeleted(
235+
opts: options.EventHandlerOptions,
236+
handler: UserEventHandler
237+
): CloudFunction<UserRecordEvent>;
238+
239+
/**
240+
* Event handler which triggers when a Firebase Auth user is deleted.
241+
*
242+
* @param optsOrHandler - Options or an event handler.
243+
* @param handler - Event handler which is run every time a Firebase Auth user is deleted.
244+
* @returns A function that you can export and deploy.
245+
*/
246+
export function onUserDeleted(
247+
optsOrHandler: options.EventHandlerOptions | UserEventHandler,
248+
handler?: UserEventHandler
249+
): CloudFunction<UserRecordEvent> {
250+
return onUserOperation(userDeletedEvent, optsOrHandler, handler);
251+
}
252+
169253
/**
170254
* Handles an event that is triggered before a user is created.
171255
* @param handler - Event handler which is run every time before a user is created.
@@ -293,6 +377,53 @@ export function beforeSmsSent(
293377
return beforeOperation("beforeSendSms", optsOrHandler, handler);
294378
}
295379

380+
function onUserOperation(
381+
eventType: string,
382+
optsOrHandler: options.EventHandlerOptions | UserEventHandler,
383+
handler?: UserEventHandler
384+
): CloudFunction<UserRecordEvent> {
385+
if (typeof optsOrHandler === "function") {
386+
handler = optsOrHandler;
387+
optsOrHandler = {};
388+
}
389+
390+
const baseOpts = options.optionsToEndpoint(options.getGlobalOptions());
391+
const specificOpts = options.optionsToEndpoint(optsOrHandler);
392+
393+
const func: any = (raw: CloudEvent<unknown>) => {
394+
const event = convertToUserRecordEvent(raw);
395+
return wrapTraceContext(withInit(handler))(event);
396+
};
397+
398+
func.run = handler;
399+
400+
const endpoint: ManifestEndpoint = {
401+
...initV2Endpoint(options.getGlobalOptions(), optsOrHandler),
402+
platform: "gcfv2",
403+
...baseOpts,
404+
...specificOpts,
405+
labels: {
406+
...baseOpts?.labels,
407+
...specificOpts?.labels,
408+
},
409+
eventTrigger: {
410+
eventType,
411+
eventFilters: {},
412+
retry: optsOrHandler.retry ?? false,
413+
},
414+
};
415+
416+
func.__endpoint = endpoint;
417+
418+
return func as CloudFunction<UserRecordEvent>;
419+
}
420+
421+
function convertToUserRecordEvent(raw: CloudEvent<unknown>): UserRecordEvent {
422+
const data = (raw.data ?? {}) as Record<string, unknown>;
423+
const user = userRecordConstructor(data);
424+
return { ...raw, data: user } as UserRecordEvent;
425+
}
426+
296427
/** @hidden */
297428
export function beforeOperation(
298429
eventType: AuthBlockingEventType,

0 commit comments

Comments
 (0)