Skip to content

Commit a4019e4

Browse files
feat(auth): Add Email Privacy support in Project and Tenant config (#2198)
* EmailPrivacy Config definition * Adding unit tests * Adding integration tests * api-extractor changes * trim whitespace changes * Fix typo : Update auth-config.ts * Addressing feedback * Apply suggestions from code review Co-authored-by: Kevin Cheung <[email protected]> --------- Co-authored-by: Kevin Cheung <[email protected]>
1 parent a4ce27d commit a4019e4

8 files changed

+258
-11
lines changed

etc/firebase-admin.auth.api.md

+9
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,11 @@ export interface EmailIdentifier {
238238
email: string;
239239
}
240240

241+
// @public
242+
export interface EmailPrivacyConfig {
243+
enableImprovedEmailPrivacy?: boolean;
244+
}
245+
241246
// @public
242247
export interface EmailSignInProviderConfig {
243248
enabled: boolean;
@@ -363,6 +368,7 @@ export class PhoneMultiFactorInfo extends MultiFactorInfo {
363368

364369
// @public
365370
export class ProjectConfig {
371+
readonly emailPrivacyConfig?: EmailPrivacyConfig;
366372
get multiFactorConfig(): MultiFactorConfig | undefined;
367373
readonly passwordPolicyConfig?: PasswordPolicyConfig;
368374
get recaptchaConfig(): RecaptchaConfig | undefined;
@@ -446,6 +452,7 @@ export class Tenant {
446452
// (undocumented)
447453
readonly anonymousSignInEnabled: boolean;
448454
readonly displayName?: string;
455+
readonly emailPrivacyConfig?: EmailPrivacyConfig;
449456
get emailSignInConfig(): EmailSignInProviderConfig | undefined;
450457
get multiFactorConfig(): MultiFactorConfig | undefined;
451458
readonly passwordPolicyConfig?: PasswordPolicyConfig;
@@ -500,6 +507,7 @@ export interface UpdatePhoneMultiFactorInfoRequest extends BaseUpdateMultiFactor
500507

501508
// @public
502509
export interface UpdateProjectConfigRequest {
510+
emailPrivacyConfig?: EmailPrivacyConfig;
503511
multiFactorConfig?: MultiFactorConfig;
504512
passwordPolicyConfig?: PasswordPolicyConfig;
505513
recaptchaConfig?: RecaptchaConfig;
@@ -524,6 +532,7 @@ export interface UpdateRequest {
524532
export interface UpdateTenantRequest {
525533
anonymousSignInEnabled?: boolean;
526534
displayName?: string;
535+
emailPrivacyConfig?: EmailPrivacyConfig;
527536
emailSignInConfig?: EmailSignInProviderConfig;
528537
multiFactorConfig?: MultiFactorConfig;
529538
passwordPolicyConfig?: PasswordPolicyConfig;

src/auth/auth-config.ts

+47
Original file line numberDiff line numberDiff line change
@@ -2279,3 +2279,50 @@ export interface CustomStrengthOptionsAuthServerConfig {
22792279
minPasswordLength?: number;
22802280
maxPasswordLength?: number;
22812281
}
2282+
2283+
/**
2284+
* The email privacy configuration of a project or tenant.
2285+
*/
2286+
export interface EmailPrivacyConfig {
2287+
/**
2288+
* Whether enhanced email privacy is enabled.
2289+
*/
2290+
enableImprovedEmailPrivacy?: boolean;
2291+
}
2292+
2293+
/**
2294+
* Defines the EmailPrivacyAuthConfig class used for validation.
2295+
*
2296+
* @internal
2297+
*/
2298+
export class EmailPrivacyAuthConfig {
2299+
public static validate(options: EmailPrivacyConfig): void {
2300+
if (!validator.isNonNullObject(options)) {
2301+
throw new FirebaseAuthError(
2302+
AuthClientErrorCode.INVALID_CONFIG,
2303+
'"EmailPrivacyConfig" must be a non-null object.',
2304+
);
2305+
}
2306+
2307+
const validKeys = {
2308+
enableImprovedEmailPrivacy: true,
2309+
};
2310+
2311+
for (const key in options) {
2312+
if (!(key in validKeys)) {
2313+
throw new FirebaseAuthError(
2314+
AuthClientErrorCode.INVALID_CONFIG,
2315+
`"${key}" is not a valid "EmailPrivacyConfig" parameter.`,
2316+
);
2317+
}
2318+
}
2319+
2320+
if (typeof options.enableImprovedEmailPrivacy !== 'undefined'
2321+
&& !validator.isBoolean(options.enableImprovedEmailPrivacy)) {
2322+
throw new FirebaseAuthError(
2323+
AuthClientErrorCode.INVALID_CONFIG,
2324+
'"EmailPrivacyConfig.enableImprovedEmailPrivacy" must be a valid boolean value.',
2325+
);
2326+
}
2327+
}
2328+
}

src/auth/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ export {
100100
UpdateRequest,
101101
TotpMultiFactorProviderConfig,
102102
PasswordPolicyConfig,
103-
PasswordPolicyEnforcementState,
103+
PasswordPolicyEnforcementState,
104104
CustomStrengthOptionsConfig,
105+
EmailPrivacyConfig,
105106
} from './auth-config';
106107

107108
export {

src/auth/project-config.ts

+34-7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
PasswordPolicyAuthConfig,
2727
PasswordPolicyAuthServerConfig,
2828
PasswordPolicyConfig,
29+
EmailPrivacyConfig,
30+
EmailPrivacyAuthConfig,
2931
} from './auth-config';
3032
import { deepCopy } from '../utils/deep-copy';
3133

@@ -53,6 +55,10 @@ export interface UpdateProjectConfigRequest {
5355
* The password policy configuration to update on the project
5456
*/
5557
passwordPolicyConfig?: PasswordPolicyConfig;
58+
/**
59+
* The email privacy configuration to update on the project
60+
*/
61+
emailPrivacyConfig?: EmailPrivacyConfig;
5662
}
5763

5864
/**
@@ -63,6 +69,7 @@ export interface ProjectConfigServerResponse {
6369
mfa?: MultiFactorAuthServerConfig;
6470
recaptchaConfig?: RecaptchaConfig;
6571
passwordPolicyConfig?: PasswordPolicyAuthServerConfig;
72+
emailPrivacyConfig?: EmailPrivacyConfig;
6673
}
6774

6875
/**
@@ -73,6 +80,7 @@ export interface ProjectConfigClientRequest {
7380
mfa?: MultiFactorAuthServerConfig;
7481
recaptchaConfig?: RecaptchaConfig;
7582
passwordPolicyConfig?: PasswordPolicyAuthServerConfig;
83+
emailPrivacyConfig?: EmailPrivacyConfig;
7684
}
7785

7886
/**
@@ -91,7 +99,12 @@ export class ProjectConfig {
9199
* Supports only phone and TOTP.
92100
*/
93101
private readonly multiFactorConfig_?: MultiFactorConfig;
94-
102+
/**
103+
* The multi-factor auth configuration.
104+
*/
105+
get multiFactorConfig(): MultiFactorConfig | undefined {
106+
return this.multiFactorConfig_;
107+
}
95108
/**
96109
* The reCAPTCHA configuration to update on the project.
97110
* By enabling reCAPTCHA Enterprise integration, you are
@@ -100,16 +113,14 @@ export class ProjectConfig {
100113
*/
101114
private readonly recaptchaConfig_?: RecaptchaAuthConfig;
102115

103-
/**
104-
* The multi-factor auth configuration.
105-
*/
106-
get multiFactorConfig(): MultiFactorConfig | undefined {
107-
return this.multiFactorConfig_;
108-
}
109116
/**
110117
* The password policy configuration for the project
111118
*/
112119
public readonly passwordPolicyConfig?: PasswordPolicyConfig;
120+
/**
121+
* The email privacy configuration for the project
122+
*/
123+
public readonly emailPrivacyConfig?: EmailPrivacyConfig;
113124

114125
/**
115126
* Validates a project config options object. Throws an error on failure.
@@ -128,6 +139,7 @@ export class ProjectConfig {
128139
multiFactorConfig: true,
129140
recaptchaConfig: true,
130141
passwordPolicyConfig: true,
142+
emailPrivacyConfig: true,
131143
}
132144
// Check for unsupported top level attributes.
133145
for (const key in request) {
@@ -156,6 +168,11 @@ export class ProjectConfig {
156168
if (typeof request.passwordPolicyConfig !== 'undefined') {
157169
PasswordPolicyAuthConfig.validate(request.passwordPolicyConfig);
158170
}
171+
172+
// Validate Email Privacy Config if provided.
173+
if (typeof request.emailPrivacyConfig !== 'undefined') {
174+
EmailPrivacyAuthConfig.validate(request.emailPrivacyConfig);
175+
}
159176
}
160177

161178
/**
@@ -180,6 +197,9 @@ export class ProjectConfig {
180197
if (typeof configOptions.passwordPolicyConfig !== 'undefined') {
181198
request.passwordPolicyConfig = PasswordPolicyAuthConfig.buildServerRequest(configOptions.passwordPolicyConfig);
182199
}
200+
if (typeof configOptions.emailPrivacyConfig !== 'undefined') {
201+
request.emailPrivacyConfig = configOptions.emailPrivacyConfig;
202+
}
183203
return request;
184204
}
185205

@@ -211,6 +231,9 @@ export class ProjectConfig {
211231
if (typeof response.passwordPolicyConfig !== 'undefined') {
212232
this.passwordPolicyConfig = new PasswordPolicyAuthConfig(response.passwordPolicyConfig);
213233
}
234+
if (typeof response.emailPrivacyConfig !== 'undefined') {
235+
this.emailPrivacyConfig = response.emailPrivacyConfig;
236+
}
214237
}
215238
/**
216239
* Returns a JSON-serializable representation of this object.
@@ -224,6 +247,7 @@ export class ProjectConfig {
224247
multiFactorConfig: deepCopy(this.multiFactorConfig),
225248
recaptchaConfig: this.recaptchaConfig_?.toJSON(),
226249
passwordPolicyConfig: deepCopy(this.passwordPolicyConfig),
250+
emailPrivacyConfig: deepCopy(this.emailPrivacyConfig),
227251
};
228252
if (typeof json.smsRegionConfig === 'undefined') {
229253
delete json.smsRegionConfig;
@@ -237,6 +261,9 @@ export class ProjectConfig {
237261
if (typeof json.passwordPolicyConfig === 'undefined') {
238262
delete json.passwordPolicyConfig;
239263
}
264+
if (typeof json.emailPrivacyConfig === 'undefined') {
265+
delete json.emailPrivacyConfig;
266+
}
240267
return json;
241268
}
242269
}

src/auth/tenant.ts

+27-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import {
2222
EmailSignInConfig, EmailSignInConfigServerRequest, MultiFactorAuthServerConfig,
2323
MultiFactorConfig, validateTestPhoneNumbers, EmailSignInProviderConfig,
2424
MultiFactorAuthConfig, SmsRegionConfig, SmsRegionsAuthConfig, RecaptchaAuthConfig, RecaptchaConfig,
25-
PasswordPolicyConfig,
26-
PasswordPolicyAuthConfig, PasswordPolicyAuthServerConfig,
25+
PasswordPolicyConfig,
26+
PasswordPolicyAuthConfig, PasswordPolicyAuthServerConfig, EmailPrivacyConfig, EmailPrivacyAuthConfig,
2727
} from './auth-config';
2828

2929
/**
@@ -73,6 +73,10 @@ export interface UpdateTenantRequest {
7373
* The password policy configuration for the tenant
7474
*/
7575
passwordPolicyConfig?: PasswordPolicyConfig;
76+
/**
77+
* The email privacy configuration for the tenant
78+
*/
79+
emailPrivacyConfig?: EmailPrivacyConfig;
7680
}
7781

7882
/**
@@ -90,6 +94,7 @@ export interface TenantOptionsServerRequest extends EmailSignInConfigServerReque
9094
smsRegionConfig?: SmsRegionConfig;
9195
recaptchaConfig?: RecaptchaConfig;
9296
passwordPolicyConfig?: PasswordPolicyAuthServerConfig;
97+
emailPrivacyConfig?: EmailPrivacyConfig;
9398
}
9499

95100
/** The tenant server response interface. */
@@ -104,6 +109,7 @@ export interface TenantServerResponse {
104109
smsRegionConfig?: SmsRegionConfig;
105110
recaptchaConfig? : RecaptchaConfig;
106111
passwordPolicyConfig?: PasswordPolicyAuthServerConfig;
112+
emailPrivacyConfig?: EmailPrivacyConfig;
107113
}
108114

109115
/**
@@ -165,6 +171,10 @@ export class Tenant {
165171
* The password policy configuration for the tenant
166172
*/
167173
public readonly passwordPolicyConfig?: PasswordPolicyConfig;
174+
/**
175+
* The email privacy configuration for the tenant
176+
*/
177+
public readonly emailPrivacyConfig?: EmailPrivacyConfig;
168178

169179
/**
170180
* Builds the corresponding server request for a TenantOptions object.
@@ -204,6 +214,9 @@ export class Tenant {
204214
if (typeof tenantOptions.passwordPolicyConfig !== 'undefined') {
205215
request.passwordPolicyConfig = PasswordPolicyAuthConfig.buildServerRequest(tenantOptions.passwordPolicyConfig);
206216
}
217+
if (typeof tenantOptions.emailPrivacyConfig !== 'undefined') {
218+
request.emailPrivacyConfig = tenantOptions.emailPrivacyConfig;
219+
}
207220
return request;
208221
}
209222

@@ -240,6 +253,7 @@ export class Tenant {
240253
smsRegionConfig: true,
241254
recaptchaConfig: true,
242255
passwordPolicyConfig: true,
256+
emailPrivacyConfig: true,
243257
};
244258
const label = createRequest ? 'CreateTenantRequest' : 'UpdateTenantRequest';
245259
if (!validator.isNonNullObject(request)) {
@@ -299,6 +313,10 @@ export class Tenant {
299313
// This will throw an error if invalid.
300314
PasswordPolicyAuthConfig.buildServerRequest(request.passwordPolicyConfig);
301315
}
316+
// Validate Email Privacy Config if provided.
317+
if (typeof request.emailPrivacyConfig !== 'undefined') {
318+
EmailPrivacyAuthConfig.validate(request.emailPrivacyConfig);
319+
}
302320
}
303321

304322
/**
@@ -342,6 +360,9 @@ export class Tenant {
342360
if (typeof response.passwordPolicyConfig !== 'undefined') {
343361
this.passwordPolicyConfig = new PasswordPolicyAuthConfig(response.passwordPolicyConfig);
344362
}
363+
if (typeof response.emailPrivacyConfig !== 'undefined') {
364+
this.emailPrivacyConfig = deepCopy(response.emailPrivacyConfig);
365+
}
345366
}
346367

347368
/**
@@ -381,6 +402,7 @@ export class Tenant {
381402
smsRegionConfig: deepCopy(this.smsRegionConfig),
382403
recaptchaConfig: this.recaptchaConfig_?.toJSON(),
383404
passwordPolicyConfig: deepCopy(this.passwordPolicyConfig),
405+
emailPrivacyConfig: deepCopy(this.emailPrivacyConfig),
384406
};
385407
if (typeof json.multiFactorConfig === 'undefined') {
386408
delete json.multiFactorConfig;
@@ -397,6 +419,9 @@ export class Tenant {
397419
if (typeof json.passwordPolicyConfig === 'undefined') {
398420
delete json.passwordPolicyConfig;
399421
}
422+
if (typeof json.emailPrivacyConfig === 'undefined') {
423+
delete json.emailPrivacyConfig;
424+
}
400425
return json;
401426
}
402427
}

0 commit comments

Comments
 (0)