fix(desktop):add secure auth ipc bridge

This commit is contained in:
Anish Sarkar 2026-06-23 12:55:36 +05:30
parent 7241a7a894
commit f481d1360a
3 changed files with 101 additions and 0 deletions

View file

@ -42,6 +42,11 @@ export const IPC_CHANNELS = {
// Auth token sync across windows
GET_AUTH_TOKENS: 'auth:get-tokens',
SET_AUTH_TOKENS: 'auth:set-tokens',
GET_ACCESS_TOKEN: 'auth:get-access-token',
REFRESH_ACCESS_TOKEN: 'auth:refresh-access-token',
LOGOUT: 'auth:logout',
AUTH_CHANGED: 'auth:changed',
AUTH_START_GOOGLE: 'auth:start-google',
// Keyboard shortcut configuration
GET_SHORTCUTS: 'shortcuts:get',
SET_SHORTCUTS: 'shortcuts:set',

View file

@ -0,0 +1,86 @@
import { app, safeStorage } from 'electron';
import fs from 'node:fs/promises';
import path from 'node:path';
export interface SecretStore {
set(key: string, value: string): Promise<void>;
get(key: string): Promise<string | null>;
clear(key: string): Promise<void>;
isHardwareBacked(): Promise<boolean>;
}
const memoryStore = new Map<string, string>();
const storePath = path.join(app.getPath('userData'), 'secrets.enc.json');
async function readDiskStore(): Promise<Record<string, string>> {
try {
const raw = await fs.readFile(storePath, 'utf8');
return JSON.parse(raw) as Record<string, string>;
} catch {
return {};
}
}
async function writeDiskStore(data: Record<string, string>): Promise<void> {
await fs.mkdir(path.dirname(storePath), { recursive: true });
await fs.writeFile(storePath, JSON.stringify(data), { encoding: 'utf8', mode: 0o600 });
}
async function canPersistEncryptedSecrets(): Promise<boolean> {
try {
if (safeStorage.getSelectedStorageBackend?.() === 'basic_text') {
return false;
}
return await safeStorage.isAsyncEncryptionAvailable();
} catch {
return false;
}
}
export const secretStore: SecretStore = {
async set(key, value) {
if (!(await canPersistEncryptedSecrets())) {
memoryStore.set(key, value);
return;
}
const encrypted = await safeStorage.encryptStringAsync(value);
const data = await readDiskStore();
data[key] = encrypted.toString('base64');
await writeDiskStore(data);
},
async get(key) {
if (!(await canPersistEncryptedSecrets())) {
return memoryStore.get(key) ?? null;
}
const data = await readDiskStore();
const encoded = data[key];
if (!encoded) return null;
try {
const decrypted = await safeStorage.decryptStringAsync(Buffer.from(encoded, 'base64'));
if (decrypted.shouldReEncrypt) {
await this.set(key, decrypted.result);
}
return decrypted.result;
} catch {
await this.clear(key);
return null;
}
},
async clear(key) {
memoryStore.delete(key);
const data = await readDiskStore();
if (key in data) {
delete data[key];
await writeDiskStore(data);
}
},
async isHardwareBacked() {
return canPersistEncryptedSecrets();
},
};

View file

@ -83,6 +83,16 @@ contextBridge.exposeInMainWorld('electronAPI', {
getAuthTokens: () => ipcRenderer.invoke(IPC_CHANNELS.GET_AUTH_TOKENS),
setAuthTokens: (bearer: string, refresh: string) =>
ipcRenderer.invoke(IPC_CHANNELS.SET_AUTH_TOKENS, { bearer, refresh }),
getAccessToken: () => ipcRenderer.invoke(IPC_CHANNELS.GET_ACCESS_TOKEN),
refreshAccessToken: () => ipcRenderer.invoke(IPC_CHANNELS.REFRESH_ACCESS_TOKEN),
logout: () => ipcRenderer.invoke(IPC_CHANNELS.LOGOUT),
startGoogleOAuth: () => ipcRenderer.invoke(IPC_CHANNELS.AUTH_START_GOOGLE),
onAuthChanged: (callback: (payload: { authed: boolean; accessToken: string | null }) => void) => {
const listener = (_event: Electron.IpcRendererEvent, payload: { authed: boolean; accessToken: string | null }) =>
callback(payload);
ipcRenderer.on(IPC_CHANNELS.AUTH_CHANGED, listener);
return () => ipcRenderer.removeListener(IPC_CHANNELS.AUTH_CHANGED, listener);
},
// Keyboard shortcut configuration
getShortcuts: () => ipcRenderer.invoke(IPC_CHANNELS.GET_SHORTCUTS),