mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-26 21:39:43 +02:00
fix(desktop):add auth cutover ipc
This commit is contained in:
parent
766fa25ea0
commit
2fd7551d36
4 changed files with 73 additions and 16 deletions
|
|
@ -40,13 +40,12 @@ export const IPC_CHANNELS = {
|
|||
READ_AGENT_LOCAL_FILE_TEXT: 'agent-filesystem:read-local-file-text',
|
||||
WRITE_AGENT_LOCAL_FILE_TEXT: 'agent-filesystem:write-local-file-text',
|
||||
// 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',
|
||||
AUTH_LOGIN_PASSWORD: 'auth:login-password',
|
||||
// Keyboard shortcut configuration
|
||||
GET_SHORTCUTS: 'shortcuts:get',
|
||||
SET_SHORTCUTS: 'shortcuts:set',
|
||||
|
|
|
|||
|
|
@ -59,6 +59,11 @@ const REFRESH_TOKEN_KEY = 'surfsense_refresh_token';
|
|||
let accessToken: string | null = null;
|
||||
let refreshInFlight: Promise<string | null> | null = null;
|
||||
|
||||
type DesktopAuthResponse = {
|
||||
access_token?: string;
|
||||
refresh_token?: string | null;
|
||||
};
|
||||
|
||||
function getBackendUrl(): string {
|
||||
return (process.env.HOSTED_BACKEND_URL || process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || '').replace(
|
||||
/\/+$/,
|
||||
|
|
@ -224,17 +229,6 @@ export function registerIpcHandlers(): void {
|
|||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.SET_AUTH_TOKENS, async (_event, tokens: { bearer: string; refresh: string }) => {
|
||||
await storeTokens(tokens);
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_AUTH_TOKENS, async () => {
|
||||
if (!accessToken) {
|
||||
await refreshAccessToken();
|
||||
}
|
||||
return accessToken ? { bearer: accessToken, refresh: '' } : null;
|
||||
});
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_ACCESS_TOKEN, async () => {
|
||||
if (!accessToken) {
|
||||
await refreshAccessToken();
|
||||
|
|
@ -275,6 +269,41 @@ export function registerIpcHandlers(): void {
|
|||
return { ok: true };
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
IPC_CHANNELS.AUTH_LOGIN_PASSWORD,
|
||||
async (_event, payload: { email: string; password: string }) => {
|
||||
const backendUrl = getBackendUrl();
|
||||
if (!backendUrl) {
|
||||
throw new Error('Backend URL is not configured');
|
||||
}
|
||||
|
||||
const response = await fetch(`${backendUrl}/auth/desktop/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let detail = 'Password login failed';
|
||||
try {
|
||||
const error = (await response.json()) as { detail?: string };
|
||||
detail = error.detail || detail;
|
||||
} catch {
|
||||
// Keep the generic error if the backend did not return JSON.
|
||||
}
|
||||
throw new Error(detail);
|
||||
}
|
||||
|
||||
const tokens = (await response.json()) as DesktopAuthResponse;
|
||||
if (!tokens.access_token || !tokens.refresh_token) {
|
||||
throw new Error('Password login did not return desktop tokens');
|
||||
}
|
||||
|
||||
await storeTokens({ bearer: tokens.access_token, refresh: tokens.refresh_token });
|
||||
return { ok: true };
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_SHORTCUTS, () => getShortcuts());
|
||||
|
||||
ipcMain.handle(IPC_CHANNELS.GET_AUTO_LAUNCH, () => getAutoLaunchState());
|
||||
|
|
|
|||
30
surfsense_desktop/src/modules/auth-cutover.ts
Normal file
30
surfsense_desktop/src/modules/auth-cutover.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { app } from 'electron';
|
||||
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { secretStore } from './secret-store';
|
||||
|
||||
const CUTOVER_FLAG_FILE = 'auth-cutover-v1.json';
|
||||
const REFRESH_TOKEN_KEY = 'surfsense_refresh_token';
|
||||
|
||||
async function hasCompletedCutover(flagPath: string): Promise<boolean> {
|
||||
try {
|
||||
const raw = await readFile(flagPath, 'utf8');
|
||||
return JSON.parse(raw)?.complete === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function purgeLegacyAuthCutover(): Promise<void> {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const flagPath = path.join(userDataPath, CUTOVER_FLAG_FILE);
|
||||
if (await hasCompletedCutover(flagPath)) return;
|
||||
|
||||
await secretStore.clear(REFRESH_TOKEN_KEY);
|
||||
await mkdir(userDataPath, { recursive: true });
|
||||
await writeFile(
|
||||
flagPath,
|
||||
JSON.stringify({ complete: true, completedAt: new Date().toISOString() }),
|
||||
{ mode: 0o600 }
|
||||
);
|
||||
}
|
||||
|
|
@ -80,13 +80,12 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||
ipcRenderer.invoke(IPC_CHANNELS.WRITE_AGENT_LOCAL_FILE_TEXT, virtualPath, content, searchSpaceId),
|
||||
|
||||
// Auth token sync across windows
|
||||
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),
|
||||
loginPassword: (email: string, password: string) =>
|
||||
ipcRenderer.invoke(IPC_CHANNELS.AUTH_LOGIN_PASSWORD, { email, password }),
|
||||
onAuthChanged: (callback: (payload: { authed: boolean; accessToken: string | null }) => void) => {
|
||||
const listener = (_event: Electron.IpcRendererEvent, payload: { authed: boolean; accessToken: string | null }) =>
|
||||
callback(payload);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue