From 1fe032eb851a55d216b77986eb51acd1af8dc5d9 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 24 Nov 2025 13:39:54 -0500 Subject: [PATCH 1/6] fix: account token encryption/decryption Signed-off-by: Adam Setch --- src/renderer/context/App.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index 6060069e2..e703277b5 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -43,6 +43,7 @@ import { removeAccount, } from '../utils/auth/utils'; import { + decryptValue, encryptValue, setAutoLaunch, setKeyboardShortcut, @@ -149,6 +150,30 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { return Promise.all(auth.accounts.map(refreshAccount)); }, [auth.accounts]); + const migrateAuthTokens = useCallback(async () => { + let tokensMigrated = false; + + for (const account of auth.accounts) { + /** + * Check if the account is using an encrypted token. + * If not encrypt it and save it. + */ + try { + await decryptValue(account.token); + } catch (_err) { + const encryptedToken = await encryptValue(account.token); + account.token = encryptedToken as Token; + + tokensMigrated = true; + } + } + + if (tokensMigrated) { + setAuth(auth); + saveState({ auth, settings }); + } + }, [auth, settings]); + // biome-ignore lint/correctness/useExhaustiveDependencies: Fetch new notifications when account count or filters change useEffect(() => { fetchNotifications({ auth, settings }); @@ -178,6 +203,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { // biome-ignore lint/correctness/useExhaustiveDependencies: Refresh account details on startup useEffect(() => { + migrateAuthTokens(); + refreshAllAccounts(); }, []); From 6c95b075149b321dd4e28a75728ee7bdec11575d Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 24 Nov 2025 16:57:34 -0500 Subject: [PATCH 2/6] fix: account token encryption/decryption Signed-off-by: Adam Setch --- src/renderer/context/App.tsx | 44 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index e703277b5..a7ebb3706 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -150,28 +150,34 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { return Promise.all(auth.accounts.map(refreshAccount)); }, [auth.accounts]); + // TODO - Remove migration logic in future release const migrateAuthTokens = useCallback(async () => { - let tokensMigrated = false; - - for (const account of auth.accounts) { - /** - * Check if the account is using an encrypted token. - * If not encrypt it and save it. - */ - try { - await decryptValue(account.token); - } catch (_err) { - const encryptedToken = await encryptValue(account.token); - account.token = encryptedToken as Token; - - tokensMigrated = true; - } - } + const migratedAccounts = await Promise.all( + auth.accounts.map(async (account) => { + try { + await decryptValue(account.token); + return account; + } catch { + const encryptedToken = (await encryptValue(account.token)) as Token; + return { ...account, token: encryptedToken }; + } + }), + ); - if (tokensMigrated) { - setAuth(auth); - saveState({ auth, settings }); + const tokensMigrated = migratedAccounts.some( + (account, index) => account.token !== auth.accounts[index]?.token, + ); + + if (!tokensMigrated) { + return; } + + const updatedAuth: AuthState = { + accounts: migratedAccounts, + }; + + setAuth(updatedAuth); + saveState({ auth: updatedAuth, settings }); }, [auth, settings]); // biome-ignore lint/correctness/useExhaustiveDependencies: Fetch new notifications when account count or filters change From ec81a6d872cac8d39ef940fa2a473b6d5058ec4d Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 24 Nov 2025 17:06:16 -0500 Subject: [PATCH 3/6] fix: write state on account refresh Signed-off-by: Adam Setch --- src/renderer/context/App.tsx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index a7ebb3706..d93d922a6 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -142,13 +142,22 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { unsubscribeNotification, } = useNotifications(); - const refreshAllAccounts = useCallback(() => { + const refreshAllAccounts = useCallback(async () => { if (!auth.accounts.length) { return; } - return Promise.all(auth.accounts.map(refreshAccount)); - }, [auth.accounts]); + const refreshedAccounts = await Promise.all( + auth.accounts.map((account) => refreshAccount(account)), + ); + + const updatedAuth: AuthState = { + accounts: refreshedAccounts, + }; + + setAuth(updatedAuth); + saveState({ auth: updatedAuth, settings }); + }, [auth, settings]); // TODO - Remove migration logic in future release const migrateAuthTokens = useCallback(async () => { @@ -209,9 +218,10 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { // biome-ignore lint/correctness/useExhaustiveDependencies: Refresh account details on startup useEffect(() => { - migrateAuthTokens(); - - refreshAllAccounts(); + void (async () => { + await migrateAuthTokens(); + await refreshAllAccounts(); + })(); }, []); // Refresh account details on interval From 1d5747d4221752477e9c9947d8874fc6c4f40da0 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 24 Nov 2025 17:13:14 -0500 Subject: [PATCH 4/6] fix: write state on account refresh Signed-off-by: Adam Setch --- src/renderer/context/App.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index d93d922a6..d3475d1d3 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -216,7 +216,10 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { settings.fetchType === FetchType.INACTIVITY ? settings.fetchInterval : null, ); - // biome-ignore lint/correctness/useExhaustiveDependencies: Refresh account details on startup + /** + * On startup, check if auth tokens need encrypting and refresh all account details + */ + // biome-ignore lint/correctness/useExhaustiveDependencies: Run once on startup useEffect(() => { void (async () => { await migrateAuthTokens(); From 7ef2b893bdcbfdaf4f225a8d105946698a300e1e Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 24 Nov 2025 17:27:47 -0500 Subject: [PATCH 5/6] fix: write state on account refresh Signed-off-by: Adam Setch --- src/renderer/context/App.tsx | 54 ++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index d3475d1d3..a7d3ceff3 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -37,6 +37,7 @@ import type { import { addAccount, authGitHub, + getAccountUUID, getToken, hasAccounts, refreshAccount, @@ -142,6 +143,14 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { unsubscribeNotification, } = useNotifications(); + const persistAuth = useCallback( + (nextAuth: AuthState) => { + setAuth(nextAuth); + saveState({ auth: nextAuth, settings }); + }, + [settings], + ); + const refreshAllAccounts = useCallback(async () => { if (!auth.accounts.length) { return; @@ -155,9 +164,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { accounts: refreshedAccounts, }; - setAuth(updatedAuth); - saveState({ auth: updatedAuth, settings }); - }, [auth, settings]); + persistAuth(updatedAuth); + }, [auth, persistAuth]); // TODO - Remove migration logic in future release const migrateAuthTokens = useCallback(async () => { @@ -173,9 +181,18 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { }), ); - const tokensMigrated = migratedAccounts.some( - (account, index) => account.token !== auth.accounts[index]?.token, - ); + const tokensMigrated = migratedAccounts.some((migratedAccount) => { + const originalAccount = auth.accounts.find( + (account) => + getAccountUUID(account) === getAccountUUID(migratedAccount), + ); + + if (!originalAccount) { + return true; + } + + return migratedAccount.token !== originalAccount.token; + }); if (!tokensMigrated) { return; @@ -185,9 +202,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { accounts: migratedAccounts, }; - setAuth(updatedAuth); - saveState({ auth: updatedAuth, settings }); - }, [auth, settings]); + persistAuth(updatedAuth); + }, [auth, persistAuth]); // biome-ignore lint/correctness/useExhaustiveDependencies: Fetch new notifications when account count or filters change useEffect(() => { @@ -364,9 +380,8 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { const updatedAuth = await addAccount(auth, 'GitHub App', token, hostname); - setAuth(updatedAuth); - saveState({ auth: updatedAuth, settings }); - }, [auth, settings]); + persistAuth(updatedAuth); + }, [auth, persistAuth]); const loginWithOAuthApp = useCallback( async (data: LoginOAuthAppOptions) => { @@ -375,10 +390,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { const updatedAuth = await addAccount(auth, 'OAuth App', token, hostname); - setAuth(updatedAuth); - saveState({ auth: updatedAuth, settings }); + persistAuth(updatedAuth); }, - [auth, settings], + [auth, persistAuth], ); const loginWithPersonalAccessToken = useCallback( @@ -393,10 +407,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { hostname, ); - setAuth(updatedAuth); - saveState({ auth: updatedAuth, settings }); + persistAuth(updatedAuth); }, - [auth, settings], + [auth, persistAuth], ); const logoutFromAccount = useCallback( @@ -405,10 +418,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { const updatedAuth = removeAccount(auth, account); - setAuth(updatedAuth); - saveState({ auth: updatedAuth, settings }); + persistAuth(updatedAuth); }, - [auth, settings, removeAccountNotifications], + [auth, removeAccountNotifications, persistAuth], ); const fetchNotificationsWithAccounts = useCallback( From 5b8df4a86a37879e264c8c2a52db0953d3aec929 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 24 Nov 2025 17:31:32 -0500 Subject: [PATCH 6/6] fix: write state on account refresh Signed-off-by: Adam Setch --- src/renderer/context/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index a7d3ceff3..068a34707 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -161,6 +161,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { ); const updatedAuth: AuthState = { + ...auth, accounts: refreshedAccounts, }; @@ -199,6 +200,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { } const updatedAuth: AuthState = { + ...auth, accounts: migratedAccounts, };