Skip to content

Commit

Permalink
supporting recaptcha verdict for auth blocking functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoshouzi-gh committed Aug 23, 2023
1 parent 3e7a4b7 commit 3f26008
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 23 deletions.
64 changes: 53 additions & 11 deletions spec/common/providers/identity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import * as identity from "../../../src/common/providers/identity";

const EVENT = "EVENT_TYPE";
const now = new Date();
const TEST_NAME = "John Doe";

describe("identity", () => {
describe("userRecordConstructor", () => {
Expand Down Expand Up @@ -232,14 +233,14 @@ describe("identity", () => {
describe("parseProviderData", () => {
const decodedUserInfo = {
provider_id: "google.com",
display_name: "John Doe",
display_name: TEST_NAME,
photo_url: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
uid: "1234567890",
email: "user@gmail.com",
};
const userInfo = {
providerId: "google.com",
displayName: "John Doe",
displayName: TEST_NAME,
photoURL: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
uid: "1234567890",
email: "user@gmail.com",
Expand Down Expand Up @@ -340,12 +341,12 @@ describe("identity", () => {
uid: "abcdefghijklmnopqrstuvwxyz",
email: "user@gmail.com",
email_verified: true,
display_name: "John Doe",
display_name: TEST_NAME,
phone_number: "+11234567890",
provider_data: [
{
provider_id: "google.com",
display_name: "John Doe",
display_name: TEST_NAME,
photo_url: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
email: "user@gmail.com",
uid: "1234567890",
Expand All @@ -366,7 +367,7 @@ describe("identity", () => {
provider_id: "password",
email: "user@gmail.com",
uid: "user@gmail.com",
display_name: "John Doe",
display_name: TEST_NAME,
},
],
password_hash: "passwordHash",
Expand Down Expand Up @@ -407,11 +408,11 @@ describe("identity", () => {
phoneNumber: "+11234567890",
emailVerified: true,
disabled: false,
displayName: "John Doe",
displayName: TEST_NAME,
providerData: [
{
providerId: "google.com",
displayName: "John Doe",
displayName: TEST_NAME,
photoURL: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
email: "user@gmail.com",
uid: "1234567890",
Expand All @@ -435,7 +436,7 @@ describe("identity", () => {
},
{
providerId: "password",
displayName: "John Doe",
displayName: TEST_NAME,
photoURL: undefined,
email: "user@gmail.com",
uid: "user@gmail.com",
Expand Down Expand Up @@ -489,8 +490,9 @@ describe("identity", () => {
});

describe("parseAuthEventContext", () => {
const TEST_RECAPTCHA_SCORE = 0.9;
const rawUserInfo = {
name: "John Doe",
name: TEST_NAME,
granted_scopes:
"openid https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile",
id: "123456789",
Expand All @@ -516,6 +518,7 @@ describe("identity", () => {
user_agent: "USER_AGENT",
locale: "en",
raw_user_info: JSON.stringify(rawUserInfo),
recaptcha_score: TEST_RECAPTCHA_SCORE,
};
const context = {
locale: "en",
Expand All @@ -534,6 +537,7 @@ describe("identity", () => {
profile: rawUserInfo,
username: undefined,
isNewUser: false,
recaptchaScore: TEST_RECAPTCHA_SCORE,
},
credential: null,
params: {},
Expand Down Expand Up @@ -563,6 +567,7 @@ describe("identity", () => {
oauth_refresh_token: "REFRESH_TOKEN",
oauth_token_secret: "OAUTH_TOKEN_SECRET",
oauth_expires_in: 3600,
recaptcha_score: TEST_RECAPTCHA_SCORE,
};
const context = {
locale: "en",
Expand All @@ -581,6 +586,7 @@ describe("identity", () => {
profile: rawUserInfo,
username: undefined,
isNewUser: false,
recaptchaScore: TEST_RECAPTCHA_SCORE,
},
credential: {
claims: undefined,
Expand Down Expand Up @@ -619,14 +625,14 @@ describe("identity", () => {
uid: "abcdefghijklmnopqrstuvwxyz",
email: "user@gmail.com",
email_verified: true,
display_name: "John Doe",
display_name: TEST_NAME,
phone_number: "+11234567890",
provider_data: [
{
provider_id: "oidc.provider",
email: "user@gmail.com",
uid: "user@gmail.com",
display_name: "John Doe",
display_name: TEST_NAME,
},
],
photo_url: "https://lh3.googleusercontent.com/1234567890/photo.jpg",
Expand All @@ -647,6 +653,7 @@ describe("identity", () => {
oauth_token_secret: "OAUTH_TOKEN_SECRET",
oauth_expires_in: 3600,
raw_user_info: JSON.stringify(rawUserInfo),
recaptcha_score: TEST_RECAPTCHA_SCORE,
};
const context = {
locale: "en",
Expand All @@ -665,6 +672,7 @@ describe("identity", () => {
providerId: "oidc.provider",
profile: rawUserInfo,
isNewUser: true,
recaptchaScore: TEST_RECAPTCHA_SCORE,
},
credential: {
claims: undefined,
Expand Down Expand Up @@ -762,4 +770,38 @@ describe("identity", () => {
);
});
});

describe("generateRequestPayload", () => {
const DISPLAY_NAME_FILED = "displayName";
const TEST_RESPONSE = {
displayName: TEST_NAME,
recaptchaPassed: false,
} as identity.BeforeCreateResponse;

const EXPECT_PAYLOAD = {
userRecord: { displayName: TEST_NAME, updateMask: DISPLAY_NAME_FILED },
recaptchaPassed: false,
};

const TEST_RESPONSE_RECAPTCHA_UNDEFINED = {
displayName: TEST_NAME,
} as identity.BeforeSignInResponse;

const EXPECT_PAYLOAD_UNDEFINED = {
userRecord: { displayName: TEST_NAME, updateMask: DISPLAY_NAME_FILED },
};
it("should return empty string on undefined response", () => {
expect(identity.generateRequestPayload()).to.eq("");
});

it("should exclude recaptchaPass field from updateMask", () => {
expect(identity.generateRequestPayload(TEST_RESPONSE)).to.deep.equal(EXPECT_PAYLOAD);
});

it("should not return recaptchaPass if undefined", () => {
const payload = identity.generateRequestPayload(TEST_RESPONSE_RECAPTCHA_UNDEFINED);
expect(payload.hasOwnProperty("recaptchaPassed")).to.be.false;
expect(payload).to.deep.equal(EXPECT_PAYLOAD_UNDEFINED);
});
});
});
50 changes: 38 additions & 12 deletions src/common/providers/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export interface AdditionalUserInfo {
profile?: any;
username?: string;
isNewUser: boolean;
recaptchaScore?: number;
}

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

/** The base handler response type for beforeCreate and beforeSignIn blocking events*/
export interface BlockingFunctionResponse {
recaptchaPassed?: boolean;
}

/** The handler response type for beforeCreate blocking events */
export interface BeforeCreateResponse {
export interface BeforeCreateResponse extends BlockingFunctionResponse {
displayName?: string;
disabled?: boolean;
emailVerified?: boolean;
Expand Down Expand Up @@ -423,6 +429,7 @@ export interface DecodedPayload {
oauth_refresh_token?: string;
oauth_token_secret?: string;
oauth_expires_in?: number;
recaptcha_score?: number;
[key: string]: any;
}

Expand Down Expand Up @@ -640,9 +647,38 @@ function parseAdditionalUserInfo(decodedJWT: DecodedPayload): AdditionalUserInfo
profile,
username,
isNewUser: decodedJWT.event_type === "beforeCreate" ? true : false,
recaptchaScore: decodedJWT.recaptcha_score,
};
}

/** Helper to generate payload to GCIP from client request.
* @internal
*/
export function generateRequestPayload(
authResponse?: BeforeCreateResponse | BeforeSignInResponse
): any {
if (!authResponse) {
return "";
}

const { recaptchaPassed, ...formattedAuthResponse } = authResponse;
const result = {} as any;
const updateMask = getUpdateMask(formattedAuthResponse);

if (updateMask.length !== 0) {
result.userRecord = {
...formattedAuthResponse,
updateMask,
};
}

if (recaptchaPassed !== undefined) {
result.recaptchaPassed = recaptchaPassed;
}

return result;
}

/** Helper to get the Credential from the decoded jwt */
function parseAuthCredential(decodedJWT: DecodedPayload, time: number): Credential {
if (
Expand Down Expand Up @@ -801,7 +837,6 @@ export function wrapHandler(eventType: AuthBlockingEventType, handler: HandlerV1
: handler.length === 2
? 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);
const authEventContext = parseAuthEventContext(decodedPayload, projectId);

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

validateAuthResponse(eventType, authResponse);
const updateMask = getUpdateMask(authResponse);
const result =
updateMask.length === 0
? {}
: {
userRecord: {
...authResponse,
updateMask,
},
};
const result = generateRequestPayload(authResponse);

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

0 comments on commit 3f26008

Please sign in to comment.