Skip to content

Commit 82db6c2

Browse files
fix(security): make isEmailAllowed case-insensitive; normalize email at client gates
1 parent daebe62 commit 82db6c2

3 files changed

Lines changed: 16 additions & 10 deletions

File tree

apps/sim/app/f/[token]/public-file-email-auth.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function PublicFileEmailAuth({ token }: PublicFileEmailAuthProps) {
4343
}
4444
setError(null)
4545
try {
46-
await requestOtp.mutateAsync({ email })
46+
await requestOtp.mutateAsync({ email: email.trim().toLowerCase() })
4747
setSent(true)
4848
setOtp('')
4949
} catch (err) {
@@ -55,7 +55,7 @@ export function PublicFileEmailAuth({ token }: PublicFileEmailAuthProps) {
5555
if (code.length !== 6) return
5656
setError(null)
5757
try {
58-
await verifyOtp.mutateAsync({ email, otp: code })
58+
await verifyOtp.mutateAsync({ email: email.trim().toLowerCase(), otp: code })
5959
router.refresh()
6060
} catch (err) {
6161
setError(getErrorMessage(err, 'Invalid verification code'))
@@ -65,7 +65,7 @@ export function PublicFileEmailAuth({ token }: PublicFileEmailAuthProps) {
6565
const resend = async () => {
6666
setCountdown(30)
6767
try {
68-
await requestOtp.mutateAsync({ email })
68+
await requestOtp.mutateAsync({ email: email.trim().toLowerCase() })
6969
setOtp('')
7070
setError(null)
7171
} catch (err) {

apps/sim/app/f/[token]/public-file-sso-auth.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ export function PublicFileSSOAuth({ token }: PublicFileSSOAuthProps) {
3434
setError(null)
3535
setIsLoading(true)
3636
try {
37+
const normalizedEmail = email.trim().toLowerCase()
3738
const { eligible } = await requestJson(publicFileSSOContract, {
3839
params: { token },
39-
body: { email },
40+
body: { email: normalizedEmail },
4041
})
4142
if (!eligible) {
4243
setError('Email not authorized for this file.')
@@ -45,7 +46,7 @@ export function PublicFileSSOAuth({ token }: PublicFileSSOAuthProps) {
4546
}
4647
const callbackUrl = `/f/${token}`
4748
router.push(
48-
`/sso?email=${encodeURIComponent(email)}&callbackUrl=${encodeURIComponent(callbackUrl)}`
49+
`/sso?email=${encodeURIComponent(normalizedEmail)}&callbackUrl=${encodeURIComponent(callbackUrl)}`
4950
)
5051
} catch (err) {
5152
setError(getErrorMessage(err, 'Email not authorized for this file.'))

apps/sim/lib/core/security/deployment.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,22 @@ export function setDeploymentAuthCookie(
103103
}
104104

105105
/**
106-
* Checks if an email matches the allowed emails list (exact match or domain match)
106+
* Checks if an email matches the allowed emails list (exact match or domain
107+
* match). Case-insensitive — email addresses are compared lowercased on both
108+
* sides, so callers don't need to normalize before calling.
107109
*/
108110
export function isEmailAllowed(email: string, allowedEmails: string[]): boolean {
109-
if (allowedEmails.includes(email)) {
111+
const normalizedEmail = email.trim().toLowerCase()
112+
const normalizedAllowed = allowedEmails.map((allowed) => allowed.trim().toLowerCase())
113+
114+
if (normalizedAllowed.includes(normalizedEmail)) {
110115
return true
111116
}
112117

113-
const atIndex = email.indexOf('@')
118+
const atIndex = normalizedEmail.indexOf('@')
114119
if (atIndex > 0) {
115-
const domain = email.substring(atIndex + 1)
116-
if (domain && allowedEmails.some((allowed: string) => allowed === `@${domain}`)) {
120+
const domain = normalizedEmail.substring(atIndex + 1)
121+
if (domain && normalizedAllowed.some((allowed) => allowed === `@${domain}`)) {
117122
return true
118123
}
119124
}

0 commit comments

Comments
 (0)