@@ -29,6 +29,7 @@ import * as Cause from "effect/Cause";
2929import { isLocalEnvironmentDisabled } from "../../localEnvironment" ;
3030import {
3131 applyWslEnableSelection ,
32+ connectDeviceName ,
3233 isWslSettingsRowVisible ,
3334 missingTailscaleEndpointDescription ,
3435 resolveLocalVersionStatusLabel ,
@@ -107,6 +108,7 @@ import {
107108 usePrimaryEnvironment ,
108109} from "~/state/environments" ;
109110import { requestConfirmDialog } from "~/confirmDialog" ;
111+ import { useUpdateEnvironmentSettings } from "~/hooks/useSettings" ;
110112import { useAtomCommand } from "../../state/use-atom-command" ;
111113import { environmentIdsKey } from "~/state/environmentAtomList" ;
112114import { environmentUpdateStatesAtom , serverEnvironment } from "~/state/server" ;
@@ -140,7 +142,13 @@ import {
140142 SavedBackendListRow ,
141143} from "./SavedEnvironmentRows" ;
142144import { AddEnvironmentDialog } from "./AddEnvironmentDialog" ;
145+ import {
146+ type ConnectDeviceRevokeRequest ,
147+ RevokeConnectDeviceDialog ,
148+ } from "./RevokeConnectDeviceDialog" ;
143149
150+ // Sessions T3 Connect minted for a device signed in to the linked account.
151+ const CLOUD_CONNECT_SESSION_SUBJECT = "cloud-connect" ;
144152const DEFAULT_TAILSCALE_SERVE_PORT = 443 ;
145153const EMPTY_ADVERTISED_ENDPOINTS : ReadonlyArray < AdvertisedEndpoint > = [ ] ;
146154
@@ -719,6 +727,46 @@ function ConnectionsSettingsContent({
719727 authAccessChanges . data ?. type === "snapshot"
720728 ? ( authAccessChanges . data . payload . connectDevices ?? null )
721729 : null ;
730+ const updateAccessSettings = useUpdateEnvironmentSettings ( accessEnvironmentId ) ;
731+ const [ pendingConnectRevoke , setPendingConnectRevoke ] =
732+ useState < ConnectDeviceRevokeRequest | null > ( null ) ;
733+ // With approval off a revoked T3 Connect device signs straight back in, so offer to turn it
734+ // on. Older servers have no such setting, and revoking there goes ahead as before.
735+ const connectApprovalOff =
736+ accessEnvironment ?. serverConfig ?. settings . requireConnectDeviceApproval === false ;
737+ const requestConnectRevoke = ( title : string , revoke : ( ) => Promise < void > ) => {
738+ if ( connectApprovalOff && canWriteAccess ) setPendingConnectRevoke ( { title, revoke } ) ;
739+ else void revoke ( ) ;
740+ } ;
741+ const requireConnectApproval = ( ) =>
742+ new Promise < boolean > ( ( resolve ) =>
743+ updateAccessSettings (
744+ { requireConnectDeviceApproval : true } ,
745+ { onSaved : ( ) => resolve ( true ) , onFailed : ( ) => resolve ( false ) } ,
746+ ) ,
747+ ) ;
748+ const requestRevokeClientSession = ( sessionId : ServerClientSessionRecord [ "sessionId" ] ) => {
749+ const session = desktopClientSessions . find ( ( candidate ) => candidate . sessionId === sessionId ) ;
750+ if ( session ?. subject === CLOUD_CONNECT_SESSION_SUBJECT ) {
751+ requestConnectRevoke ( `Revoke ${ connectDeviceName ( session ) } ?` , ( ) =>
752+ handleRevokeDesktopClientSession ( sessionId ) ,
753+ ) ;
754+ } else {
755+ void handleRevokeDesktopClientSession ( sessionId ) ;
756+ }
757+ } ;
758+ const requestRevokeOtherClients = ( ) => {
759+ const revokesConnectDevices =
760+ ( connectDevices ?. length ?? 0 ) > 0 ||
761+ desktopClientSessions . some (
762+ ( session ) => ! session . current && session . subject === CLOUD_CONNECT_SESSION_SUBJECT ,
763+ ) ;
764+ if ( revokesConnectDevices ) {
765+ requestConnectRevoke ( "Revoke all other clients?" , handleRevokeOtherDesktopClients ) ;
766+ } else {
767+ void handleRevokeOtherDesktopClients ( ) ;
768+ }
769+ } ;
722770 const tailscaleHttpsEndpoint = desktopAdvertisedEndpoints . find ( isTailscaleHttpsEndpoint ) ?? null ;
723771 const visibleDesktopNetworkAdvertisedEndpoints = isLocalBackendNetworkAccessible
724772 ? desktopAdvertisedEndpoints . filter ( ( endpoint ) => ! isTailscaleHttpsEndpoint ( endpoint ) )
@@ -1061,7 +1109,7 @@ function ConnectionsSettingsContent({
10611109 revokingPairingLinkId = { revokingDesktopPairingLinkId }
10621110 revokingClientSessionId = { revokingDesktopClientSessionId }
10631111 onRevokePairingLink = { handleRevokeDesktopPairingLink }
1064- onRevokeClientSession = { handleRevokeDesktopClientSession }
1112+ onRevokeClientSession = { requestRevokeClientSession }
10651113 canRevoke = { canWriteAccess }
10661114 />
10671115 ) ;
@@ -1307,7 +1355,7 @@ function ConnectionsSettingsContent({
13071355 : null
13081356 }
13091357 isRevokingOtherClients = { isRevokingOtherDesktopClients }
1310- onRevokeOtherClients = { handleRevokeOtherDesktopClients }
1358+ onRevokeOtherClients = { requestRevokeOtherClients }
13111359 />
13121360 ) : undefined
13131361 }
@@ -1336,8 +1384,14 @@ function ConnectionsSettingsContent({
13361384 requests = { connectDeviceRequests }
13371385 devices = { connectDevices }
13381386 canWrite = { canWriteAccess }
1387+ requestRevoke = { requestConnectRevoke }
13391388 />
13401389 ) : null }
1390+ < RevokeConnectDeviceDialog
1391+ request = { pendingConnectRevoke }
1392+ onClose = { ( ) => setPendingConnectRevoke ( null ) }
1393+ requireApproval = { requireConnectApproval }
1394+ />
13411395 < AlertDialog
13421396 open = { pendingDesktopServerExposureMode !== null && canManageLocalBackend }
13431397 onOpenChange = { ( open ) => {
0 commit comments