mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-30 21:59:46 +02:00
feat: implement auth token synchronization between Electron and web app
- Added IPC channels for getting and setting auth tokens in Electron. - Implemented functions to sync tokens from localStorage to Electron and vice versa. - Updated components to ensure tokens are retrieved from Electron when not available locally. - Enhanced user authentication flow by integrating token management across windows.
This commit is contained in:
parent
eb5799336c
commit
e920923fa4
8 changed files with 82 additions and 12 deletions
|
|
@ -3,7 +3,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { USER_QUERY_KEY } from "@/atoms/user/user-query.atoms";
|
||||
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
|
||||
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
|
||||
import { getBearerToken, ensureTokensFromElectron, redirectToLogin } from "@/lib/auth-utils";
|
||||
import { queryClient } from "@/lib/query-client/client";
|
||||
|
||||
interface DashboardLayoutProps {
|
||||
|
|
@ -17,15 +17,20 @@ export default function DashboardLayout({ children }: DashboardLayoutProps) {
|
|||
useGlobalLoadingEffect(isCheckingAuth);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if user is authenticated
|
||||
const token = getBearerToken();
|
||||
if (!token) {
|
||||
// Save current path and redirect to login
|
||||
redirectToLogin();
|
||||
return;
|
||||
async function checkAuth() {
|
||||
let token = getBearerToken();
|
||||
if (!token) {
|
||||
const synced = await ensureTokensFromElectron();
|
||||
if (synced) token = getBearerToken();
|
||||
}
|
||||
if (!token) {
|
||||
redirectToLogin();
|
||||
return;
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: [...USER_QUERY_KEY] });
|
||||
setIsCheckingAuth(false);
|
||||
}
|
||||
queryClient.invalidateQueries({ queryKey: [...USER_QUERY_KEY] });
|
||||
setIsCheckingAuth(false);
|
||||
checkAuth();
|
||||
}, []);
|
||||
|
||||
// Return null while loading - the global provider handles the loading UI
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getBearerToken } from "@/lib/auth-utils";
|
||||
import { getBearerToken, ensureTokensFromElectron } from "@/lib/auth-utils";
|
||||
|
||||
type SSEEvent =
|
||||
| { type: "text-delta"; id: string; delta: string }
|
||||
|
|
@ -65,7 +65,11 @@ export default function SuggestionPage() {
|
|||
setSuggestion("");
|
||||
setError(null);
|
||||
|
||||
const token = getBearerToken();
|
||||
let token = getBearerToken();
|
||||
if (!token) {
|
||||
await ensureTokensFromElectron();
|
||||
token = getBearerToken();
|
||||
}
|
||||
if (!token) {
|
||||
setError(friendlyError("not authenticated"));
|
||||
setIsLoading(false);
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ export function getBearerToken(): string | null {
|
|||
export function setBearerToken(token: string): void {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(BEARER_TOKEN_KEY, token);
|
||||
syncTokensToElectron();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -111,6 +112,7 @@ export function getRefreshToken(): string | null {
|
|||
export function setRefreshToken(token: string): void {
|
||||
if (typeof window === "undefined") return;
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, token);
|
||||
syncTokensToElectron();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -129,6 +131,44 @@ export function clearAllTokens(): void {
|
|||
clearRefreshToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the current localStorage tokens into the Electron main process
|
||||
* so that other BrowserWindows (Quick Ask, Autocomplete) can access them.
|
||||
*/
|
||||
function syncTokensToElectron(): void {
|
||||
if (typeof window === "undefined" || !window.electronAPI?.setAuthTokens) return;
|
||||
const bearer = localStorage.getItem(BEARER_TOKEN_KEY) || "";
|
||||
const refresh = localStorage.getItem(REFRESH_TOKEN_KEY) || "";
|
||||
if (bearer) {
|
||||
window.electronAPI.setAuthTokens(bearer, refresh);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to pull auth tokens from the Electron main process into localStorage.
|
||||
* Useful for popup windows (Quick Ask, Autocomplete) on platforms where
|
||||
* localStorage is not reliably shared across BrowserWindow instances.
|
||||
* Returns true if tokens were found and written to localStorage.
|
||||
*/
|
||||
export async function ensureTokensFromElectron(): Promise<boolean> {
|
||||
if (typeof window === "undefined" || !window.electronAPI?.getAuthTokens) return false;
|
||||
if (getBearerToken()) return true;
|
||||
|
||||
try {
|
||||
const tokens = await window.electronAPI.getAuthTokens();
|
||||
if (tokens?.bearer) {
|
||||
localStorage.setItem(BEARER_TOKEN_KEY, tokens.bearer);
|
||||
if (tokens.refresh) {
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
// IPC failure — fall through
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout the current user by revoking the refresh token and clearing localStorage.
|
||||
* Returns true if logout was successful (or tokens were cleared), false otherwise.
|
||||
|
|
|
|||
3
surfsense_web/types/window.d.ts
vendored
3
surfsense_web/types/window.d.ts
vendored
|
|
@ -78,6 +78,9 @@ interface ElectronAPI {
|
|||
// Browse files/folders via native dialogs
|
||||
browseFiles: () => Promise<string[] | null>;
|
||||
readLocalFiles: (paths: string[]) => Promise<LocalFileData[]>;
|
||||
// Auth token sync across windows
|
||||
getAuthTokens: () => Promise<{ bearer: string; refresh: string } | null>;
|
||||
setAuthTokens: (bearer: string, refresh: string) => Promise<void>;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue