@@ -79,27 +79,50 @@ public static Vault OpenVault(VaultInfo info, Session session)
7979
8080 public static OneOf < Account , SshKey , NoItem > GetItem ( string itemId , string vaultId , Session session )
8181 {
82- var oneOf3 = GetVaultItem ( itemId , vaultId , session . Keychain , session . Key , session . Rest ) ;
82+ // 1. On the first request we fetch everything we need to decrypt the item. This is also allows us to check
83+ // upfront if the vault exists and if it's accessible.
84+ if ( session . AccountInfo == null )
85+ {
86+ var accountInfo = GetAccountInfo ( session . Key , session . Rest ) ;
87+ var keysetsInfo = GetKeysets ( session . Key , session . Rest ) ;
8388
84- // The item is not available
85- if ( oneOf3 . TryPickT2 ( out var failure , out var oneOf2 ) )
86- return failure ;
89+ // Decrypt into the session keychain
90+ DecryptKeysets ( keysetsInfo . Keysets , session . Credentials , session . Keychain ) ;
8791
88- var item = oneOf2 . Match < VaultItem > ( a => a , k => k ) ;
92+ // Figure out which vaults are accessible with the current keychain
93+ var accessibleVaults = GetAccessibleVaults ( accountInfo , session . Keychain ) . ToArray ( ) ;
8994
90- if ( CanDecrypt ( item ) )
91- return oneOf3 ;
95+ // Decrypt all the vault keys into the session keychain
96+ foreach ( var v in accessibleVaults )
97+ v . DecryptKeyIntoKeychain ( ) ;
98+
99+ // Store last to ensure consistent state
100+ session . AccountInfo = accountInfo ;
101+ session . AccessibleVaults = accessibleVaults ;
102+ }
103+
104+ // 2. Check if the vault ID is valid and the vault exists
105+ if ( ! session . AccountInfo . Vaults . Any ( x => x . Id == vaultId ) )
106+ return NoItem . NotFound ;
107+
108+ // 3. Even if the vault is there, it might not be accessible
109+ if ( ! session . AccessibleVaults . Any ( x => x . Id == vaultId ) )
110+ return NoItem . Inaccessible ;
111+
112+ // 4. Download the item
113+ var oneOf3 = GetVaultItem ( itemId , vaultId , session . Keychain , session . Key , session . Rest ) ;
114+
115+ // 5. Check if the item is not available
116+ if ( oneOf3 . TryPickT2 ( out var noItem , out var oneOf2 ) )
117+ return noItem ;
92118
93- // Attempt to fetch everything necessary to decrypt the item
94- var accountInfo = GetAccountInfo ( session . Key , session . Rest ) ;
95- var keysets = GetKeysets ( session . Key , session . Rest ) ;
96- DecryptKeysets ( keysets . Keysets , session . Credentials , session . Keychain ) ;
97- GetAccessibleVaults ( accountInfo , session . Keychain ) . FirstOrDefault ( x => x . Id == vaultId ) ? . DecryptKeyIntoKeychain ( ) ;
119+ // 6. It's either an account or a SSH key
120+ var item = oneOf2 . Match < VaultItem > ( a => a , k => k ) ;
98121
99122 if ( CanDecrypt ( item ) )
100123 return oneOf3 ;
101124
102- throw new InternalErrorException ( "Failed to fetch the keys to decrypt the item" ) ;
125+ return NoItem . Inaccessible ;
103126
104127 bool CanDecrypt ( VaultItem i ) => session . Keychain . CanDecrypt ( i . EncryptedOverview ) && session . Keychain . CanDecrypt ( i . EncryptedDetails ) ;
105128 }
@@ -647,6 +670,7 @@ internal static string SubmitSecondFactorResult(SecondFactorKind factor, SecondF
647670 }
648671 }
649672
673+ // TODO: Rename to RequestAccountInfo
650674 internal static R . AccountInfo GetAccountInfo ( AesKey sessionKey , RestClient rest )
651675 {
652676 return GetEncryptedJson < R . AccountInfo > (
@@ -656,11 +680,13 @@ internal static R.AccountInfo GetAccountInfo(AesKey sessionKey, RestClient rest)
656680 ) ;
657681 }
658682
683+ // TODO: Rename to RequestKeysets
659684 internal static R . KeysetsInfo GetKeysets ( AesKey sessionKey , RestClient rest )
660685 {
661686 return GetEncryptedJson < R . KeysetsInfo > ( "v1/account/keysets" , sessionKey , rest ) ;
662687 }
663688
689+ // TODO: We don't really need IEnumerable here
664690 internal static IEnumerable < VaultInfo > GetAccessibleVaults ( R . AccountInfo accountInfo , Keychain keychain )
665691 {
666692 return from vault in accountInfo . Vaults
@@ -736,15 +762,18 @@ internal static OneOf<Account, SshKey, NoItem> GetVaultItem(
736762 RestClient rest
737763 )
738764 {
765+ // TODO: Make a request to var response = rest.Get<R.Encrypted>($"v1/vault/{vaultId}/"); to check if the vault exists!
739766 var response = rest . Get < R . Encrypted > ( $ "v1/vault/{ vaultId } /item/{ itemId } ") ;
740767 if ( response . IsSuccessful )
741768 return ConvertVaultItem ( keychain , DecryptResponse < R . SingleVaultItem > ( response . Data , sessionKey ) . Item ) ;
742769
743- // Special case: the item not found
744- if ( response . StatusCode == System . Net . HttpStatusCode . BadRequest && response . Content . Trim ( ) == "{}" )
745- return NoItem . NotFound ;
746-
747- throw MakeError ( response ) ;
770+ var error = MakeError ( response ) ;
771+ return error switch
772+ {
773+ // Special case: the item not found
774+ NotFoundException => NoItem . NotFound ,
775+ _ => throw error ,
776+ } ;
748777 }
749778
750779 // TODO: Rename to RequestVaultAccounts? It should clearer from the name that it's a slow operation.
@@ -844,9 +873,11 @@ internal static AesKey DeriveMasterKey(string algorithm, int iterations, byte[]
844873 }
845874
846875 //
847- // HTTP
876+ // Error handling
848877 //
849878
879+ internal class NotFoundException ( string message ) : BaseException ( message ) ;
880+
850881 internal static BaseException MakeError( RestResponse < string > response )
851882 {
852883 if ( response . IsNetworkError )
@@ -869,6 +900,8 @@ internal static BaseException ParseServerError(string response)
869900 {
870901 case 102 :
871902 return new BadCredentialsException ( "Username, password or account key is incorrect" ) ;
903+ case 117 :
904+ return new NotFoundException ( $ "The requested item not found: '{ error . Message } '") ;
872905 default :
873906 return new InternalErrorException ( $ "The server responded with the error code { error . Code } and the message '{ error . Message } '") ;
874907 }
@@ -892,6 +925,10 @@ internal static BaseException ParseServerError(string response)
892925 return null ;
893926 }
894927
928+ //
929+ // Network
930+ //
931+
895932 internal static T GetEncryptedJson < T > ( string endpoint , AesKey sessionKey , RestClient rest )
896933 {
897934 var response = rest . Get < R . Encrypted > ( endpoint ) ;
0 commit comments