Skip to content

Commit 680ea69

Browse files
committed
test: fix issues raised by test fialures
1 parent 436409d commit 680ea69

5 files changed

Lines changed: 57 additions & 9 deletions

File tree

src-electron/ipc-security.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,15 @@ function isTrustedOrigin(url) {
3030
}
3131
}
3232

33-
// In dev stage, also allow localhost URLs
33+
// In dev stage, also allow localhost URLs (but NOT asset:// protocol)
34+
// asset:// is for static file serving only and should never have API access
3435
if (stage === 'dev') {
3536
try {
3637
const parsed = new URL(url);
38+
// Exclude asset:// protocol - it's sandboxed like Tauri's asset protocol
39+
if (parsed.protocol === 'asset:') {
40+
return false;
41+
}
3742
if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1') {
3843
return true;
3944
}

src-electron/main-cred-ipc.js

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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

src-electron/main-window-ipc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,17 @@ function registerWindowIpcHandlers() {
167167
}
168168
});
169169

170+
// Close a window by its label
171+
ipcMain.handle('close-window-by-label', async (event, label) => {
172+
assertTrusted(event);
173+
const win = windowRegistry.get(label);
174+
if (win && !win.isDestroyed()) {
175+
win.close();
176+
return true;
177+
}
178+
return false;
179+
});
180+
170181
// Focus current window and bring to front
171182
ipcMain.handle('focus-window', (event) => {
172183
assertTrusted(event);

src-electron/main.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ if (process.argv.includes('-v') || process.argv.includes('--version')) {
2626
process.exit(0);
2727
}
2828

29-
// Register phtauri:// as a privileged scheme (must be done before app ready)
29+
// Register custom schemes as privileged (must be done before app ready)
3030
// This enables standard web features: fetch, localStorage, cookies, etc.
3131
protocol.registerSchemesAsPrivileged([
3232
{
@@ -38,6 +38,15 @@ protocol.registerSchemesAsPrivileged([
3838
corsEnabled: true,
3939
stream: true
4040
}
41+
},
42+
{
43+
// asset:// is for serving static files only - minimal privileges to match Tauri's security posture
44+
// No standard/secure/corsEnabled - just enough for fetch() to read local assets
45+
scheme: 'asset',
46+
privileges: {
47+
supportFetchAPI: true,
48+
stream: true
49+
}
4150
}
4251
]);
4352

src-electron/preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
105105
getCurrentWindowLabel: () => ipcRenderer.invoke('get-current-window-label'),
106106
createPhoenixWindow: (url, options) => ipcRenderer.invoke('create-phoenix-window', url, options),
107107
closeWindow: () => ipcRenderer.invoke('close-window'),
108+
closeWindowByLabel: (label) => ipcRenderer.invoke('close-window-by-label', label),
108109
quitApp: (exitCode) => ipcRenderer.invoke('quit-app', exitCode),
109110
focusWindow: () => ipcRenderer.invoke('focus-window'),
110111

0 commit comments

Comments
 (0)