mirror of
https://github.com/soberhacker/obsidian-telegram-sync.git
synced 2026-07-22 07:50:31 +00:00
feat!: add encryption by pin code
This commit is contained in:
parent
f404ac5b49
commit
791178dbd8
10 changed files with 95 additions and 51 deletions
|
|
@ -5,9 +5,7 @@
|
|||
✅ processing messages older than 24 hours if Obsidian wasn't running<br/>
|
||||
✅ easy installing the latest published beta versions<br/>
|
||||
✅ plugin updates in the channel instead of informational messages in your bot<br/>
|
||||
✅ processing messages older than 24 hours if Obsidian wasn't running<br/>
|
||||
❌ sending notes from Telegram to Obsidian (_not implemented_)<br/>
|
||||
❌ getting messages from other bots in connected group chats (_not implemented_)<br/>
|
||||
❌ posting messages in selected chats (_not implemented_)<br/>
|
||||
❌ processing messages from other bots in connected group chats (_not implemented_)<br/>
|
||||
❌ posting notes as messages in selected Telegram chats (_not implemented_)<br/>
|
||||
|
||||
**⚠ For all of these features, you must connect both your Telegram user and bot.**
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
// TODO NEXT: check reconnecting
|
||||
// TODO NEXT: bur in reconnecting on MacBook https://t.me/sm1rnov_id
|
||||
import { compareVersions } from "compare-versions";
|
||||
export const releaseVersion = "3.3.0";
|
||||
export const releaseVersion = "4.0.0";
|
||||
export const showNewFeatures = true;
|
||||
export let showBreakingChanges = false;
|
||||
export let showBreakingChanges = true;
|
||||
|
||||
const newFeatures = `In this release, security has been enhanced by encrypting the bot token with a PIN code`;
|
||||
export const breakingChanges = `⚠️ <b><i>Breaking changes!\n\nThe user's connection to the plugin may need to be reestablished due to the GramJs library update.\n\nGrant your bot admin rights if you want to use bot reactions in groups and channels.</i></b> ⚠️`;
|
||||
const newFeatures = `In this release, security has been enhanced by encrypting the bot token`;
|
||||
export const breakingChanges = `⚠️ <b><i>Breaking changes!\n\nBot token may need to be re-entered.</i></b> ⚠️`;
|
||||
export const telegramChannelLink = "https://t.me/obsidian_telegram_sync";
|
||||
export const insiderFeaturesLink =
|
||||
"https://github.com/soberhacker/obsidian-telegram-sync/blob/main/docs/Telegram%20Sync%20Insider%20Features.md";
|
||||
|
|
@ -20,7 +20,7 @@ const telegramChannelAHref = `<a href='${telegramChannelLink}'>channel</a>`;
|
|||
const insiderFeaturesAHref = `<a href='${insiderFeaturesLink}'>insider features</a>`;
|
||||
const telegramChannelIntroduction = `Subscribe for free to the plugin's ${telegramChannelAHref} and enjoy access to ${insiderFeaturesAHref} and the latest beta versions, several months ahead of public release.`;
|
||||
const telegramChatLink = "<a href='https://t.me/tribute/app?startapp=sfFf'>chat</a>";
|
||||
const telegramChatIntroduction = `Join the plugin's ${telegramChatLink} - your space to seek advice, ask questions, and share knowledge (access via the @tribute bot).`;
|
||||
const telegramChatIntroduction = `Join the plugin's ${telegramChatLink} - your space to seek advice, ask questions, and share knowledge (access via the tribute bot).`;
|
||||
const donation = `If you appreciate this plugin and would like to support its continued development, please consider donating through the buttons below or via Telegram Stars in the ${telegramChannelAHref}!`;
|
||||
const bestRegards = "Best regards,\nYour soberhacker🍃🧘💻\n⌞";
|
||||
|
||||
|
|
|
|||
36
src/main.ts
36
src/main.ts
|
|
@ -32,7 +32,7 @@ import {
|
|||
} from "./settings/messageDistribution";
|
||||
import os from "os";
|
||||
import { clearCachedUnprocessedMessages, forwardUnprocessedMessages } from "./telegram/user/sync";
|
||||
import { decrypt } from "./utils/crypto256";
|
||||
import { decrypt, encrypt } from "./utils/crypto256";
|
||||
import { PinCodeModal } from "./settings/modals/PinCode";
|
||||
|
||||
// TODO LOW: add "connecting"
|
||||
|
|
@ -61,7 +61,7 @@ export default class TelegramSyncPlugin extends Plugin {
|
|||
status: PluginStatus = "loading";
|
||||
time4processOldMessages = false;
|
||||
processOldMessagesIntervalId?: NodeJS.Timer;
|
||||
pinCode? = "";
|
||||
pinCode?: string = undefined;
|
||||
|
||||
async initTelegram(initType?: Client.SessionType) {
|
||||
this.lastPollingErrors = [];
|
||||
|
|
@ -253,6 +253,11 @@ export default class TelegramSyncPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
if (!this.settings.botTokenEncrypted) {
|
||||
this.botTokenEncrypt();
|
||||
needToSaveSettings = true;
|
||||
}
|
||||
|
||||
needToSaveSettings && (await this.saveSettings());
|
||||
}
|
||||
|
||||
|
|
@ -277,15 +282,26 @@ export default class TelegramSyncPlugin extends Plugin {
|
|||
else displayAndLogError(this, error, StatusMessages.BOT_DISCONNECTED, checkConnectionMessage, undefined, 0);
|
||||
}
|
||||
|
||||
getBotToken(): string {
|
||||
if (!this.settings.botTokenEncryption) return this.settings.botToken;
|
||||
if (!this.pinCode) {
|
||||
const pinCodeModal = new PinCodeModal(this, true);
|
||||
pinCodeModal.onClose = async () => {
|
||||
if (!this.pinCode) displayAndLog(this, "Plugin Telegram Sync stopped. No pin code entered.");
|
||||
};
|
||||
pinCodeModal.open();
|
||||
async getBotToken(): Promise<string> {
|
||||
if (!this.settings.botTokenEncrypted) return this.settings.botToken;
|
||||
|
||||
if (this.settings.encryptionByPinCode && !this.pinCode) {
|
||||
await new Promise((resolve) => {
|
||||
const pinCodeModal = new PinCodeModal(this, true);
|
||||
pinCodeModal.onClose = async () => {
|
||||
if (!this.pinCode) displayAndLog(this, "Plugin Telegram Sync stopped. No pin code entered.");
|
||||
resolve(undefined);
|
||||
};
|
||||
pinCodeModal.open();
|
||||
});
|
||||
}
|
||||
return decrypt(this.settings.botToken, this.pinCode);
|
||||
}
|
||||
|
||||
botTokenEncrypt(saveSettings = false) {
|
||||
this.settings.botToken = encrypt(this.settings.botToken, this.pinCode);
|
||||
this.settings.botTokenEncrypted = true;
|
||||
saveSettings && this.saveSettings();
|
||||
displayAndLog(this, "Bot token encrypted", 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@ export interface RefreshValues {
|
|||
|
||||
export interface TelegramSyncSettings {
|
||||
botToken: string;
|
||||
botTokenEncryption: boolean;
|
||||
encryptionByPinCode: boolean;
|
||||
botTokenEncrypted: boolean;
|
||||
deleteMessagesFromTelegram: boolean;
|
||||
allowedChats: string[];
|
||||
mainDeviceId: string;
|
||||
|
|
@ -73,7 +74,8 @@ export interface TelegramSyncSettings {
|
|||
|
||||
export const DEFAULT_SETTINGS: TelegramSyncSettings = {
|
||||
botToken: "",
|
||||
botTokenEncryption: false,
|
||||
encryptionByPinCode: false,
|
||||
botTokenEncrypted: false,
|
||||
deleteMessagesFromTelegram: false,
|
||||
allowedChats: [""],
|
||||
mainDeviceId: "",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Modal, Setting } from "obsidian";
|
||||
import TelegramSyncPlugin from "src/main";
|
||||
import { encrypt } from "src/utils/crypto256";
|
||||
import { _5sec, displayAndLog } from "src/utils/logUtils";
|
||||
import { PinCodeModal } from "./PinCode";
|
||||
|
||||
|
|
@ -18,7 +17,7 @@ export class BotSettingsModal extends Modal {
|
|||
this.addBotToken();
|
||||
this.addAllowedChatsSetting();
|
||||
this.addDeviceId();
|
||||
this.addBotTokenEncryption();
|
||||
this.addEncryptionByPinCode();
|
||||
this.addFooterButtons();
|
||||
}
|
||||
|
||||
|
|
@ -49,9 +48,9 @@ export class BotSettingsModal extends Modal {
|
|||
new Setting(this.botSettingsDiv)
|
||||
.setName("Bot token (required)")
|
||||
.setDesc("Enter your Telegram bot token.")
|
||||
.addText((text) => {
|
||||
.addText(async (text) => {
|
||||
text.setPlaceholder("example: 6123456784:AAX9mXnFE2q9WahQ")
|
||||
.setValue(this.plugin.settings.botToken)
|
||||
.setValue(await this.plugin.getBotToken())
|
||||
.onChange(async (value: string) => {
|
||||
if (!value) {
|
||||
text.inputEl.style.borderColor = "red";
|
||||
|
|
@ -59,6 +58,7 @@ export class BotSettingsModal extends Modal {
|
|||
text.inputEl.style.borderStyle = "solid";
|
||||
}
|
||||
this.plugin.settings.botToken = value;
|
||||
this.plugin.settings.botTokenEncrypted = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -129,24 +129,28 @@ export class BotSettingsModal extends Modal {
|
|||
});
|
||||
}
|
||||
|
||||
addBotTokenEncryption() {
|
||||
addEncryptionByPinCode() {
|
||||
const botTokenSetting = new Setting(this.botSettingsDiv)
|
||||
.setName("Bot token encryption using a PIN code")
|
||||
.setDesc(
|
||||
"Encrypt the bot token for enhanced security. When enabled, a PIN code is required at each Obsidian launch. ",
|
||||
)
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(this.plugin.settings.botTokenEncryption);
|
||||
toggle.setValue(this.plugin.settings.encryptionByPinCode);
|
||||
toggle.onChange(async (value) => {
|
||||
this.plugin.settings.botTokenEncryption = value;
|
||||
await this.plugin.saveSettings();
|
||||
if (!value) return;
|
||||
if (this.plugin.settings.botTokenEncrypted) {
|
||||
this.plugin.settings.botToken = await this.plugin.getBotToken();
|
||||
this.plugin.settings.botTokenEncrypted = false;
|
||||
}
|
||||
this.plugin.settings.encryptionByPinCode = value;
|
||||
if (!value) {
|
||||
this.plugin.pinCode = undefined;
|
||||
return;
|
||||
}
|
||||
const pinCodeModal = new PinCodeModal(this.plugin, false);
|
||||
pinCodeModal.onClose = async () => {
|
||||
if (pinCodeModal.saved && this.plugin.pinCode) {
|
||||
this.plugin.settings.botToken = encrypt(this.plugin.settings.botToken, this.plugin.pinCode);
|
||||
} else this.plugin.settings.botTokenEncryption = false;
|
||||
await this.plugin.saveSettings();
|
||||
if (pinCodeModal.saved && this.plugin.pinCode) return;
|
||||
this.plugin.settings.encryptionByPinCode = false;
|
||||
};
|
||||
pinCodeModal.open();
|
||||
});
|
||||
|
|
@ -164,6 +168,7 @@ export class BotSettingsModal extends Modal {
|
|||
b.setTooltip("Connect")
|
||||
.setIcon("checkmark")
|
||||
.onClick(async () => {
|
||||
if (!this.plugin.settings.botTokenEncrypted) this.plugin.botTokenEncrypt();
|
||||
await this.plugin.saveSettings();
|
||||
this.saved = true;
|
||||
this.close();
|
||||
|
|
|
|||
|
|
@ -18,10 +18,15 @@ export class PinCodeModal extends Modal {
|
|||
this.addFooterButtons();
|
||||
}
|
||||
|
||||
success = async () => {
|
||||
this.saved = true;
|
||||
this.close();
|
||||
};
|
||||
|
||||
addHeader() {
|
||||
this.contentEl.empty();
|
||||
this.pinCodeDiv = this.contentEl.createDiv();
|
||||
this.titleEl.setText((this.decrypt ? "Decrypting" : "Encrypting") + " bot token");
|
||||
this.titleEl.setText("Telegram Sync: " + (this.decrypt ? "Decrypting" : "Encrypting") + " bot token");
|
||||
}
|
||||
|
||||
addPinCode() {
|
||||
|
|
@ -37,6 +42,10 @@ export class PinCodeModal extends Modal {
|
|||
}
|
||||
this.plugin.pinCode = value;
|
||||
});
|
||||
text.inputEl.addEventListener("keydown", (event: KeyboardEvent) => {
|
||||
if (!(event.key === "Enter")) return;
|
||||
this.success.call(this);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -44,12 +53,7 @@ export class PinCodeModal extends Modal {
|
|||
this.pinCodeDiv.createEl("br");
|
||||
const footerButtons = new Setting(this.contentEl.createDiv());
|
||||
footerButtons.addButton((b) => {
|
||||
b.setTooltip("Connect")
|
||||
.setIcon("checkmark")
|
||||
.onClick(async () => {
|
||||
this.saved = true;
|
||||
this.close();
|
||||
});
|
||||
b.setTooltip("Connect").setIcon("checkmark").onClick(this.success);
|
||||
return b;
|
||||
});
|
||||
footerButtons.addExtraButton((b) => {
|
||||
|
|
@ -57,7 +61,7 @@ export class PinCodeModal extends Modal {
|
|||
.setTooltip("Cancel")
|
||||
.onClick(async () => {
|
||||
this.saved = false;
|
||||
this.plugin.pinCode = "";
|
||||
this.plugin.pinCode = undefined;
|
||||
this.close();
|
||||
});
|
||||
return b;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import TelegramSyncPlugin from "src/main";
|
|||
import { _1sec, displayAndLog } from "src/utils/logUtils";
|
||||
import { handleMessage } from "./message/handlers";
|
||||
import { reconnect } from "../user/user";
|
||||
import { enqueueByCondition } from "src/utils/queues";
|
||||
import { enqueue, enqueueByCondition } from "src/utils/queues";
|
||||
|
||||
// Initialize the Telegram bot and set up message handling
|
||||
export async function connect(plugin: TelegramSyncPlugin) {
|
||||
|
|
@ -18,7 +18,7 @@ export async function connect(plugin: TelegramSyncPlugin) {
|
|||
return;
|
||||
}
|
||||
// Create a new bot instance and start polling
|
||||
plugin.bot = new TelegramBot(plugin.getBotToken());
|
||||
plugin.bot = new TelegramBot(await enqueue(plugin, plugin.getBotToken));
|
||||
const bot = plugin.bot;
|
||||
// Set connected flag to false and log errors when a polling error occurs
|
||||
bot.on("polling_error", async (error: unknown) => {
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ export async function handleMessage(plugin: TelegramSyncPlugin, msg: TelegramBot
|
|||
const { fileObject, fileType } = getFileObject(msg);
|
||||
// skip system messages
|
||||
|
||||
!isChannelPost && (await enqueue(ifNewReleaseThenShowChanges, plugin, msg));
|
||||
|
||||
if (!msg.text && !fileObject) {
|
||||
displayAndLog(plugin, `System message skipped`, 0);
|
||||
return;
|
||||
|
|
@ -143,7 +145,6 @@ export async function handleMessage(plugin: TelegramSyncPlugin, msg: TelegramBot
|
|||
try {
|
||||
if (!msg.text && distributionRule.filePathTemplate) await handleFiles(plugin, msg, distributionRule);
|
||||
else await handleMessageText(plugin, msg, distributionRule);
|
||||
!isChannelPost && (await enqueue(ifNewReleaseThenShowChanges, plugin, msg));
|
||||
} catch (error) {
|
||||
await displayAndLogError(plugin, error, "", "", msg, _15sec);
|
||||
} finally {
|
||||
|
|
@ -225,6 +226,7 @@ export async function handleFiles(
|
|||
const chatId = msg.chat.id < 0 ? msg.chat.id.toString().slice(4) : msg.chat.id.toString();
|
||||
telegramFileName =
|
||||
telegramFileName || fileLink?.split("/").pop()?.replace(/file/, `${fileType}_${chatId}`) || "";
|
||||
// TODO add bot file size limits to error "...file is too big..." (https://t.me/c/1536715535/1266)
|
||||
const fileStream = plugin.bot.getFileStream(fileId);
|
||||
const fileChunks: Uint8Array[] = [];
|
||||
|
||||
|
|
@ -410,7 +412,6 @@ export async function ifNewReleaseThenShowChanges(plugin: TelegramSyncPlugin, ms
|
|||
parse_mode: "HTML",
|
||||
reply_markup: { inline_keyboard: donationInlineKeyboard },
|
||||
};
|
||||
|
||||
await plugin.bot?.sendMessage(msg.chat.id, release.notes, options);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import TelegramSyncPlugin from "src/main";
|
||||
import * as Client from "./client";
|
||||
import { StatusMessages, displayAndLogError } from "src/utils/logUtils";
|
||||
import { enqueue } from "src/utils/queues";
|
||||
|
||||
export async function connect(
|
||||
plugin: TelegramSyncPlugin,
|
||||
|
|
@ -31,7 +32,7 @@ export async function connect(
|
|||
plugin.userConnected = await Client.isAuthorizedAsUser();
|
||||
|
||||
if (sessionType == "bot" || !plugin.userConnected) {
|
||||
await Client.signInAsBot(plugin.getBotToken());
|
||||
await Client.signInAsBot(await enqueue(plugin, plugin.getBotToken));
|
||||
}
|
||||
|
||||
if (sessionType == "user" && !plugin.userConnected) {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,36 @@
|
|||
import crypto from "crypto";
|
||||
import { base64ToString } from "src/utils/fsUtils";
|
||||
|
||||
const id1 = "c29iZXJoYWNrZXI=";
|
||||
const id2 = "S2V5";
|
||||
const id3 = "SVY=";
|
||||
|
||||
const algorithm = "aes-256-cbc";
|
||||
const defaultKey = "soberhackerKey";
|
||||
const defaultIV = "soberhackerIV";
|
||||
const defaultKey = base64ToString(id1) + base64ToString(id2);
|
||||
const defaultIV = base64ToString(id1) + base64ToString(id3);
|
||||
|
||||
export function encrypt(text: string, key = defaultKey, iv = defaultIV): string {
|
||||
const cipher = crypto.createCipheriv(algorithm, key, iv);
|
||||
const cipher = crypto.createCipheriv(
|
||||
algorithm,
|
||||
Buffer.from(padOrTrim(key, 32), "utf8"),
|
||||
Buffer.from(padOrTrim(iv, 16), "utf8"),
|
||||
);
|
||||
let encrypted = cipher.update(text, "utf8", "hex");
|
||||
encrypted += cipher.final("hex");
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
export function decrypt(encryptedText: string, key = defaultKey, iv = defaultIV): string {
|
||||
const decipher = crypto.createDecipheriv(algorithm, key, iv);
|
||||
const decipher = crypto.createDecipheriv(
|
||||
algorithm,
|
||||
Buffer.from(padOrTrim(key, 32), "utf8"),
|
||||
Buffer.from(padOrTrim(iv, 16), "utf8"),
|
||||
);
|
||||
let decrypted = decipher.update(encryptedText, "hex", "utf8");
|
||||
decrypted += decipher.final("utf8");
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
export function padOrTrim(input: string, length: number) {
|
||||
return input.length > length ? input.slice(0, length) : input.padEnd(length, "0");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue