feat(git): add token authentication and credential discovery

Add support for token-based git authentication in identity profiles.
Enable discovery and import of credentials from ~/.git-credentials file.
Introduce remote URL-based filtering for token identities in git view.
This commit is contained in:
btriapitsyn
2026-01-14 02:47:26 +02:00
parent d56eef8aa6
commit fa83c1e645
16 changed files with 704 additions and 44 deletions
@@ -0,0 +1,87 @@
import fs from 'fs';
import path from 'path';
import os from 'os';
const GIT_CREDENTIALS_PATH = path.join(os.homedir(), '.git-credentials');
/**
* Parse ~/.git-credentials file and return discovered credentials.
* Format: https://username:token@host or https://username:token@host/path
* @returns {Array<{host: string, username: string}>}
*/
export function discoverGitCredentials() {
const credentials = [];
if (!fs.existsSync(GIT_CREDENTIALS_PATH)) {
return credentials;
}
try {
const content = fs.readFileSync(GIT_CREDENTIALS_PATH, 'utf8');
const lines = content.split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const url = new URL(line.trim());
const hostname = url.hostname;
const pathname = url.pathname && url.pathname !== '/' ? url.pathname : '';
// Include path for repo-specific tokens (e.g., github.com/user/repo)
const host = hostname + pathname;
const username = url.username || '';
if (host && username) {
// Avoid duplicates
const exists = credentials.some(c => c.host === host && c.username === username);
if (!exists) {
credentials.push({ host, username });
}
}
} catch {
// Skip malformed lines
continue;
}
}
} catch (error) {
console.error('Failed to read .git-credentials:', error);
}
return credentials;
}
/**
* Get credential for a specific host from ~/.git-credentials
* @param {string} host - The host to look up (e.g., "github.com" or "github.com/user/repo")
* @returns {{username: string, token: string} | null}
*/
export function getCredentialForHost(host) {
if (!fs.existsSync(GIT_CREDENTIALS_PATH)) {
return null;
}
try {
const content = fs.readFileSync(GIT_CREDENTIALS_PATH, 'utf8');
const lines = content.split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const url = new URL(line.trim());
const hostname = url.hostname;
const pathname = url.pathname && url.pathname !== '/' ? url.pathname : '';
const credHost = hostname + pathname;
if (credHost === host) {
return {
username: url.username || '',
token: url.password || ''
};
}
} catch {
continue;
}
}
} catch (error) {
console.error('Failed to read .git-credentials for host lookup:', error);
}
return null;
}