@@ -847,6 +847,30 @@ describe("device-pair /pair default setup code", () => {
847847 expect ( requireText ( result ) ) . toContain ( "Gateway: ws://192.168.1.20:18789" ) ;
848848 } ) ;
849849
850+ it . each ( [
851+ "ws://[fc00::1]:18789" ,
852+ "ws://[fd7a:115c:a1e0::1]:18789" ,
853+ "ws://[fe80::1]:18789" ,
854+ "ws://[febf::1]:18789" ,
855+ ] ) ( "allows IPv6 ULA and link-local cleartext setup url %s" , async ( publicUrl ) => {
856+ const command = registerPairCommand ( {
857+ pluginConfig : {
858+ publicUrl,
859+ } ,
860+ } ) ;
861+ const result = await command . handler (
862+ createCommandContext ( {
863+ channel : "webchat" ,
864+ args : "" ,
865+ commandBody : "/pair" ,
866+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
867+ } ) ,
868+ ) ;
869+
870+ expect ( pluginApiMocks . issueDeviceBootstrapToken ) . toHaveBeenCalledTimes ( 1 ) ;
871+ expect ( requireText ( result ) ) . toContain ( `Gateway: ${ publicUrl } ` ) ;
872+ } ) ;
873+
850874 it ( "allows mdns cleartext setup urls" , async ( ) => {
851875 const command = registerPairCommand ( {
852876 pluginConfig : {
@@ -1015,6 +1039,30 @@ describe("device-pair /pair default setup code", () => {
10151039 expect ( requireText ( result ) ) . toContain ( "prefer gateway.tailscale.mode=serve" ) ;
10161040 } ) ;
10171041
1042+ it . each ( [ "ws://[2001:db8::1]:18789" , "ws://[fe7f::1]:18789" , "ws://[fec0::1]:18789" ] ) (
1043+ "rejects non-LAN IPv6 cleartext setup url %s before issuing setup codes" ,
1044+ async ( publicUrl ) => {
1045+ const command = registerPairCommand ( {
1046+ pluginConfig : {
1047+ publicUrl,
1048+ } ,
1049+ } ) ;
1050+ const result = await command . handler (
1051+ createCommandContext ( {
1052+ channel : "webchat" ,
1053+ args : "" ,
1054+ commandBody : "/pair" ,
1055+ gatewayClientScopes : INTERNAL_SETUP_SCOPES ,
1056+ } ) ,
1057+ ) ;
1058+
1059+ expect ( pluginApiMocks . issueDeviceBootstrapToken ) . not . toHaveBeenCalled ( ) ;
1060+ expect ( requireText ( result ) ) . toContain (
1061+ "Tailscale and public mobile pairing require a secure gateway URL" ,
1062+ ) ;
1063+ } ,
1064+ ) ;
1065+
10181066 it ( "uses Tailscale Serve MagicDNS as a secure setup url" , async ( ) => {
10191067 vi . mocked ( resolveTailnetHostWithRunner ) . mockResolvedValueOnce ( "gateway.tailnet.ts.net" ) ;
10201068 const command = registerPairCommand ( {
0 commit comments