@@ -69,7 +69,9 @@ export const POST = withRouteHandler(
6969 const parsed = await parseRequest ( requestPublicFileOtpContract , request , context )
7070 if ( ! parsed . success ) return parsed . response
7171 const { token } = parsed . data . params
72- const { email } = parsed . data . body
72+ // Normalize once so allow-list matching, OTP storage, and the verify lookup
73+ // all key off the same value (allow-list entries are stored lowercase).
74+ const email = parsed . data . body . email . trim ( ) . toLowerCase ( )
7375
7476 const resolved = await resolveActiveShareByToken ( token )
7577 if ( ! resolved ) {
@@ -87,7 +89,7 @@ export const POST = withRouteHandler(
8789 }
8890
8991 const emailRateLimit = await rateLimiter . checkRateLimitDirect (
90- `file-otp:email:${ resolved . share . id } :${ email . toLowerCase ( ) } ` ,
92+ `file-otp:email:${ resolved . share . id } :${ email } ` ,
9193 OTP_EMAIL_RATE_LIMIT
9294 )
9395 if ( ! emailRateLimit . allowed ) {
@@ -130,7 +132,8 @@ export const PUT = withRouteHandler(
130132 const parsed = await parseRequest ( verifyPublicFileOtpContract , request , context )
131133 if ( ! parsed . success ) return parsed . response
132134 const { token } = parsed . data . params
133- const { email, otp } = parsed . data . body
135+ const { otp } = parsed . data . body
136+ const email = parsed . data . body . email . trim ( ) . toLowerCase ( )
134137
135138 const resolved = await resolveActiveShareByToken ( token )
136139 if ( ! resolved ) {
0 commit comments