@@ -347,7 +347,8 @@ test.describe("api public and auth", { concurrency: false }, () => {
347347 } ) ;
348348 } ) ;
349349
350- test ( "email subscription throttle does not let a mismatched email block a valid update" , async ( ) => {
350+ test ( "email subscription throttle cannot be bypassed by changing email fields" , async ( ) => {
351+ let nowValue = 0 ;
351352 let updateCalls = 0 ;
352353 const mysql = createMysql ( async function handler ( sql , params ) {
353354 if ( sql . startsWith ( "UPDATE users SET enable_email = ?, email = ? WHERE username = ? AND email = ?" ) ) {
@@ -365,7 +366,7 @@ test.describe("api public and auth", { concurrency: false }, () => {
365366 config : createConfig ( ) ,
366367 database : createDatabase ( { caches : { } } ) ,
367368 mysql : mysql ,
368- now : ( ) => 0 ,
369+ now : ( ) => nowValue ,
369370 support : createSupport ( )
370371 } , async ( port ) => {
371372 const attackerFailure = await requestJson ( port , "POST" , "/user/subscribeEmail" , {
@@ -377,6 +378,17 @@ test.describe("api public and auth", { concurrency: false }, () => {
377378 assert . equal ( attackerFailure . statusCode , 401 ) ;
378379 assert . deepEqual ( attackerFailure . json , { error : "FROM email does not match" } ) ;
379380
381+ const variedGuess = await requestJson ( port , "POST" , "/user/subscribeEmail" , {
382+ username : "wallet" ,
383+ enabled : 0 ,
384+ from : "old@example.com" ,
385+ to : "new@example.com"
386+ } ) ;
387+ assert . equal ( variedGuess . statusCode , 429 ) ;
388+ assert . match ( variedGuess . json . msg , / T o o m a n y a t t e m p t s / ) ;
389+ assert . equal ( updateCalls , 1 ) ;
390+
391+ nowValue += 2001 ;
380392 const validUpdate = await requestJson ( port , "POST" , "/user/subscribeEmail" , {
381393 username : "wallet" ,
382394 enabled : 1 ,
0 commit comments