feat(telegram): Zugriffskontrolle mit Chat-ID-Whitelist

- TelegramService: IsChatIdAllowed() prüft Owner + Whitelist
- /start von unbekannter ChatId: Zugriff verweigert + ChatId angezeigt
- Owner wird bei Zugriffsanfrage benachrichtigt (Username + ChatId)
- Alle Commands/Messages prüfen Whitelist vor Verarbeitung
- Neues Feld telegram_allowed_chat_ids in sessions-Tabelle
- Frontend: Input für erlaubte Chat-IDs in Session-Telegram-Settings
- API: allowedChatIds in Save/Get durchgereicht
This commit is contained in:
Meleki committed 2026-03-06 13:04:02 +01:00
1 parent 5661e55898
commit 011b26c37f
7 files changed
+91 -10

No files matched your search

+3 -1
View File
@@ -81,8 +81,9 @@ public static class TelegramEndpoint
var body = await JsonSerializer.DeserializeAsync<JsonElement>(req.Body);
var botToken = body.TryGetProperty("botToken", out var t) ? t.GetString() : null;
var webhookSecret = body.TryGetProperty("webhookSecret", out var s) ? s.GetString() : null;
var allowedChatIds = body.TryGetProperty("allowedChatIds", out var a) ? a.GetString() : session.TelegramAllowedChatIds;
await db.UpdateSessionTelegram(sessionId, botToken, webhookSecret, session.TelegramChatId);
await db.UpdateSessionTelegram(sessionId, botToken, webhookSecret, session.TelegramChatId, allowedChatIds);
await res.WriteAsJsonAsync(new { ok = true });
}
@@ -102,6 +103,7 @@ public static class TelegramEndpoint
botToken = session.TelegramBotToken ?? "",
webhookSecret = session.TelegramWebhookSecret ?? "",
chatId = session.TelegramChatId ?? "",
allowedChatIds = session.TelegramAllowedChatIds ?? "",
configured = !string.IsNullOrEmpty(session.TelegramBotToken)
});
}
+1
View File
@@ -16,6 +16,7 @@ public class SessionRecord
public string TelegramBotToken { get; set; }
public string TelegramWebhookSecret { get; set; }
public string TelegramChatId { get; set; }
public string TelegramAllowedChatIds { get; set; }
public double AvatarScale { get; set; } = 1.0;
public string CreatedAt { get; set; }
public string UpdatedAt { get; set; }
+4 -3
View File
@@ -162,6 +162,7 @@ public class DatabaseService
TryAddColumn(conn, "sessions", "telegram_bot_token", "TEXT");
TryAddColumn(conn, "sessions", "telegram_webhook_secret", "TEXT");
TryAddColumn(conn, "sessions", "telegram_chat_id", "TEXT");
TryAddColumn(conn, "sessions", "telegram_allowed_chat_ids", "TEXT");
TryAddColumn(conn, "agents", "user_id", "TEXT");
TryAddColumn(conn, "users", "is_admin", "INTEGER NOT NULL DEFAULT 0");
TryAddColumn(conn, "users", "persona", "TEXT");
@@ -956,14 +957,14 @@ public class DatabaseService
new { chatId = chatId.ToString() });
}
public async Task UpdateSessionTelegram(string sessionId, string botToken, string webhookSecret, string chatId)
public async Task UpdateSessionTelegram(string sessionId, string botToken, string webhookSecret, string chatId, string allowedChatIds = null)
{
using var conn = CreateConnection();
await conn.ExecuteAsync(@"
UPDATE sessions SET telegram_bot_token = @botToken, telegram_webhook_secret = @webhookSecret,
telegram_chat_id = @chatId, updated_at = @now
telegram_chat_id = @chatId, telegram_allowed_chat_ids = @allowedChatIds, updated_at = @now
WHERE session_id = @sessionId",
new { sessionId, botToken, webhookSecret, chatId, now = DateTime.UtcNow.ToString("o") });
new { sessionId, botToken, webhookSecret, chatId, allowedChatIds, now = DateTime.UtcNow.ToString("o") });
}
public async Task SetSessionTelegramChatId(string sessionId, string chatId)
+73 -3
View File
@@ -60,7 +60,46 @@ public class TelegramService
if (text.StartsWith("/start"))
{
await _db.SetSessionTelegramChatId(session.SessionId, chatId.ToString());
if (!IsChatIdAllowed(session, chatId))
{
// Telegram-Username aus dem Update extrahieren
var username = "";
if (message.TryGetProperty("from", out var from))
{
if (from.TryGetProperty("username", out var uname))
username = uname.GetString() ?? "";
else if (from.TryGetProperty("first_name", out var fname))
username = fname.GetString() ?? "";
}
await SendMessage(chatId, botToken,
"🔒 Zugriff verweigert.\n\n" +
"Du bist nicht für diesen Bot freigegeben. " +
"Bitte kontaktiere den Inhaber und teile deine Chat-ID mit:\n\n" +
$"📋 Deine Chat-ID: `{chatId}`",
parseMode: "Markdown");
// Owner benachrichtigen (wenn chatId gesetzt)
if (!string.IsNullOrEmpty(session.TelegramChatId))
{
var ownerChatId = long.Parse(session.TelegramChatId);
var requesterInfo = string.IsNullOrEmpty(username) ? chatId.ToString() : $"@{username} ({chatId})";
await SendMessage(ownerChatId, botToken,
$"🔔 *Zugriffsanfrage*\n\n" +
$"Benutzer {EscapeMd(requesterInfo)} möchte diesen Bot nutzen\\.\n\n" +
$"Chat\\-ID: `{chatId}`\n\n" +
"Zum Freigeben: Chat\\-ID in den Session\\-Einstellungen unter _Erlaubte Chat\\-IDs_ hinzufügen\\.",
parseMode: "MarkdownV2");
}
return;
}
// Erste Verbindung: chatId als Owner setzen (wenn noch keine gesetzt)
if (string.IsNullOrEmpty(session.TelegramChatId))
{
await _db.SetSessionTelegramChatId(session.SessionId, chatId.ToString());
}
var sessionName = session.Name ?? session.SessionId;
await SendMessage(chatId, botToken,
$"✅ *Verbunden mit Session: {EscapeMd(sessionName)}*\n\n" +
@@ -73,6 +112,13 @@ public class TelegramService
return;
}
// Alle anderen Befehle/Nachrichten: Zugriff prüfen
if (!IsChatIdAllowed(session, chatId))
{
await SendMessage(chatId, botToken, "🔒 Du bist nicht für diesen Bot freigegeben. Sende /start für mehr Informationen.");
return;
}
if (text.StartsWith("/"))
{
await HandleCommand(chatId, text, botToken, session);
@@ -122,9 +168,9 @@ public class TelegramService
private async Task HandleMessage(long chatId, string userText, string botToken, SessionRecord session)
{
if (session.TelegramChatId != chatId.ToString())
if (!IsChatIdAllowed(session, chatId))
{
await SendMessage(chatId, botToken, "❌ Bitte sende zuerst /start um dich mit dieser Session zu verbinden.");
await SendMessage(chatId, botToken, "🔒 Du bist nicht für diesen Bot freigegeben. Sende /start für mehr Informationen.");
return;
}
@@ -280,6 +326,30 @@ public class TelegramService
// ── Helper ──
private static bool IsChatIdAllowed(SessionRecord session, long chatId)
{
var chatIdStr = chatId.ToString();
// Owner (die primäre verknüpfte chatId) ist immer erlaubt
if (session.TelegramChatId == chatIdStr) return true;
// Wenn noch kein Owner gesetzt und keine Whitelist → erster User wird Owner
if (string.IsNullOrEmpty(session.TelegramChatId) && string.IsNullOrEmpty(session.TelegramAllowedChatIds))
return true;
// Whitelist prüfen
if (!string.IsNullOrEmpty(session.TelegramAllowedChatIds))
{
var allowed = session.TelegramAllowedChatIds.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var id in allowed)
{
if (id == chatIdStr) return true;
}
}
return false;
}
private static string EscapeMd(string text)
{
if (string.IsNullOrEmpty(text)) return "";
+3
View File
@@ -127,6 +127,9 @@
<input type="text" id="sessionTelegramToken" placeholder="123456:ABC-DEF..." autocomplete="off">
<label>Webhook-Secret</label>
<input type="text" id="sessionTelegramSecret" readonly style="opacity: var(--semi-transparent); ">
<label>Erlaubte Chat-IDs</label>
<input type="text" id="sessionTelegramAllowed" placeholder="Kommagetrennt, z.B. 123456,789012">
<small class="form-hint">Der verknüpfte Owner ist automatisch erlaubt. Weitere Chat-IDs hier eintragen.</small>
<label>Status</label>
<div id="sessionTelegramStatusBox" class="telegram-status-box">Kein Status verfügbar</div>
<div class="telegram-actions">
+2 -2
View File
@@ -271,11 +271,11 @@ async function apiSessionTelegramGet(sessionId) {
return resp.json();
}
async function apiSessionTelegramSave(sessionId, botToken, webhookSecret) {
async function apiSessionTelegramSave(sessionId, botToken, webhookSecret, allowedChatIds) {
const resp = await fetch(`/api/sessions/${sessionId}/telegram`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ botToken, webhookSecret })
body: JSON.stringify({ botToken, webhookSecret, allowedChatIds })
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
+5 -1
View File
@@ -180,6 +180,7 @@ registerPage('session', {
function initSessionTelegram(sessionId) {
var tokenInput = document.getElementById('sessionTelegramToken');
var secretInput = document.getElementById('sessionTelegramSecret');
var allowedInput = document.getElementById('sessionTelegramAllowed');
var registerBtn = document.getElementById('sessionTelegramRegisterBtn');
var deleteBtn = document.getElementById('sessionTelegramDeleteBtn');
var statusBox = document.getElementById('sessionTelegramStatusBox');
@@ -187,6 +188,7 @@ function initSessionTelegram(sessionId) {
// Vorhandene Config laden + Status abrufen
apiSessionTelegramGet(sessionId).then(async function(config) {
if (config.botToken) tokenInput.value = config.botToken;
if (config.allowedChatIds) allowedInput.value = config.allowedChatIds;
if (config.webhookSecret) {
secretInput.value = config.webhookSecret;
} else {
@@ -294,7 +296,9 @@ async function refreshTelegramStatus(sessionId, statusBox, registerBtn, deleteBt
async function saveTelegramConfig(sessionId, tokenInput, secretInput) {
var token = tokenInput.value.trim();
var secret = secretInput.value.trim();
await apiSessionTelegramSave(sessionId, token, secret);
var allowedInput = document.getElementById('sessionTelegramAllowed');
var allowed = allowedInput ? allowedInput.value.trim() : '';
await apiSessionTelegramSave(sessionId, token, secret, allowed);
}
function escapeHtml(str) {