Skip to content

Commit ec17863

Browse files
authored
feat(core): add email blocklist policy paywall guard (#7377)
add email blocklist policy payeall guard
1 parent 9d8914e commit ec17863

File tree

4 files changed

+77
-10
lines changed

4 files changed

+77
-10
lines changed

packages/core/src/libraries/sign-in-experience/email-blocklist-policy.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import RequestError from '#src/errors/RequestError/index.js';
66
import {
77
parseEmailBlocklistPolicy,
88
validateEmailAgainstBlocklistPolicy,
9+
isEmailBlocklistPolicyEnabled,
910
} from './email-blocklist-policy.js';
1011

1112
const invalidCustomBlockList = ['bar', 'bar@foo', '@foo', '@foo.', 'bar@foo.'];
1213
const validCustomBlockList = ['[email protected]', '@foo.com', '[email protected]', '[email protected]'];
1314

14-
describe('validateEmailBlocklistPolicy', () => {
15+
describe('parseEmailBlocklistPolicy', () => {
1516
it.each(invalidCustomBlockList)(
1617
'should throw error for invalid custom block list item: %s',
1718
(item) => {
@@ -88,3 +89,44 @@ describe('validateEmailAgainstBlocklistPolicy', () => {
8889
).resolves.not.toThrow();
8990
});
9091
});
92+
93+
describe('isEmailBlocklistPolicyEnabled', () => {
94+
it('isEmailBlocklistPolicyEnabled should return true if any of the blocklist policies are enabled', () => {
95+
const emailBlocklistPolicy: EmailBlocklistPolicy = {
96+
blockDisposableAddresses: false,
97+
blockSubaddressing: false,
98+
customBlocklist: [],
99+
};
100+
101+
expect(
102+
isEmailBlocklistPolicyEnabled({
103+
...emailBlocklistPolicy,
104+
blockDisposableAddresses: true,
105+
})
106+
).toBe(true);
107+
108+
expect(
109+
isEmailBlocklistPolicyEnabled({
110+
...emailBlocklistPolicy,
111+
blockSubaddressing: true,
112+
})
113+
).toBe(true);
114+
115+
expect(
116+
isEmailBlocklistPolicyEnabled({
117+
...emailBlocklistPolicy,
118+
customBlocklist: ['@bar.com'],
119+
})
120+
).toBe(true);
121+
});
122+
123+
it('isEmailBlocklistPolicyEnabled should return false if all blocklist policies are disabled', () => {
124+
const emailBlocklistPolicy: EmailBlocklistPolicy = {
125+
blockDisposableAddresses: false,
126+
blockSubaddressing: false,
127+
customBlocklist: [],
128+
};
129+
130+
expect(isEmailBlocklistPolicyEnabled(emailBlocklistPolicy)).toBe(false);
131+
});
132+
});

packages/core/src/libraries/sign-in-experience/email-blocklist-policy.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,15 @@ export const validateEmailAgainstBlocklistPolicy = async (
181181
);
182182
}
183183
};
184+
185+
export const isEmailBlocklistPolicyEnabled = (emailBlockListPolicy: EmailBlocklistPolicy) => {
186+
const { blockDisposableAddresses, blockSubaddressing, customBlocklist } = emailBlockListPolicy;
187+
188+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
189+
return (
190+
blockDisposableAddresses ||
191+
blockSubaddressing ||
192+
(customBlocklist && customBlocklist.length > 0)
193+
);
194+
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
195+
};

packages/core/src/queries/tenant-usage/index.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,12 +374,18 @@ export default class TenantUsageQuery {
374374
] => {
375375
return [
376376
sql`
377-
select exists (
378-
select * from ${signInExperienceTable}
379-
where ${signInExperienceFields.captchaPolicy}->>'enabled' = 'true'
380-
or ${signInExperienceFields.sentinelPolicy}->>'maxAttempts' is not null
381-
or ${signInExperienceFields.sentinelPolicy}->>'lockoutDuration' is not null
382-
)
377+
select
378+
CASE
379+
WHEN ${signInExperienceFields.captchaPolicy}->>'enabled' = 'true' THEN true
380+
WHEN ${signInExperienceFields.sentinelPolicy}->>'maxAttempts' is not null THEN true
381+
WHEN ${signInExperienceFields.sentinelPolicy}->>'lockoutDuration' is not null THEN true
382+
WHEN ${signInExperienceFields.emailBlocklistPolicy} ->> 'blockDisposableAddresses' = 'true' THEN true
383+
WHEN ${signInExperienceFields.emailBlocklistPolicy} ->> 'blockSubaddressing' = 'true' THEN true
384+
WHEN ${signInExperienceFields.emailBlocklistPolicy} ->> 'customBlocklist' is not null
385+
AND jsonb_array_length(${signInExperienceFields.emailBlocklistPolicy} -> 'customBlocklist') > 0 THEN true
386+
ELSE false
387+
END as isSecurityFeaturesEnabled
388+
from ${signInExperienceTable}
383389
`,
384390
sql`securityFeaturesEnabled`,
385391
];

packages/core/src/routes/sign-in-experience/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
validateSignUp,
99
validateSignIn,
1010
parseEmailBlocklistPolicy,
11+
isEmailBlocklistPolicyEnabled,
1112
} from '#src/libraries/sign-in-experience/index.js';
1213
import { validateMfa } from '#src/libraries/sign-in-experience/mfa.js';
1314
import koaGuard from '#src/middleware/koa-guard.js';
@@ -115,13 +116,19 @@ export default function signInExperiencesRoutes<T extends ManagementApiRouter>(
115116
validateMfa(mfa);
116117
}
117118

119+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
118120
// Guard the quota for the security features enabled. Guarded properties are:
119121
// - sentinelPolicy: if sentinelPolicy is not empty object, security features are guarded
120122
// - captchaPolicy: if captchaPolicy is enabled, security features are guarded
121-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
122-
if ((sentinelPolicy && Object.keys(sentinelPolicy).length > 0) || captchaPolicy?.enabled) {
123+
// - emailBlocklistPolicy: if any of the blocklist policies are enabled, security features are guarded
124+
if (
125+
(sentinelPolicy && Object.keys(sentinelPolicy).length > 0) ||
126+
(emailBlocklistPolicy && isEmailBlocklistPolicyEnabled(emailBlocklistPolicy)) ||
127+
captchaPolicy?.enabled
128+
) {
123129
await quota.guardTenantUsageByKey('securityFeaturesEnabled');
124130
}
131+
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
125132

126133
if (removeUnusedDemoSocialConnector && filteredSocialSignInConnectorTargets) {
127134
// Remove unused demo social connectors, those that are not selected in onboarding SIE config.
@@ -156,7 +163,7 @@ export default function signInExperiencesRoutes<T extends ManagementApiRouter>(
156163

157164
void quota.reportSubscriptionUpdatesUsage('mfaEnabled');
158165

159-
if (sentinelPolicy ?? captchaPolicy) {
166+
if (sentinelPolicy ?? captchaPolicy ?? emailBlocklistPolicy) {
160167
void quota.reportSubscriptionUpdatesUsage('securityFeaturesEnabled');
161168
}
162169

0 commit comments

Comments
 (0)