Skip to content

Commit ea509af

Browse files
committed
supporting recaptcha verdict for auth blocking functions
1 parent 2841ebd commit ea509af

File tree

2 files changed

+91
-23
lines changed

2 files changed

+91
-23
lines changed

spec/common/providers/identity.spec.ts

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import * as identity from "../../../src/common/providers/identity";
2626

2727
const EVENT = "EVENT_TYPE";
2828
const now = new Date();
29+
const TEST_NAME = "John Doe";
2930

3031
describe("identity", () => {
3132
describe("userRecordConstructor", () => {
@@ -232,14 +233,14 @@ describe("identity", () => {
232233
describe("parseProviderData", () => {
233234
const decodedUserInfo = {
234235
provider_id: "google.com",
235-
display_name: "John Doe",
236+
display_name: TEST_NAME,
236237
photo_url: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
237238
uid: "1234567890",
238239
239240
};
240241
const userInfo = {
241242
providerId: "google.com",
242-
displayName: "John Doe",
243+
displayName: TEST_NAME,
243244
photoURL: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
244245
uid: "1234567890",
245246
@@ -340,12 +341,12 @@ describe("identity", () => {
340341
uid: "abcdefghijklmnopqrstuvwxyz",
341342
342343
email_verified: true,
343-
display_name: "John Doe",
344+
display_name: TEST_NAME,
344345
phone_number: "+11234567890",
345346
provider_data: [
346347
{
347348
provider_id: "google.com",
348-
display_name: "John Doe",
349+
display_name: TEST_NAME,
349350
photo_url: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
350351
351352
uid: "1234567890",
@@ -366,7 +367,7 @@ describe("identity", () => {
366367
provider_id: "password",
367368
368369
369-
display_name: "John Doe",
370+
display_name: TEST_NAME,
370371
},
371372
],
372373
password_hash: "passwordHash",
@@ -407,11 +408,11 @@ describe("identity", () => {
407408
phoneNumber: "+11234567890",
408409
emailVerified: true,
409410
disabled: false,
410-
displayName: "John Doe",
411+
displayName: TEST_NAME,
411412
providerData: [
412413
{
413414
providerId: "google.com",
414-
displayName: "John Doe",
415+
displayName: TEST_NAME,
415416
photoURL: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
416417
417418
uid: "1234567890",
@@ -435,7 +436,7 @@ describe("identity", () => {
435436
},
436437
{
437438
providerId: "password",
438-
displayName: "John Doe",
439+
displayName: TEST_NAME,
439440
photoURL: undefined,
440441
441442
@@ -489,8 +490,9 @@ describe("identity", () => {
489490
});
490491

491492
describe("parseAuthEventContext", () => {
493+
const TEST_RECAPTCHA_SCORE = 0.9;
492494
const rawUserInfo = {
493-
name: "John Doe",
495+
name: TEST_NAME,
494496
granted_scopes:
495497
"openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
496498
id: "123456789",
@@ -516,6 +518,7 @@ describe("identity", () => {
516518
user_agent: "USER_AGENT",
517519
locale: "en",
518520
raw_user_info: JSON.stringify(rawUserInfo),
521+
recaptcha_score: TEST_RECAPTCHA_SCORE,
519522
};
520523
const context = {
521524
locale: "en",
@@ -534,6 +537,7 @@ describe("identity", () => {
534537
profile: rawUserInfo,
535538
username: undefined,
536539
isNewUser: false,
540+
recaptchaScore: TEST_RECAPTCHA_SCORE,
537541
},
538542
credential: null,
539543
params: {},
@@ -563,6 +567,7 @@ describe("identity", () => {
563567
oauth_refresh_token: "REFRESH_TOKEN",
564568
oauth_token_secret: "OAUTH_TOKEN_SECRET",
565569
oauth_expires_in: 3600,
570+
recaptcha_score: TEST_RECAPTCHA_SCORE,
566571
};
567572
const context = {
568573
locale: "en",
@@ -581,6 +586,7 @@ describe("identity", () => {
581586
profile: rawUserInfo,
582587
username: undefined,
583588
isNewUser: false,
589+
recaptchaScore: TEST_RECAPTCHA_SCORE,
584590
},
585591
credential: {
586592
claims: undefined,
@@ -619,14 +625,14 @@ describe("identity", () => {
619625
uid: "abcdefghijklmnopqrstuvwxyz",
620626
621627
email_verified: true,
622-
display_name: "John Doe",
628+
display_name: TEST_NAME,
623629
phone_number: "+11234567890",
624630
provider_data: [
625631
{
626632
provider_id: "oidc.provider",
627633
628634
629-
display_name: "John Doe",
635+
display_name: TEST_NAME,
630636
},
631637
],
632638
photo_url: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
@@ -647,6 +653,7 @@ describe("identity", () => {
647653
oauth_token_secret: "OAUTH_TOKEN_SECRET",
648654
oauth_expires_in: 3600,
649655
raw_user_info: JSON.stringify(rawUserInfo),
656+
recaptcha_score: TEST_RECAPTCHA_SCORE,
650657
};
651658
const context = {
652659
locale: "en",
@@ -665,6 +672,7 @@ describe("identity", () => {
665672
providerId: "oidc.provider",
666673
profile: rawUserInfo,
667674
isNewUser: true,
675+
recaptchaScore: TEST_RECAPTCHA_SCORE,
668676
},
669677
credential: {
670678
claims: undefined,
@@ -762,4 +770,38 @@ describe("identity", () => {
762770
);
763771
});
764772
});
773+
774+
describe("generateRequestPayload", () => {
775+
const DISPLAY_NAME_FILED = "displayName";
776+
const TEST_RESPONSE = {
777+
displayName: TEST_NAME,
778+
recaptchaPassed: false,
779+
} as identity.BeforeCreateResponse;
780+
781+
const EXPECT_PAYLOAD = {
782+
userRecord: { displayName: TEST_NAME, updateMask: DISPLAY_NAME_FILED },
783+
recaptchaPassed: false,
784+
};
785+
786+
const TEST_RESPONSE_RECAPTCHA_UNDEFINED = {
787+
displayName: TEST_NAME,
788+
} as identity.BeforeSignInResponse;
789+
790+
const EXPECT_PAYLOAD_UNDEFINED = {
791+
userRecord: { displayName: TEST_NAME, updateMask: DISPLAY_NAME_FILED },
792+
};
793+
it("should return empty string on undefined response", () => {
794+
expect(identity.generateRequestPayload()).to.eq("");
795+
});
796+
797+
it("should exclude recaptchaPass field from updateMask", () => {
798+
expect(identity.generateRequestPayload(TEST_RESPONSE)).to.deep.equal(EXPECT_PAYLOAD);
799+
});
800+
801+
it("should not return recaptchaPass if undefined", () => {
802+
const payload = identity.generateRequestPayload(TEST_RESPONSE_RECAPTCHA_UNDEFINED);
803+
expect(payload.hasOwnProperty("recaptchaPassed")).to.be.false;
804+
expect(payload).to.deep.equal(EXPECT_PAYLOAD_UNDEFINED);
805+
});
806+
});
765807
});

src/common/providers/identity.ts

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ export interface AdditionalUserInfo {
310310
profile?: any;
311311
username?: string;
312312
isNewUser: boolean;
313+
recaptchaScore?: number;
313314
}
314315

315316
/** The credential component of the auth event context */
@@ -338,8 +339,13 @@ export interface AuthBlockingEvent extends AuthEventContext {
338339
data: AuthUserRecord;
339340
}
340341

342+
/** The base handler response type for beforeCreate and beforeSignIn blocking events*/
343+
export interface BlockingFunctionResponse {
344+
recaptchaPassed?: boolean;
345+
}
346+
341347
/** The handler response type for beforeCreate blocking events */
342-
export interface BeforeCreateResponse {
348+
export interface BeforeCreateResponse extends BlockingFunctionResponse {
343349
displayName?: string;
344350
disabled?: boolean;
345351
emailVerified?: boolean;
@@ -423,6 +429,7 @@ export interface DecodedPayload {
423429
oauth_refresh_token?: string;
424430
oauth_token_secret?: string;
425431
oauth_expires_in?: number;
432+
recaptcha_score?: number;
426433
[key: string]: any;
427434
}
428435

@@ -640,9 +647,38 @@ function parseAdditionalUserInfo(decodedJWT: DecodedPayload): AdditionalUserInfo
640647
profile,
641648
username,
642649
isNewUser: decodedJWT.event_type === "beforeCreate" ? true : false,
650+
recaptchaScore: decodedJWT.recaptcha_score,
643651
};
644652
}
645653

654+
/** Helper to generate payload to GCIP from client request.
655+
* @internal
656+
*/
657+
export function generateRequestPayload(
658+
authResponse?: BeforeCreateResponse | BeforeSignInResponse
659+
): any {
660+
if (!authResponse) {
661+
return "";
662+
}
663+
664+
const { recaptchaPassed, ...formattedAuthResponse } = authResponse;
665+
const result = {} as any;
666+
const updateMask = getUpdateMask(formattedAuthResponse);
667+
668+
if (updateMask.length !== 0) {
669+
result.userRecord = {
670+
...formattedAuthResponse,
671+
updateMask,
672+
};
673+
}
674+
675+
if (recaptchaPassed !== undefined) {
676+
result.recaptchaPassed = recaptchaPassed;
677+
}
678+
679+
return result;
680+
}
681+
646682
/** Helper to get the Credential from the decoded jwt */
647683
function parseAuthCredential(decodedJWT: DecodedPayload, time: number): Credential {
648684
if (
@@ -801,7 +837,6 @@ export function wrapHandler(eventType: AuthBlockingEventType, handler: HandlerV1
801837
: handler.length === 2
802838
? await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt)
803839
: await auth.getAuth(getApp())._verifyAuthBlockingToken(req.body.data.jwt, "run.app");
804-
805840
const authUserRecord = parseAuthUserRecord(decodedPayload.user_record);
806841
const authEventContext = parseAuthEventContext(decodedPayload, projectId);
807842

@@ -818,16 +853,7 @@ export function wrapHandler(eventType: AuthBlockingEventType, handler: HandlerV1
818853
}
819854

820855
validateAuthResponse(eventType, authResponse);
821-
const updateMask = getUpdateMask(authResponse);
822-
const result =
823-
updateMask.length === 0
824-
? {}
825-
: {
826-
userRecord: {
827-
...authResponse,
828-
updateMask,
829-
},
830-
};
856+
const result = generateRequestPayload(authResponse);
831857

832858
res.status(200);
833859
res.setHeader("Content-Type", "application/json");

0 commit comments

Comments
 (0)