Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/auth-flows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ vi.mock('@octokit/rest', () => ({
id: 'id1',
login: 'user1',
},
headers: {
'x-oauth-scopes': 'admin:org, read:user, read:project',
},
})),
},
},
Expand Down Expand Up @@ -116,18 +119,20 @@ test('deviceFlow user canceled', async () => {
test('PATFlow', async () => {
const sessionIdBeforeCall = sessionId;
vi.mocked(extensionApi.window.showInputBox).mockResolvedValue('PATtoken1234');
const consoleWarn = vi.spyOn(console, 'warn');

const inputBoxOptions = {
title: 'Authenticate to GitHub with Personal Access Token',
prompt: 'Enter you GitHub Personal Access Token in the input box below. Make sure that this PAT has all the necessary permissions: scope1, scope_2',
prompt: 'Enter you GitHub Personal Access Token in the input box. Make sure that this PAT has all the necessary permissions: read:user, write:org, some scope',
placeHolder: 'Enter PAT',
password: true,
};

const newPATSession = await PATFlow(['scope1', 'scope_2']);
const newPATSession = await PATFlow(['read:user', 'write:org', 'some scope']);

expect(extensionApi.window.showInputBox).toBeCalledWith(inputBoxOptions);
expect(Octokit).toHaveBeenCalledWith({auth: 'PATtoken1234'});
expect(consoleWarn).toHaveBeenCalledWith('Some required permission scopes are missing from the PAT scopes: some scope. Please check and update the token as necessary.');

expect(newPATSession).toEqual({
id: `github-PAT-${sessionIdBeforeCall}`,
Expand All @@ -136,7 +141,7 @@ test('PATFlow', async () => {
id: 'id1',
label: 'user1',
},
scopes: ['scope1', 'scope_2'],
scopes: ['admin:org', 'write:org', 'read:org', 'read:user', 'read:project'],
});
});

Expand All @@ -145,7 +150,7 @@ test('PATFlow error', async () => {

const inputBoxOptions = {
title: 'Authenticate to GitHub with Personal Access Token',
prompt: 'Enter you GitHub Personal Access Token in the input box below. Make sure that this PAT has all the necessary permissions: scope1, scope_2',
prompt: 'Enter you GitHub Personal Access Token in the input box. Make sure that this PAT has all the necessary permissions: scope1, scope_2',
placeHolder: 'Enter PAT',
password: true,
};
Expand Down
12 changes: 10 additions & 2 deletions src/auth-flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import * as extensionApi from '@podman-desktop/api';

import { waitForDeviceCodeAccessToken } from './auth-flows-helpers';
import { config } from './config';
import { GITHUB_SCOPES } from './github-scopes';

export let sessionId = 1;

Expand Down Expand Up @@ -83,7 +84,7 @@ export async function deviceFlow(scopes: string[]): Promise<extensionApi.Authent
export async function PATFlow(scopes: string[]): Promise<extensionApi.AuthenticationSession> {
const inputBoxOptions: extensionApi.InputBoxOptions = {
title: 'Authenticate to GitHub with Personal Access Token',
prompt: `Enter you GitHub Personal Access Token in the input box below. Make sure that this PAT has all the necessary permissions: ${scopes.join(', ')}`,
prompt: `Enter you GitHub Personal Access Token in the input box. Make sure that this PAT has all the necessary permissions: ${scopes.join(', ')}`,
placeHolder: 'Enter PAT',
password: true,
};
Expand All @@ -99,13 +100,20 @@ export async function PATFlow(scopes: string[]): Promise<extensionApi.Authentica
});
const user = await octokit.rest.users.getAuthenticated();

const authorizedScopes = user.headers['x-oauth-scopes']?.split(', ').flatMap(scope => GITHUB_SCOPES[scope] ? [scope, ...GITHUB_SCOPES[scope]] : [scope]);

const missingScopes = scopes.filter(scope => !authorizedScopes?.includes(scope));
if (missingScopes.length > 0) {
console.warn(`Some required permission scopes are missing from the PAT scopes: ${missingScopes.join(', ')}. Please check and update the token as necessary.`);
}

return {
id: `github-PAT-${sessionId++}`,
accessToken: PATToken,
account: {
id: `${user.data.id}`,
label: user.data.login,
},
scopes: scopes,
scopes: authorizedScopes ?? [],
};
}
66 changes: 66 additions & 0 deletions src/github-scopes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**********************************************************************
* Copyright (C) 2025 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/

export const GITHUB_SCOPES: { [key: string] : string[]} = {
'repo': [
'repo:status',
'repo_deployment',
'public_repo',
'repo:invite',
'security_events',
],
'admin:repo_hook': [
'write:repo_hook',
'read:repo_hook',
],
'write:repo_hook': [
'read:repo_hook',
],
'admin:org': [
'write:org',
'read:org',
],
'write:org': [
'read:org',
],
'admin:public_key': [
'write:public_key',
'read:public_key',
],
'write:public_key': [
'read:public_key',
],
'user': [
'read:user',
'user:email',
'user:follow',
],
'project': [
'read:project',
],
'write:packages': [
'read:packages',
],
'admin:gpg_key': [
'write:gpg_key',
'read:gpg_key',
],
'write:gpg_key': [
'read:gpg_key',
],
};