From 2b7c546f84c5bc9d3b1e677652cd0d669c5daf9f Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Thu, 10 Sep 2026 21:34:42 -0400 Subject: [PATCH 1/3] fix: clarify GitHub auth methods Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/renderer/hooks/useLogins.test.tsx | 29 +++++++++++++++++-- src/renderer/hooks/useLogins.ts | 13 +++++++-- src/renderer/routes/Accounts.tsx | 6 +++- .../github/LoginWithDeviceFlow.test.tsx | 1 + .../routes/github/LoginWithDeviceFlow.tsx | 2 +- .../LoginWithPersonalAccessToken.test.tsx | 8 +++++ .../github/LoginWithPersonalAccessToken.tsx | 6 ++-- ...LoginWithPersonalAccessToken.test.tsx.snap | 8 ++--- src/renderer/utils/auth/types.ts | 2 +- .../utils/forges/github/adapter.test.ts | 3 +- src/renderer/utils/forges/github/adapter.ts | 11 +++---- src/renderer/utils/forges/github/auth.test.ts | 18 ++++++++++++ src/renderer/utils/forges/github/auth.ts | 16 +++++++++- 13 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/renderer/hooks/useLogins.test.tsx b/src/renderer/hooks/useLogins.test.tsx index b734fce2b..3dc6178fc 100644 --- a/src/renderer/hooks/useLogins.test.tsx +++ b/src/renderer/hooks/useLogins.test.tsx @@ -4,7 +4,11 @@ import type { ReactNode } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { setNotificationsOverrides } from '../__helpers__/hook-mocks'; -import { mockBitbucketAccount, mockGitHubCloudAccount } from '../__mocks__/account-mocks'; +import { + mockBitbucketAccount, + mockGitHubAppAccount, + mockGitHubCloudAccount, +} from '../__mocks__/account-mocks'; import { Constants } from '../constants'; @@ -87,7 +91,28 @@ describe('renderer/hooks/useLogins.ts', () => { ); }); - expect(createAccountSpy).toHaveBeenCalledWith('GitHub App', 'token', 'github.com', 'github'); + expect(createAccountSpy).toHaveBeenCalledWith( + 'Gitify OAuth App', + 'token', + 'github.com', + 'github', + ); + }); + + it('migrates a legacy GitHub App device-flow account after re-authentication', async () => { + useAccountsStore.setState({ accounts: [mockGitHubAppAccount] }); + const { result } = renderLoginsHook(); + + await act(async () => { + await result.current.loginWithDeviceFlowComplete( + 'github', + 'token' as Token, + Constants.GITHUB_HOSTNAME, + ); + }); + + expect(removeAccountNotificationsMock).toHaveBeenCalledWith(mockGitHubAppAccount); + expect(removeAccountSpy).toHaveBeenCalledWith(mockGitHubAppAccount); }); it('loginWithOAuthApp delegates to the forge adapter', async () => { diff --git a/src/renderer/hooks/useLogins.ts b/src/renderer/hooks/useLogins.ts index 76f510678..bf6007b6b 100644 --- a/src/renderer/hooks/useLogins.ts +++ b/src/renderer/hooks/useLogins.ts @@ -94,14 +94,23 @@ export const useLogins = (): LoginsState => { } const method = deviceFlow.authMethod; - const existingAccount = accounts.find((a) => a.hostname === hostname && a.method === method); + const existingAccount = accounts.find( + (a) => + a.hostname === hostname && + (a.method === method || + (forge === 'github' && method === 'Gitify OAuth App' && a.method === 'GitHub App')), + ); if (existingAccount) { await removeAccountNotifications(existingAccount); } await createAccount(method, token, hostname, forge); + + if (existingAccount?.method === 'GitHub App' && method === 'Gitify OAuth App') { + removeAccount(existingAccount); + } }, - [accounts, createAccount, removeAccountNotifications], + [accounts, createAccount, removeAccount, removeAccountNotifications], ); /** diff --git a/src/renderer/routes/Accounts.tsx b/src/renderer/routes/Accounts.tsx index d86b75459..810b984a6 100644 --- a/src/renderer/routes/Accounts.tsx +++ b/src/renderer/routes/Accounts.tsx @@ -112,8 +112,12 @@ export const AccountsRoute: FC = () => { }; const handleReAuthenticate = (account: Account) => { + const authMethod = + account.forge === 'github' && account.method === 'GitHub App' + ? 'Gitify OAuth App' + : account.method; const loginMethod = getAdapter(account).loginMethods.find( - (method) => method.authMethod === account.method, + (method) => method.authMethod === authMethod, ); if (!loginMethod) { diff --git a/src/renderer/routes/github/LoginWithDeviceFlow.test.tsx b/src/renderer/routes/github/LoginWithDeviceFlow.test.tsx index 8b42a6158..9488ba50e 100644 --- a/src/renderer/routes/github/LoginWithDeviceFlow.test.tsx +++ b/src/renderer/routes/github/LoginWithDeviceFlow.test.tsx @@ -30,6 +30,7 @@ describe('renderer/routes/github/LoginWithDeviceFlow.tsx', () => { }); expect(screen.getByText('Receive notifications for:')).toBeInTheDocument(); + expect(screen.getByText("Authorize Gitify's OAuth App")).toBeInTheDocument(); expect(screen.getByTestId('device-scope-public')).toBeInTheDocument(); expect(screen.getByTestId('device-scope-full')).toBeInTheDocument(); diff --git a/src/renderer/routes/github/LoginWithDeviceFlow.tsx b/src/renderer/routes/github/LoginWithDeviceFlow.tsx index 1de9eb1fc..e4e160913 100644 --- a/src/renderer/routes/github/LoginWithDeviceFlow.tsx +++ b/src/renderer/routes/github/LoginWithDeviceFlow.tsx @@ -295,7 +295,7 @@ export const GitHubLoginWithDeviceFlowRoute: FC = () => { return ( -
Authorize with GitHub
+
Authorize Gitify's OAuth App
{error && ( diff --git a/src/renderer/routes/github/LoginWithPersonalAccessToken.test.tsx b/src/renderer/routes/github/LoginWithPersonalAccessToken.test.tsx index 6ea5c7297..cabff5b2f 100644 --- a/src/renderer/routes/github/LoginWithPersonalAccessToken.test.tsx +++ b/src/renderer/routes/github/LoginWithPersonalAccessToken.test.tsx @@ -25,6 +25,14 @@ describe('renderer/routes/github/LoginWithPersonalAccessToken.tsx', () => { expect(tree.container).toMatchSnapshot(); }); + it('explains that fine-grained tokens are unsupported', () => { + renderWithProviders(); + + expect( + screen.getByText(/Fine-grained personal access tokens are not supported/), + ).toBeInTheDocument(); + }); + it('let us go back', async () => { renderWithProviders(); diff --git a/src/renderer/routes/github/LoginWithPersonalAccessToken.tsx b/src/renderer/routes/github/LoginWithPersonalAccessToken.tsx index 36c8c997e..6eceba31a 100644 --- a/src/renderer/routes/github/LoginWithPersonalAccessToken.tsx +++ b/src/renderer/routes/github/LoginWithPersonalAccessToken.tsx @@ -12,13 +12,13 @@ export const GitHubLoginWithPersonalAccessTokenRoute: FC = () => ( forge="github" hostnameCaption="Change only if you are using GitHub Enterprise Server" hostnamePlaceholder="github.com" - title="Login with Personal Access Token" - tokenPlaceholder="Your generated token (40 characters)" + title="Login with Classic Personal Access Token" + tokenPlaceholder="Your classic token (40 characters)" tokenSettingsCaption="on GitHub to paste the token below." tokenSettingsLabel="Generate a PAT" > - The{' '} + Fine-grained personal access tokens are not supported. The{' '}