@@ -11,12 +11,14 @@ use std::str::FromStr;
1111use std:: time:: Duration ;
1212
1313use e2e_tests:: {
14- find_available_port, mine_and_sync, run_cli, run_cli_raw, setup_funded_channel,
15- wait_for_onchain_balance, LdkServerHandle , RabbitMqEventConsumer , TestBitcoind ,
14+ create_restricted_client, find_available_port, make_client, mine_and_sync, run_cli,
15+ run_cli_raw, setup_funded_channel, wait_for_onchain_balance, LdkServerHandle ,
16+ RabbitMqEventConsumer , TestBitcoind ,
1617} ;
1718use ldk_node:: lightning:: ln:: msgs:: SocketAddress ;
1819use ldk_server_client:: ldk_server_protos:: api:: {
19- Bolt11ReceiveRequest , Bolt12ReceiveRequest , OnchainReceiveRequest ,
20+ Bolt11ReceiveRequest , Bolt12ReceiveRequest , GetNodeInfoRequest , GetPermissionsRequest ,
21+ OnchainReceiveRequest ,
2022} ;
2123use ldk_server_client:: ldk_server_protos:: types:: {
2224 bolt11_invoice_description, Bolt11InvoiceDescription ,
@@ -611,3 +613,162 @@ async fn test_forwarded_payment_event() {
611613
612614 node_c. stop ( ) . unwrap ( ) ;
613615}
616+
617+ #[ tokio:: test]
618+ async fn test_get_permissions_admin ( ) {
619+ let bitcoind = TestBitcoind :: new ( ) ;
620+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
621+
622+ let resp = server. client ( ) . get_permissions ( GetPermissionsRequest { } ) . await . unwrap ( ) ;
623+ assert ! (
624+ resp. endpoints. contains( & "*" . to_string( ) ) ,
625+ "Expected admin key to have wildcard permission"
626+ ) ;
627+ }
628+
629+ #[ tokio:: test]
630+ async fn test_create_api_key_and_get_permissions ( ) {
631+ let bitcoind = TestBitcoind :: new ( ) ;
632+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
633+
634+ let restricted_client = create_restricted_client (
635+ & server,
636+ "read-only" ,
637+ vec ! [ "GetNodeInfo" . to_string( ) , "GetBalances" . to_string( ) ] ,
638+ )
639+ . await ;
640+
641+ let resp = restricted_client. get_permissions ( GetPermissionsRequest { } ) . await . unwrap ( ) ;
642+ assert_eq ! ( resp. endpoints, vec![ "GetBalances" , "GetNodeInfo" ] ) ;
643+ }
644+
645+ #[ tokio:: test]
646+ async fn test_restricted_key_allowed_endpoint ( ) {
647+ let bitcoind = TestBitcoind :: new ( ) ;
648+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
649+
650+ let restricted_client =
651+ create_restricted_client ( & server, "node-info-only" , vec ! [ "GetNodeInfo" . to_string( ) ] ) . await ;
652+
653+ let resp = restricted_client. get_node_info ( GetNodeInfoRequest { } ) . await ;
654+ assert ! ( resp. is_ok( ) , "Restricted key should be able to call allowed endpoint" ) ;
655+ assert_eq ! ( resp. unwrap( ) . node_id, server. node_id( ) ) ;
656+ }
657+
658+ #[ tokio:: test]
659+ async fn test_restricted_key_denied_endpoint ( ) {
660+ let bitcoind = TestBitcoind :: new ( ) ;
661+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
662+
663+ let restricted_client =
664+ create_restricted_client ( & server, "info-only" , vec ! [ "GetNodeInfo" . to_string( ) ] ) . await ;
665+
666+ let resp = restricted_client. onchain_receive ( OnchainReceiveRequest { } ) . await ;
667+ assert ! ( resp. is_err( ) , "Restricted key should be denied access to unauthorized endpoint" ) ;
668+ }
669+
670+ #[ tokio:: test]
671+ async fn test_restricted_key_get_permissions_always_allowed ( ) {
672+ let bitcoind = TestBitcoind :: new ( ) ;
673+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
674+
675+ // Create key with no endpoints at all (except GetPermissions which is always allowed)
676+ let restricted_client =
677+ create_restricted_client ( & server, "perms-only" , vec ! [ "GetNodeInfo" . to_string( ) ] ) . await ;
678+
679+ let resp = restricted_client. get_permissions ( GetPermissionsRequest { } ) . await ;
680+ assert ! ( resp. is_ok( ) , "GetPermissions should always be allowed" ) ;
681+ }
682+
683+ #[ tokio:: test]
684+ async fn test_create_api_key_via_cli ( ) {
685+ let bitcoind = TestBitcoind :: new ( ) ;
686+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
687+
688+ let output =
689+ run_cli ( & server, & [ "create-api-key" , "cli-test-key" , "-e" , "GetNodeInfo" , "GetBalances" ] ) ;
690+ let api_key = output[ "api_key" ] . as_str ( ) . unwrap ( ) ;
691+ assert_eq ! ( api_key. len( ) , 64 ) ;
692+ assert ! ( api_key. chars( ) . all( |c| c. is_ascii_hexdigit( ) ) ) ;
693+ }
694+
695+ #[ tokio:: test]
696+ async fn test_invalid_api_key_rejected ( ) {
697+ let bitcoind = TestBitcoind :: new ( ) ;
698+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
699+
700+ let bad_key = "ff" . repeat ( 32 ) ;
701+ let bad_client = make_client ( & server, & bad_key) ;
702+
703+ let resp = bad_client. get_node_info ( GetNodeInfoRequest { } ) . await ;
704+ assert ! ( resp. is_err( ) , "Invalid API key should be rejected" ) ;
705+ }
706+
707+ #[ tokio:: test]
708+ async fn test_restricted_key_cannot_create_api_key ( ) {
709+ use ldk_server_client:: ldk_server_protos:: api:: CreateApiKeyRequest ;
710+
711+ let bitcoind = TestBitcoind :: new ( ) ;
712+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
713+
714+ let restricted = create_restricted_client ( & server, "limited" , vec ! [ "GetNodeInfo" . to_string( ) ] )
715+ . await ;
716+
717+ // Restricted key should not be able to create new keys
718+ let result = restricted
719+ . create_api_key ( CreateApiKeyRequest {
720+ name : "sneaky" . to_string ( ) ,
721+ endpoints : vec ! [ "*" . to_string( ) ] ,
722+ } )
723+ . await ;
724+ assert ! ( result. is_err( ) , "Restricted key should not be able to create API keys" ) ;
725+ assert_eq ! (
726+ result. unwrap_err( ) . error_code,
727+ ldk_server_client:: error:: LdkServerErrorCode :: AuthError
728+ ) ;
729+ }
730+
731+ #[ tokio:: test]
732+ async fn test_create_api_key_duplicate_name_rejected ( ) {
733+ use ldk_server_client:: ldk_server_protos:: api:: CreateApiKeyRequest ;
734+
735+ let bitcoind = TestBitcoind :: new ( ) ;
736+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
737+
738+ // First creation should succeed
739+ let result = server
740+ . client ( )
741+ . create_api_key ( CreateApiKeyRequest {
742+ name : "my-key" . to_string ( ) ,
743+ endpoints : vec ! [ "GetNodeInfo" . to_string( ) ] ,
744+ } )
745+ . await ;
746+ assert ! ( result. is_ok( ) ) ;
747+
748+ // Duplicate name should fail
749+ let result = server
750+ . client ( )
751+ . create_api_key ( CreateApiKeyRequest {
752+ name : "my-key" . to_string ( ) ,
753+ endpoints : vec ! [ "GetNodeInfo" . to_string( ) ] ,
754+ } )
755+ . await ;
756+ assert ! ( result. is_err( ) , "Duplicate API key name should be rejected" ) ;
757+ }
758+
759+ #[ tokio:: test]
760+ async fn test_create_api_key_invalid_endpoint_rejected ( ) {
761+ use ldk_server_client:: ldk_server_protos:: api:: CreateApiKeyRequest ;
762+
763+ let bitcoind = TestBitcoind :: new ( ) ;
764+ let server = LdkServerHandle :: start ( & bitcoind) . await ;
765+
766+ let result = server
767+ . client ( )
768+ . create_api_key ( CreateApiKeyRequest {
769+ name : "bad-key" . to_string ( ) ,
770+ endpoints : vec ! [ "NonExistentEndpoint" . to_string( ) ] ,
771+ } )
772+ . await ;
773+ assert ! ( result. is_err( ) , "Unknown endpoint should be rejected" ) ;
774+ }
0 commit comments