From c9d328ccf7434b7c8c2baf62e26cd71feb6adf63 Mon Sep 17 00:00:00 2001 From: Pragati Date: Sat, 27 May 2023 21:13:02 -0700 Subject: [PATCH 1/8] EmailPrivacy Config definition --- src/auth/auth-config.ts | 73 +++++++++++++++++++++++++++++++------- src/auth/project-config.ts | 48 +++++++++++++++++++------ src/auth/tenant.ts | 31 ++++++++++++++-- 3 files changed, 125 insertions(+), 27 deletions(-) diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index f5022b3b14..735acd4cb7 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -509,14 +509,14 @@ export interface MultiFactorConfig { factorIds?: AuthFactorType[]; /** - * A list of multi-factor provider configurations. + * A list of multi-factor provider configurations. * MFA providers (except phone) indicate whether they're enabled through this field. */ providerConfigs?: MultiFactorProviderConfig[]; } /** - * Interface representing a multi-factor auth provider configuration. - * This interface is used for second factor auth providers other than SMS. + * Interface representing a multi-factor auth provider configuration. + * This interface is used for second factor auth providers other than SMS. * Currently, only TOTP is supported. */export interface MultiFactorProviderConfig { /** @@ -528,7 +528,7 @@ export interface MultiFactorConfig { } /** - * Interface representing configuration settings for TOTP second factor auth. + * Interface representing configuration settings for TOTP second factor auth. */ export interface TotpMultiFactorProviderConfig { /** @@ -540,7 +540,7 @@ export interface TotpMultiFactorProviderConfig { /** * Defines the multi-factor config class used to convert client side MultiFactorConfig * to a format that is understood by the Auth server. - * + * * @internal */ export class MultiFactorAuthConfig implements MultiFactorConfig { @@ -555,7 +555,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { */ public readonly factorIds: AuthFactorType[]; /** - * A list of multi-factor provider specific config. + * A list of multi-factor provider specific config. * New MFA providers (except phone) will indicate enablement/disablement through this field. */ public readonly providerConfigs: MultiFactorProviderConfig[]; @@ -1947,8 +1947,8 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { } } -/** - * A password policy configuration for a project or tenant +/** + * A password policy configuration for a project or tenant */ export interface PasswordPolicyConfig { /** @@ -2003,7 +2003,7 @@ export interface CustomStrengthOptionsConfig { /** * Defines the password policy config class used to convert client side PasswordPolicyConfig * to a format that is understood by the Auth server. - * + * * @internal */ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { @@ -2110,7 +2110,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { '"PasswordPolicyConfig.enforcementState" must be either "ENFORCE" or "OFF".', ); } - + if (typeof options.forceUpgradeOnSignin !== 'undefined') { if (!validator.isBoolean(options.forceUpgradeOnSignin)) { throw new FirebaseAuthError( @@ -2254,7 +2254,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { } } -/** +/** * Server side password policy configuration. */ export interface PasswordPolicyAuthServerConfig { @@ -2264,14 +2264,14 @@ export interface PasswordPolicyAuthServerConfig { } /** - * Server side password policy versions configuration. + * Server side password policy versions configuration. */ export interface PasswordPolicyVersionsAuthServerConfig { customStrengthOptions?: CustomStrengthOptionsAuthServerConfig; } /** - * Server side password policy constraints configuration. + * Server side password policy constraints configuration. */ export interface CustomStrengthOptionsAuthServerConfig { containsLowercaseCharacter?: boolean; @@ -2281,3 +2281,50 @@ export interface CustomStrengthOptionsAuthServerConfig { minPasswordLength?: number; maxPasswordLength?: number; } + +/** + * The configuration for the email privacy on the project or tenant. +*/ +export interface EmailPrivacyConfig { + /** + * Variable indiciating email privacy enabled of not. + */ + enableImprovedEmailPrivacy?: boolean; +} + +/** + * Defines the EmailPrivacyAuthConfig class used for validation. + * + * @internal + */ +export class EmailPrivacyAuthConfig { + public static validate(options: EmailPrivacyConfig): void { + if (!validator.isNonNullObject(options)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + '"EmailPrivacyConfig" must be a non-null object.', + ); + } + + const validKeys = { + enableImprovedEmailPrivacy: true, + }; + + for (const key in options) { + if (!(key in validKeys)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + `"${key}" is not a valid "EmailPrivacyConfig" parameter.`, + ); + } + } + + if (typeof options.enableImprovedEmailPrivacy !== 'undefined' + && !validator.isBoolean(options.enableImprovedEmailPrivacy)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + '"EmailPrivacyConfig.enableImprovedEmailPrivacy" must be a valid boolean value.', + ); + } + } +} diff --git a/src/auth/project-config.ts b/src/auth/project-config.ts index 2748be0423..5468f50fa6 100644 --- a/src/auth/project-config.ts +++ b/src/auth/project-config.ts @@ -26,6 +26,8 @@ import { PasswordPolicyAuthConfig, PasswordPolicyAuthServerConfig, PasswordPolicyConfig, + EmailPrivacyConfig, + EmailPrivacyAuthConfig, } from './auth-config'; import { deepCopy } from '../utils/deep-copy'; @@ -53,6 +55,10 @@ export interface UpdateProjectConfigRequest { * The password policy configuration to update on the project */ passwordPolicyConfig?: PasswordPolicyConfig; + /** + * The email privacy configuration to update on the project + */ + emailPrivacyConfig?: EmailPrivacyConfig; } /** @@ -63,6 +69,7 @@ export interface ProjectConfigServerResponse { mfa?: MultiFactorAuthServerConfig; recaptchaConfig?: RecaptchaConfig; passwordPolicyConfig?: PasswordPolicyAuthServerConfig; + emailPrivacyConfig?: EmailPrivacyConfig; } /** @@ -73,6 +80,7 @@ export interface ProjectConfigClientRequest { mfa?: MultiFactorAuthServerConfig; recaptchaConfig?: RecaptchaConfig; passwordPolicyConfig?: PasswordPolicyAuthServerConfig; + emailPrivacyConfig?: EmailPrivacyConfig; } /** @@ -89,9 +97,14 @@ export class ProjectConfig { /** * The project's multi-factor auth configuration. * Supports only phone and TOTP. - */ + */ private readonly multiFactorConfig_?: MultiFactorConfig; - + /** + * The multi-factor auth configuration. + */ + get multiFactorConfig(): MultiFactorConfig | undefined { + return this.multiFactorConfig_; + } /** * The reCAPTCHA configuration to update on the project. * By enabling reCAPTCHA Enterprise integration, you are @@ -99,17 +112,14 @@ export class ProjectConfig { * {@link https://cloud.google.com/terms/service-terms | Term of Service}. */ private readonly recaptchaConfig_?: RecaptchaAuthConfig; - - /** - * The multi-factor auth configuration. - */ - get multiFactorConfig(): MultiFactorConfig | undefined { - return this.multiFactorConfig_; - } /** * The password policy configuration for the project */ public readonly passwordPolicyConfig?: PasswordPolicyConfig; + /** + * The email privacy configuration for the project + */ + public readonly emailPrivacyConfig?: EmailPrivacyConfig; /** * Validates a project config options object. Throws an error on failure. @@ -128,6 +138,7 @@ export class ProjectConfig { multiFactorConfig: true, recaptchaConfig: true, passwordPolicyConfig: true, + emailPrivacyConfig: true, } // Check for unsupported top level attributes. for (const key in request) { @@ -156,6 +167,11 @@ export class ProjectConfig { if (typeof request.passwordPolicyConfig !== 'undefined') { PasswordPolicyAuthConfig.validate(request.passwordPolicyConfig); } + + // Validate Email Privacy Config if provided. + if (typeof request.emailPrivacyConfig !== 'undefined') { + EmailPrivacyAuthConfig.validate(request.emailPrivacyConfig); + } } /** @@ -180,9 +196,12 @@ export class ProjectConfig { if (typeof configOptions.passwordPolicyConfig !== 'undefined') { request.passwordPolicyConfig = PasswordPolicyAuthConfig.buildServerRequest(configOptions.passwordPolicyConfig); } + if (typeof configOptions.emailPrivacyConfig !== 'undefined') { + request.emailPrivacyConfig = configOptions.emailPrivacyConfig; + } return request; } - + /** * The reCAPTCHA configuration. */ @@ -200,7 +219,7 @@ export class ProjectConfig { if (typeof response.smsRegionConfig !== 'undefined') { this.smsRegionConfig = response.smsRegionConfig; } - //Backend API returns "mfa" in case of project config and "mfaConfig" in case of tenant config. + //Backend API returns "mfa" in case of project config and "mfaConfig" in case of tenant config. //The SDK exposes it as multiFactorConfig always. if (typeof response.mfa !== 'undefined') { this.multiFactorConfig_ = new MultiFactorAuthConfig(response.mfa); @@ -211,6 +230,9 @@ export class ProjectConfig { if (typeof response.passwordPolicyConfig !== 'undefined') { this.passwordPolicyConfig = new PasswordPolicyAuthConfig(response.passwordPolicyConfig); } + if (typeof response.emailPrivacyConfig !== 'undefined') { + this.emailPrivacyConfig = response.emailPrivacyConfig; + } } /** * Returns a JSON-serializable representation of this object. @@ -224,6 +246,7 @@ export class ProjectConfig { multiFactorConfig: deepCopy(this.multiFactorConfig), recaptchaConfig: this.recaptchaConfig_?.toJSON(), passwordPolicyConfig: deepCopy(this.passwordPolicyConfig), + emailPrivacyConfig: deepCopy(this.emailPrivacyConfig), }; if (typeof json.smsRegionConfig === 'undefined') { delete json.smsRegionConfig; @@ -237,6 +260,9 @@ export class ProjectConfig { if (typeof json.passwordPolicyConfig === 'undefined') { delete json.passwordPolicyConfig; } + if (typeof json.emailPrivacyConfig === 'undefined') { + delete json.emailPrivacyConfig; + } return json; } } diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 15e941a28f..6f30f42e75 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -22,8 +22,8 @@ import { EmailSignInConfig, EmailSignInConfigServerRequest, MultiFactorAuthServerConfig, MultiFactorConfig, validateTestPhoneNumbers, EmailSignInProviderConfig, MultiFactorAuthConfig, SmsRegionConfig, SmsRegionsAuthConfig, RecaptchaAuthConfig, RecaptchaConfig, - PasswordPolicyConfig, - PasswordPolicyAuthConfig, PasswordPolicyAuthServerConfig, + PasswordPolicyConfig, + PasswordPolicyAuthConfig, PasswordPolicyAuthServerConfig, EmailPrivacyConfig, EmailPrivacyAuthConfig, } from './auth-config'; /** @@ -61,7 +61,7 @@ export interface UpdateTenantRequest { * The SMS configuration to update on the project. */ smsRegionConfig?: SmsRegionConfig; - + /** * The reCAPTCHA configuration to update on the tenant. * By enabling reCAPTCHA Enterprise integration, you are @@ -73,6 +73,10 @@ export interface UpdateTenantRequest { * The password policy configuration for the tenant */ passwordPolicyConfig?: PasswordPolicyConfig; + /** + * The email privacy configuration for the tenant + */ + emailPrivacyConfig?: EmailPrivacyConfig; } /** @@ -90,6 +94,7 @@ export interface TenantOptionsServerRequest extends EmailSignInConfigServerReque smsRegionConfig?: SmsRegionConfig; recaptchaConfig?: RecaptchaConfig; passwordPolicyConfig?: PasswordPolicyAuthServerConfig; + emailPrivacyConfig?: EmailPrivacyConfig; } /** The tenant server response interface. */ @@ -104,6 +109,7 @@ export interface TenantServerResponse { smsRegionConfig?: SmsRegionConfig; recaptchaConfig? : RecaptchaConfig; passwordPolicyConfig?: PasswordPolicyAuthServerConfig; + emailPrivacyConfig?: EmailPrivacyConfig; } /** @@ -165,6 +171,10 @@ export class Tenant { * The password policy configuration for the tenant */ public readonly passwordPolicyConfig?: PasswordPolicyConfig; + /** + * The email privacy configuration for the tenant + */ + public readonly emailPrivacyConfig?: EmailPrivacyConfig; /** * Builds the corresponding server request for a TenantOptions object. @@ -204,6 +214,9 @@ export class Tenant { if (typeof tenantOptions.passwordPolicyConfig !== 'undefined') { request.passwordPolicyConfig = PasswordPolicyAuthConfig.buildServerRequest(tenantOptions.passwordPolicyConfig); } + if (typeof tenantOptions.emailPrivacyConfig !== 'undefined') { + request.emailPrivacyConfig = tenantOptions.emailPrivacyConfig; + } return request; } @@ -240,6 +253,7 @@ export class Tenant { smsRegionConfig: true, recaptchaConfig: true, passwordPolicyConfig: true, + emailPrivacyConfig: true, }; const label = createRequest ? 'CreateTenantRequest' : 'UpdateTenantRequest'; if (!validator.isNonNullObject(request)) { @@ -299,6 +313,10 @@ export class Tenant { // This will throw an error if invalid. PasswordPolicyAuthConfig.buildServerRequest(request.passwordPolicyConfig); } + // Validate Email Privacy Config if provided. + if (typeof request.emailPrivacyConfig != 'undefined') { + EmailPrivacyAuthConfig.validate(request.emailPrivacyConfig); + } } /** @@ -342,6 +360,9 @@ export class Tenant { if (typeof response.passwordPolicyConfig !== 'undefined') { this.passwordPolicyConfig = new PasswordPolicyAuthConfig(response.passwordPolicyConfig); } + if (typeof response.emailPrivacyConfig !== 'undefined') { + this.emailPrivacyConfig = deepCopy(response.emailPrivacyConfig); + } } /** @@ -381,6 +402,7 @@ export class Tenant { smsRegionConfig: deepCopy(this.smsRegionConfig), recaptchaConfig: this.recaptchaConfig_?.toJSON(), passwordPolicyConfig: deepCopy(this.passwordPolicyConfig), + emailPrivacyConfig: deepCopy(this.emailPrivacyConfig), }; if (typeof json.multiFactorConfig === 'undefined') { delete json.multiFactorConfig; @@ -397,6 +419,9 @@ export class Tenant { if (typeof json.passwordPolicyConfig === 'undefined') { delete json.passwordPolicyConfig; } + if (typeof json.emailPrivacyConfig === 'undefined') { + delete json.emailPrivacyConfig; + } return json; } } From 0b89b63af768e3d919d3cf1cff58ac3544739ab7 Mon Sep 17 00:00:00 2001 From: Pragati Date: Sat, 27 May 2023 21:14:02 -0700 Subject: [PATCH 2/8] Adding unit tests --- test/unit/auth/project-config.spec.ts | 44 +++++++++++++- test/unit/auth/tenant.spec.ts | 88 ++++++++++++++++++++++++--- 2 files changed, 123 insertions(+), 9 deletions(-) diff --git a/test/unit/auth/project-config.spec.ts b/test/unit/auth/project-config.spec.ts index eb32cae88e..f85acb841e 100644 --- a/test/unit/auth/project-config.spec.ts +++ b/test/unit/auth/project-config.spec.ts @@ -67,6 +67,9 @@ describe('ProjectConfig', () => { }, ], }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const updateProjectConfigRequest1: UpdateProjectConfigRequest = { @@ -87,6 +90,9 @@ describe('ProjectConfig', () => { maxLength: 30, }, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + }, }; const updateProjectConfigRequest2: UpdateProjectConfigRequest = { @@ -316,7 +322,7 @@ describe('ProjectConfig', () => { ProjectConfig.buildServerRequest(tenantOptionsClientRequest); }).to.throw('"PasswordPolicyConfig.constraints" must be defined.'); }); - + it('should throw on invalid constraints attribute', ()=> { const tenantOptionsClientRequest = deepCopy(updateProjectConfigRequest1) as any; tenantOptionsClientRequest.passwordPolicyConfig.constraints.invalidParameter = 'invalid'; @@ -398,7 +404,7 @@ describe('ProjectConfig', () => { tenantOptionsClientRequest.passwordPolicyConfig.constraints.minLength = 45; expect(() => { ProjectConfig.buildServerRequest(tenantOptionsClientRequest); - }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + + }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.'); }); @@ -421,6 +427,30 @@ describe('ProjectConfig', () => { ' must be greater than or equal to minLength and at max 4096.'); }); + it('should throw on null EmailPrivacyConfig attribute', () => { + const configOptionsClientRequest = deepCopy(updateProjectConfigRequest1) as any; + configOptionsClientRequest.emailPrivacyConfig = null; + expect(() => { + ProjectConfig.buildServerRequest(configOptionsClientRequest); + }).to.throw('"EmailPrivacyConfig" must be a non-null object.'); + }); + + it('should throw on invalid EmailPrivacyConfig attribute', () => { + const configOptionsClientRequest = deepCopy(updateProjectConfigRequest1) as any; + configOptionsClientRequest.emailPrivacyConfig.invalidParameter = 'invalid'; + expect(() => { + ProjectConfig.buildServerRequest(configOptionsClientRequest); + }).to.throw('"invalidParameter" is not a valid "EmailPrivacyConfig" parameter.'); + }); + + it('should throw on invalid enableImprovedEmailPrivacy attribute', () => { + const configOptionsClientRequest = deepCopy(updateProjectConfigRequest1) as any; + configOptionsClientRequest.emailPrivacyConfig.enableImprovedEmailPrivacy = []; + expect(() => { + ProjectConfig.buildServerRequest(configOptionsClientRequest); + }).to.throw('"EmailPrivacyConfig.enableImprovedEmailPrivacy" must be a valid boolean value.'); + }); + const nonObjects = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], _.noop]; nonObjects.forEach((request) => { it('should throw on invalid UpdateProjectConfigRequest:' + JSON.stringify(request), () => { @@ -505,6 +535,13 @@ describe('ProjectConfig', () => { }; expect(projectConfig.passwordPolicyConfig).to.deep.equal(expectedPasswordPolicyConfig); }); + + it('should set readonly property emailPrivacyConfig', () => { + const expectedEmailPrivacyConfig = { + enableImprovedEmailPrivacy: true, + }; + expect(projectConfig.emailPrivacyConfig).to.deep.equal(expectedEmailPrivacyConfig); + }); }); describe('toJSON()', () => { @@ -515,6 +552,7 @@ describe('ProjectConfig', () => { multiFactorConfig: deepCopy(serverResponse.mfa), recaptchaConfig: deepCopy(serverResponse.recaptchaConfig), passwordPolicyConfig: deepCopy(serverResponse.passwordPolicyConfig), + emailPrivacyConfig: deepCopy(serverResponse.emailPrivacyConfig), }); }); @@ -526,6 +564,8 @@ describe('ProjectConfig', () => { delete serverResponseOptionalCopy.recaptchaConfig?.managedRules; delete serverResponseOptionalCopy.recaptchaConfig?.useAccountDefender; delete serverResponseOptionalCopy.passwordPolicyConfig; + delete serverResponseOptionalCopy.passwordPolicyConfig; + delete serverResponseOptionalCopy.emailPrivacyConfig; expect(new ProjectConfig(serverResponseOptionalCopy).toJSON()).to.deep.equal({ recaptchaConfig: { recaptchaKeys: deepCopy(serverResponse.recaptchaConfig?.recaptchaKeys), diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index 0d4d9e8a90..e726514979 100644 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -20,7 +20,7 @@ import * as sinonChai from 'sinon-chai'; import * as chaiAsPromised from 'chai-as-promised'; import { deepCopy } from '../../../src/utils/deep-copy'; -import { EmailSignInConfig, MultiFactorAuthConfig, RecaptchaAuthConfig, +import { EmailSignInConfig, MultiFactorAuthConfig, RecaptchaAuthConfig, PasswordPolicyAuthServerConfig, PasswordPolicyConfig, } from '../../../src/auth/auth-config'; import { TenantServerResponse } from '../../../src/auth/tenant'; @@ -100,6 +100,9 @@ describe('Tenant', () => { }, smsRegionConfig: smsAllowByDefault, passwordPolicyConfig: passwordPolicyServerConfig, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const clientRequest: UpdateTenantRequest = { @@ -126,6 +129,9 @@ describe('Tenant', () => { }, smsRegionConfig: smsAllowByDefault, passwordPolicyConfig: passwordPolicyClientConfig, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const serverRequestWithoutMfa: TenantServerResponse = { @@ -134,6 +140,9 @@ describe('Tenant', () => { allowPasswordSignup: true, enableEmailLinkSignin: true, passwordPolicyConfig: passwordPolicyServerConfig, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const clientRequestWithoutMfa: UpdateTenantRequest = { @@ -143,6 +152,9 @@ describe('Tenant', () => { passwordRequired: false, }, passwordPolicyConfig: passwordPolicyClientConfig, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const clientRequestWithRecaptcha: UpdateTenantRequest = { @@ -203,6 +215,10 @@ describe('Tenant', () => { useAccountDefender: true, }, smsRegionConfig: smsAllowByDefault, + passwordPolicyConfig: passwordPolicyServerConfig, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; describe('buildServerRequest()', () => { @@ -369,7 +385,7 @@ describe('Tenant', () => { it('should throw on non-array disallowedRegions attribute', () => { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; - tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; + tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); }).to.throw('"SmsRegionConfig.allowByDefault.disallowedRegions" must be a valid string array.'); @@ -439,7 +455,7 @@ describe('Tenant', () => { Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); }).to.throw('"PasswordPolicyConfig.constraints" must be defined.'); }); - + it('should throw on invalid constraints attribute', ()=> { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; tenantOptionsClientRequest.passwordPolicyConfig.constraints.invalidParameter = 'invalid'; @@ -521,7 +537,7 @@ describe('Tenant', () => { tenantOptionsClientRequest.passwordPolicyConfig.constraints.minLength = 45; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); - }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + + }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.'); }); @@ -544,6 +560,30 @@ describe('Tenant', () => { ' must be greater than or equal to minLength and at max 4096.'); }); + it('should throw on null EmailPrivacyConfig attribute', () => { + const tenantOptionsClientRequest = deepCopy(clientRequest) as any; + tenantOptionsClientRequest.emailPrivacyConfig = null; + expect(() => { + Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); + }).to.throw('"EmailPrivacyConfig" must be a non-null object.'); + }); + + it('should throw on invalid EmailPrivacyConfig attribute', () => { + const tenantOptionsClientRequest = deepCopy(clientRequest) as any; + tenantOptionsClientRequest.emailPrivacyConfig.invalidParameter = 'invalid'; + expect(() => { + Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); + }).to.throw('"invalidParameter" is not a valid "EmailPrivacyConfig" parameter.'); + }); + + it('should throw on invalid enableImprovedEmailPrivacy attribute', () => { + const tenantOptionsClientRequest = deepCopy(clientRequest) as any; + tenantOptionsClientRequest.emailPrivacyConfig.enableImprovedEmailPrivacy = []; + expect(() => { + Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); + }).to.throw('"EmailPrivacyConfig.enableImprovedEmailPrivacy" must be a valid boolean value.'); + }); + it('should not throw on valid client request object', () => { const tenantOptionsClientRequest = deepCopy(clientRequestWithRecaptcha); expect(() => { @@ -740,7 +780,7 @@ describe('Tenant', () => { it('should throw on non-array disallowedRegions attribute', () => { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; - tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; + tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); }).to.throw('"SmsRegionConfig.allowByDefault.disallowedRegions" must be a valid string array.'); @@ -810,7 +850,7 @@ describe('Tenant', () => { Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); }).to.throw('"PasswordPolicyConfig.constraints" must be defined.'); }); - + it('should throw on invalid constraints attribute', ()=> { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; tenantOptionsClientRequest.passwordPolicyConfig.constraints.invalidParameter = 'invalid'; @@ -892,7 +932,7 @@ describe('Tenant', () => { tenantOptionsClientRequest.passwordPolicyConfig.constraints.minLength = 45; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); - }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + + }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.'); }); @@ -915,6 +955,30 @@ describe('Tenant', () => { ' must be greater than or equal to minLength and at max 4096.'); }); + it('should throw on null EmailPrivacyConfig attribute', () => { + const tenantOptionsClientRequest = deepCopy(clientRequest) as any; + tenantOptionsClientRequest.emailPrivacyConfig = null; + expect(() => { + Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); + }).to.throw('"EmailPrivacyConfig" must be a non-null object.'); + }); + + it('should throw on invalid EmailPrivacyConfig attribute', () => { + const tenantOptionsClientRequest = deepCopy(clientRequest) as any; + tenantOptionsClientRequest.emailPrivacyConfig.invalidParameter = 'invalid'; + expect(() => { + Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); + }).to.throw('"invalidParameter" is not a valid "EmailPrivacyConfig" parameter.'); + }); + + it('should throw on invalid enableImprovedEmailPrivacy attribute', () => { + const tenantOptionsClientRequest = deepCopy(clientRequest) as any; + tenantOptionsClientRequest.emailPrivacyConfig.enableImprovedEmailPrivacy = []; + expect(() => { + Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); + }).to.throw('"EmailPrivacyConfig.enableImprovedEmailPrivacy" must be a valid boolean value.'); + }); + const nonObjects = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], _.noop]; nonObjects.forEach((request) => { it('should throw on invalid CreateTenantRequest:' + JSON.stringify(request), () => { @@ -1034,6 +1098,13 @@ describe('Tenant', () => { deepCopy(clientRequest.passwordPolicyConfig)); }); + it('should set readonly property emailPrivacyConfig', () => { + const expectedEmailPrivacyConfig = { + enableImprovedEmailPrivacy: true, + }; + expect(clientRequest.emailPrivacyConfig).to.deep.equal(expectedEmailPrivacyConfig); + }); + it('should throw when no tenant ID is provided', () => { const invalidOptions = deepCopy(serverRequest); // Use resource name that does not include a tenant ID. @@ -1074,6 +1145,8 @@ describe('Tenant', () => { testPhoneNumbers: deepCopy(clientRequest.testPhoneNumbers), smsRegionConfig: deepCopy(clientRequest.smsRegionConfig), recaptchaConfig: deepCopy(serverResponseWithRecaptcha.recaptchaConfig), + passwordPolicyConfig: deepCopy(clientRequest.passwordPolicyConfig), + emailPrivacyConfig: deepCopy(clientRequest.emailPrivacyConfig), }); }); @@ -1084,6 +1157,7 @@ describe('Tenant', () => { delete serverRequestCopyWithoutMfa.smsRegionConfig; delete serverRequestCopyWithoutMfa.recaptchaConfig; delete serverRequestCopyWithoutMfa.passwordPolicyConfig; + delete serverRequestCopyWithoutMfa.emailPrivacyConfig; expect(new Tenant(serverRequestCopyWithoutMfa).toJSON()).to.deep.equal({ tenantId: 'TENANT-ID', displayName: 'TENANT-DISPLAY-NAME', From 577813dec63a9e965da7dea382030e349d9241a8 Mon Sep 17 00:00:00 2001 From: Pragati Date: Sat, 27 May 2023 21:14:29 -0700 Subject: [PATCH 3/8] Adding integration tests --- test/integration/auth.spec.ts | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index c88fdd8722..75d5080669 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -1222,7 +1222,7 @@ describe('admin.auth', () => { minLength: 6, } } - }) + }) }); const mfaSmsEnabledTotpEnabledConfig: MultiFactorConfig = { @@ -1283,6 +1283,9 @@ describe('admin.auth', () => { ], useAccountDefender: true, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + } }; const projectConfigOption2: UpdateProjectConfigRequest = { smsRegionConfig: smsRegionAllowlistOnlyConfig, @@ -1290,6 +1293,9 @@ describe('admin.auth', () => { emailPasswordEnforcementState: 'OFF', useAccountDefender: false, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + } }; const projectConfigOptionSmsEnabledTotpDisabled: UpdateProjectConfigRequest = { smsRegionConfig: smsRegionAllowlistOnlyConfig, @@ -1309,6 +1315,9 @@ describe('admin.auth', () => { ], useAccountDefender: true, }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const expectedProjectConfig2: any = { smsRegionConfig: smsRegionAllowlistOnlyConfig, @@ -1323,6 +1332,7 @@ describe('admin.auth', () => { }, ], }, + emailPrivacyConfig: {}, }; const expectedProjectConfigSmsEnabledTotpDisabled: any = { smsRegionConfig: smsRegionAllowlistOnlyConfig, @@ -1337,6 +1347,7 @@ describe('admin.auth', () => { }, ], }, + emailPrivacyConfig: {}, }; it('updateProjectConfig() should resolve with the updated project config', () => { @@ -1417,6 +1428,9 @@ describe('admin.auth', () => { '+16505551234': '019287', '+16505550676': '985235', }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const expectedCreatedTenant: any = { displayName: 'testTenant1', @@ -1434,6 +1448,9 @@ describe('admin.auth', () => { '+16505551234': '019287', '+16505550676': '985235', }, + emailPrivacyConfig: { + enableImprovedEmailPrivacy: true, + }, }; const expectedUpdatedTenant: any = { displayName: 'testTenantUpdated', @@ -1459,6 +1476,7 @@ describe('admin.auth', () => { ], useAccountDefender: true, }, + emailPrivacyConfig: {}, }; const expectedUpdatedTenant2: any = { displayName: 'testTenantUpdated', @@ -1479,6 +1497,7 @@ describe('admin.auth', () => { ], useAccountDefender: false, }, + emailPrivacyConfig: {}, }; const expectedUpdatedTenantSmsEnabledTotpDisabled: any = { displayName: 'testTenantUpdated', @@ -1499,6 +1518,7 @@ describe('admin.auth', () => { ], useAccountDefender: false, }, + emailPrivacyConfig: {}, }; // https://mochajs.org/ @@ -1912,6 +1932,7 @@ describe('admin.auth', () => { multiFactorConfig: deepCopy(expectedUpdatedTenant.multiFactorConfig), testPhoneNumbers: deepCopy(expectedUpdatedTenant.testPhoneNumbers), recaptchaConfig: deepCopy(expectedUpdatedTenant.recaptchaConfig), + emailPrivacyConfig: { enableImprovedEmailPrivacy: false }, }; const updatedOptions2: UpdateTenantRequest = { emailSignInConfig: { @@ -1923,6 +1944,9 @@ describe('admin.auth', () => { testPhoneNumbers: null, smsRegionConfig: deepCopy(expectedUpdatedTenant2.smsRegionConfig), recaptchaConfig: deepCopy(expectedUpdatedTenant2.recaptchaConfig), + emailPrivacyConfig: { + enableImprovedEmailPrivacy: false, + }, }; if (authEmulatorHost) { return getAuth().tenantManager().updateTenant(createdTenantId, updatedOptions) @@ -1998,7 +2022,7 @@ describe('admin.auth', () => { } return getAuth().tenantManager().updateTenant(createdTenantId, updateRequestNoMfaConfig) }); - + it('updateTenant() should not update tenant reCAPTCHA config is undefined', () => { expectedUpdatedTenant.tenantId = createdTenantId; const updatedOptions2: UpdateTenantRequest = { From 17035febbf33e44090ca502705f0581a9a9d595a Mon Sep 17 00:00:00 2001 From: Pragati Date: Sat, 27 May 2023 21:15:00 -0700 Subject: [PATCH 4/8] api-extractor changes --- etc/firebase-admin.auth.api.md | 9 +++++++++ src/auth/index.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/etc/firebase-admin.auth.api.md b/etc/firebase-admin.auth.api.md index f8ae6d897a..3723abd051 100644 --- a/etc/firebase-admin.auth.api.md +++ b/etc/firebase-admin.auth.api.md @@ -238,6 +238,11 @@ export interface EmailIdentifier { email: string; } +// @public +export interface EmailPrivacyConfig { + enableImprovedEmailPrivacy?: boolean; +} + // @public export interface EmailSignInProviderConfig { enabled: boolean; @@ -363,6 +368,7 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo { // @public export class ProjectConfig { + readonly emailPrivacyConfig?: EmailPrivacyConfig; get multiFactorConfig(): MultiFactorConfig | undefined; readonly passwordPolicyConfig?: PasswordPolicyConfig; get recaptchaConfig(): RecaptchaConfig | undefined; @@ -446,6 +452,7 @@ export class Tenant { // (undocumented) readonly anonymousSignInEnabled: boolean; readonly displayName?: string; + readonly emailPrivacyConfig?: EmailPrivacyConfig; get emailSignInConfig(): EmailSignInProviderConfig | undefined; get multiFactorConfig(): MultiFactorConfig | undefined; readonly passwordPolicyConfig?: PasswordPolicyConfig; @@ -500,6 +507,7 @@ export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactor // @public export interface UpdateProjectConfigRequest { + emailPrivacyConfig?: EmailPrivacyConfig; multiFactorConfig?: MultiFactorConfig; passwordPolicyConfig?: PasswordPolicyConfig; recaptchaConfig?: RecaptchaConfig; @@ -524,6 +532,7 @@ export interface UpdateRequest { export interface UpdateTenantRequest { anonymousSignInEnabled?: boolean; displayName?: string; + emailPrivacyConfig?: EmailPrivacyConfig; emailSignInConfig?: EmailSignInProviderConfig; multiFactorConfig?: MultiFactorConfig; passwordPolicyConfig?: PasswordPolicyConfig; diff --git a/src/auth/index.ts b/src/auth/index.ts index 2450dd1adf..a559a706f8 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -100,8 +100,9 @@ export { UpdateRequest, TotpMultiFactorProviderConfig, PasswordPolicyConfig, - PasswordPolicyEnforcementState, + PasswordPolicyEnforcementState, CustomStrengthOptionsConfig, + EmailPrivacyConfig, } from './auth-config'; export { From fb395ece8108c024912afd4bac4a36a2ed2e57ab Mon Sep 17 00:00:00 2001 From: Pragati Date: Thu, 1 Jun 2023 21:54:12 -0700 Subject: [PATCH 5/8] trim whitespace changes --- package-lock.json | 95 +++++++++++++-------------- package.json | 2 +- src/auth/auth-config.ts | 26 ++++---- src/auth/project-config.ts | 7 +- src/auth/tenant.ts | 2 +- test/integration/auth.spec.ts | 4 +- test/unit/auth/project-config.spec.ts | 4 +- test/unit/auth/tenant.spec.ts | 12 ++-- 8 files changed, 73 insertions(+), 79 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85505ba07f..9b43e2d7a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1439,9 +1439,9 @@ } }, "@types/firebase-token-generator": { - "version": "2.0.29", - "resolved": "https://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.29.tgz", - "integrity": "sha512-IHFsMicKhaDYFvJmTokF8wLtIGUZVgh1Tie0jYOcgnwHFT1es+hoj2d+SlVx63q91fRHDP2veTUq1yIkqEOT/A==", + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/@types/firebase-token-generator/-/firebase-token-generator-2.0.30.tgz", + "integrity": "sha512-GcNz25MRki9ZpVfvNNrthx4t3XXjgIZ2wv729ea9F4n/1PZf4QIZlzTGoDTDeV417vmd6cPTYKUzPf4rR+qGhw==", "dev": true }, "@types/glob": { @@ -1455,9 +1455,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, "@types/jsonwebtoken": { @@ -1635,15 +1635,15 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz", - "integrity": "sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz", + "integrity": "sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.7", - "@typescript-eslint/type-utils": "5.59.7", - "@typescript-eslint/utils": "5.59.7", + "@typescript-eslint/scope-manager": "5.59.8", + "@typescript-eslint/type-utils": "5.59.8", + "@typescript-eslint/utils": "5.59.8", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -1708,41 +1708,41 @@ } }, "@typescript-eslint/scope-manager": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.7.tgz", - "integrity": "sha512-FL6hkYWK9zBGdxT2wWEd2W8ocXMu3K94i3gvMrjXpx+koFYdYV7KprKfirpgY34vTGzEPPuKoERpP8kD5h7vZQ==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz", + "integrity": "sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.7", - "@typescript-eslint/visitor-keys": "5.59.7" + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/visitor-keys": "5.59.8" } }, "@typescript-eslint/type-utils": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz", - "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz", + "integrity": "sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.59.7", - "@typescript-eslint/utils": "5.59.7", + "@typescript-eslint/typescript-estree": "5.59.8", + "@typescript-eslint/utils": "5.59.8", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz", - "integrity": "sha512-UnVS2MRRg6p7xOSATscWkKjlf/NDKuqo5TdbWck6rIRZbmKpVNTLALzNvcjIfHBE7736kZOFc/4Z3VcZwuOM/A==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz", + "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.7.tgz", - "integrity": "sha512-4A1NtZ1I3wMN2UGDkU9HMBL+TIQfbrh4uS0WDMMpf3xMRursDbqEf1ahh6vAAe3mObt8k3ZATnezwG4pdtWuUQ==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz", + "integrity": "sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.7", - "@typescript-eslint/visitor-keys": "5.59.7", + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/visitor-keys": "5.59.8", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1751,28 +1751,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz", - "integrity": "sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.8.tgz", + "integrity": "sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.7", - "@typescript-eslint/types": "5.59.7", - "@typescript-eslint/typescript-estree": "5.59.7", + "@typescript-eslint/scope-manager": "5.59.8", + "@typescript-eslint/types": "5.59.8", + "@typescript-eslint/typescript-estree": "5.59.8", "eslint-scope": "^5.1.1", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.59.7", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz", - "integrity": "sha512-tyN+X2jvMslUszIiYbF0ZleP+RqQsFVpGrKI6e0Eet1w8WmhsAtmzaqm8oM8WJQ1ysLwhnsK/4hYHJjOgJVfQQ==", + "version": "5.59.8", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz", + "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.59.7", + "@typescript-eslint/types": "5.59.8", "eslint-visitor-keys": "^3.3.0" } }, @@ -10310,9 +10310,9 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -10334,20 +10334,13 @@ "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true } } }, "yargs-parser": { "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "optional": true + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" }, "yargs-unparser": { "version": "2.0.0", diff --git a/package.json b/package.json index 661bc8c8b8..7aeaed554c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "firebase-admin", - "version": "11.8.0", + "version": "11.9.0", "description": "Firebase admin SDK for Node.js", "author": "Firebase (https://firebase.google.com/)", "license": "Apache-2.0", diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 735acd4cb7..927c199178 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -509,14 +509,14 @@ export interface MultiFactorConfig { factorIds?: AuthFactorType[]; /** - * A list of multi-factor provider configurations. + * A list of multi-factor provider configurations. * MFA providers (except phone) indicate whether they're enabled through this field. */ providerConfigs?: MultiFactorProviderConfig[]; } /** - * Interface representing a multi-factor auth provider configuration. - * This interface is used for second factor auth providers other than SMS. + * Interface representing a multi-factor auth provider configuration. + * This interface is used for second factor auth providers other than SMS. * Currently, only TOTP is supported. */export interface MultiFactorProviderConfig { /** @@ -528,7 +528,7 @@ export interface MultiFactorConfig { } /** - * Interface representing configuration settings for TOTP second factor auth. + * Interface representing configuration settings for TOTP second factor auth. */ export interface TotpMultiFactorProviderConfig { /** @@ -540,7 +540,7 @@ export interface TotpMultiFactorProviderConfig { /** * Defines the multi-factor config class used to convert client side MultiFactorConfig * to a format that is understood by the Auth server. - * + * * @internal */ export class MultiFactorAuthConfig implements MultiFactorConfig { @@ -555,7 +555,7 @@ export class MultiFactorAuthConfig implements MultiFactorConfig { */ public readonly factorIds: AuthFactorType[]; /** - * A list of multi-factor provider specific config. + * A list of multi-factor provider specific config. * New MFA providers (except phone) will indicate enablement/disablement through this field. */ public readonly providerConfigs: MultiFactorProviderConfig[]; @@ -1947,8 +1947,8 @@ export class RecaptchaAuthConfig implements RecaptchaConfig { } } -/** - * A password policy configuration for a project or tenant +/** + * A password policy configuration for a project or tenant */ export interface PasswordPolicyConfig { /** @@ -2003,7 +2003,7 @@ export interface CustomStrengthOptionsConfig { /** * Defines the password policy config class used to convert client side PasswordPolicyConfig * to a format that is understood by the Auth server. - * + * * @internal */ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { @@ -2110,7 +2110,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { '"PasswordPolicyConfig.enforcementState" must be either "ENFORCE" or "OFF".', ); } - + if (typeof options.forceUpgradeOnSignin !== 'undefined') { if (!validator.isBoolean(options.forceUpgradeOnSignin)) { throw new FirebaseAuthError( @@ -2254,7 +2254,7 @@ export class PasswordPolicyAuthConfig implements PasswordPolicyConfig { } } -/** +/** * Server side password policy configuration. */ export interface PasswordPolicyAuthServerConfig { @@ -2264,14 +2264,14 @@ export interface PasswordPolicyAuthServerConfig { } /** - * Server side password policy versions configuration. + * Server side password policy versions configuration. */ export interface PasswordPolicyVersionsAuthServerConfig { customStrengthOptions?: CustomStrengthOptionsAuthServerConfig; } /** - * Server side password policy constraints configuration. + * Server side password policy constraints configuration. */ export interface CustomStrengthOptionsAuthServerConfig { containsLowercaseCharacter?: boolean; diff --git a/src/auth/project-config.ts b/src/auth/project-config.ts index 5468f50fa6..250d6549ac 100644 --- a/src/auth/project-config.ts +++ b/src/auth/project-config.ts @@ -97,7 +97,7 @@ export class ProjectConfig { /** * The project's multi-factor auth configuration. * Supports only phone and TOTP. - */ + */ private readonly multiFactorConfig_?: MultiFactorConfig; /** * The multi-factor auth configuration. @@ -112,6 +112,7 @@ export class ProjectConfig { * {@link https://cloud.google.com/terms/service-terms | Term of Service}. */ private readonly recaptchaConfig_?: RecaptchaAuthConfig; + /** * The password policy configuration for the project */ @@ -201,7 +202,7 @@ export class ProjectConfig { } return request; } - + /** * The reCAPTCHA configuration. */ @@ -219,7 +220,7 @@ export class ProjectConfig { if (typeof response.smsRegionConfig !== 'undefined') { this.smsRegionConfig = response.smsRegionConfig; } - //Backend API returns "mfa" in case of project config and "mfaConfig" in case of tenant config. + //Backend API returns "mfa" in case of project config and "mfaConfig" in case of tenant config. //The SDK exposes it as multiFactorConfig always. if (typeof response.mfa !== 'undefined') { this.multiFactorConfig_ = new MultiFactorAuthConfig(response.mfa); diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 6f30f42e75..436237c19d 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -61,7 +61,7 @@ export interface UpdateTenantRequest { * The SMS configuration to update on the project. */ smsRegionConfig?: SmsRegionConfig; - + /** * The reCAPTCHA configuration to update on the tenant. * By enabling reCAPTCHA Enterprise integration, you are diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 75d5080669..7b113b3156 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -1222,7 +1222,7 @@ describe('admin.auth', () => { minLength: 6, } } - }) + }) }); const mfaSmsEnabledTotpEnabledConfig: MultiFactorConfig = { @@ -2022,7 +2022,7 @@ describe('admin.auth', () => { } return getAuth().tenantManager().updateTenant(createdTenantId, updateRequestNoMfaConfig) }); - + it('updateTenant() should not update tenant reCAPTCHA config is undefined', () => { expectedUpdatedTenant.tenantId = createdTenantId; const updatedOptions2: UpdateTenantRequest = { diff --git a/test/unit/auth/project-config.spec.ts b/test/unit/auth/project-config.spec.ts index f85acb841e..5934dd15fd 100644 --- a/test/unit/auth/project-config.spec.ts +++ b/test/unit/auth/project-config.spec.ts @@ -322,7 +322,7 @@ describe('ProjectConfig', () => { ProjectConfig.buildServerRequest(tenantOptionsClientRequest); }).to.throw('"PasswordPolicyConfig.constraints" must be defined.'); }); - + it('should throw on invalid constraints attribute', ()=> { const tenantOptionsClientRequest = deepCopy(updateProjectConfigRequest1) as any; tenantOptionsClientRequest.passwordPolicyConfig.constraints.invalidParameter = 'invalid'; @@ -404,7 +404,7 @@ describe('ProjectConfig', () => { tenantOptionsClientRequest.passwordPolicyConfig.constraints.minLength = 45; expect(() => { ProjectConfig.buildServerRequest(tenantOptionsClientRequest); - }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + + }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.'); }); diff --git a/test/unit/auth/tenant.spec.ts b/test/unit/auth/tenant.spec.ts index e726514979..e1006e47b7 100644 --- a/test/unit/auth/tenant.spec.ts +++ b/test/unit/auth/tenant.spec.ts @@ -385,7 +385,7 @@ describe('Tenant', () => { it('should throw on non-array disallowedRegions attribute', () => { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; - tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; + tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); }).to.throw('"SmsRegionConfig.allowByDefault.disallowedRegions" must be a valid string array.'); @@ -455,7 +455,7 @@ describe('Tenant', () => { Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); }).to.throw('"PasswordPolicyConfig.constraints" must be defined.'); }); - + it('should throw on invalid constraints attribute', ()=> { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; tenantOptionsClientRequest.passwordPolicyConfig.constraints.invalidParameter = 'invalid'; @@ -537,7 +537,7 @@ describe('Tenant', () => { tenantOptionsClientRequest.passwordPolicyConfig.constraints.minLength = 45; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, !createRequest); - }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + + }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.'); }); @@ -780,7 +780,7 @@ describe('Tenant', () => { it('should throw on non-array disallowedRegions attribute', () => { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; - tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; + tenantOptionsClientRequest.smsRegionConfig.allowByDefault.disallowedRegions = 'non-array'; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); }).to.throw('"SmsRegionConfig.allowByDefault.disallowedRegions" must be a valid string array.'); @@ -850,7 +850,7 @@ describe('Tenant', () => { Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); }).to.throw('"PasswordPolicyConfig.constraints" must be defined.'); }); - + it('should throw on invalid constraints attribute', ()=> { const tenantOptionsClientRequest = deepCopy(clientRequest) as any; tenantOptionsClientRequest.passwordPolicyConfig.constraints.invalidParameter = 'invalid'; @@ -932,7 +932,7 @@ describe('Tenant', () => { tenantOptionsClientRequest.passwordPolicyConfig.constraints.minLength = 45; expect(() => { Tenant.buildServerRequest(tenantOptionsClientRequest, createRequest); - }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + + }).to.throw('"PasswordPolicyConfig.constraints.minLength"' + ' must be an integer between 6 and 30, inclusive.'); }); From 3664cea8226662f7d9a94faf307ef6035f431ee8 Mon Sep 17 00:00:00 2001 From: pragatimodi <110490169+pragatimodi@users.noreply.github.com> Date: Mon, 5 Jun 2023 09:35:18 -0700 Subject: [PATCH 6/8] Fix typo : Update auth-config.ts --- src/auth/auth-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 927c199178..937dd0c192 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -2287,7 +2287,7 @@ export interface CustomStrengthOptionsAuthServerConfig { */ export interface EmailPrivacyConfig { /** - * Variable indiciating email privacy enabled of not. + * Variable indicating email privacy enabled of not. */ enableImprovedEmailPrivacy?: boolean; } From e58e11706481207e18865ae9592072d7b2e3a928 Mon Sep 17 00:00:00 2001 From: pragatimodi <110490169+pragatimodi@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:10:37 +0000 Subject: [PATCH 7/8] Addressing feedback --- src/auth/tenant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/tenant.ts b/src/auth/tenant.ts index 6f30f42e75..223caac9f4 100644 --- a/src/auth/tenant.ts +++ b/src/auth/tenant.ts @@ -314,7 +314,7 @@ export class Tenant { PasswordPolicyAuthConfig.buildServerRequest(request.passwordPolicyConfig); } // Validate Email Privacy Config if provided. - if (typeof request.emailPrivacyConfig != 'undefined') { + if (typeof request.emailPrivacyConfig !== 'undefined') { EmailPrivacyAuthConfig.validate(request.emailPrivacyConfig); } } From d885be7b0ceccdfaf509bfc85941a5b6566c1083 Mon Sep 17 00:00:00 2001 From: pragatimodi <110490169+pragatimodi@users.noreply.github.com> Date: Tue, 6 Jun 2023 18:50:05 -0700 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Kevin Cheung --- src/auth/auth-config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 937dd0c192..565109c7fd 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -2283,11 +2283,11 @@ export interface CustomStrengthOptionsAuthServerConfig { } /** - * The configuration for the email privacy on the project or tenant. -*/ + * The email privacy configuration of a project or tenant. + */ export interface EmailPrivacyConfig { /** - * Variable indicating email privacy enabled of not. + * Whether enhanced email privacy is enabled. */ enableImprovedEmailPrivacy?: boolean; }