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{' '}