@@ -2,7 +2,6 @@ use axum::{
22 Json , Router ,
33 extract:: State ,
44 http:: StatusCode ,
5- middleware,
65 response:: { IntoResponse , Response } ,
76 routing:: post,
87} ;
@@ -22,22 +21,9 @@ pub async fn router(
2221 db : & crate :: infrastructure:: sql:: SqlDb ,
2322) -> Result < Router , HttpServerError > {
2423 let state = AppState :: new ( config, db. clone ( ) ) ;
25- let homeserver_api = state. homeserver_admin_api . clone ( ) ;
26-
27- // NOTE: SMS verification routes REQUIRE homeserver health check middleware.
28- // Unlike LN verification, SMS cannot use database transactions for atomicity because:
29- // 1. Prelude API destructively marks verification as complete when check_code() succeeds
30- // 2. If generate_signup_token() fails after that, we can't rollback Prelude's state
31- // 3. This would create an inconsistent state: Prelude says "verified", our DB says "failed"
32- //
33- // The middleware mitigates this by failing fast (503) if homeserver is unreachable.
3424 Ok ( Router :: new ( )
3525 . route ( "/send_code" , post ( send_code_handler) )
3626 . route ( "/validate_code" , post ( validate_code_handler) )
37- . route_layer ( middleware:: from_fn ( move |req, next| {
38- let api = homeserver_api. clone ( ) ;
39- crate :: shared:: check_homeserver_health ( api, req, next)
40- } ) )
4127 . with_state ( state) )
4228}
4329
@@ -95,6 +81,10 @@ impl IntoResponse for SmsVerificationError {
9581 }
9682 return response;
9783 }
84+ SmsVerificationError :: HomeserverUnavailable => {
85+ tracing:: error!( "Homeserver unavailable during SMS verification" ) ;
86+ StatusCode :: INTERNAL_SERVER_ERROR
87+ }
9888 SmsVerificationError :: RequestFailed ( ref err) => {
9989 tracing:: error!( error = %err, "Failed to communicate with SMS provider" ) ;
10090 StatusCode :: INTERNAL_SERVER_ERROR
@@ -309,124 +299,4 @@ mod tests {
309299
310300 // Wiremock verifies all expected calls were made
311301 }
312-
313- /// Integration tests that verify the homeserver health middleware actually works
314- /// when integrated with the full router (not router_with_db which bypasses middleware)
315- mod middleware_integration {
316- use super :: * ;
317- use crate :: e2e:: WiremockServers ;
318- use wiremock:: {
319- Mock , ResponseTemplate ,
320- matchers:: { method, path} ,
321- } ;
322-
323- /// Helper to create a router with the production path that includes middleware
324- async fn create_router_with_middleware (
325- pool : PgPool ,
326- servers : & WiremockServers ,
327- ) -> ( TestServer , PgPool ) {
328- use crate :: EnvConfig ;
329- use crate :: infrastructure:: sql:: SqlDb ;
330-
331- let config = EnvConfig :: for_test (
332- servers. prelude_server . uri ( ) . parse ( ) . unwrap ( ) ,
333- servers. homeserver_server . uri ( ) . parse ( ) . unwrap ( ) ,
334- ) ;
335-
336- let db = SqlDb :: test ( pool. clone ( ) ) . await ;
337-
338- // Use the production router() function which includes middleware
339- let sms_verification_router =
340- router ( & config, & db) . await . expect ( "Failed to create router" ) ;
341-
342- let router = Router :: new ( ) . nest ( "/sms_verification" , sms_verification_router) ;
343- let app = router. into_make_service_with_connect_info :: < SocketAddr > ( ) ;
344- let server = TestServer :: new ( app) . expect ( "Failed to create test server" ) ;
345-
346- ( server, pool)
347- }
348-
349- #[ sqlx:: test]
350- async fn middleware_blocks_send_code_when_homeserver_is_down ( pool : PgPool ) {
351- let servers = WiremockServers :: start ( ) . await ;
352-
353- // Setup homeserver to be unresponsive (return 500)
354- Mock :: given ( method ( "GET" ) )
355- . and ( path ( "/" ) )
356- . respond_with ( ResponseTemplate :: new ( 500 ) )
357- . expect ( 1 )
358- . mount ( & servers. homeserver_server )
359- . await ;
360-
361- let ( server, _pool) = create_router_with_middleware ( pool, & servers) . await ;
362-
363- // Attempt to send code
364- let response = server
365- . post ( "/sms_verification/send_code" )
366- . json ( & serde_json:: json!( { "phoneNumber" : "+30123456789" } ) )
367- . await ;
368-
369- // Should return 503 due to homeserver being down
370- response. assert_status ( StatusCode :: SERVICE_UNAVAILABLE ) ;
371- assert_eq ! (
372- response. text( ) ,
373- "Homeserver temporarily unavailable, please retry"
374- ) ;
375- }
376-
377- #[ sqlx:: test]
378- async fn middleware_blocks_validate_code_when_homeserver_is_down ( pool : PgPool ) {
379- let servers = WiremockServers :: start ( ) . await ;
380-
381- // Setup homeserver to return 500
382- Mock :: given ( method ( "GET" ) )
383- . and ( path ( "/" ) )
384- . respond_with ( ResponseTemplate :: new ( 500 ) )
385- . expect ( 1 )
386- . mount ( & servers. homeserver_server )
387- . await ;
388-
389- let ( server, _pool) = create_router_with_middleware ( pool, & servers) . await ;
390-
391- // Attempt to validate code when homeserver is down
392- // Should be blocked by middleware before reaching the handler
393- let response = server
394- . post ( "/sms_verification/validate_code" )
395- . json ( & serde_json:: json!( {
396- "phoneNumber" : "+30123456789" ,
397- "code" : "123456"
398- } ) )
399- . await ;
400-
401- // Should return 503 due to homeserver being down
402- // The middleware blocks the request before it reaches the handler
403- response. assert_status ( StatusCode :: SERVICE_UNAVAILABLE ) ;
404- assert_eq ! (
405- response. text( ) ,
406- "Homeserver temporarily unavailable, please retry"
407- ) ;
408- }
409-
410- #[ sqlx:: test]
411- async fn middleware_blocks_when_homeserver_is_unreachable ( pool : PgPool ) {
412- let servers = WiremockServers :: start ( ) . await ;
413-
414- // Don't mount any mock - this simulates connection refused
415- // The middleware will try to connect and fail
416-
417- let ( server, _pool) = create_router_with_middleware ( pool, & servers) . await ;
418-
419- // Attempt to send code - should be blocked immediately by middleware
420- let response = server
421- . post ( "/sms_verification/send_code" )
422- . json ( & serde_json:: json!( { "phoneNumber" : "+30123456789" } ) )
423- . await ;
424-
425- response. assert_status ( StatusCode :: SERVICE_UNAVAILABLE ) ;
426- assert_eq ! (
427- response. text( ) ,
428- "Homeserver temporarily unavailable, please retry"
429- ) ;
430- }
431- }
432302}
0 commit comments