Limit users that can send to telegram bot
This commit is contained in:
parent
4d86549ac6
commit
d1f451da6a
6 changed files with 263 additions and 0 deletions
|
|
@ -0,0 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
const { safeAddColumns } = require('../utils/migration-utils');
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await safeAddColumns(queryInterface, 'users', [
|
||||
{
|
||||
name: 'telegram_allowed_users',
|
||||
definition: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true,
|
||||
comment:
|
||||
'Comma-separated list of allowed Telegram usernames or user IDs',
|
||||
},
|
||||
},
|
||||
]);
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
await queryInterface.removeColumn('users', 'telegram_allowed_users');
|
||||
},
|
||||
};
|
||||
|
|
@ -93,6 +93,12 @@ module.exports = (sequelize) => {
|
|||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
},
|
||||
telegram_allowed_users: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment:
|
||||
'Comma-separated list of allowed Telegram usernames or user IDs',
|
||||
},
|
||||
task_intelligence_enabled: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ router.get('/profile', async (req, res) => {
|
|||
'avatar_image',
|
||||
'telegram_bot_token',
|
||||
'telegram_chat_id',
|
||||
'telegram_allowed_users',
|
||||
'task_summary_enabled',
|
||||
'task_summary_frequency',
|
||||
'task_intelligence_enabled',
|
||||
|
|
@ -81,6 +82,7 @@ router.patch('/profile', async (req, res) => {
|
|||
timezone,
|
||||
avatar_image,
|
||||
telegram_bot_token,
|
||||
telegram_allowed_users,
|
||||
task_intelligence_enabled,
|
||||
task_summary_enabled,
|
||||
task_summary_frequency,
|
||||
|
|
@ -100,6 +102,8 @@ router.patch('/profile', async (req, res) => {
|
|||
allowedUpdates.avatar_image = avatar_image;
|
||||
if (telegram_bot_token !== undefined)
|
||||
allowedUpdates.telegram_bot_token = telegram_bot_token;
|
||||
if (telegram_allowed_users !== undefined)
|
||||
allowedUpdates.telegram_allowed_users = telegram_allowed_users;
|
||||
if (task_intelligence_enabled !== undefined)
|
||||
allowedUpdates.task_intelligence_enabled =
|
||||
task_intelligence_enabled;
|
||||
|
|
@ -158,6 +162,7 @@ router.patch('/profile', async (req, res) => {
|
|||
'avatar_image',
|
||||
'telegram_bot_token',
|
||||
'telegram_chat_id',
|
||||
'telegram_allowed_users',
|
||||
'task_intelligence_enabled',
|
||||
'task_summary_enabled',
|
||||
'task_summary_frequency',
|
||||
|
|
|
|||
|
|
@ -229,6 +229,50 @@ const createInboxItem = async (content, userId, messageId) => {
|
|||
});
|
||||
};
|
||||
|
||||
// Function to check if a Telegram user is authorized
|
||||
const isAuthorizedTelegramUser = (user, message) => {
|
||||
// If no whitelist is configured, allow all users (default behavior)
|
||||
if (
|
||||
!user.telegram_allowed_users ||
|
||||
user.telegram_allowed_users.trim() === ''
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const allowedUsers = user.telegram_allowed_users
|
||||
.split(',')
|
||||
.map((u) => u.trim().toLowerCase())
|
||||
.filter((u) => u.length > 0);
|
||||
|
||||
if (allowedUsers.length === 0) {
|
||||
return true; // Empty whitelist means allow all
|
||||
}
|
||||
|
||||
const fromUser = message.from;
|
||||
if (!fromUser) {
|
||||
return false; // No sender information
|
||||
}
|
||||
|
||||
// Check by user ID (numeric)
|
||||
const userId = fromUser.id.toString();
|
||||
if (allowedUsers.includes(userId)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check by username (with or without @ prefix)
|
||||
if (fromUser.username) {
|
||||
const username = fromUser.username.toLowerCase();
|
||||
if (
|
||||
allowedUsers.includes(username) ||
|
||||
allowedUsers.includes(`@${username}`)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Function to handle bot commands
|
||||
const handleBotCommand = async (command, user, chatId, messageId) => {
|
||||
const botToken = user.telegram_bot_token;
|
||||
|
|
@ -268,6 +312,14 @@ const processMessage = async (user, update) => {
|
|||
const chatId = message.chat.id.toString();
|
||||
const messageId = message.message_id;
|
||||
|
||||
// Check if the user is authorized to send messages to this bot
|
||||
if (!isAuthorizedTelegramUser(user, message)) {
|
||||
console.log(
|
||||
`Ignoring message from unauthorized Telegram user ${message.from.id} (@${message.from.username || 'no_username'}) for bot owner ${user.id}`
|
||||
);
|
||||
return; // Silently ignore unauthorized users
|
||||
}
|
||||
|
||||
// If this user already has a chat bound and it doesn't match this update, ignore
|
||||
if (user.telegram_chat_id && user.telegram_chat_id !== chatId) {
|
||||
return;
|
||||
|
|
@ -518,4 +570,5 @@ module.exports = {
|
|||
_getHighestUpdateId: getHighestUpdateId,
|
||||
_createMessageParams: createMessageParams,
|
||||
_createTelegramUrl: createTelegramUrl,
|
||||
_isAuthorizedTelegramUser: isAuthorizedTelegramUser,
|
||||
};
|
||||
|
|
|
|||
114
backend/tests/unit/services/telegramAuth.test.js
Normal file
114
backend/tests/unit/services/telegramAuth.test.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
const {
|
||||
_isAuthorizedTelegramUser,
|
||||
} = require('../../../services/telegramPoller');
|
||||
|
||||
describe('Telegram Authorization', () => {
|
||||
describe('isAuthorizedTelegramUser', () => {
|
||||
it('should allow all users when no whitelist is configured', () => {
|
||||
const user = { telegram_allowed_users: null };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow all users when whitelist is empty', () => {
|
||||
const user = { telegram_allowed_users: '' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow all users when whitelist is whitespace only', () => {
|
||||
const user = { telegram_allowed_users: ' ' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow user when ID is in whitelist', () => {
|
||||
const user = { telegram_allowed_users: '123456,789012' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow user when username is in whitelist (without @)', () => {
|
||||
const user = { telegram_allowed_users: 'testuser,anotheruser' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow user when username is in whitelist (with @)', () => {
|
||||
const user = { telegram_allowed_users: '@testuser,@anotheruser' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow user in mixed whitelist by ID match', () => {
|
||||
const user = { telegram_allowed_users: '123456,@anotheruser' };
|
||||
const message = { from: { id: 123456, username: 'differentuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should allow user in mixed whitelist by username match', () => {
|
||||
const user = { telegram_allowed_users: '789012,testuser' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny user not in whitelist', () => {
|
||||
const user = { telegram_allowed_users: '789012,@anotheruser' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(false);
|
||||
});
|
||||
|
||||
it('should deny user without username if ID not in whitelist', () => {
|
||||
const user = { telegram_allowed_users: '789012,@testuser' };
|
||||
const message = { from: { id: 123456 } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(false);
|
||||
});
|
||||
|
||||
it('should allow user without username if ID is in whitelist', () => {
|
||||
const user = { telegram_allowed_users: '123456,@testuser' };
|
||||
const message = { from: { id: 123456 } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle case insensitive username matching', () => {
|
||||
const user = { telegram_allowed_users: 'TestUser,@AnotherUser' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny when no sender information is provided', () => {
|
||||
const user = { telegram_allowed_users: 'testuser' };
|
||||
const message = {};
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle whitelist with extra spaces and commas', () => {
|
||||
const user = {
|
||||
telegram_allowed_users: ' testuser , @anotheruser , 123456 ',
|
||||
};
|
||||
const message = { from: { id: 123456, username: 'differentuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true);
|
||||
});
|
||||
|
||||
it('should deny when whitelist contains only empty values after trimming', () => {
|
||||
const user = { telegram_allowed_users: ' , , ' };
|
||||
const message = { from: { id: 123456, username: 'testuser' } };
|
||||
|
||||
expect(_isAuthorizedTelegramUser(user, message)).toBe(true); // Empty after filtering means allow all
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -44,6 +44,7 @@ interface Profile {
|
|||
avatar_image: string | null;
|
||||
telegram_bot_token: string | null;
|
||||
telegram_chat_id: string | null;
|
||||
telegram_allowed_users: string | null;
|
||||
task_summary_enabled: boolean;
|
||||
task_summary_frequency: string;
|
||||
task_intelligence_enabled: boolean;
|
||||
|
|
@ -103,6 +104,7 @@ const ProfileSettings: React.FC<ProfileSettingsProps> = ({
|
|||
timezone: 'UTC',
|
||||
avatar_image: '',
|
||||
telegram_bot_token: '',
|
||||
telegram_allowed_users: '',
|
||||
task_intelligence_enabled: true,
|
||||
task_summary_enabled: false,
|
||||
task_summary_frequency: 'daily',
|
||||
|
|
@ -259,6 +261,7 @@ const ProfileSettings: React.FC<ProfileSettingsProps> = ({
|
|||
timezone: data.timezone || 'UTC',
|
||||
avatar_image: data.avatar_image || '',
|
||||
telegram_bot_token: data.telegram_bot_token || '',
|
||||
telegram_allowed_users: data.telegram_allowed_users || '',
|
||||
task_intelligence_enabled:
|
||||
data.task_intelligence_enabled !== undefined
|
||||
? data.task_intelligence_enabled
|
||||
|
|
@ -643,6 +646,10 @@ const ProfileSettings: React.FC<ProfileSettingsProps> = ({
|
|||
updatedProfile.telegram_bot_token !== undefined
|
||||
? updatedProfile.telegram_bot_token
|
||||
: prev.telegram_bot_token || '',
|
||||
telegram_allowed_users:
|
||||
updatedProfile.telegram_allowed_users !== undefined
|
||||
? updatedProfile.telegram_allowed_users
|
||||
: prev.telegram_allowed_users || '',
|
||||
task_intelligence_enabled:
|
||||
updatedProfile.task_intelligence_enabled !== undefined
|
||||
? updatedProfile.task_intelligence_enabled
|
||||
|
|
@ -1423,6 +1430,61 @@ const ProfileSettings: React.FC<ProfileSettingsProps> = ({
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{t(
|
||||
'profile.telegramAllowedUsers',
|
||||
'Allowed Users'
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="telegram_allowed_users"
|
||||
value={
|
||||
formData.telegram_allowed_users || ''
|
||||
}
|
||||
onChange={handleChange}
|
||||
placeholder="@username1, 123456789, @username2"
|
||||
className="mt-1 block w-full border border-gray-300 dark:border-gray-700 rounded-md shadow-sm px-3 py-2 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
<div className="mt-2 text-xs text-gray-500 dark:text-gray-400 space-y-1">
|
||||
<p>
|
||||
{t(
|
||||
'profile.telegramAllowedUsersDescription',
|
||||
'Control who can send messages to your bot. Leave empty to allow all users.'
|
||||
)}
|
||||
</p>
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold text-gray-600 dark:text-gray-300">
|
||||
{t('profile.examples', 'Examples:')}
|
||||
</p>
|
||||
<ul className="list-disc list-inside ml-2 space-y-0.5">
|
||||
<li>
|
||||
<span className="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded">
|
||||
@alice, @bob
|
||||
</span>
|
||||
{' - '}
|
||||
{t('profile.exampleUsernames', 'Allow specific usernames')}
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded">
|
||||
123456789, 987654321
|
||||
</span>
|
||||
{' - '}
|
||||
{t('profile.exampleUserIds', 'Allow specific user IDs')}
|
||||
</li>
|
||||
<li>
|
||||
<span className="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded">
|
||||
@alice, 123456789
|
||||
</span>
|
||||
{' - '}
|
||||
{t('profile.exampleMixed', 'Mix usernames and user IDs')}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{profile?.telegram_chat_id && (
|
||||
<div className="p-2 bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-800 rounded text-green-800 dark:text-green-200">
|
||||
<p className="text-sm">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue