mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-26 21:39:43 +02:00
fix(desktop):add secure auth ipc bridge
This commit is contained in:
parent
7241a7a894
commit
f481d1360a
3 changed files with 101 additions and 0 deletions
|
|
@ -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',
|
||||
|
|
|
|||
86
surfsense_desktop/src/modules/secret-store.ts
Normal file
86
surfsense_desktop/src/modules/secret-store.ts
Normal 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();
|
||||
},
|
||||
};
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue