77using SshManager . Core . Models ;
88using SshManager . Data . Repositories ;
99using SshManager . Security ;
10+ using SshManager . Security . OnePassword ;
1011using SshManager . Terminal . Services ;
1112
1213namespace SshManager . App . ViewModels . HostEdit ;
@@ -22,6 +23,7 @@ public partial class SshConnectionSettingsViewModel : ObservableObject
2223 private readonly ISecretProtector _secretProtector ;
2324 private readonly IAgentDiagnosticsService ? _agentDiagnosticsService ;
2425 private readonly IKerberosAuthService ? _kerberosAuthService ;
26+ private readonly IOnePasswordService ? _onePasswordService ;
2527 private readonly IHostProfileRepository ? _hostProfileRepo ;
2628 private readonly IProxyJumpProfileRepository ? _proxyJumpRepo ;
2729 private readonly IPortForwardingProfileRepository ? _portForwardingRepo ;
@@ -110,6 +112,25 @@ public partial class SshConnectionSettingsViewModel : ObservableObject
110112
111113 #endregion
112114
115+ #region 1Password Properties
116+
117+ [ ObservableProperty ]
118+ private string _onePasswordReference = "" ;
119+
120+ [ ObservableProperty ]
121+ private string _onePasswordKeyReference = "" ;
122+
123+ [ ObservableProperty ]
124+ private bool _isOnePasswordAvailable ;
125+
126+ [ ObservableProperty ]
127+ private string _onePasswordStatusText = "Checking..." ;
128+
129+ [ ObservableProperty ]
130+ private bool _isCheckingOnePassword ;
131+
132+ #endregion
133+
113134 #region Kerberos Properties
114135
115136 [ ObservableProperty ]
@@ -151,6 +172,11 @@ public partial class SshConnectionSettingsViewModel : ObservableObject
151172 /// </summary>
152173 public bool ShowKerberosSettings => AuthType == AuthType . Kerberos ;
153174
175+ /// <summary>
176+ /// Gets whether to show 1Password settings.
177+ /// </summary>
178+ public bool ShowOnePasswordSettings => AuthType == AuthType . OnePassword ;
179+
154180 /// <summary>
155181 /// Gets the display text for port forwarding status.
156182 /// </summary>
@@ -206,6 +232,7 @@ public SshConnectionSettingsViewModel(
206232 ISecretProtector secretProtector ,
207233 IAgentDiagnosticsService ? agentDiagnosticsService = null ,
208234 IKerberosAuthService ? kerberosAuthService = null ,
235+ IOnePasswordService ? onePasswordService = null ,
209236 IHostProfileRepository ? hostProfileRepo = null ,
210237 IProxyJumpProfileRepository ? proxyJumpRepo = null ,
211238 IPortForwardingProfileRepository ? portForwardingRepo = null ,
@@ -215,6 +242,7 @@ public SshConnectionSettingsViewModel(
215242 _secretProtector = secretProtector ;
216243 _agentDiagnosticsService = agentDiagnosticsService ;
217244 _kerberosAuthService = kerberosAuthService ;
245+ _onePasswordService = onePasswordService ;
218246 _hostProfileRepo = hostProfileRepo ;
219247 _proxyJumpRepo = proxyJumpRepo ;
220248 _portForwardingRepo = portForwardingRepo ;
@@ -259,6 +287,10 @@ public void LoadFromHost(HostEntry host)
259287 KerberosServicePrincipal = host . KerberosServicePrincipal ;
260288 KerberosDelegateCredentials = host . KerberosDelegateCredentials ;
261289
290+ // 1Password settings
291+ OnePasswordReference = host . OnePasswordReference ?? "" ;
292+ OnePasswordKeyReference = host . OnePasswordKeyReference ?? "" ;
293+
262294 // Keep-alive settings
263295 if ( host . KeepAliveIntervalSeconds . HasValue )
264296 {
@@ -320,6 +352,22 @@ public void PopulateHost(HostEntry host)
320352 host . KerberosDelegateCredentials = false ;
321353 }
322354
355+ // 1Password settings
356+ if ( AuthType == AuthType . OnePassword )
357+ {
358+ host . OnePasswordReference = string . IsNullOrWhiteSpace ( OnePasswordReference )
359+ ? null
360+ : OnePasswordReference . Trim ( ) ;
361+ host . OnePasswordKeyReference = string . IsNullOrWhiteSpace ( OnePasswordKeyReference )
362+ ? null
363+ : OnePasswordKeyReference . Trim ( ) ;
364+ }
365+ else
366+ {
367+ host . OnePasswordReference = null ;
368+ host . OnePasswordKeyReference = null ;
369+ }
370+
323371 // Host/Proxy profiles
324372 host . HostProfileId = SelectedHostProfile ? . Id ;
325373 host . HostProfile = SelectedHostProfile ;
@@ -375,6 +423,11 @@ public async Task LoadAsync(CancellationToken ct = default)
375423 {
376424 _ = RefreshKerberosStatusAsync ( ) ;
377425 }
426+
427+ if ( AuthType == AuthType . OnePassword )
428+ {
429+ _ = RefreshOnePasswordStatusAsync ( ) ;
430+ }
378431 }
379432
380433 /// <summary>
@@ -615,6 +668,58 @@ private async Task RefreshKerberosStatusAsync()
615668 }
616669 }
617670
671+ /// <summary>
672+ /// Refreshes 1Password CLI status information.
673+ /// </summary>
674+ [ RelayCommand ]
675+ private async Task RefreshOnePasswordStatusAsync ( )
676+ {
677+ if ( _onePasswordService == null )
678+ {
679+ OnePasswordStatusText = "1Password service not available" ;
680+ IsOnePasswordAvailable = false ;
681+ return ;
682+ }
683+
684+ IsCheckingOnePassword = true ;
685+
686+ try
687+ {
688+ var status = await _onePasswordService . GetStatusAsync ( ) ;
689+
690+ IsOnePasswordAvailable = status . IsInstalled && status . IsAuthenticated ;
691+
692+ if ( status . IsAuthenticated )
693+ {
694+ OnePasswordStatusText = ! string . IsNullOrEmpty ( status . AccountEmail )
695+ ? $ "Connected as { status . AccountEmail } "
696+ : "Authenticated" ;
697+ }
698+ else if ( status . IsInstalled )
699+ {
700+ var hint = ! string . IsNullOrEmpty ( status . ErrorMessage ) ? status . ErrorMessage
701+ : "Enable CLI integration in 1Password: Settings > Developer" ;
702+ OnePasswordStatusText = $ "Not authenticated — { hint } ";
703+ }
704+ else
705+ {
706+ OnePasswordStatusText = "1Password CLI not installed" ;
707+ }
708+
709+ _logger . LogDebug ( "1Password status refreshed: {StatusText}" , OnePasswordStatusText ) ;
710+ }
711+ catch ( Exception ex )
712+ {
713+ OnePasswordStatusText = "Error checking 1Password status" ;
714+ IsOnePasswordAvailable = false ;
715+ _logger . LogWarning ( ex , "Failed to refresh 1Password status" ) ;
716+ }
717+ finally
718+ {
719+ IsCheckingOnePassword = false ;
720+ }
721+ }
722+
618723 #endregion
619724
620725 #region Property Changed Handlers
@@ -625,6 +730,7 @@ partial void OnAuthTypeChanged(AuthType value)
625730 OnPropertyChanged ( nameof ( ShowPassword ) ) ;
626731 OnPropertyChanged ( nameof ( ShowAgentStatus ) ) ;
627732 OnPropertyChanged ( nameof ( ShowKerberosSettings ) ) ;
733+ OnPropertyChanged ( nameof ( ShowOnePasswordSettings ) ) ;
628734
629735 // Refresh agent status when switching to SSH Agent auth
630736 if ( value == AuthType . SshAgent )
@@ -637,6 +743,12 @@ partial void OnAuthTypeChanged(AuthType value)
637743 {
638744 _ = RefreshKerberosStatusAsync ( ) ;
639745 }
746+
747+ // Refresh 1Password status when switching to OnePassword auth
748+ if ( value == AuthType . OnePassword )
749+ {
750+ _ = RefreshOnePasswordStatusAsync ( ) ;
751+ }
640752 }
641753
642754 partial void OnPortForwardingProfileCountChanged ( int value )
@@ -709,6 +821,23 @@ public List<string> Validate()
709821 errors . Add ( $ "Private key file not found: { keyPath } ") ;
710822 }
711823 break ;
824+
825+ case AuthType . OnePassword :
826+ var pwdRef = OnePasswordReference ? . Trim ( ) ?? "" ;
827+ var keyRef = OnePasswordKeyReference ? . Trim ( ) ?? "" ;
828+ if ( string . IsNullOrWhiteSpace ( pwdRef ) && string . IsNullOrWhiteSpace ( keyRef ) )
829+ {
830+ errors . Add ( "At least one 1Password reference (password or SSH key) is required" ) ;
831+ }
832+ if ( ! string . IsNullOrWhiteSpace ( pwdRef ) && ! pwdRef . StartsWith ( "op://" , StringComparison . OrdinalIgnoreCase ) )
833+ {
834+ errors . Add ( "Password reference must start with 'op://'" ) ;
835+ }
836+ if ( ! string . IsNullOrWhiteSpace ( keyRef ) && ! keyRef . StartsWith ( "op://" , StringComparison . OrdinalIgnoreCase ) )
837+ {
838+ errors . Add ( "SSH key reference must start with 'op://'" ) ;
839+ }
840+ break ;
712841 }
713842
714843 return errors ;
0 commit comments