Skip to content

Commit 900f86e

Browse files
committed
improvement(auth): dedupe denylist entries, extract isEmailInDenylist with tests
1 parent d63166f commit 900f86e

2 files changed

Lines changed: 64 additions & 6 deletions

File tree

apps/sim/lib/auth/auth.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { isEmailInDenylist } from '@/lib/auth/auth'
6+
7+
describe('isEmailInDenylist', () => {
8+
it('returns false when denylist is null, empty, or email is missing', () => {
9+
expect(isEmailInDenylist('a@example.com', null)).toBe(false)
10+
expect(isEmailInDenylist('a@example.com', [])).toBe(false)
11+
expect(isEmailInDenylist(null, ['example.com'])).toBe(false)
12+
expect(isEmailInDenylist(undefined, ['example.com'])).toBe(false)
13+
expect(isEmailInDenylist('', ['example.com'])).toBe(false)
14+
})
15+
16+
it('returns false when email has no @', () => {
17+
expect(isEmailInDenylist('not-an-email', ['example.com'])).toBe(false)
18+
})
19+
20+
it('matches exact domain', () => {
21+
expect(isEmailInDenylist('user@dpdns.org', ['dpdns.org'])).toBe(true)
22+
expect(isEmailInDenylist('user@DPDNS.ORG', ['dpdns.org'])).toBe(true)
23+
})
24+
25+
it('matches arbitrary-depth subdomains of a listed parent zone', () => {
26+
expect(isEmailInDenylist('user@xx.lucky04.dpdns.org', ['dpdns.org'])).toBe(true)
27+
expect(isEmailInDenylist('user@a.b.c.qzz.io', ['qzz.io'])).toBe(true)
28+
})
29+
30+
it('does not match look-alike domains', () => {
31+
expect(isEmailInDenylist('user@xdpdns.org', ['dpdns.org'])).toBe(false)
32+
expect(isEmailInDenylist('user@notdpdns.org', ['dpdns.org'])).toBe(false)
33+
})
34+
35+
it('does not match disallowed domains', () => {
36+
expect(isEmailInDenylist('user@gmail.com', ['dpdns.org', 'qzz.io'])).toBe(false)
37+
expect(isEmailInDenylist('user@example.com', ['dpdns.org'])).toBe(false)
38+
})
39+
40+
it('handles multiple denylist entries', () => {
41+
const denylist = ['dpdns.org', 'qzz.io', 'cc.cd']
42+
expect(isEmailInDenylist('user@foo.dpdns.org', denylist)).toBe(true)
43+
expect(isEmailInDenylist('user@bar.qzz.io', denylist)).toBe(true)
44+
expect(isEmailInDenylist('user@baz.cc.cd', denylist)).toBe(true)
45+
expect(isEmailInDenylist('user@example.com', denylist)).toBe(false)
46+
})
47+
})

apps/sim/lib/auth/auth.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,27 @@ function getMicrosoftUserInfoFromIdToken(tokens: { accessToken?: string }, provi
143143
}
144144

145145
const blockedSignupDomains = env.BLOCKED_SIGNUP_DOMAINS
146-
? env.BLOCKED_SIGNUP_DOMAINS.split(',')
147-
.map((d) => d.trim().toLowerCase())
148-
.filter(Boolean)
146+
? Array.from(
147+
new Set(
148+
env.BLOCKED_SIGNUP_DOMAINS.split(',')
149+
.map((d) => d.trim().toLowerCase())
150+
.filter(Boolean)
151+
)
152+
)
149153
: null
150154

151-
function isSignupEmailBlocked(email: string | undefined | null): boolean {
152-
if (!blockedSignupDomains || !email) return false
155+
export function isEmailInDenylist(
156+
email: string | undefined | null,
157+
denylist: readonly string[] | null
158+
): boolean {
159+
if (!denylist || denylist.length === 0 || !email) return false
153160
const domain = email.split('@')[1]?.toLowerCase()
154161
if (!domain) return false
155-
return blockedSignupDomains.some((entry) => domain === entry || domain.endsWith(`.${entry}`))
162+
return denylist.some((entry) => domain === entry || domain.endsWith(`.${entry}`))
163+
}
164+
165+
function isSignupEmailBlocked(email: string | undefined | null): boolean {
166+
return isEmailInDenylist(email, blockedSignupDomains)
156167
}
157168

158169
const additionalTrustedOrigins = parseOriginList(env.TRUSTED_ORIGINS, (value) =>

0 commit comments

Comments
 (0)