From 4ebc87da9471433037936e95f06dffdb117f9731 Mon Sep 17 00:00:00 2001 From: aalej Date: Sat, 20 Jan 2024 01:24:42 +0800 Subject: [PATCH 1/9] Added rudimentary suppport for email enumeration protection --- package.json | 4 +- src/emulator/auth/operations.ts | 49 ++++++++++++++----- src/emulator/auth/state.ts | 20 ++++++++ src/test/emulators/auth/createAuthUri.spec.ts | 49 +++++++++++++++++++ src/test/emulators/auth/oob.spec.ts | 39 +++++++++++++++ src/test/emulators/auth/password.spec.ts | 45 +++++++++++++++++ 6 files changed, 193 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index cc52eb84b64..41a6c3663d5 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,11 @@ "lint:quiet": "npm run lint:ts -- --quiet && npm run lint:other", "lint:ts": "eslint --config .eslintrc.js --ext .ts,.js .", "mocha": "nyc mocha 'src/test/**/*.{ts,js}'", + "mocha:auth": "nyc mocha 'src/test/emulators/auth/{oob,password,createAuthUri}.spec.ts'", "prepare": "npm run clean && npm run build:publish", "test": "npm run lint:quiet && npm run test:compile && npm run mocha", "test:client-integration": "bash ./scripts/client-integration-tests/run.sh", + "test:auth": "npm run lint:quiet && npm run test:compile && npm run mocha:auth", "test:compile": "tsc --project tsconfig.compile.json", "test:emulator": "bash ./scripts/emulator-tests/run.sh", "test:extensions-deploy": "bash ./scripts/extensions-deploy-tests/run.sh", @@ -239,4 +241,4 @@ "typescript-json-schema": "^0.50.1", "vite": "^4.2.1" } -} +} \ No newline at end of file diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 1467c806eea..8147cae5927 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -638,13 +638,20 @@ function createAuthUri( } } - return { - kind: "identitytoolkit#CreateAuthUriResponse", - registered, - allProviders, - sessionId, - signinMethods, - }; + if (state.enableImprovedEmailPrivacy) { + return { + kind: "identitytoolkit#CreateAuthUriResponse", + sessionId, + }; + } else { + return { + kind: "identitytoolkit#CreateAuthUriResponse", + registered, + allProviders, + sessionId, + signinMethods, + }; + } } const SESSION_COOKIE_MIN_VALID_DURATION = 5 * 60; /* 5 minutes in seconds */ @@ -879,7 +886,14 @@ function sendOobCode( mode = "resetPassword"; assert(reqBody.email, "MISSING_EMAIL"); email = canonicalizeEmailAddress(reqBody.email); - assert(state.getUserByEmail(email), "EMAIL_NOT_FOUND"); + const maybeUser = state.getUserByEmail(email); + if (state.enableImprovedEmailPrivacy && !maybeUser) { + return { + kind: "identitytoolkit#GetOobConfirmationCodeResponse", + email, + }; + } + assert(maybeUser, "EMAIL_NOT_FOUND"); break; case "VERIFY_EMAIL": mode = "verifyEmail"; @@ -1726,10 +1740,21 @@ async function signInWithPassword( const email = canonicalizeEmailAddress(reqBody.email); let user = state.getUserByEmail(email); - assert(user, "EMAIL_NOT_FOUND"); - assert(!user.disabled, "USER_DISABLED"); - assert(user.passwordHash && user.salt, "INVALID_PASSWORD"); - assert(user.passwordHash === hashPassword(reqBody.password, user.salt), "INVALID_PASSWORD"); + + if (state.enableImprovedEmailPrivacy) { + assert(user, "INVALID_LOGIN_CREDENTIALS"); + assert(!user.disabled, "USER_DISABLED"); + assert(user.passwordHash && user.salt, "INVALID_LOGIN_CREDENTIALS"); + assert( + user.passwordHash === hashPassword(reqBody.password, user.salt), + "INVALID_LOGIN_CREDENTIALS" + ); + } else { + assert(user, "EMAIL_NOT_FOUND"); + assert(!user.disabled, "USER_DISABLED"); + assert(user.passwordHash && user.salt, "INVALID_PASSWORD"); + assert(user.passwordHash === hashPassword(reqBody.password, user.salt), "INVALID_PASSWORD"); + } const response = { kind: "identitytoolkit#VerifyPasswordResponse", diff --git a/src/emulator/auth/state.ts b/src/emulator/auth/state.ts index cfa215fa2fa..ea8ed092ce2 100644 --- a/src/emulator/auth/state.ts +++ b/src/emulator/auth/state.ts @@ -41,6 +41,8 @@ export abstract class ProjectState { abstract get oneAccountPerEmail(): boolean; + abstract get enableImprovedEmailPrivacy(): boolean; + abstract get authCloudFunction(): AuthCloudFunction; abstract get allowPasswordSignup(): boolean; @@ -578,6 +580,9 @@ export class AgentProjectState extends ProjectState { private _config: Config = { signIn: { allowDuplicateEmails: false }, blockingFunctions: {}, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + }, }; constructor(projectId: string) { @@ -596,6 +601,14 @@ export class AgentProjectState extends ProjectState { this._config.signIn.allowDuplicateEmails = !oneAccountPerEmail; } + get enableImprovedEmailPrivacy() { + return !!this._config.emailPrivacyConfig.enableImprovedEmailPrivacy; + } + + set enableImprovedEmailPrivacy(improveEmailPrivacy: boolean) { + this._config.emailPrivacyConfig.enableImprovedEmailPrivacy = improveEmailPrivacy; + } + get allowPasswordSignup() { return true; } @@ -748,6 +761,10 @@ export class TenantProjectState extends ProjectState { return this.parentProject.oneAccountPerEmail; } + get enableImprovedEmailPrivacy() { + return this.parentProject.enableImprovedEmailPrivacy; + } + get authCloudFunction() { return this.parentProject.authCloudFunction; } @@ -853,6 +870,8 @@ export type SignInConfig = MakeRequired< export type BlockingFunctionsConfig = Schemas["GoogleCloudIdentitytoolkitAdminV2BlockingFunctionsConfig"]; +export type EmailPrivacyConfig = Schemas["GoogleCloudIdentitytoolkitAdminV2EmailPrivacyConfig"]; + // Serves as a substitute for Schemas["GoogleCloudIdentitytoolkitAdminV2Config"], // i.e. the configuration object for top-level AgentProjectStates. Emulator // fixes certain configurations for ease of use / testing, so as non-standard @@ -860,6 +879,7 @@ export type BlockingFunctionsConfig = export type Config = { signIn: SignInConfig; blockingFunctions: BlockingFunctionsConfig; + emailPrivacyConfig: EmailPrivacyConfig; }; export interface RefreshTokenRecord { diff --git a/src/test/emulators/auth/createAuthUri.spec.ts b/src/test/emulators/auth/createAuthUri.spec.ts index 6452fe28087..e9f9bd2a37d 100644 --- a/src/test/emulators/auth/createAuthUri.spec.ts +++ b/src/test/emulators/auth/createAuthUri.spec.ts @@ -8,6 +8,7 @@ import { signInWithEmailLink, updateProjectConfig, registerTenant, + updateConfig, } from "./helpers"; describeAuthEmulator("accounts:createAuthUri", ({ authApi }) => { @@ -23,6 +24,28 @@ describeAuthEmulator("accounts:createAuthUri", ({ authApi }) => { }); }); + it("should not show registered field with improved email privacy enabled", async () => { + await updateConfig( + authApi(), + PROJECT_ID, + { + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, + }, + "emailPrivacyConfig" + ); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:createAuthUri") + .send({ continueUri: "http://example.com/", identifier: "notregistered@example.com" }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.not.have.property("registered"); + expect(res.body).to.have.property("sessionId").that.is.a("string"); + }); + }); + it("should return providers for a registered user", async () => { const user = { email: "alice@example.com", password: "notasecret" }; await registerUser(authApi(), user); @@ -39,6 +62,32 @@ describeAuthEmulator("accounts:createAuthUri", ({ authApi }) => { }); }); + it("should not return providers for a registered user with improved email privacy enabled", async () => { + const user = { email: "alice@example.com", password: "notasecret" }; + await updateConfig( + authApi(), + PROJECT_ID, + { + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, + }, + "emailPrivacyConfig" + ); + await registerUser(authApi(), user); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:createAuthUri") + .send({ continueUri: "http://example.com/", identifier: user.email }) + .query({ key: "fake-api-key" }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.not.have.property("registered"); + expect(res.body).to.not.have.property("allProviders").eql(["password"]); + expect(res.body).to.not.have.property("signinMethods").eql(["password"]); + expect(res.body).to.have.property("sessionId").that.is.a("string"); + }); + }); + it("should return existing sessionId if provided", async () => { await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:createAuthUri") diff --git a/src/test/emulators/auth/oob.spec.ts b/src/test/emulators/auth/oob.spec.ts index d2eeae0b802..ea4ab17df88 100644 --- a/src/test/emulators/auth/oob.spec.ts +++ b/src/test/emulators/auth/oob.spec.ts @@ -8,6 +8,7 @@ import { expectIdTokenExpired, inspectOobs, registerTenant, + updateConfig, } from "./helpers"; describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => { @@ -357,4 +358,42 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => { expect(res.body.error).to.have.property("message").equals("PASSWORD_LOGIN_DISABLED"); }); }); + + it("should error when sending a password reset with improved email privacy disabled", async () => { + const user = { email: "alice@example.com", password: "notasecret" }; + // await registerUser(authApi(), user); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:sendOobCode") + .set("Authorization", "Bearer owner") + .send({ email: user.email, requestType: "PASSWORD_RESET", returnOobLink: true }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error).to.have.property("message").equals("EMAIL_NOT_FOUND"); + }); + }); + + it("should error when sending a password reset with improved email privacy enabled", async () => { + const user = { email: "alice@example.com", password: "notasecret" }; + await updateConfig( + authApi(), + PROJECT_ID, + { + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, + }, + "emailPrivacyConfig" + ); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:sendOobCode") + .set("Authorization", "Bearer owner") + .send({ email: user.email, requestType: "PASSWORD_RESET", returnOobLink: true }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body) + .to.have.property("kind") + .equals("identitytoolkit#GetOobConfirmationCodeResponse"); + expect(res.body).to.have.property("email").equals(user.email); + }); + }); }); diff --git a/src/test/emulators/auth/password.spec.ts b/src/test/emulators/auth/password.spec.ts index 29314547fc8..b6537b6b509 100644 --- a/src/test/emulators/auth/password.spec.ts +++ b/src/test/emulators/auth/password.spec.ts @@ -131,6 +131,27 @@ describeAuthEmulator("accounts:signInWithPassword", ({ authApi, getClock }) => { }); }); + it("should error if email is not found with improved email privacy enabled", async () => { + await updateConfig( + authApi(), + PROJECT_ID, + { + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, + }, + "emailPrivacyConfig" + ); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword") + .query({ key: "fake-api-key" }) + .send({ email: "nosuchuser@example.com", password: "notasecret" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).equals("INVALID_LOGIN_CREDENTIALS"); + }); + }); + it("should error if password is wrong", async () => { const user = { email: "alice@example.com", password: "notasecret" }; await registerUser(authApi(), user); @@ -145,6 +166,30 @@ describeAuthEmulator("accounts:signInWithPassword", ({ authApi, getClock }) => { }); }); + it("should error if password is wrong with improved email privacy enabled", async () => { + const user = { email: "alice@example.com", password: "notasecret" }; + await updateConfig( + authApi(), + PROJECT_ID, + { + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, + }, + "emailPrivacyConfig" + ); + await registerUser(authApi(), user); + await authApi() + .post("/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword") + .query({ key: "fake-api-key" }) + // Passwords are case sensitive. The uppercase one below doesn't match. + .send({ email: user.email, password: "NOTASECRET" }) + .then((res) => { + expectStatusCode(400, res); + expect(res.body.error.message).equals("INVALID_LOGIN_CREDENTIALS"); + }); + }); + it("should error if user is disabled", async () => { const user = { email: "alice@example.com", password: "notasecret" }; const { localId } = await registerUser(authApi(), user); From 5b68063a30751ed2a5d0a60250c78306ee9c43b1 Mon Sep 17 00:00:00 2001 From: aalej Date: Sat, 20 Jan 2024 01:26:34 +0800 Subject: [PATCH 2/9] Removed custom test script for auth --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 41a6c3663d5..cc52eb84b64 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,9 @@ "lint:quiet": "npm run lint:ts -- --quiet && npm run lint:other", "lint:ts": "eslint --config .eslintrc.js --ext .ts,.js .", "mocha": "nyc mocha 'src/test/**/*.{ts,js}'", - "mocha:auth": "nyc mocha 'src/test/emulators/auth/{oob,password,createAuthUri}.spec.ts'", "prepare": "npm run clean && npm run build:publish", "test": "npm run lint:quiet && npm run test:compile && npm run mocha", "test:client-integration": "bash ./scripts/client-integration-tests/run.sh", - "test:auth": "npm run lint:quiet && npm run test:compile && npm run mocha:auth", "test:compile": "tsc --project tsconfig.compile.json", "test:emulator": "bash ./scripts/emulator-tests/run.sh", "test:extensions-deploy": "bash ./scripts/extensions-deploy-tests/run.sh", @@ -241,4 +239,4 @@ "typescript-json-schema": "^0.50.1", "vite": "^4.2.1" } -} \ No newline at end of file +} From ea35ec5cbc09f5e6f064bb6c0a4f986c2161063b Mon Sep 17 00:00:00 2001 From: aalej Date: Sat, 20 Jan 2024 19:47:15 +0800 Subject: [PATCH 3/9] Updated tests for improved email privacy --- src/test/emulators/auth/oob.spec.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/emulators/auth/oob.spec.ts b/src/test/emulators/auth/oob.spec.ts index ea4ab17df88..2e74457eda1 100644 --- a/src/test/emulators/auth/oob.spec.ts +++ b/src/test/emulators/auth/oob.spec.ts @@ -359,9 +359,8 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => { }); }); - it("should error when sending a password reset with improved email privacy disabled", async () => { + it("should error when sending a password reset to non-existent user with improved email privacy disabled", async () => { const user = { email: "alice@example.com", password: "notasecret" }; - // await registerUser(authApi(), user); await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:sendOobCode") .set("Authorization", "Bearer owner") @@ -372,7 +371,7 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => { }); }); - it("should error when sending a password reset with improved email privacy enabled", async () => { + it("should return email when sending a password reset to non-existent user with improved email privacy enabled", async () => { const user = { email: "alice@example.com", password: "notasecret" }; await updateConfig( authApi(), From 90a65a9f151c40a4831b772854647fe37ec62ab9 Mon Sep 17 00:00:00 2001 From: aalej Date: Tue, 23 Jan 2024 23:22:47 +0800 Subject: [PATCH 4/9] Added Emulator-specific configuration for enableImprovedEmailPrivacy --- src/emulator/auth/operations.ts | 6 +++ src/emulator/auth/schema.ts | 3 ++ src/emulator/auth/state.ts | 2 + src/test/emulators/auth/misc.spec.ts | 71 +++++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 8147cae5927..79ff1ac66ef 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1929,6 +1929,9 @@ function getEmulatorProjectConfig(state: ProjectState): Schemas["EmulatorV1Proje signIn: { allowDuplicateEmails: !state.oneAccountPerEmail, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: state.enableImprovedEmailPrivacy, + }, }; } @@ -1943,6 +1946,9 @@ function updateEmulatorProjectConfig( if (reqBody.signIn?.allowDuplicateEmails != null) { updateMask.push("signIn.allowDuplicateEmails"); } + if (reqBody.emailPrivacyConfig?.enableImprovedEmailPrivacy != null) { + updateMask.push("emailPrivacyConfig.enableImprovedEmailPrivacy"); + } ctx.params.query.updateMask = updateMask.join(); updateConfig(state, reqBody, ctx); diff --git a/src/emulator/auth/schema.ts b/src/emulator/auth/schema.ts index de449e89540..489a3e5f141 100644 --- a/src/emulator/auth/schema.ts +++ b/src/emulator/auth/schema.ts @@ -4144,6 +4144,9 @@ export interface components { signIn?: { allowDuplicateEmails?: boolean; }; + emailPrivacyConfig?: { + enableImprovedEmailPrivacy?: boolean, + }, }; /** @description Details of all pending confirmation codes. */ EmulatorV1ProjectsOobCodes: { diff --git a/src/emulator/auth/state.ts b/src/emulator/auth/state.ts index ea8ed092ce2..02276b459b3 100644 --- a/src/emulator/auth/state.ts +++ b/src/emulator/auth/state.ts @@ -672,6 +672,8 @@ export class AgentProjectState extends ProjectState { if (!updateMask) { this.oneAccountPerEmail = !update.signIn?.allowDuplicateEmails ?? true; this.blockingFunctionsConfig = update.blockingFunctions ?? {}; + this.enableImprovedEmailPrivacy = + update.emailPrivacyConfig?.enableImprovedEmailPrivacy ?? false; return this.config; } return applyMask(updateMask, this.config, update); diff --git a/src/test/emulators/auth/misc.spec.ts b/src/test/emulators/auth/misc.spec.ts index 9ade23381f2..fad375f6a0a 100644 --- a/src/test/emulators/auth/misc.spec.ts +++ b/src/test/emulators/auth/misc.spec.ts @@ -552,6 +552,9 @@ describeAuthEmulator("emulator utility APIs", ({ authApi }) => { expect(res.body).to.have.property("signIn").eql({ allowDuplicateEmails: false /* default value */, }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: false /* default value */, + }); }); }); @@ -564,7 +567,7 @@ describeAuthEmulator("emulator utility APIs", ({ authApi }) => { }); }); - it("should update allowDuplicateEmails on PATCH /emulator/v1/projects/{PROJECT_ID}/config", async () => { + it("should only update allowDuplicateEmails on PATCH /emulator/v1/projects/{PROJECT_ID}/config", async () => { await authApi() .patch(`/emulator/v1/projects/${PROJECT_ID}/config`) .send({ signIn: { allowDuplicateEmails: true } }) @@ -573,6 +576,9 @@ describeAuthEmulator("emulator utility APIs", ({ authApi }) => { expect(res.body).to.have.property("signIn").eql({ allowDuplicateEmails: true, }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: false, + }); }); await authApi() .patch(`/emulator/v1/projects/${PROJECT_ID}/config`) @@ -582,6 +588,69 @@ describeAuthEmulator("emulator utility APIs", ({ authApi }) => { expect(res.body).to.have.property("signIn").eql({ allowDuplicateEmails: false, }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: false, + }); + }); + }); + + it("should only update enableImprovedEmailPrivacy on PATCH /emulator/v1/projects/{PROJECT_ID}/config", async () => { + await authApi() + .patch(`/emulator/v1/projects/${PROJECT_ID}/config`) + .send({ emailPrivacyConfig: { enableImprovedEmailPrivacy: true } }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.have.property("signIn").eql({ + allowDuplicateEmails: false, + }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: true, + }); + }); + await authApi() + .patch(`/emulator/v1/projects/${PROJECT_ID}/config`) + .send({ emailPrivacyConfig: { enableImprovedEmailPrivacy: false } }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.have.property("signIn").eql({ + allowDuplicateEmails: false, + }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: false, + }); + }); + }); + + it("should update both allowDuplicateEmails and enableImprovedEmailPrivacy on PATCH /emulator/v1/projects/{PROJECT_ID}/config", async () => { + await authApi() + .patch(`/emulator/v1/projects/${PROJECT_ID}/config`) + .send({ + signIn: { allowDuplicateEmails: true }, + emailPrivacyConfig: { enableImprovedEmailPrivacy: true }, + }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.have.property("signIn").eql({ + allowDuplicateEmails: true, + }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: true, + }); + }); + await authApi() + .patch(`/emulator/v1/projects/${PROJECT_ID}/config`) + .send({ + signIn: { allowDuplicateEmails: false }, + emailPrivacyConfig: { enableImprovedEmailPrivacy: false }, + }) + .then((res) => { + expectStatusCode(200, res); + expect(res.body).to.have.property("signIn").eql({ + allowDuplicateEmails: false, + }); + expect(res.body).to.have.property("emailPrivacyConfig").eql({ + enableImprovedEmailPrivacy: false, + }); }); }); }); From 8e81ec5dbf356f78f967eeb37f00ab16c72c37a2 Mon Sep 17 00:00:00 2001 From: aalej Date: Wed, 24 Jan 2024 02:28:11 +0800 Subject: [PATCH 5/9] Updated integration tests to include enableImprovedEmailPrivacy config --- scripts/emulator-import-export-tests/tests.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/emulator-import-export-tests/tests.ts b/scripts/emulator-import-export-tests/tests.ts index 823cc9653b8..5f28952a252 100644 --- a/scripts/emulator-import-export-tests/tests.ts +++ b/scripts/emulator-import-export-tests/tests.ts @@ -277,6 +277,9 @@ describe("import/export end to end", () => { signIn: { allowDuplicateEmails: false, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + }, }); const accountsPath = path.join(exportPath, "auth_export", "accounts.json"); @@ -382,6 +385,9 @@ describe("import/export end to end", () => { signIn: { allowDuplicateEmails: false, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + }, }); const accountsPath = path.join(exportPath, "auth_export", "accounts.json"); @@ -447,6 +453,9 @@ describe("import/export end to end", () => { signIn: { allowDuplicateEmails: false, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + }, }); const accountsPath = path.join(exportPath, "auth_export", "accounts.json"); From 49ef19babe4b34aae42f4ddfc617eebe7544a0fc Mon Sep 17 00:00:00 2001 From: aalej Date: Wed, 24 Jan 2024 03:06:29 +0800 Subject: [PATCH 6/9] Added TODO message to implement case for VERIFY_AND_CHANGE_EMAIL --- src/emulator/auth/operations.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index 79ff1ac66ef..27eb6ae0f2a 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -912,7 +912,7 @@ function sendOobCode( email = user.email; } break; - + // TODO: implement case for requestType VERIFY_AND_CHANGE_EMAIL. default: throw new NotImplementedError(reqBody.requestType); } From e5ec84a0f34f6cf92f145eeaaeb81a38b6558865 Mon Sep 17 00:00:00 2001 From: aalej Date: Thu, 25 Jan 2024 20:25:42 +0800 Subject: [PATCH 7/9] Updated test title --- src/test/emulators/auth/oob.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/emulators/auth/oob.spec.ts b/src/test/emulators/auth/oob.spec.ts index 2e74457eda1..88c6a6ba45f 100644 --- a/src/test/emulators/auth/oob.spec.ts +++ b/src/test/emulators/auth/oob.spec.ts @@ -371,7 +371,7 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => { }); }); - it("should return email when sending a password reset to non-existent user with improved email privacy enabled", async () => { + it("should return email address when sending a password reset to non-existent user with improved email privacy enabled", async () => { const user = { email: "alice@example.com", password: "notasecret" }; await updateConfig( authApi(), From 8c0d2e955cabd5788d5a48f8210ef37b86912c7f Mon Sep 17 00:00:00 2001 From: aalej Date: Mon, 29 Jan 2024 23:01:47 +0800 Subject: [PATCH 8/9] Ran new formatter --- src/emulator/auth/operations.ts | 2 +- src/test/emulators/auth/createAuthUri.spec.ts | 4 ++-- src/test/emulators/auth/oob.spec.ts | 2 +- src/test/emulators/auth/password.spec.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/emulator/auth/operations.ts b/src/emulator/auth/operations.ts index fd43f933b2c..2cb15dd67cb 100644 --- a/src/emulator/auth/operations.ts +++ b/src/emulator/auth/operations.ts @@ -1747,7 +1747,7 @@ async function signInWithPassword( assert(user.passwordHash && user.salt, "INVALID_LOGIN_CREDENTIALS"); assert( user.passwordHash === hashPassword(reqBody.password, user.salt), - "INVALID_LOGIN_CREDENTIALS" + "INVALID_LOGIN_CREDENTIALS", ); } else { assert(user, "EMAIL_NOT_FOUND"); diff --git a/src/test/emulators/auth/createAuthUri.spec.ts b/src/test/emulators/auth/createAuthUri.spec.ts index e9f9bd2a37d..bb2b9c3fb9b 100644 --- a/src/test/emulators/auth/createAuthUri.spec.ts +++ b/src/test/emulators/auth/createAuthUri.spec.ts @@ -33,7 +33,7 @@ describeAuthEmulator("accounts:createAuthUri", ({ authApi }) => { enableImprovedEmailPrivacy: true, }, }, - "emailPrivacyConfig" + "emailPrivacyConfig", ); await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:createAuthUri") @@ -72,7 +72,7 @@ describeAuthEmulator("accounts:createAuthUri", ({ authApi }) => { enableImprovedEmailPrivacy: true, }, }, - "emailPrivacyConfig" + "emailPrivacyConfig", ); await registerUser(authApi(), user); await authApi() diff --git a/src/test/emulators/auth/oob.spec.ts b/src/test/emulators/auth/oob.spec.ts index 88c6a6ba45f..1558a69dc8e 100644 --- a/src/test/emulators/auth/oob.spec.ts +++ b/src/test/emulators/auth/oob.spec.ts @@ -381,7 +381,7 @@ describeAuthEmulator("accounts:sendOobCode", ({ authApi, getClock }) => { enableImprovedEmailPrivacy: true, }, }, - "emailPrivacyConfig" + "emailPrivacyConfig", ); await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:sendOobCode") diff --git a/src/test/emulators/auth/password.spec.ts b/src/test/emulators/auth/password.spec.ts index 218062e2434..5d411577558 100644 --- a/src/test/emulators/auth/password.spec.ts +++ b/src/test/emulators/auth/password.spec.ts @@ -140,7 +140,7 @@ describeAuthEmulator("accounts:signInWithPassword", ({ authApi, getClock }) => { enableImprovedEmailPrivacy: true, }, }, - "emailPrivacyConfig" + "emailPrivacyConfig", ); await authApi() .post("/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword") @@ -176,7 +176,7 @@ describeAuthEmulator("accounts:signInWithPassword", ({ authApi, getClock }) => { enableImprovedEmailPrivacy: true, }, }, - "emailPrivacyConfig" + "emailPrivacyConfig", ); await registerUser(authApi(), user); await authApi() From 1c7d01c773d7f6577452602c1410f6c69659fc6c Mon Sep 17 00:00:00 2001 From: aalej Date: Tue, 30 Jan 2024 01:48:50 +0800 Subject: [PATCH 9/9] Added a changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29bb2d..6d609478fc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1 @@ +- Added rudimentary email enumeration protection for auth emulator. (#6702)