From 44e4b9202ac8559a98b7e0a66d298af2cbf466eb Mon Sep 17 00:00:00 2001 From: Brian Li Date: Tue, 28 Nov 2023 16:47:55 -0500 Subject: [PATCH 01/17] add support for beforeEmail trigger --- spec/common/providers/identity.spec.ts | 50 +++++++++++++ spec/v1/providers/auth.spec.ts | 90 +++++++++++++++++++++++ spec/v2/providers/identity.spec.ts | 95 ++++++++++++++++++++++++ src/common/providers/identity.ts | 78 +++++++++++--------- src/v1/providers/auth.ts | 35 ++++----- src/v2/providers/identity.ts | 99 +++++++++++++------------- 6 files changed, 346 insertions(+), 101 deletions(-) diff --git a/spec/common/providers/identity.spec.ts b/spec/common/providers/identity.spec.ts index cfbaca770..167befff3 100644 --- a/spec/common/providers/identity.spec.ts +++ b/spec/common/providers/identity.spec.ts @@ -528,6 +528,7 @@ describe("identity", () => { userAgent: "USER_AGENT", eventId: "EVENT_ID", eventType: EVENT, + emailType: undefined, authType: "UNAUTHENTICATED", resource: { service: "identitytoolkit.googleapis.com", @@ -540,6 +541,7 @@ describe("identity", () => { username: undefined, isNewUser: false, recaptchaScore: TEST_RECAPTCHA_SCORE, + email: undefined, }, credential: null, params: {}, @@ -577,6 +579,7 @@ describe("identity", () => { userAgent: "USER_AGENT", eventId: "EVENT_ID", eventType: "providers/cloud.auth/eventTypes/user.beforeSignIn:password", + emailType: undefined, authType: "UNAUTHENTICATED", resource: { service: "identitytoolkit.googleapis.com", @@ -589,6 +592,7 @@ describe("identity", () => { username: undefined, isNewUser: false, recaptchaScore: TEST_RECAPTCHA_SCORE, + email: undefined, }, credential: { claims: undefined, @@ -663,6 +667,7 @@ describe("identity", () => { userAgent: "USER_AGENT", eventId: "EVENT_ID", eventType: "providers/cloud.auth/eventTypes/user.beforeCreate:oidc.provider", + emailType: undefined, authType: "USER", resource: { service: "identitytoolkit.googleapis.com", @@ -675,6 +680,7 @@ describe("identity", () => { profile: rawUserInfo, isNewUser: true, recaptchaScore: TEST_RECAPTCHA_SCORE, + email: undefined, }, credential: { claims: undefined, @@ -691,6 +697,50 @@ describe("identity", () => { expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); }); + + it("should parse a beforeSendEmail event", () => { + const time = now.getTime(); + const decodedJwt = { + iss: "https://securetoken.google.com/project_id", + aud: "https://us-east1-project_id.cloudfunctions.net/function-1", + iat: 1, + exp: 60 * 60 + 1, + event_id: "EVENT_ID", + event_type: "beforeSendEmail", + user_agent: "USER_AGENT", + ip_address: "1.2.3.4", + locale: "en", + recaptcha_score: TEST_RECAPTCHA_SCORE, + email_type: "RESET_PASSWORD", + email: "johndoe@gmail.com", + }; + const context = { + locale: "en", + ipAddress: "1.2.3.4", + userAgent: "USER_AGENT", + eventId: "EVENT_ID", + eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + emailType: "RESET_PASSWORD", + authType: "UNAUTHENTICATED", + resource: { + service: "identitytoolkit.googleapis.com", + name: "projects/project-id", + }, + timestamp: new Date(1000).toUTCString(), + additionalUserInfo: { + isNewUser: false, + profile: undefined, + providerId: undefined, + username: undefined, + recaptchaScore: TEST_RECAPTCHA_SCORE, + email: "johndoe@gmail.com", + }, + credential: null, + params: {}, + }; + + expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); + }); }); describe("validateAuthResponse", () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index f5f6a806d..6901e3fdf 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -305,6 +305,96 @@ describe("Auth Functions", () => { }); }); + describe("beforeEmail", () => { + it("should create function without options", () => { + const fn = auth.user().beforeEmail(() => Promise.resolve()); + + expect(fn.__trigger).to.deep.equal({ + labels: {}, + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V1_ENDPOINT, + platform: "gcfv1", + labels: {}, + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + + it("should create the function with options", () => { + const fn = functions + .region("us-east1") + .runWith({ + timeoutSeconds: 90, + memory: "256MB", + }) + .auth.user({ + blockingOptions: { + accessToken: true, + refreshToken: false, + }, + }) + .beforeEmail(() => Promise.resolve()); + + expect(fn.__trigger).to.deep.equal({ + labels: {}, + regions: ["us-east1"], + availableMemoryMb: 256, + timeout: "90s", + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + options: { + accessToken: true, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V1_ENDPOINT, + platform: "gcfv1", + labels: {}, + region: ["us-east1"], + availableMemoryMb: 256, + timeoutSeconds: 90, + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + options: { + accessToken: true, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + }); + describe("#_dataConstructor", () => { let cloudFunctionDelete: CloudFunction; diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 7559a4133..7d18f0762 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -41,6 +41,15 @@ const BEFORE_SIGN_IN_TRIGGER = { }, }; +const BEFORE_EMAIL_TRIGGER = { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, +}; + const opts: identity.BlockingOptions = { accessToken: true, refreshToken: false, @@ -137,6 +146,50 @@ describe("identity", () => { }); }); + describe("beforeEmailSent", () => { + it("should accept a handler", () => { + const fn = identity.beforeEmailSent(() => Promise.resolve()); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + blockingTrigger: BEFORE_EMAIL_TRIGGER, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + }); + + it("should accept options and a handler", () => { + const fn = identity.beforeEmailSent(opts, () => Promise.resolve()); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + minInstances: 1, + region: ["us-west1"], + blockingTrigger: { + ...BEFORE_EMAIL_TRIGGER, + options: { + ...BEFORE_EMAIL_TRIGGER.options, + accessToken: true, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + describe("beforeOperation", () => { it("should handle eventType and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", () => Promise.resolve(), undefined); @@ -172,6 +225,23 @@ describe("identity", () => { ]); }); + it("should handle eventType and handler for before email events", () => { + const fn = identity.beforeOperation("beforeSendEmail", () => Promise.resolve(), undefined); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + blockingTrigger: BEFORE_EMAIL_TRIGGER, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + it("should handle eventType, options, and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", opts, () => Promise.resolve()); @@ -221,6 +291,31 @@ describe("identity", () => { }, ]); }); + + it("should handle eventType, options, and handler for before send email events", () => { + const fn = identity.beforeOperation("beforeSendEmail", opts, () => Promise.resolve()); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + minInstances: 1, + region: ["us-west1"], + blockingTrigger: { + ...BEFORE_EMAIL_TRIGGER, + options: { + ...BEFORE_EMAIL_TRIGGER.options, + accessToken: true, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); }); describe("getOpts", () => { diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index 5e9551bb7..156e25ba8 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -55,11 +55,12 @@ const CLAIMS_MAX_PAYLOAD_SIZE = 1000; * @hidden * @alpha */ -export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn"; +export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn" | "beforeSendEmail"; const EVENT_MAPPING: Record = { beforeCreate: "providers/cloud.auth/eventTypes/user.beforeCreate", beforeSignIn: "providers/cloud.auth/eventTypes/user.beforeSignIn", + beforeSendEmail: "providers/cloud.auth/eventTypes/user.beforeSendEmail", }; /** @@ -307,11 +308,12 @@ export interface AuthUserRecord { /** The additional user info component of the auth event context */ export interface AdditionalUserInfo { - providerId: string; + providerId?: string; profile?: any; username?: string; isNewUser: boolean; recaptchaScore?: number; + email?: string; } /** The credential component of the auth event context */ @@ -326,6 +328,9 @@ export interface Credential { signInMethod: string; } +/** Possible types of emails as described by the GCIP backend. */ +export type EmailType = "EMAIL_SIGNIN" | "PASSWORD_RESET"; + /** Defines the auth event context for blocking events */ export interface AuthEventContext extends EventContext { locale?: string; @@ -333,19 +338,22 @@ export interface AuthEventContext extends EventContext { userAgent: string; additionalUserInfo?: AdditionalUserInfo; credential?: Credential; + emailType?: EmailType; } /** Defines the auth event for 2nd gen blocking events */ export interface AuthBlockingEvent extends AuthEventContext { - data: AuthUserRecord; + data?: AuthUserRecord; } -/** - * The reCAPTCHA action options. - */ +/** The reCAPTCHA action options. */ export type RecaptchaActionOptions = "ALLOW" | "BLOCK"; -/** The handler response type for `beforeCreate` blocking events */ +export interface BeforeEmailResponse { + recaptchaActionOverride?: RecaptchaActionOptions; +} + +/** The handler response type for beforeCreate blocking events */ export interface BeforeCreateResponse { displayName?: string; disabled?: boolean; @@ -391,6 +399,7 @@ interface DecodedPayloadUserRecordEnrolledFactors { export interface DecodedPayloadUserRecord { uid: string; email?: string; + email_type?: string; email_verified?: boolean; phone_number?: string; display_name?: string; @@ -413,7 +422,7 @@ export interface DecodedPayload { exp: number; iat: number; iss: string; - sub: string; + sub?: string; event_id: string; event_type: string; ip_address: string; @@ -432,6 +441,8 @@ export interface DecodedPayload { oauth_token_secret?: string; oauth_expires_in?: number; recaptcha_score?: number; + email?: string; + email_type?: string; [key: string]: any; } @@ -451,26 +462,23 @@ export interface UserRecordResponsePayload updateMask?: string; } -type HandlerV1 = ( - user: AuthUserRecord, - context: AuthEventContext -) => - | BeforeCreateResponse - | BeforeSignInResponse - | void - | Promise - | Promise - | Promise; +export type MaybeAsync = T | Promise; -type HandlerV2 = ( +// N.B. As we add support for new auth blocking functions, some auth blocking event handlers +// will not receive a user record object. However, we can't make the user record parameter +// optional because it is listed before the required context parameter. +export type HandlerV1 = ( + userOrContext: AuthUserRecord | AuthEventContext, + context?: AuthEventContext +) => MaybeAsync; + +export type HandlerV2 = ( event: AuthBlockingEvent -) => - | BeforeCreateResponse - | BeforeSignInResponse - | void - | Promise - | Promise - | Promise; +) => MaybeAsync; + +export type AgnosticHandler = (HandlerV1 | HandlerV2) & { + platform: string; +}; /** * Checks for a valid identity platform web request, otherwise throws an HttpsError. @@ -666,6 +674,7 @@ function parseAdditionalUserInfo(decodedJWT: DecodedPayload): AdditionalUserInfo username, isNewUser: decodedJWT.event_type === "beforeCreate" ? true : false, recaptchaScore: decodedJWT.recaptcha_score, + email: decodedJWT.email, }; } @@ -752,6 +761,7 @@ export function parseAuthEventContext( timestamp: new Date(decodedJWT.iat * 1000).toUTCString(), additionalUserInfo: parseAdditionalUserInfo(decodedJWT), credential: parseAuthCredential(decodedJWT, time), + emailType: decodedJWT.email_type as EmailType, params: {}, }; } @@ -836,7 +846,7 @@ export function getUpdateMask(authResponse?: BeforeCreateResponse | BeforeSignIn } /** @internal */ -export function wrapHandler(eventType: AuthBlockingEventType, handler: HandlerV1 | HandlerV2) { +export function wrapHandler(eventType: AuthBlockingEventType, handler: AgnosticHandler) { return async (req: express.Request, res: express.Response): Promise => { try { const projectId = process.env.GCLOUD_PROJECT; @@ -853,16 +863,20 @@ export function wrapHandler(eventType: AuthBlockingEventType, handler: HandlerV1 const decodedPayload: DecodedPayload = isDebugFeatureEnabled("skipTokenVerification") ? unsafeDecodeAuthBlockingToken(req.body.data.jwt) - : handler.length === 2 + : handler.platform === "gcfv1" ? await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt) : await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt, "run.app"); - const authUserRecord = parseAuthUserRecord(decodedPayload.user_record); + let authUserRecord: AuthUserRecord | undefined; + if (decodedPayload.user_record) { + authUserRecord = parseAuthUserRecord(decodedPayload.user_record); + } const authEventContext = parseAuthEventContext(decodedPayload, projectId); let authResponse; - if (handler.length === 2) { - authResponse = - (await (handler as HandlerV1)(authUserRecord, authEventContext)) || undefined; + if (handler.platform === "gcfv1") { + authResponse = authUserRecord + ? (await (handler as HandlerV1)(authUserRecord, authEventContext)) || undefined + : (await (handler as HandlerV1)(authEventContext)) || undefined; } else { authResponse = (await (handler as HandlerV2)({ diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index edef7b0bb..c4435b798 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -25,8 +25,12 @@ import { AuthEventContext, AuthUserRecord, BeforeCreateResponse, + BeforeEmailResponse, BeforeSignInResponse, + AgnosticHandler, + HandlerV1, HttpsError, + MaybeAsync, UserInfo, UserRecord, userRecordConstructor, @@ -151,7 +155,7 @@ export class UserBuilder { handler: ( user: AuthUserRecord, context: AuthEventContext - ) => BeforeCreateResponse | void | Promise | Promise + ) => MaybeAsync ): BlockingFunction { return this.beforeOperation(handler, "beforeCreate"); } @@ -167,11 +171,17 @@ export class UserBuilder { handler: ( user: AuthUserRecord, context: AuthEventContext - ) => BeforeSignInResponse | void | Promise | Promise + ) => MaybeAsync ): BlockingFunction { return this.beforeOperation(handler, "beforeSignIn"); } + beforeEmail( + handler: (context: AuthEventContext) => MaybeAsync + ): BlockingFunction { + return this.beforeOperation(handler, "beforeSendEmail"); + } + private onOperation( handler: (user: UserRecord, context: EventContext) => PromiseLike | any, eventType: string @@ -189,28 +199,13 @@ export class UserBuilder { }); } - private beforeOperation( - handler: ( - user: AuthUserRecord, - context: AuthEventContext - ) => - | BeforeCreateResponse - | BeforeSignInResponse - | void - | Promise - | Promise - | Promise, - eventType: AuthBlockingEventType - ): BlockingFunction { + private beforeOperation(handler: HandlerV1, eventType: AuthBlockingEventType): BlockingFunction { const accessToken = this.userOptions?.blockingOptions?.accessToken || false; const idToken = this.userOptions?.blockingOptions?.idToken || false; const refreshToken = this.userOptions?.blockingOptions?.refreshToken || false; - // Create our own function that just calls the provided function so we know for sure that - // handler takes two arguments. This is something common/providers/identity depends on. - const wrappedHandler = (user: AuthUserRecord, context: AuthEventContext) => - handler(user, context); - const func: any = wrapHandler(eventType, wrappedHandler); + const annotatedHandler: AgnosticHandler = Object.assign(handler, { platform: "gcfv1" }); + const func: any = wrapHandler(eventType, annotatedHandler); const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`; diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index 3a0b1b7fc..5f0876bd5 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -31,8 +31,12 @@ import { AuthUserRecord, BeforeCreateResponse, BeforeSignInResponse, + BeforeEmailResponse, + HandlerV2, HttpsError, wrapHandler, + MaybeAsync, + AgnosticHandler, } from "../../common/providers/identity"; import { BlockingFunction } from "../../v1/cloud-functions"; import { wrapTraceContext } from "../trace"; @@ -165,9 +169,7 @@ export interface BlockingOptions { * @param handler - Event handler which is run every time before a user is created */ export function beforeUserCreated( - handler: ( - event: AuthBlockingEvent - ) => BeforeCreateResponse | Promise | void | Promise + handler: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction; /** @@ -177,9 +179,7 @@ export function beforeUserCreated( */ export function beforeUserCreated( opts: BlockingOptions, - handler: ( - event: AuthBlockingEvent - ) => BeforeCreateResponse | Promise | void | Promise + handler: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction; /** @@ -190,12 +190,8 @@ export function beforeUserCreated( export function beforeUserCreated( optsOrHandler: | BlockingOptions - | (( - event: AuthBlockingEvent - ) => BeforeCreateResponse | Promise | void | Promise), - handler?: ( - event: AuthBlockingEvent - ) => BeforeCreateResponse | Promise | void | Promise + | ((event: AuthBlockingEvent) => MaybeAsync), + handler?: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction { return beforeOperation("beforeCreate", optsOrHandler, handler); } @@ -205,9 +201,7 @@ export function beforeUserCreated( * @param handler - Event handler which is run every time before a user is signed in */ export function beforeUserSignedIn( - handler: ( - event: AuthBlockingEvent - ) => BeforeSignInResponse | Promise | void | Promise + handler: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction; /** @@ -217,9 +211,7 @@ export function beforeUserSignedIn( */ export function beforeUserSignedIn( opts: BlockingOptions, - handler: ( - event: AuthBlockingEvent - ) => BeforeSignInResponse | Promise | void | Promise + handler: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction; /** @@ -230,16 +222,44 @@ export function beforeUserSignedIn( export function beforeUserSignedIn( optsOrHandler: | BlockingOptions - | (( - event: AuthBlockingEvent - ) => BeforeSignInResponse | Promise | void | Promise), - handler?: ( - event: AuthBlockingEvent - ) => BeforeSignInResponse | Promise | void | Promise + | ((event: AuthBlockingEvent) => MaybeAsync), + handler?: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction { return beforeOperation("beforeSignIn", optsOrHandler, handler); } +/** + * Handles an event that is triggered before an email is sent to a user. + * @param handler - Event handler that is run before an email is sent to a user. + */ +export function beforeEmailSent( + handler: (event: AuthBlockingEvent) => MaybeAsync +): BlockingFunction; + +/** + * Handles an event that is triggered before an email is sent to a user. + * @param opts - Object containing function options + * @param handler - Event handler that is run before an email is sent to a user. + */ +export function beforeEmailSent( + opts: BlockingOptions, + handler: (event: AuthBlockingEvent) => MaybeAsync +): BlockingFunction; + +/** + * Handles an event that is triggered before an email is sent to a user. + * @param optsOrHandler- Either an object containing function options, or an event handler that is run before an email is sent to a user. + * @param handler - Event handler that is run before an email is sent to a user. + */ +export function beforeEmailSent( + optsOrHandler: + | BlockingOptions + | ((event: AuthBlockingEvent) => MaybeAsync), + handler?: (event: AuthBlockingEvent) => MaybeAsync +): BlockingFunction { + return beforeOperation("beforeSendEmail", optsOrHandler, handler); +} + /** @hidden */ export function beforeOperation( eventType: AuthBlockingEventType, @@ -247,33 +267,13 @@ export function beforeOperation( | BlockingOptions | (( event: AuthBlockingEvent - ) => - | BeforeCreateResponse - | BeforeSignInResponse - | void - | Promise - | Promise - | Promise), - handler: ( - event: AuthBlockingEvent - ) => - | BeforeCreateResponse - | BeforeSignInResponse - | void - | Promise - | Promise - | Promise + ) => MaybeAsync), + handler: HandlerV2 ): BlockingFunction { if (!handler || typeof optsOrHandler === "function") { handler = optsOrHandler as ( event: AuthBlockingEvent - ) => - | BeforeCreateResponse - | BeforeSignInResponse - | void - | Promise - | Promise - | Promise; + ) => BeforeEmailResponse | void | Promise | Promise; optsOrHandler = {}; } @@ -281,8 +281,9 @@ export function beforeOperation( // Create our own function that just calls the provided function so we know for sure that // handler takes one argument. This is something common/providers/identity depends on. - const wrappedHandler = (event: AuthBlockingEvent) => handler(event); - const func: any = wrapTraceContext(wrapHandler(eventType, wrappedHandler)); + // const wrappedHandler = (event: AuthBlockingEvent) => handler(event); + const annotatedHandler: AgnosticHandler = Object.assign(handler, { platform: "gcfv2" }); + const func: any = wrapTraceContext(wrapHandler(eventType, annotatedHandler)); const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`; From 9f7b83bfe6e7c521eb194af560903decaf35517b Mon Sep 17 00:00:00 2001 From: Brian Li Date: Mon, 22 Jul 2024 17:45:13 -0500 Subject: [PATCH 02/17] remove opts from beforeemail & decode user record only for beforeCreate and beforeSignIn --- spec/v2/providers/identity.spec.ts | 47 +++++++++++++++--------------- src/common/providers/identity.ts | 14 +++++---- src/v1/providers/auth.ts | 3 +- src/v2/providers/identity.ts | 7 ++--- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 822231f18..d7ca6aeb2 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -26,6 +26,9 @@ import { onInit } from "../../../src/v2/core"; import { MockRequest } from "../../fixtures/mockrequest"; import { runHandler } from "../../helper"; +const IDENTITY_TOOLKIT_API = "identitytoolkit.googleapis.com"; +const REGION = "us-west1"; + const BEFORE_CREATE_TRIGGER = { eventType: "providers/cloud.auth/eventTypes/user.beforeCreate", options: { @@ -46,18 +49,14 @@ const BEFORE_SIGN_IN_TRIGGER = { const BEFORE_EMAIL_TRIGGER = { eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", - options: { - accessToken: false, - idToken: false, - refreshToken: false, - }, + options: {}, }; const opts: identity.BlockingOptions = { accessToken: true, refreshToken: false, minInstances: 1, - region: "us-west1", + region: REGION, }; describe("identity", () => { @@ -73,7 +72,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -87,7 +86,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_CREATE_TRIGGER, options: { @@ -98,7 +97,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -138,7 +137,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -152,7 +151,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_SIGN_IN_TRIGGER, options: { @@ -163,7 +162,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -203,7 +202,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -218,7 +217,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_EMAIL_TRIGGER, options: { @@ -229,7 +228,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -247,7 +246,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -264,7 +263,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -281,7 +280,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -295,7 +294,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_CREATE_TRIGGER, options: { @@ -306,7 +305,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -320,7 +319,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_SIGN_IN_TRIGGER, options: { @@ -331,7 +330,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -345,7 +344,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_EMAIL_TRIGGER, options: { @@ -356,7 +355,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index 156e25ba8..bac3cb52e 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -349,6 +349,7 @@ export interface AuthBlockingEvent extends AuthEventContext { /** The reCAPTCHA action options. */ export type RecaptchaActionOptions = "ALLOW" | "BLOCK"; +/** The handler response type for `beforeEmailSent` blocking events */ export interface BeforeEmailResponse { recaptchaActionOverride?: RecaptchaActionOptions; } @@ -399,7 +400,6 @@ interface DecodedPayloadUserRecordEnrolledFactors { export interface DecodedPayloadUserRecord { uid: string; email?: string; - email_type?: string; email_verified?: boolean; phone_number?: string; display_name?: string; @@ -476,8 +476,9 @@ export type HandlerV2 = ( event: AuthBlockingEvent ) => MaybeAsync; -export type AgnosticHandler = (HandlerV1 | HandlerV2) & { - platform: string; +export type AuthBlockingEventHandler = (HandlerV1 | HandlerV2) & { + // Specify the GCF gen of the trigger that the auth blocking event handler was written for + platform: "gcfv1" | "gcfv2"; }; /** @@ -846,7 +847,7 @@ export function getUpdateMask(authResponse?: BeforeCreateResponse | BeforeSignIn } /** @internal */ -export function wrapHandler(eventType: AuthBlockingEventType, handler: AgnosticHandler) { +export function wrapHandler(eventType: AuthBlockingEventType, handler: AuthBlockingEventHandler) { return async (req: express.Request, res: express.Response): Promise => { try { const projectId = process.env.GCLOUD_PROJECT; @@ -867,7 +868,10 @@ export function wrapHandler(eventType: AuthBlockingEventType, handler: AgnosticH ? await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt) : await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt, "run.app"); let authUserRecord: AuthUserRecord | undefined; - if (decodedPayload.user_record) { + if ( + decodedPayload.event_type === "beforeCreate" || + decodedPayload.event_type === "beforeSignIn" + ) { authUserRecord = parseAuthUserRecord(decodedPayload.user_record); } const authEventContext = parseAuthEventContext(decodedPayload, projectId); diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index c4435b798..efab2f488 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -27,7 +27,6 @@ import { BeforeCreateResponse, BeforeEmailResponse, BeforeSignInResponse, - AgnosticHandler, HandlerV1, HttpsError, MaybeAsync, @@ -204,7 +203,7 @@ export class UserBuilder { const idToken = this.userOptions?.blockingOptions?.idToken || false; const refreshToken = this.userOptions?.blockingOptions?.refreshToken || false; - const annotatedHandler: AgnosticHandler = Object.assign(handler, { platform: "gcfv1" }); + const annotatedHandler = Object.assign(handler, { platform: "gcfv1" as const }); const func: any = wrapHandler(eventType, annotatedHandler); const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`; diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index ffeac8952..737976ac3 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -36,7 +36,6 @@ import { HttpsError, wrapHandler, MaybeAsync, - AgnosticHandler, } from "../../common/providers/identity"; import { BlockingFunction } from "../../v1/cloud-functions"; import { wrapTraceContext } from "../trace"; @@ -243,7 +242,7 @@ export function beforeEmailSent( * @param handler - Event handler that is run before an email is sent to a user. */ export function beforeEmailSent( - opts: BlockingOptions, + opts: Omit, handler: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction; @@ -254,7 +253,7 @@ export function beforeEmailSent( */ export function beforeEmailSent( optsOrHandler: - | BlockingOptions + | Omit | ((event: AuthBlockingEvent) => MaybeAsync), handler?: (event: AuthBlockingEvent) => MaybeAsync ): BlockingFunction { @@ -283,7 +282,7 @@ export function beforeOperation( // Create our own function that just calls the provided function so we know for sure that // handler takes one argument. This is something common/providers/identity depends on. // const wrappedHandler = (event: AuthBlockingEvent) => handler(event); - const annotatedHandler: AgnosticHandler = Object.assign(handler, { platform: "gcfv2" }); + const annotatedHandler = Object.assign(handler, { platform: "gcfv2" as const }); const func: any = wrapTraceContext(withInit(wrapHandler(eventType, annotatedHandler))); const legacyEventType = `providers/cloud.auth/eventTypes/user.${eventType}`; From 5919c318706291fe5b702ee570ad74dcf59a512f Mon Sep 17 00:00:00 2001 From: Brian Li Date: Mon, 22 Jul 2024 19:17:31 -0500 Subject: [PATCH 03/17] fix unit tests --- spec/v2/providers/identity.spec.ts | 47 +++++++++++++----------------- src/v2/providers/identity.ts | 8 ++--- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index d7ca6aeb2..5d1a7aee0 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -207,31 +207,30 @@ describe("identity", () => { }, ]); }); - }); - it("should accept options and a handler", () => { - const fn = identity.beforeEmailSent(opts, () => Promise.resolve()); - - expect(fn.__endpoint).to.deep.equal({ - ...MINIMAL_V2_ENDPOINT, - platform: "gcfv2", - labels: {}, - minInstances: 1, - region: [REGION], - blockingTrigger: { - ...BEFORE_EMAIL_TRIGGER, - options: { - ...BEFORE_EMAIL_TRIGGER.options, - accessToken: true, + it("should accept options and a handler", () => { + const fn = identity.beforeEmailSent( + { region: opts.region, minInstances: opts.minInstances }, + () => Promise.resolve() + ); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + minInstances: 1, + region: [REGION], + blockingTrigger: { + ...BEFORE_EMAIL_TRIGGER, }, - }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: IDENTITY_TOOLKIT_API, + reason: "Needed for auth blocking functions", + }, + ]); }); - expect(fn.__requiredAPIs).to.deep.equal([ - { - api: IDENTITY_TOOLKIT_API, - reason: "Needed for auth blocking functions", - }, - ]); }); describe("beforeOperation", () => { @@ -347,10 +346,6 @@ describe("identity", () => { region: [REGION], blockingTrigger: { ...BEFORE_EMAIL_TRIGGER, - options: { - ...BEFORE_EMAIL_TRIGGER.options, - accessToken: true, - }, }, }); expect(fn.__requiredAPIs).to.deep.equal([ diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index 737976ac3..2829cd349 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -273,11 +273,11 @@ export function beforeOperation( if (!handler || typeof optsOrHandler === "function") { handler = optsOrHandler as ( event: AuthBlockingEvent - ) => BeforeEmailResponse | void | Promise | Promise; + ) => MaybeAsync; optsOrHandler = {}; } - const { opts, accessToken, idToken, refreshToken } = getOpts(optsOrHandler); + const { opts, ...blockingOptions } = getOpts(optsOrHandler); // Create our own function that just calls the provided function so we know for sure that // handler takes one argument. This is something common/providers/identity depends on. @@ -302,9 +302,7 @@ export function beforeOperation( blockingTrigger: { eventType: legacyEventType, options: { - accessToken, - idToken, - refreshToken, + ...((eventType === "beforeCreate" || eventType === "beforeSignIn") && blockingOptions), }, }, }; From b63afc98c4545fb50f83e3352bcb07e792e5ec42 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Mon, 22 Jul 2024 19:18:29 -0500 Subject: [PATCH 04/17] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb..3fc76447f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Add support for beforeEmailSent auth blocking triggers. (#1492) From 9f7b42f334441f3577aa112a8f29d768a1fa7b69 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Tue, 6 Aug 2024 15:23:20 -0400 Subject: [PATCH 05/17] add comment to authblockingevent type --- src/common/providers/identity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index bac3cb52e..e4373d43a 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -343,7 +343,7 @@ export interface AuthEventContext extends EventContext { /** Defines the auth event for 2nd gen blocking events */ export interface AuthBlockingEvent extends AuthEventContext { - data?: AuthUserRecord; + data?: AuthUserRecord; // will be undefined for beforeEmailSent and beforeSmsSent event types } /** The reCAPTCHA action options. */ From a3a8c5dc88d66a6e08e751f5f93d894e962e6865 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Fri, 13 Sep 2024 12:29:54 -0400 Subject: [PATCH 06/17] rebase --- spec/v2/providers/identity.spec.ts | 42 ++++++++++++++++++++++++++++++ src/common/providers/identity.ts | 1 + 2 files changed, 43 insertions(+) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 5d1a7aee0..5390f74c2 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -285,6 +285,23 @@ describe("identity", () => { ]); }); + it("should handle eventType and handler for before email events", () => { + const fn = identity.beforeOperation("beforeSendEmail", () => Promise.resolve(), undefined); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + blockingTrigger: BEFORE_EMAIL_TRIGGER, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + it("should handle eventType, options, and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", opts, () => Promise.resolve()); @@ -355,6 +372,31 @@ describe("identity", () => { }, ]); }); + + it("should handle eventType, options, and handler for before send email events", () => { + const fn = identity.beforeOperation("beforeSendEmail", opts, () => Promise.resolve()); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + minInstances: 1, + region: ["us-west1"], + blockingTrigger: { + ...BEFORE_EMAIL_TRIGGER, + options: { + ...BEFORE_EMAIL_TRIGGER.options, + accessToken: true, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); }); describe("getOpts", () => { diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index e4373d43a..33c16027a 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -400,6 +400,7 @@ interface DecodedPayloadUserRecordEnrolledFactors { export interface DecodedPayloadUserRecord { uid: string; email?: string; + email_type?: string; email_verified?: boolean; phone_number?: string; display_name?: string; From 53b61273e83325b7494c9a5d693e6573546682b7 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Fri, 13 Sep 2024 12:31:18 -0400 Subject: [PATCH 07/17] rebase --- spec/v2/providers/identity.spec.ts | 6 +++--- src/common/providers/identity.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 5390f74c2..1a688b957 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -296,7 +296,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); @@ -381,7 +381,7 @@ describe("identity", () => { platform: "gcfv2", labels: {}, minInstances: 1, - region: ["us-west1"], + region: [REGION], blockingTrigger: { ...BEFORE_EMAIL_TRIGGER, options: { @@ -392,7 +392,7 @@ describe("identity", () => { }); expect(fn.__requiredAPIs).to.deep.equal([ { - api: "identitytoolkit.googleapis.com", + api: IDENTITY_TOOLKIT_API, reason: "Needed for auth blocking functions", }, ]); diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index 33c16027a..e4373d43a 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -400,7 +400,6 @@ interface DecodedPayloadUserRecordEnrolledFactors { export interface DecodedPayloadUserRecord { uid: string; email?: string; - email_type?: string; email_verified?: boolean; phone_number?: string; display_name?: string; From 61fc45a1b290aa9edda72c743495a9b4937520e5 Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 5 Aug 2024 16:17:42 -0700 Subject: [PATCH 08/17] Add beforeSmsSent blocking functions trigger --- src/common/providers/identity.ts | 22 ++++++++++++++++++- src/v1/providers/auth.ts | 7 +++++++ src/v2/providers/identity.ts | 36 ++++++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index e4373d43a..b23323aab 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -55,12 +55,13 @@ const CLAIMS_MAX_PAYLOAD_SIZE = 1000; * @hidden * @alpha */ -export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn" | "beforeSendEmail"; +export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn" | "beforeSendEmail" | "beforeSendSms"; const EVENT_MAPPING: Record = { beforeCreate: "providers/cloud.auth/eventTypes/user.beforeCreate", beforeSignIn: "providers/cloud.auth/eventTypes/user.beforeSignIn", beforeSendEmail: "providers/cloud.auth/eventTypes/user.beforeSendEmail", + beforeSendSms: "providers/cloud.auth/eventTypes/user.beforeSendSms", }; /** @@ -314,6 +315,7 @@ export interface AdditionalUserInfo { isNewUser: boolean; recaptchaScore?: number; email?: string; + phoneNumber?: string; } /** The credential component of the auth event context */ @@ -331,6 +333,14 @@ export interface Credential { /** Possible types of emails as described by the GCIP backend. */ export type EmailType = "EMAIL_SIGNIN" | "PASSWORD_RESET"; +/** + * The type of SMS message + */ +export type SmsType = + | "SIGN_IN_OR_SIGN_UP" // A sign-in or sign up SMS message + | "MULTI_FACTOR_SIGN_IN" // A multi-factor sign-in SMS message + | "MULTI_FACTOR_ENROLLMENT"; // A multi-factor enrollment SMS message + /** Defines the auth event context for blocking events */ export interface AuthEventContext extends EventContext { locale?: string; @@ -339,6 +349,7 @@ export interface AuthEventContext extends EventContext { additionalUserInfo?: AdditionalUserInfo; credential?: Credential; emailType?: EmailType; + smsType?: SmsType; } /** Defines the auth event for 2nd gen blocking events */ @@ -354,6 +365,11 @@ export interface BeforeEmailResponse { recaptchaActionOverride?: RecaptchaActionOptions; } +/** The handler response type for `beforeSmsSent` blocking events */ +export interface BeforeSmsResponse { + recaptchaActionOverride?: RecaptchaActionOptions; +} + /** The handler response type for beforeCreate blocking events */ export interface BeforeCreateResponse { displayName?: string; @@ -443,6 +459,8 @@ export interface DecodedPayload { recaptcha_score?: number; email?: string; email_type?: string; + phone_number?: string; + sms_type?: string; [key: string]: any; } @@ -676,6 +694,7 @@ function parseAdditionalUserInfo(decodedJWT: DecodedPayload): AdditionalUserInfo isNewUser: decodedJWT.event_type === "beforeCreate" ? true : false, recaptchaScore: decodedJWT.recaptcha_score, email: decodedJWT.email, + phoneNumber: decodedJWT.phone_number, }; } @@ -763,6 +782,7 @@ export function parseAuthEventContext( additionalUserInfo: parseAdditionalUserInfo(decodedJWT), credential: parseAuthCredential(decodedJWT, time), emailType: decodedJWT.email_type as EmailType, + smsType: decodedJWT.sms_type as SmsType, params: {}, }; } diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index efab2f488..6eaf3f563 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -27,6 +27,7 @@ import { BeforeCreateResponse, BeforeEmailResponse, BeforeSignInResponse, + BeforeSmsResponse, HandlerV1, HttpsError, MaybeAsync, @@ -180,6 +181,12 @@ export class UserBuilder { ): BlockingFunction { return this.beforeOperation(handler, "beforeSendEmail"); } + + beforeSms( + handler: (context: AuthEventContext) => MaybeAsync + ): BlockingFunction { + return this.beforeOperation(handler, "beforeSendSms"); + } private onOperation( handler: (user: UserRecord, context: EventContext) => PromiseLike | any, diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index 2829cd349..42b66d050 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -32,6 +32,7 @@ import { BeforeCreateResponse, BeforeSignInResponse, BeforeEmailResponse, + BeforeSmsResponse, HandlerV2, HttpsError, wrapHandler, @@ -259,6 +260,37 @@ export function beforeEmailSent( ): BlockingFunction { return beforeOperation("beforeSendEmail", optsOrHandler, handler); } +/** + * Handles an event that is triggered before an SMS is sent to a user. + * @param handler - Event handler that is run before an SMS is sent to a user. + */ +export function beforeSmsSent( + handler: (event: AuthBlockingEvent) => MaybeAsync +): BlockingFunction; + +/** + * Handles an event that is triggered before an SMS is sent to a user. + * @param opts - Object containing function options + * @param handler - Event handler that is run before an SMS is sent to a user. + */ +export function beforeSmsSent( + opts: Omit, + handler: (event: AuthBlockingEvent) => MaybeAsync +): BlockingFunction; + +/** + * Handles an event that is triggered before an SMS is sent to a user. + * @param optsOrHandler - Either an object containing function options, or an event handler that is run before an SMS is sent to a user. + * @param handler - Event handler that is run before an SMS is sent to a user. + */ +export function beforeSmsSent( + optsOrHandler: + | Omit + | ((event: AuthBlockingEvent) => MaybeAsync), + handler?: (event: AuthBlockingEvent) => MaybeAsync +): BlockingFunction { + return beforeOperation("beforeSendSms", optsOrHandler, handler); +} /** @hidden */ export function beforeOperation( @@ -267,13 +299,13 @@ export function beforeOperation( | BlockingOptions | (( event: AuthBlockingEvent - ) => MaybeAsync), + ) => MaybeAsync), handler: HandlerV2 ): BlockingFunction { if (!handler || typeof optsOrHandler === "function") { handler = optsOrHandler as ( event: AuthBlockingEvent - ) => MaybeAsync; + ) => MaybeAsync; optsOrHandler = {}; } From 2a76f23c21d04b446b2708be812fa941482de457 Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 19 Aug 2024 17:14:27 -0700 Subject: [PATCH 09/17] adding spec.ts changes --- spec/common/providers/identity.spec.ts | 56 +++++++++++++++- spec/v1/providers/auth.spec.ts | 90 ++++++++++++++++++++++++++ spec/v2/providers/identity.spec.ts | 49 ++++++++++++++ src/common/providers/identity.ts | 4 +- 4 files changed, 196 insertions(+), 3 deletions(-) diff --git a/spec/common/providers/identity.spec.ts b/spec/common/providers/identity.spec.ts index 167befff3..db82468b9 100644 --- a/spec/common/providers/identity.spec.ts +++ b/spec/common/providers/identity.spec.ts @@ -529,6 +529,7 @@ describe("identity", () => { eventId: "EVENT_ID", eventType: EVENT, emailType: undefined, + smsType: undefined, authType: "UNAUTHENTICATED", resource: { service: "identitytoolkit.googleapis.com", @@ -542,6 +543,7 @@ describe("identity", () => { isNewUser: false, recaptchaScore: TEST_RECAPTCHA_SCORE, email: undefined, + phoneNumber: undefined, }, credential: null, params: {}, @@ -580,6 +582,7 @@ describe("identity", () => { eventId: "EVENT_ID", eventType: "providers/cloud.auth/eventTypes/user.beforeSignIn:password", emailType: undefined, + smsType: undefined, authType: "UNAUTHENTICATED", resource: { service: "identitytoolkit.googleapis.com", @@ -593,6 +596,7 @@ describe("identity", () => { isNewUser: false, recaptchaScore: TEST_RECAPTCHA_SCORE, email: undefined, + phoneNumber: undefined, }, credential: { claims: undefined, @@ -668,6 +672,7 @@ describe("identity", () => { eventId: "EVENT_ID", eventType: "providers/cloud.auth/eventTypes/user.beforeCreate:oidc.provider", emailType: undefined, + smsType: undefined, authType: "USER", resource: { service: "identitytoolkit.googleapis.com", @@ -681,6 +686,7 @@ describe("identity", () => { isNewUser: true, recaptchaScore: TEST_RECAPTCHA_SCORE, email: undefined, + phoneNumber: undefined, }, credential: { claims: undefined, @@ -721,6 +727,7 @@ describe("identity", () => { eventId: "EVENT_ID", eventType: "providers/cloud.auth/eventTypes/user.beforeSendEmail", emailType: "RESET_PASSWORD", + smsType: undefined, authType: "UNAUTHENTICATED", resource: { service: "identitytoolkit.googleapis.com", @@ -734,6 +741,7 @@ describe("identity", () => { username: undefined, recaptchaScore: TEST_RECAPTCHA_SCORE, email: "johndoe@gmail.com", + phoneNumber: undefined, }, credential: null, params: {}, @@ -741,7 +749,53 @@ describe("identity", () => { expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); }); - }); + + it("should parse a beforeSendSms event", () => { + const time = now.getTime(); + const decodedJwt = { + iss: "https://securetoken.google.com/project_id", + aud: "https://us-east1-project_id.cloudfunctions.net/function-1", + iat: 1, + exp: 60 * 60 + 1, + event_id: "EVENT_ID", + event_type: "beforeSendSms", + user_agent: "USER_AGENT", + ip_address: "1.2.3.4", + locale: "en", + recaptcha_score: TEST_RECAPTCHA_SCORE, + sms_type: "SIGN_IN_OR_SIGN_UP", + phone_number: "+11234567890", + }; + const context = { + locale: "en", + ipAddress: "1.2.3.4", + userAgent: "USER_AGENT", + eventId: "EVENT_ID", + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + emailType: undefined, + smsType: "SIGN_IN_OR_SIGN_UP", + authType: "UNAUTHENTICATED", + resource: { + service: "identitytoolkit.googleapis.com", + name: "projects/project-id", + }, + timestamp: new Date(1000).toUTCString(), + additionalUserInfo: { + isNewUser: false, + profile: undefined, + providerId: undefined, + username: undefined, + recaptchaScore: TEST_RECAPTCHA_SCORE, + email: undefined, + phoneNumber: "+11234567890", + }, + credential: null, + params: {}, + }; + + expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); + }); +}); describe("validateAuthResponse", () => { it("should not throw on undefined request", () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index 6901e3fdf..e2c21a690 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -394,6 +394,96 @@ describe("Auth Functions", () => { ]); }); }); + + describe("beforeSms", () => { + it("should create function without options", () => { + const fn = auth.user().beforeSms(() => Promise.resolve()); + + expect(fn.__trigger).to.deep.equal({ + labels: {}, + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V1_ENDPOINT, + platform: "gcfv1", + labels: {}, + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + options: { + accessToken: false, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + + it("should create the function with options", () => { + const fn = functions + .region("us-east1") + .runWith({ + timeoutSeconds: 90, + memory: "256MB", + }) + .auth.user({ + blockingOptions: { + accessToken: true, + refreshToken: false, + }, + }) + .beforeSms(() => Promise.resolve()); + + expect(fn.__trigger).to.deep.equal({ + labels: {}, + regions: ["us-east1"], + availableMemoryMb: 256, + timeout: "90s", + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + options: { + accessToken: true, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V1_ENDPOINT, + platform: "gcfv1", + labels: {}, + region: ["us-east1"], + availableMemoryMb: 256, + timeoutSeconds: 90, + blockingTrigger: { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + options: { + accessToken: true, + idToken: false, + refreshToken: false, + }, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: "identitytoolkit.googleapis.com", + reason: "Needed for auth blocking functions", + }, + ]); + }); + }); describe("#_dataConstructor", () => { let cloudFunctionDelete: CloudFunction; diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 1a688b957..ee68aa8ad 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -52,6 +52,12 @@ const BEFORE_EMAIL_TRIGGER = { options: {}, }; + +const BEFORE_SMS_TRIGGER = { + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + options: {}, +}; + const opts: identity.BlockingOptions = { accessToken: true, refreshToken: false, @@ -232,6 +238,49 @@ describe("identity", () => { ]); }); }); + + describe("beforeSmsSent", () => { + it("should accept a handler", () => { + const fn = identity.beforeSmsSent(() => Promise.resolve()); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + blockingTrigger: BEFORE_SMS_TRIGGER, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: IDENTITY_TOOLKIT_API, + reason: "Needed for auth blocking functions", + }, + ]); + }); + + it("should accept options and a handler", () => { + const fn = identity.beforeSmsSent( + { region: opts.region, minInstances: opts.minInstances }, + () => Promise.resolve() + ); + + expect(fn.__endpoint).to.deep.equal({ + ...MINIMAL_V2_ENDPOINT, + platform: "gcfv2", + labels: {}, + minInstances: 1, + region: [REGION], + blockingTrigger: { + ...BEFORE_SMS_TRIGGER, + }, + }); + expect(fn.__requiredAPIs).to.deep.equal([ + { + api: IDENTITY_TOOLKIT_API, + reason: "Needed for auth blocking functions", + }, + ]); + }); + }); describe("beforeOperation", () => { it("should handle eventType and handler for before create events", () => { diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index b23323aab..2f34858aa 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -488,11 +488,11 @@ export type MaybeAsync = T | Promise; export type HandlerV1 = ( userOrContext: AuthUserRecord | AuthEventContext, context?: AuthEventContext -) => MaybeAsync; +) => MaybeAsync; export type HandlerV2 = ( event: AuthBlockingEvent -) => MaybeAsync; +) => MaybeAsync; export type AuthBlockingEventHandler = (HandlerV1 | HandlerV2) & { // Specify the GCF gen of the trigger that the auth blocking event handler was written for From 659955ecd3363f497cbb32829621165605d8e12a Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 19 Aug 2024 17:18:09 -0700 Subject: [PATCH 10/17] lint --- spec/common/providers/identity.spec.ts | 94 +++++++++++++------------- spec/v1/providers/auth.spec.ts | 10 +-- spec/v2/providers/identity.spec.ts | 11 ++- src/common/providers/identity.ts | 14 +++- src/v1/providers/auth.ts | 2 +- src/v2/providers/identity.ts | 8 ++- 6 files changed, 75 insertions(+), 64 deletions(-) diff --git a/spec/common/providers/identity.spec.ts b/spec/common/providers/identity.spec.ts index db82468b9..253a337b2 100644 --- a/spec/common/providers/identity.spec.ts +++ b/spec/common/providers/identity.spec.ts @@ -749,53 +749,53 @@ describe("identity", () => { expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); }); - - it("should parse a beforeSendSms event", () => { - const time = now.getTime(); - const decodedJwt = { - iss: "https://securetoken.google.com/project_id", - aud: "https://us-east1-project_id.cloudfunctions.net/function-1", - iat: 1, - exp: 60 * 60 + 1, - event_id: "EVENT_ID", - event_type: "beforeSendSms", - user_agent: "USER_AGENT", - ip_address: "1.2.3.4", - locale: "en", - recaptcha_score: TEST_RECAPTCHA_SCORE, - sms_type: "SIGN_IN_OR_SIGN_UP", - phone_number: "+11234567890", - }; - const context = { - locale: "en", - ipAddress: "1.2.3.4", - userAgent: "USER_AGENT", - eventId: "EVENT_ID", - eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", - emailType: undefined, - smsType: "SIGN_IN_OR_SIGN_UP", - authType: "UNAUTHENTICATED", - resource: { - service: "identitytoolkit.googleapis.com", - name: "projects/project-id", - }, - timestamp: new Date(1000).toUTCString(), - additionalUserInfo: { - isNewUser: false, - profile: undefined, - providerId: undefined, - username: undefined, - recaptchaScore: TEST_RECAPTCHA_SCORE, - email: undefined, - phoneNumber: "+11234567890", - }, - credential: null, - params: {}, - }; - - expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); - }); -}); + + it("should parse a beforeSendSms event", () => { + const time = now.getTime(); + const decodedJwt = { + iss: "https://securetoken.google.com/project_id", + aud: "https://us-east1-project_id.cloudfunctions.net/function-1", + iat: 1, + exp: 60 * 60 + 1, + event_id: "EVENT_ID", + event_type: "beforeSendSms", + user_agent: "USER_AGENT", + ip_address: "1.2.3.4", + locale: "en", + recaptcha_score: TEST_RECAPTCHA_SCORE, + sms_type: "SIGN_IN_OR_SIGN_UP", + phone_number: "+11234567890", + }; + const context = { + locale: "en", + ipAddress: "1.2.3.4", + userAgent: "USER_AGENT", + eventId: "EVENT_ID", + eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", + emailType: undefined, + smsType: "SIGN_IN_OR_SIGN_UP", + authType: "UNAUTHENTICATED", + resource: { + service: "identitytoolkit.googleapis.com", + name: "projects/project-id", + }, + timestamp: new Date(1000).toUTCString(), + additionalUserInfo: { + isNewUser: false, + profile: undefined, + providerId: undefined, + username: undefined, + recaptchaScore: TEST_RECAPTCHA_SCORE, + email: undefined, + phoneNumber: "+11234567890", + }, + credential: null, + params: {}, + }; + + expect(identity.parseAuthEventContext(decodedJwt, "project-id", time)).to.deep.equal(context); + }); + }); describe("validateAuthResponse", () => { it("should not throw on undefined request", () => { diff --git a/spec/v1/providers/auth.spec.ts b/spec/v1/providers/auth.spec.ts index e2c21a690..ec1a793f5 100644 --- a/spec/v1/providers/auth.spec.ts +++ b/spec/v1/providers/auth.spec.ts @@ -394,11 +394,11 @@ describe("Auth Functions", () => { ]); }); }); - + describe("beforeSms", () => { it("should create function without options", () => { const fn = auth.user().beforeSms(() => Promise.resolve()); - + expect(fn.__trigger).to.deep.equal({ labels: {}, blockingTrigger: { @@ -430,7 +430,7 @@ describe("Auth Functions", () => { }, ]); }); - + it("should create the function with options", () => { const fn = functions .region("us-east1") @@ -445,7 +445,7 @@ describe("Auth Functions", () => { }, }) .beforeSms(() => Promise.resolve()); - + expect(fn.__trigger).to.deep.equal({ labels: {}, regions: ["us-east1"], @@ -483,7 +483,7 @@ describe("Auth Functions", () => { }, ]); }); - }); + }); describe("#_dataConstructor", () => { let cloudFunctionDelete: CloudFunction; diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index ee68aa8ad..d5b8dd824 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -52,7 +52,6 @@ const BEFORE_EMAIL_TRIGGER = { options: {}, }; - const BEFORE_SMS_TRIGGER = { eventType: "providers/cloud.auth/eventTypes/user.beforeSendSms", options: {}, @@ -238,11 +237,11 @@ describe("identity", () => { ]); }); }); - + describe("beforeSmsSent", () => { it("should accept a handler", () => { const fn = identity.beforeSmsSent(() => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -256,13 +255,13 @@ describe("identity", () => { }, ]); }); - + it("should accept options and a handler", () => { const fn = identity.beforeSmsSent( { region: opts.region, minInstances: opts.minInstances }, () => Promise.resolve() ); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -280,7 +279,7 @@ describe("identity", () => { }, ]); }); - }); + }); describe("beforeOperation", () => { it("should handle eventType and handler for before create events", () => { diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index 2f34858aa..ae9c22ac7 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -55,7 +55,11 @@ const CLAIMS_MAX_PAYLOAD_SIZE = 1000; * @hidden * @alpha */ -export type AuthBlockingEventType = "beforeCreate" | "beforeSignIn" | "beforeSendEmail" | "beforeSendSms"; +export type AuthBlockingEventType = + | "beforeCreate" + | "beforeSignIn" + | "beforeSendEmail" + | "beforeSendSms"; const EVENT_MAPPING: Record = { beforeCreate: "providers/cloud.auth/eventTypes/user.beforeCreate", @@ -488,11 +492,15 @@ export type MaybeAsync = T | Promise; export type HandlerV1 = ( userOrContext: AuthUserRecord | AuthEventContext, context?: AuthEventContext -) => MaybeAsync; +) => MaybeAsync< + BeforeCreateResponse | BeforeSignInResponse | BeforeEmailResponse | BeforeSmsResponse | void +>; export type HandlerV2 = ( event: AuthBlockingEvent -) => MaybeAsync; +) => MaybeAsync< + BeforeCreateResponse | BeforeSignInResponse | BeforeEmailResponse | BeforeSmsResponse | void +>; export type AuthBlockingEventHandler = (HandlerV1 | HandlerV2) & { // Specify the GCF gen of the trigger that the auth blocking event handler was written for diff --git a/src/v1/providers/auth.ts b/src/v1/providers/auth.ts index 6eaf3f563..2a88cd41a 100644 --- a/src/v1/providers/auth.ts +++ b/src/v1/providers/auth.ts @@ -181,7 +181,7 @@ export class UserBuilder { ): BlockingFunction { return this.beforeOperation(handler, "beforeSendEmail"); } - + beforeSms( handler: (context: AuthEventContext) => MaybeAsync ): BlockingFunction { diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index 42b66d050..01ece673b 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -299,13 +299,17 @@ export function beforeOperation( | BlockingOptions | (( event: AuthBlockingEvent - ) => MaybeAsync), + ) => MaybeAsync< + BeforeCreateResponse | BeforeSignInResponse | BeforeEmailResponse | BeforeSmsResponse | void + >), handler: HandlerV2 ): BlockingFunction { if (!handler || typeof optsOrHandler === "function") { handler = optsOrHandler as ( event: AuthBlockingEvent - ) => MaybeAsync; + ) => MaybeAsync< + BeforeCreateResponse | BeforeSignInResponse | BeforeEmailResponse | BeforeSmsResponse | void + >; optsOrHandler = {}; } From 965cf065cdeff50fd64fc7d945ee6411b9cab577 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Fri, 13 Sep 2024 12:31:41 -0400 Subject: [PATCH 11/17] rebase --- spec/v2/providers/identity.spec.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index d5b8dd824..86d1c175e 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -284,7 +284,7 @@ describe("identity", () => { describe("beforeOperation", () => { it("should handle eventType and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", () => Promise.resolve(), undefined); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -298,10 +298,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType and handler for before sign in events", () => { const fn = identity.beforeOperation("beforeSignIn", () => Promise.resolve(), undefined); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -315,10 +315,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType and handler for before email events", () => { const fn = identity.beforeOperation("beforeSendEmail", () => Promise.resolve(), undefined); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -349,10 +349,9 @@ describe("identity", () => { }, ]); }); - it("should handle eventType, options, and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", opts, () => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -374,10 +373,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType, options, and handler for before sign in events", () => { const fn = identity.beforeOperation("beforeSignIn", opts, () => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -399,10 +398,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType, options, and handler for before send email events", () => { const fn = identity.beforeOperation("beforeSendEmail", opts, () => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", From 47ca9982695fb1b8c27ad1aacfd30036acb88be1 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Fri, 13 Sep 2024 12:31:58 -0400 Subject: [PATCH 12/17] rebase --- spec/v2/providers/identity.spec.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 86d1c175e..5be6f9f8e 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -284,7 +284,7 @@ describe("identity", () => { describe("beforeOperation", () => { it("should handle eventType and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", () => Promise.resolve(), undefined); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -298,10 +298,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType and handler for before sign in events", () => { const fn = identity.beforeOperation("beforeSignIn", () => Promise.resolve(), undefined); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -315,10 +315,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType and handler for before email events", () => { const fn = identity.beforeOperation("beforeSendEmail", () => Promise.resolve(), undefined); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -351,7 +351,7 @@ describe("identity", () => { }); it("should handle eventType, options, and handler for before create events", () => { const fn = identity.beforeOperation("beforeCreate", opts, () => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -373,10 +373,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType, options, and handler for before sign in events", () => { const fn = identity.beforeOperation("beforeSignIn", opts, () => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", @@ -398,10 +398,10 @@ describe("identity", () => { }, ]); }); - + it("should handle eventType, options, and handler for before send email events", () => { const fn = identity.beforeOperation("beforeSendEmail", opts, () => Promise.resolve()); - + expect(fn.__endpoint).to.deep.equal({ ...MINIMAL_V2_ENDPOINT, platform: "gcfv2", From 4fc280310aeade4bad81e05ede45e43560d961e0 Mon Sep 17 00:00:00 2001 From: Pragati Date: Mon, 19 Aug 2024 17:47:12 -0700 Subject: [PATCH 13/17] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fc76447f..51926d296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1 @@ -- Add support for beforeEmailSent auth blocking triggers. (#1492) +- Add support for beforeSmsSent auth blocking triggers. (#1589) From cce1fc36111746125a7f4b98124fe54d56564315 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Thu, 19 Sep 2024 14:24:31 -0400 Subject: [PATCH 14/17] remove duplicate unit test --- spec/v2/providers/identity.spec.ts | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/spec/v2/providers/identity.spec.ts b/spec/v2/providers/identity.spec.ts index 5be6f9f8e..dbda1189c 100644 --- a/spec/v2/providers/identity.spec.ts +++ b/spec/v2/providers/identity.spec.ts @@ -419,31 +419,6 @@ describe("identity", () => { }, ]); }); - - it("should handle eventType, options, and handler for before send email events", () => { - const fn = identity.beforeOperation("beforeSendEmail", opts, () => Promise.resolve()); - - expect(fn.__endpoint).to.deep.equal({ - ...MINIMAL_V2_ENDPOINT, - platform: "gcfv2", - labels: {}, - minInstances: 1, - region: [REGION], - blockingTrigger: { - ...BEFORE_EMAIL_TRIGGER, - options: { - ...BEFORE_EMAIL_TRIGGER.options, - accessToken: true, - }, - }, - }); - expect(fn.__requiredAPIs).to.deep.equal([ - { - api: IDENTITY_TOOLKIT_API, - reason: "Needed for auth blocking functions", - }, - ]); - }); }); describe("getOpts", () => { From fe4516504a7be8e0c9f54610b8683fedbfd8b7cf Mon Sep 17 00:00:00 2001 From: Brian Li Date: Thu, 3 Oct 2024 14:19:41 -0400 Subject: [PATCH 15/17] update EmailType to EMAIL_SIGN_IN | PASSWORD_RESET --- src/common/providers/identity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index ae9c22ac7..a9a9e7bf6 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -335,7 +335,7 @@ export interface Credential { } /** Possible types of emails as described by the GCIP backend. */ -export type EmailType = "EMAIL_SIGNIN" | "PASSWORD_RESET"; +export type EmailType = "EMAIL_SIGN_IN" | "PASSWORD_RESET"; /** * The type of SMS message From 030cb596ae83bfd88b73434388262c9810fca07a Mon Sep 17 00:00:00 2001 From: Brian Li Date: Wed, 9 Oct 2024 14:05:44 -0400 Subject: [PATCH 16/17] api comment fixes --- src/common/providers/identity.ts | 20 ++++++++++++-------- src/v2/providers/identity.ts | 27 +++++++++++++-------------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index a9a9e7bf6..fe39029e5 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -334,16 +334,20 @@ export interface Credential { signInMethod: string; } -/** Possible types of emails as described by the GCIP backend. */ +/** + * Possible types of emails as described by the GCIP backend, which can be: + * - An sign-in email + * - A password reset email + */ export type EmailType = "EMAIL_SIGN_IN" | "PASSWORD_RESET"; /** - * The type of SMS message + * The type of SMS message, which can be: + * - A sign-in or sign up SMS message + * - A multi-factor sign-in SMS message + * - A multi-factor enrollment SMS message */ -export type SmsType = - | "SIGN_IN_OR_SIGN_UP" // A sign-in or sign up SMS message - | "MULTI_FACTOR_SIGN_IN" // A multi-factor sign-in SMS message - | "MULTI_FACTOR_ENROLLMENT"; // A multi-factor enrollment SMS message +export type SmsType = "SIGN_IN_OR_SIGN_UP" | "MULTI_FACTOR_SIGN_IN" | "MULTI_FACTOR_ENROLLMENT"; /** Defines the auth event context for blocking events */ export interface AuthEventContext extends EventContext { @@ -374,7 +378,7 @@ export interface BeforeSmsResponse { recaptchaActionOverride?: RecaptchaActionOptions; } -/** The handler response type for beforeCreate blocking events */ +/** The handler response type for `beforeCreate` blocking events */ export interface BeforeCreateResponse { displayName?: string; disabled?: boolean; @@ -472,7 +476,7 @@ export interface DecodedPayload { * Internal definition to include all the fields that can be sent as * a response from the blocking function to the backend. * This is added mainly to have a type definition for 'generateResponsePayload' - * @internal */ + @internal */ export interface ResponsePayload { userRecord?: UserRecordResponsePayload; recaptchaActionOverride?: RecaptchaActionOptions; diff --git a/src/v2/providers/identity.ts b/src/v2/providers/identity.ts index 01ece673b..e5b276516 100644 --- a/src/v2/providers/identity.ts +++ b/src/v2/providers/identity.ts @@ -167,7 +167,7 @@ export interface BlockingOptions { /** * Handles an event that is triggered before a user is created. - * @param handler - Event handler which is run every time before a user is created + * @param handler - Event handler which is run every time before a user is created. */ export function beforeUserCreated( handler: (event: AuthBlockingEvent) => MaybeAsync @@ -175,8 +175,8 @@ export function beforeUserCreated( /** * Handles an event that is triggered before a user is created. - * @param opts - Object containing function options - * @param handler - Event handler which is run every time before a user is created + * @param opts - Object containing function options. + * @param handler - Event handler which is run every time before a user is created. */ export function beforeUserCreated( opts: BlockingOptions, @@ -184,9 +184,9 @@ export function beforeUserCreated( ): BlockingFunction; /** - * Handles an event that is triggered before a user is created - * @param optsOrHandler - Either an object containing function options, or an event handler (run before user creation) - * @param handler? - If defined, an event handler which is run every time before a user is created + * Handles an event that is triggered before a user is created. + * @param optsOrHandler - Either an object containing function options, or an event handler (run before user creation). + * @param handler? - If defined, an event handler which is run every time before a user is created. */ export function beforeUserCreated( optsOrHandler: @@ -199,7 +199,7 @@ export function beforeUserCreated( /** * Handles an event that is triggered before a user is signed in. - * @param handler - Event handler which is run every time before a user is signed in + * @param handler - Event handler which is run every time before a user is signed in. */ export function beforeUserSignedIn( handler: (event: AuthBlockingEvent) => MaybeAsync @@ -207,8 +207,8 @@ export function beforeUserSignedIn( /** * Handles an event that is triggered before a user is signed in. - * @param opts - Object containing function options - * @param handler - Event handler which is run every time before a user is signed in + * @param opts - Object containing function options. + * @param handler - Event handler which is run every time before a user is signed in. */ export function beforeUserSignedIn( opts: BlockingOptions, @@ -217,8 +217,8 @@ export function beforeUserSignedIn( /** * Handles an event that is triggered before a user is signed in. - * @param optsOrHandler - Either an object containing function options, or an event handler (run before user signin) - * @param handler - Event handler which is run every time before a user is signed in + * @param optsOrHandler - Either an object containing function options, or an event handler (run before user signin). + * @param handler - Event handler which is run every time before a user is signed in. */ export function beforeUserSignedIn( optsOrHandler: @@ -239,7 +239,7 @@ export function beforeEmailSent( /** * Handles an event that is triggered before an email is sent to a user. - * @param opts - Object containing function options + * @param opts - Object containing function options. * @param handler - Event handler that is run before an email is sent to a user. */ export function beforeEmailSent( @@ -270,7 +270,7 @@ export function beforeSmsSent( /** * Handles an event that is triggered before an SMS is sent to a user. - * @param opts - Object containing function options + * @param opts - Object containing function options. * @param handler - Event handler that is run before an SMS is sent to a user. */ export function beforeSmsSent( @@ -317,7 +317,6 @@ export function beforeOperation( // Create our own function that just calls the provided function so we know for sure that // handler takes one argument. This is something common/providers/identity depends on. - // const wrappedHandler = (event: AuthBlockingEvent) => handler(event); const annotatedHandler = Object.assign(handler, { platform: "gcfv2" as const }); const func: any = wrapTraceContext(withInit(wrapHandler(eventType, annotatedHandler))); From dc34350e4676d8333118c5156275bd42aeb08136 Mon Sep 17 00:00:00 2001 From: Brian Li Date: Thu, 17 Oct 2024 15:32:11 -0400 Subject: [PATCH 17/17] minor docstring fix --- src/common/providers/identity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/providers/identity.ts b/src/common/providers/identity.ts index fe39029e5..f2a8a3949 100644 --- a/src/common/providers/identity.ts +++ b/src/common/providers/identity.ts @@ -336,7 +336,7 @@ export interface Credential { /** * Possible types of emails as described by the GCIP backend, which can be: - * - An sign-in email + * - A sign-in email * - A password reset email */ export type EmailType = "EMAIL_SIGN_IN" | "PASSWORD_RESET";