Skip to content

Commit 1032f7c

Browse files
committed
Add auth for desktop app
1 parent dee2c26 commit 1032f7c

File tree

4 files changed

+110
-22
lines changed

4 files changed

+110
-22
lines changed

desktop/forge.config.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { ForgeConfig } from "@electron-forge/shared-types";
2-
import path from "path";
32
import fs from "fs-extra";
3+
import path from "path";
44

5-
function moveModule(moduleList: string[], resourcePath: string) {
5+
function copyModule(moduleList: string[], resourcePath: string) {
66
moduleList.forEach((module) => {
7-
fs.moveSync(
7+
fs.copySync(
88
path.join("./node_modules", module),
9-
path.join(resourcePath, "app/node_modules", module)
9+
path.join(resourcePath, "app/node_modules", module),
1010
);
1111
});
1212
}
@@ -27,11 +27,11 @@ const config: ForgeConfig = {
2727
// We need electron-serve to exist inside the electron build's node_modules.
2828
// All other modules from nextjs are not needed and can be removed.
2929
if (platform === "win32") {
30-
moveModule(electronModules, path.join(extractPath, "resources"));
30+
copyModule(electronModules, path.join(extractPath, "resources"));
3131
} else if (platform === "darwin") {
32-
moveModule(
32+
copyModule(
3333
electronModules,
34-
path.join(extractPath, "pulse-editor.app/Contents/Resources")
34+
path.join(extractPath, "pulse-editor.app/Contents/Resources"),
3535
);
3636
} else if (platform === "linux") {
3737
}
@@ -47,7 +47,7 @@ const config: ForgeConfig = {
4747
options: {
4848
icon: path.join(
4949
__dirname,
50-
"../shared-assets/icons/electron/pulse_logo_round"
50+
"../shared-assets/icons/electron/pulse_logo_round",
5151
),
5252
},
5353
},

desktop/main.mjs

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
import { app, BrowserWindow, dialog, ipcMain } from "electron";
1+
import { app, BrowserWindow, dialog, ipcMain, session } from "electron";
22
import serve from "electron-serve";
33
import path from "path";
44
import { fileURLToPath } from "url";
55

66
import fs from "fs";
7-
import { createTerminalServer } from "./lib/node-pty-server.js";
87
import ignore from "ignore";
8+
import { createTerminalServer } from "./lib/node-pty-server.js";
99

1010
// Change path to "Pulse Editor"
1111
app.setName("Pulse Editor");
1212
app.setPath(
1313
"userData",
14-
app.getPath("userData").replace("pulse-editor-desktop", "Pulse Editor")
14+
app.getPath("userData").replace("pulse-editor-desktop", "Pulse Editor"),
1515
);
1616

1717
// Get the file path of the current module
@@ -34,7 +34,9 @@ serve({
3434
scheme: "extension",
3535
});
3636

37-
function createWindow() {
37+
let mainWindow = null;
38+
39+
function createMainWindow() {
3840
const win = new BrowserWindow({
3941
width: 960,
4042
height: 600,
@@ -47,6 +49,7 @@ function createWindow() {
4749
},
4850
icon: path.join(__dirname, "../shared-assets/icons/electron/pulse_editor"),
4951
});
52+
mainWindow = win;
5053

5154
win.menuBarVisible = false;
5255

@@ -134,7 +137,7 @@ async function listPathContent(uri, options, baseUri = undefined) {
134137
(file) =>
135138
(options?.include === "folders" && file.isDirectory()) ||
136139
(options?.include === "files" && file.isFile()) ||
137-
options?.include === "all"
140+
options?.include === "all",
138141
)
139142
// Filter by gitignore
140143
.filter((file) => {
@@ -263,6 +266,77 @@ function handleGetInstallationPath(event) {
263266
return uri;
264267
}
265268

269+
async function handleLogin(event) {
270+
const cookieName = "pulse-editor.session-token";
271+
272+
// Use the default session so cookies are shared automatically
273+
const loginWindow = new BrowserWindow({
274+
width: 600,
275+
height: 700,
276+
show: true,
277+
webPreferences: {
278+
nodeIntegration: false,
279+
contextIsolation: true,
280+
session: session.defaultSession, // ← important
281+
},
282+
});
283+
284+
const signinUrl = "https://pulse-editor.com/api/auth/signin";
285+
await loginWindow.loadURL(signinUrl);
286+
287+
const loginSession = loginWindow.webContents.session;
288+
289+
const interval = setInterval(async () => {
290+
try {
291+
const cookies = await loginSession.cookies.get({ name: cookieName });
292+
if (cookies.length > 0) {
293+
clearInterval(interval);
294+
295+
// Login successful
296+
loginWindow.close();
297+
console.log(`Login successful, cookie "${cookieName}" present.`);
298+
299+
// Reload main window
300+
if (mainWindow) {
301+
mainWindow.reload();
302+
}
303+
}
304+
} catch (err) {
305+
console.error("Error checking cookie:", err);
306+
}
307+
}, 1000);
308+
}
309+
310+
async function handleLogout() {
311+
const mainSession = session.defaultSession;
312+
const cookieName = "pulse-editor.session-token";
313+
314+
try {
315+
const cookies = await mainSession.cookies.get({ name: cookieName });
316+
317+
for (const cookie of cookies) {
318+
const url = cookie.domain.startsWith(".")
319+
? `https://${cookie.domain.slice(1)}${cookie.path}`
320+
: `https://${cookie.domain}${cookie.path}`;
321+
322+
await mainSession.cookies.remove(url, cookie.name);
323+
}
324+
325+
console.log(`Cookie "${cookieName}" removed. Logout successful.`);
326+
327+
// Reload main window
328+
if (mainWindow) {
329+
mainWindow.reload();
330+
}
331+
332+
// Return success to renderer
333+
return { success: true };
334+
} catch (err) {
335+
console.error("Error during logout:", err);
336+
return { success: false, error: err.message };
337+
}
338+
}
339+
266340
let isCreatedTerminal = false;
267341
function handleCreateTerminal(event) {
268342
if (!isCreatedTerminal) {
@@ -303,7 +377,10 @@ app.whenReady().then(() => {
303377

304378
ipcMain.handle("create-terminal", handleCreateTerminal);
305379

306-
createWindow();
380+
ipcMain.handle("login", handleLogin);
381+
ipcMain.handle("logout", handleLogout);
382+
383+
createMainWindow();
307384
});
308385

309386
app.on("window-all-closed", () => {

desktop/preload.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,7 @@ contextBridge.exposeInMainWorld("electronAPI", {
3131
getInstallationPath: () => ipcRenderer.invoke("get-installation-path"),
3232

3333
createTerminal: () => ipcRenderer.invoke("create-terminal"),
34+
35+
login: () => ipcRenderer.invoke("login"),
36+
logout: () => ipcRenderer.invoke("logout"),
3437
});

web/lib/hooks/use-auth.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,16 @@ export function useAuth() {
5959
return;
6060
}
6161

62-
const url = getAPIUrl(`/api/auth/signin`);
6362
if (getPlatform() === PlatformEnum.Electron) {
64-
url.searchParams.set("callbackUrl", "/");
63+
// In Electron, open the sign-in page in the system browser.
64+
// TODO: move this to the platform API layer
65+
// @ts-expect-error window.electronAPI is exposed by the Electron main process
66+
window.electronAPI.login();
6567
} else {
68+
const url = getAPIUrl(`/api/auth/signin`);
6669
url.searchParams.set("callbackUrl", window.location.href);
70+
window.location.href = url.toString();
6771
}
68-
69-
window.location.href = url.toString();
7072
}
7173

7274
// Open a sign-out page if the user is signed in.
@@ -75,10 +77,16 @@ export function useAuth() {
7577
return;
7678
}
7779

78-
const url = getAPIUrl(`/api/auth/signout`);
79-
url.searchParams.set("callbackUrl", window.location.href);
80-
81-
window.location.href = url.toString();
80+
if (getPlatform() === PlatformEnum.Electron) {
81+
// In Electron, open the sign-out page in the system browser.
82+
// TODO: move this to the platform API layer
83+
// @ts-expect-error window.electronAPI is exposed by the Electron main process
84+
window.electronAPI.logout();
85+
} else {
86+
const url = getAPIUrl(`/api/auth/signout`);
87+
url.searchParams.set("callbackUrl", window.location.href);
88+
window.location.href = url.toString();
89+
}
8290
}
8391

8492
return {

0 commit comments

Comments
 (0)