@@ -48,7 +48,8 @@ function registerCredIpcHandlers() {
4848 const stored = windowTrustMap . get ( webContentsId ) ;
4949
5050 if ( ! stored ) {
51- throw new Error ( 'No trust established for this window.' ) ;
51+ // Match Tauri's error message
52+ throw new Error ( 'No trust association found for this window.' ) ;
5253 }
5354 if ( stored . key !== key || stored . iv !== iv ) {
5455 throw new Error ( 'Provided key and IV do not match.' ) ;
@@ -59,14 +60,22 @@ function registerCredIpcHandlers() {
5960 console . log ( `AES trust removed for window: ${ getWindowLabel ( webContentsId ) } (webContentsId: ${ webContentsId } )` ) ;
6061 } ) ;
6162
63+ // Special marker for empty string credentials (keytar doesn't allow empty passwords)
64+ // Using a unique string that's unlikely to be a real credential value
65+ const EMPTY_CREDENTIAL_MARKER = '___PHCODE_EMPTY_CREDENTIAL_MARKER___' ;
66+
6267 // Store credential in system keychain
6368 ipcMain . handle ( 'store-credential' , async ( event , scopeName , secretVal ) => {
6469 assertTrusted ( event ) ;
6570 if ( ! keytar ) {
6671 throw new Error ( 'keytar module not available.' ) ;
6772 }
6873 const service = PHOENIX_CRED_PREFIX + scopeName ;
69- await keytar . setPassword ( service , process . env . USER || 'user' , secretVal ) ;
74+ // Handle empty strings by storing a special marker (keytar requires non-empty passwords)
75+ // Check for empty string, null, or undefined - all become the marker
76+ const isEmpty = secretVal === '' || secretVal === null || secretVal === undefined ;
77+ const valueToStore = isEmpty ? EMPTY_CREDENTIAL_MARKER : secretVal ;
78+ await keytar . setPassword ( service , process . env . USER || 'user' , valueToStore ) ;
7079 } ) ;
7180
7281 // Get credential (encrypted with window's AES key)
@@ -79,15 +88,22 @@ function registerCredIpcHandlers() {
7988 const webContentsId = event . sender . id ;
8089 const trustData = windowTrustMap . get ( webContentsId ) ;
8190 if ( ! trustData ) {
82- throw new Error ( 'Trust needs to be established first.' ) ;
91+ // Match Tauri's error message
92+ throw new Error ( 'Trust needs to be first established for this window to get or set credentials.' ) ;
8393 }
8494
8595 const service = PHOENIX_CRED_PREFIX + scopeName ;
86- const credential = await keytar . getPassword ( service , process . env . USER || 'user' ) ;
87- if ( ! credential ) {
96+ const account = process . env . USER || 'user' ;
97+ let credential = await keytar . getPassword ( service , account ) ;
98+ if ( credential === null || credential === undefined ) {
8899 return null ;
89100 }
90101
102+ // Convert empty credential marker back to empty string
103+ if ( credential === EMPTY_CREDENTIAL_MARKER ) {
104+ credential = '' ;
105+ }
106+
91107 // Encrypt with AES-256-GCM (same as Tauri)
92108 const keyBytes = Buffer . from ( trustData . key , 'hex' ) ;
93109 const ivBytes = Buffer . from ( trustData . iv , 'hex' ) ;
@@ -96,7 +112,9 @@ function registerCredIpcHandlers() {
96112 encrypted += cipher . final ( 'hex' ) ;
97113 const authTag = cipher . getAuthTag ( ) . toString ( 'hex' ) ;
98114
99- return encrypted + authTag ; // Return ciphertext + authTag as hex string
115+ // For empty credentials, encrypted will be empty string, but authTag will still be present
116+ const result = encrypted + authTag ;
117+ return result ;
100118 } ) ;
101119
102120 // Delete credential from system keychain
@@ -106,7 +124,11 @@ function registerCredIpcHandlers() {
106124 throw new Error ( 'keytar module not available.' ) ;
107125 }
108126 const service = PHOENIX_CRED_PREFIX + scopeName ;
109- await keytar . deletePassword ( service , process . env . USER || 'user' ) ;
127+ const deleted = await keytar . deletePassword ( service , process . env . USER || 'user' ) ;
128+ // Match Tauri's behavior: throw if credential didn't exist
129+ if ( ! deleted ) {
130+ throw new Error ( 'No matching entry found in secure storage' ) ;
131+ }
110132 } ) ;
111133}
112134
0 commit comments