@@ -9,6 +9,8 @@ type VerifyBootstrapTokenFn = Parameters<
99
1010function createRateLimiter ( params ?: { allowed ?: boolean ; retryAfterMs ?: number } ) : {
1111 limiter : AuthRateLimiter ;
12+ check : ReturnType < typeof vi . fn > ;
13+ recordFailure : ReturnType < typeof vi . fn > ;
1214 reset : ReturnType < typeof vi . fn > ;
1315} {
1416 const allowed = params ?. allowed ?? true ;
@@ -22,6 +24,8 @@ function createRateLimiter(params?: { allowed?: boolean; retryAfterMs?: number }
2224 reset,
2325 recordFailure,
2426 } as unknown as AuthRateLimiter ,
27+ check,
28+ recordFailure,
2529 reset,
2630 } ;
2731}
@@ -141,11 +145,14 @@ describe("resolveConnectAuthDecision", () => {
141145 } ) ;
142146
143147 it ( "accepts valid bootstrap tokens before device-token fallback" , async ( ) => {
148+ const rateLimiter = createRateLimiter ( ) ;
144149 const verifyBootstrapToken = vi . fn < VerifyBootstrapTokenFn > ( async ( ) => ( { ok : true } ) ) ;
145150 const verifyDeviceToken = vi . fn < VerifyDeviceTokenFn > ( async ( ) => ( { ok : true } ) ) ;
146151 const decision = await resolveDeviceTokenDecision ( {
147152 verifyBootstrapToken,
148153 verifyDeviceToken,
154+ rateLimiter : rateLimiter . limiter ,
155+ clientIp : "203.0.113.23" ,
149156 stateOverrides : {
150157 bootstrapTokenCandidate : "bootstrap-token" ,
151158 deviceTokenCandidate : "device-token" ,
@@ -154,10 +161,12 @@ describe("resolveConnectAuthDecision", () => {
154161 expect ( decision . authOk ) . toBe ( true ) ;
155162 expect ( decision . authMethod ) . toBe ( "bootstrap-token" ) ;
156163 expect ( verifyBootstrapToken ) . toHaveBeenCalledOnce ( ) ;
164+ expect ( rateLimiter . reset ) . toHaveBeenCalledWith ( "203.0.113.23" , "bootstrap-token" ) ;
157165 expect ( verifyDeviceToken ) . not . toHaveBeenCalled ( ) ;
158166 } ) ;
159167
160168 it ( "reports invalid bootstrap tokens when no device token fallback is available" , async ( ) => {
169+ const rateLimiter = createRateLimiter ( ) ;
161170 const verifyBootstrapToken = vi . fn < VerifyBootstrapTokenFn > ( async ( ) => ( {
162171 ok : false ,
163172 reason : "bootstrap_token_invalid" ,
@@ -166,6 +175,8 @@ describe("resolveConnectAuthDecision", () => {
166175 const decision = await resolveDeviceTokenDecision ( {
167176 verifyBootstrapToken,
168177 verifyDeviceToken,
178+ rateLimiter : rateLimiter . limiter ,
179+ clientIp : "203.0.113.21" ,
169180 stateOverrides : {
170181 bootstrapTokenCandidate : "bootstrap-token" ,
171182 deviceTokenCandidate : undefined ,
@@ -175,6 +186,31 @@ describe("resolveConnectAuthDecision", () => {
175186 expect ( decision . authOk ) . toBe ( false ) ;
176187 expect ( decision . authResult . reason ) . toBe ( "bootstrap_token_invalid" ) ;
177188 expect ( verifyBootstrapToken ) . toHaveBeenCalledOnce ( ) ;
189+ expect ( rateLimiter . check ) . toHaveBeenCalledWith ( "203.0.113.21" , "bootstrap-token" ) ;
190+ expect ( rateLimiter . recordFailure ) . toHaveBeenCalledWith ( "203.0.113.21" , "bootstrap-token" ) ;
191+ expect ( verifyDeviceToken ) . not . toHaveBeenCalled ( ) ;
192+ } ) ;
193+
194+ it ( "returns rate-limited auth result without verifying bootstrap token" , async ( ) => {
195+ const rateLimiter = createRateLimiter ( { allowed : false , retryAfterMs : 60_000 } ) ;
196+ const verifyBootstrapToken = vi . fn < VerifyBootstrapTokenFn > ( async ( ) => ( { ok : true } ) ) ;
197+ const verifyDeviceToken = vi . fn < VerifyDeviceTokenFn > ( async ( ) => ( { ok : true } ) ) ;
198+ const decision = await resolveDeviceTokenDecision ( {
199+ verifyBootstrapToken,
200+ verifyDeviceToken,
201+ rateLimiter : rateLimiter . limiter ,
202+ clientIp : "203.0.113.22" ,
203+ stateOverrides : {
204+ bootstrapTokenCandidate : "bootstrap-token" ,
205+ deviceTokenCandidate : undefined ,
206+ deviceTokenCandidateSource : undefined ,
207+ } ,
208+ } ) ;
209+
210+ expect ( decision . authOk ) . toBe ( false ) ;
211+ expect ( decision . authResult . reason ) . toBe ( "rate_limited" ) ;
212+ expect ( decision . authResult . retryAfterMs ) . toBe ( 60_000 ) ;
213+ expect ( verifyBootstrapToken ) . not . toHaveBeenCalled ( ) ;
178214 expect ( verifyDeviceToken ) . not . toHaveBeenCalled ( ) ;
179215 } ) ;
180216
0 commit comments