fix: treat gh CLI token as GitHub account
This commit is contained in:
@@ -12,6 +12,7 @@ const SETTINGS_FILE = path.join(OPENCHAMBER_DATA_DIR, 'settings.json');
|
||||
|
||||
const DEFAULT_GITHUB_CLIENT_ID = 'Ov23lizomPOC3eFYo56r';
|
||||
const DEFAULT_GITHUB_SCOPES = 'repo read:org workflow read:user user:email';
|
||||
export const GH_CLI_ACCOUNT_ID = 'gh-cli';
|
||||
|
||||
function ensureStorageDir() {
|
||||
if (!fs.existsSync(STORAGE_DIR)) {
|
||||
@@ -159,6 +160,34 @@ function writeAuthList(list) {
|
||||
writeJsonFile(list);
|
||||
}
|
||||
|
||||
function readSettingsFile() {
|
||||
try {
|
||||
if (fs.existsSync(SETTINGS_FILE)) {
|
||||
return JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8')) || {};
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function writeSettingsFile(settings) {
|
||||
ensureStorageDir();
|
||||
const tmpFile = `${SETTINGS_FILE}.${process.pid}.${Date.now()}.tmp`;
|
||||
fs.writeFileSync(tmpFile, JSON.stringify(settings, null, 2), 'utf8');
|
||||
try {
|
||||
fs.chmodSync(tmpFile, 0o600);
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
fs.renameSync(tmpFile, SETTINGS_FILE);
|
||||
try {
|
||||
fs.chmodSync(SETTINGS_FILE, 0o600);
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
}
|
||||
|
||||
export function getGitHubAuth() {
|
||||
const list = readAuthList();
|
||||
if (!list.length) {
|
||||
@@ -237,6 +266,7 @@ export function activateGitHubAuth(accountId) {
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
setGhCliActive(false);
|
||||
list.forEach((entry, idx) => {
|
||||
entry.current = idx === index;
|
||||
});
|
||||
@@ -307,39 +337,25 @@ export function getGitHubScopes() {
|
||||
export const GITHUB_AUTH_FILE = STORAGE_FILE;
|
||||
|
||||
export function isGhCliDisabled() {
|
||||
try {
|
||||
if (fs.existsSync(SETTINGS_FILE)) {
|
||||
const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
||||
return Boolean(parsed?.ghCliDisabled);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return false;
|
||||
return Boolean(readSettingsFile()?.ghCliDisabled);
|
||||
}
|
||||
|
||||
export function setGhCliDisabled(disabled) {
|
||||
ensureStorageDir();
|
||||
let settings = {};
|
||||
try {
|
||||
if (fs.existsSync(SETTINGS_FILE)) {
|
||||
settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8')) || {};
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
const settings = readSettingsFile();
|
||||
settings.ghCliDisabled = Boolean(disabled);
|
||||
const tmpFile = `${SETTINGS_FILE}.${process.pid}.${Date.now()}.tmp`;
|
||||
fs.writeFileSync(tmpFile, JSON.stringify(settings, null, 2), 'utf8');
|
||||
try {
|
||||
fs.chmodSync(tmpFile, 0o600);
|
||||
} catch {
|
||||
// best-effort
|
||||
}
|
||||
fs.renameSync(tmpFile, SETTINGS_FILE);
|
||||
try {
|
||||
fs.chmodSync(SETTINGS_FILE, 0o600);
|
||||
} catch {
|
||||
// best-effort
|
||||
if (settings.ghCliDisabled) {
|
||||
settings.ghCliActive = false;
|
||||
}
|
||||
writeSettingsFile(settings);
|
||||
}
|
||||
|
||||
export function isGhCliActive() {
|
||||
const settings = readSettingsFile();
|
||||
return !settings?.ghCliDisabled && Boolean(settings?.ghCliActive);
|
||||
}
|
||||
|
||||
export function setGhCliActive(active) {
|
||||
const settings = readSettingsFile();
|
||||
settings.ghCliActive = Boolean(active) && !settings.ghCliDisabled;
|
||||
writeSettingsFile(settings);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ export {
|
||||
clearGitHubAuth,
|
||||
getGitHubClientId,
|
||||
getGitHubScopes,
|
||||
GH_CLI_ACCOUNT_ID,
|
||||
isGhCliDisabled,
|
||||
isGhCliActive,
|
||||
setGhCliActive,
|
||||
setGhCliDisabled,
|
||||
GITHUB_AUTH_FILE,
|
||||
} from './auth.js';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { getGitHubAuth, isGhCliDisabled } from './auth.js';
|
||||
import { getGitHubAuth, isGhCliActive, isGhCliDisabled } from './auth.js';
|
||||
import { getGhCliToken } from './gh-cli-credential.js';
|
||||
|
||||
export function getOctokitOrNull() {
|
||||
const auth = getGitHubAuth();
|
||||
const token = auth?.accessToken || (!isGhCliDisabled() ? getGhCliToken() : null);
|
||||
const ghToken = !isGhCliDisabled() ? getGhCliToken() : null;
|
||||
const token = isGhCliActive() ? ghToken || auth?.accessToken : auth?.accessToken || ghToken;
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -76,17 +76,18 @@ export function registerGitHubRoutes(app) {
|
||||
|
||||
app.get('/api/github/auth/status', async (_req, res) => {
|
||||
try {
|
||||
const { getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts, isGhCliDisabled } = await getGitHubLibraries();
|
||||
const { getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts, GH_CLI_ACCOUNT_ID, isGhCliActive, isGhCliDisabled, setGhCliActive } = await getGitHubLibraries();
|
||||
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
||||
|
||||
const auth = getGitHubAuth();
|
||||
const accounts = getGitHubAuthAccounts();
|
||||
let accounts = getGitHubAuthAccounts();
|
||||
const ghCliDisabled = isGhCliDisabled();
|
||||
const ghCliActive = isGhCliActive();
|
||||
const ghToken = getGhCliToken();
|
||||
const usingOwnToken = Boolean(auth?.accessToken);
|
||||
let ghCliUser = null;
|
||||
|
||||
if (ghToken !== null && !ghCliDisabled && usingOwnToken) {
|
||||
if (ghToken !== null && !ghCliDisabled) {
|
||||
try {
|
||||
const { Octokit } = await import('@octokit/rest');
|
||||
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
||||
@@ -94,11 +95,26 @@ export function registerGitHubRoutes(app) {
|
||||
ghCliUser = null;
|
||||
}
|
||||
}
|
||||
if (ghCliActive && !ghCliUser) {
|
||||
setGhCliActive(false);
|
||||
}
|
||||
|
||||
const ghCliCurrent = ghToken !== null && !ghCliDisabled && Boolean(ghCliUser) && (ghCliActive || !usingOwnToken);
|
||||
if (ghCliUser) {
|
||||
accounts = accounts
|
||||
.map((account) => ({ ...account, current: ghCliCurrent ? false : Boolean(account.current) }))
|
||||
.concat({
|
||||
id: GH_CLI_ACCOUNT_ID,
|
||||
user: ghCliUser,
|
||||
current: ghCliCurrent,
|
||||
source: 'gh-cli',
|
||||
});
|
||||
}
|
||||
|
||||
const buildGhCli = (activeUser = null) => ({
|
||||
available: ghToken !== null,
|
||||
disabled: ghCliDisabled,
|
||||
active: !usingOwnToken && ghToken !== null && !ghCliDisabled,
|
||||
active: ghCliCurrent,
|
||||
...(!ghCliDisabled && (activeUser || ghCliUser) ? { user: activeUser || ghCliUser } : {}),
|
||||
});
|
||||
|
||||
@@ -119,14 +135,12 @@ export function registerGitHubRoutes(app) {
|
||||
|
||||
const fallback = usingOwnToken ? auth.user : null;
|
||||
const mergedUser = user || fallback;
|
||||
const ghIsActive = !usingOwnToken && ghToken !== null && !ghCliDisabled;
|
||||
|
||||
return res.json({
|
||||
connected: true,
|
||||
user: mergedUser,
|
||||
scope: usingOwnToken ? auth.scope : undefined,
|
||||
scope: ghCliCurrent ? undefined : auth?.scope,
|
||||
accounts,
|
||||
ghCli: buildGhCli(ghIsActive ? mergedUser : null),
|
||||
ghCli: buildGhCli(ghCliCurrent ? mergedUser : null),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get GitHub auth status:', error);
|
||||
@@ -238,25 +252,70 @@ export function registerGitHubRoutes(app) {
|
||||
|
||||
app.post('/api/github/auth/activate', async (req, res) => {
|
||||
try {
|
||||
const { activateGitHubAuth, getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts } = await getGitHubLibraries();
|
||||
const { activateGitHubAuth, getGitHubAuth, getOctokitOrNull, clearGitHubAuth, getGitHubAuthAccounts, GH_CLI_ACCOUNT_ID, isGhCliDisabled, setGhCliActive } = await getGitHubLibraries();
|
||||
const accountId = typeof req.body?.accountId === 'string' ? req.body.accountId : '';
|
||||
if (!accountId) {
|
||||
return res.status(400).json({ error: 'accountId is required' });
|
||||
}
|
||||
if (accountId === GH_CLI_ACCOUNT_ID) {
|
||||
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
||||
const ghToken = !isGhCliDisabled() ? getGhCliToken() : null;
|
||||
if (!ghToken) {
|
||||
return res.status(404).json({ error: 'GitHub CLI account not found' });
|
||||
}
|
||||
|
||||
const { Octokit } = await import('@octokit/rest');
|
||||
const user = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
||||
setGhCliActive(true);
|
||||
const accounts = getGitHubAuthAccounts()
|
||||
.map((account) => ({ ...account, current: false }))
|
||||
.concat({ id: GH_CLI_ACCOUNT_ID, user, current: true, source: 'gh-cli' });
|
||||
return res.json({
|
||||
connected: true,
|
||||
user,
|
||||
accounts,
|
||||
ghCli: {
|
||||
available: true,
|
||||
disabled: false,
|
||||
active: true,
|
||||
user,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const activated = activateGitHubAuth(accountId);
|
||||
if (!activated) {
|
||||
return res.status(404).json({ error: 'GitHub account not found' });
|
||||
}
|
||||
|
||||
const auth = getGitHubAuth();
|
||||
const accounts = getGitHubAuthAccounts();
|
||||
let accounts = getGitHubAuthAccounts();
|
||||
if (!auth?.accessToken) {
|
||||
return res.json({ connected: false, accounts });
|
||||
}
|
||||
|
||||
const { getGhCliToken } = await import('./gh-cli-credential.js');
|
||||
const ghCliDisabled = isGhCliDisabled();
|
||||
const ghToken = !ghCliDisabled ? getGhCliToken() : null;
|
||||
let ghCliUser = null;
|
||||
if (ghToken) {
|
||||
try {
|
||||
const { Octokit } = await import('@octokit/rest');
|
||||
ghCliUser = await getGitHubUserSummary(new Octokit({ auth: ghToken }));
|
||||
accounts = accounts.concat({
|
||||
id: GH_CLI_ACCOUNT_ID,
|
||||
user: ghCliUser,
|
||||
current: false,
|
||||
source: 'gh-cli',
|
||||
});
|
||||
} catch {
|
||||
ghCliUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
const octokit = getOctokitOrNull();
|
||||
if (!octokit) {
|
||||
return res.json({ connected: false, accounts });
|
||||
return res.json({ connected: false, accounts, ghCli: { available: ghToken !== null, disabled: ghCliDisabled, active: false, ...(ghCliUser ? { user: ghCliUser } : {}) } });
|
||||
}
|
||||
|
||||
let user = auth.user || null;
|
||||
@@ -274,6 +333,12 @@ export function registerGitHubRoutes(app) {
|
||||
user,
|
||||
scope: auth.scope,
|
||||
accounts,
|
||||
ghCli: {
|
||||
available: ghToken !== null,
|
||||
disabled: ghCliDisabled,
|
||||
active: false,
|
||||
...(ghCliUser ? { user: ghCliUser } : {}),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to activate GitHub account:', error);
|
||||
|
||||
Reference in New Issue
Block a user