Skip to content

feat(accounts): warn when missing scopes #1688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Dec 28, 2024
1 change: 1 addition & 0 deletions src/renderer/__mocks__/partial-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function partialMockNotification(
hostname: Constants.GITHUB_API_BASE_URL as Hostname,
token: mockToken,
user: mockGitifyUser,
hasRequiredScopes: true,
},
subject: subject as Subject,
};
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/__mocks__/state-mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const mockPersonalAccessTokenAccount: Account = {
token: 'token-123-456' as Token,
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockOAuthAccount: Account = {
Expand All @@ -42,6 +43,7 @@ export const mockOAuthAccount: Account = {
token: '1234568790' as Token,
hostname: 'github.gitify.io' as Hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockGitHubCloudAccount: Account = {
Expand All @@ -51,6 +53,7 @@ export const mockGitHubCloudAccount: Account = {
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
user: mockGitifyUser,
version: 'latest',
hasRequiredScopes: true,
};

export const mockGitHubEnterpriseServerAccount: Account = {
Expand All @@ -59,6 +62,7 @@ export const mockGitHubEnterpriseServerAccount: Account = {
token: '1234568790' as Token,
hostname: 'github.gitify.io' as Hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockGitHubAppAccount: Account = {
Expand All @@ -67,6 +71,7 @@ export const mockGitHubAppAccount: Account = {
token: '987654321' as Token,
hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname,
user: mockGitifyUser,
hasRequiredScopes: true,
};

export const mockAuth: AuthState = {
Expand Down
39 changes: 39 additions & 0 deletions src/renderer/routes/Accounts.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,45 @@ describe('renderer/routes/Accounts.tsx', () => {
expect(screen.getByTestId('accounts')).toMatchSnapshot();
});

it('should render with PAT scopes warning', async () => {
const openExternalLinkMock = jest
.spyOn(comms, 'openExternalLink')
.mockImplementation();

await act(async () => {
render(
<AppContext.Provider
value={{
auth: {
accounts: [
{
...mockPersonalAccessTokenAccount,
hasRequiredScopes: false,
},
mockOAuthAccount,
mockGitHubAppAccount,
],
},
settings: mockSettings,
}}
>
<MemoryRouter>
<AccountsRoute />
</MemoryRouter>
</AppContext.Provider>,
);
});

expect(screen.getByTestId('accounts')).toMatchSnapshot();

fireEvent.click(screen.getByLabelText('missing-scopes'));

expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
expect(openExternalLinkMock).toHaveBeenCalledWith(
'https://github.com/settings/tokens',
);
});

it('should go back by pressing the icon', async () => {
await act(async () => {
render(
Expand Down
21 changes: 20 additions & 1 deletion src/renderer/routes/Accounts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
AlertFillIcon,
FeedPersonIcon,
KeyIcon,
MarkGithubIcon,
Expand All @@ -23,6 +24,7 @@ import { type Account, IconColor, Size } from '../types';
import { getAccountUUID, refreshAccount } from '../utils/auth/utils';
import { cn } from '../utils/cn';
import { updateTrayIcon, updateTrayTitle } from '../utils/comms';
import { Constants } from '../utils/constants';
import {
openAccountProfile,
openDeveloperSettings,
Expand Down Expand Up @@ -78,7 +80,7 @@ export const AccountsRoute: FC = () => {
className="mb-4 flex items-center justify-between rounded-md bg-gray-100 p-2 dark:bg-gray-sidebar"
>
<div className="ml-2 text-xs">
<div>
<div className="flex flex-1 items-center gap-2">
<button
type="button"
className="flex flex-1 gap-2 items-center justify-center mb-1 cursor-pointer text-sm font-semibold"
Expand All @@ -99,6 +101,23 @@ export const AccountsRoute: FC = () => {
({account.user?.name})
</span>
</button>

{account.hasRequiredScopes === false && (
<span className="text-xs font-medium italic">
<button
type="button"
className="cursor-pointer"
title={`This account is missing one or more required scopes: \n - ${Constants.AUTH_SCOPE.join('\n - ')}`}
aria-label="missing-scopes"
onClick={() => openDeveloperSettings(account)}
>
<AlertFillIcon
size={Size.XSMALL}
className={IconColor.RED}
/>
</button>
</span>
)}
</div>
<div>
<button
Expand Down
Loading
Loading