mirror of
https://github.com/civsocit/olgram.git
synced 2023-07-22 01:29:12 +03:00
Шифрование токенов
This commit is contained in:
parent
188b58d8e2
commit
2e61640f5a
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ __pycache__
|
|||||||
*.pyc
|
*.pyc
|
||||||
config.json
|
config.json
|
||||||
docker-compose-release.yaml
|
docker-compose-release.yaml
|
||||||
|
docs/build
|
@ -11,4 +11,5 @@ fi
|
|||||||
|
|
||||||
sleep 10
|
sleep 10
|
||||||
aerich upgrade
|
aerich upgrade
|
||||||
|
python migrate.py
|
||||||
python main.py
|
python main.py
|
||||||
|
@ -5,6 +5,8 @@ POSTGRES_PASSWORD=SOME_RANDOM_PASSWORD_HERE # example: x2y0n27ihiez93kmzj82
|
|||||||
POSTGRES_DB=olgram
|
POSTGRES_DB=olgram
|
||||||
POSTGRES_HOST=postgres
|
POSTGRES_HOST=postgres
|
||||||
|
|
||||||
|
TOKEN_ENCRYPTION_KEY=SOME_RANDOM_KEY # example:
|
||||||
|
|
||||||
WEBHOOK_HOST=YOUR_HOST_HERE # example: 11.143.142.140 or my_domain.com
|
WEBHOOK_HOST=YOUR_HOST_HERE # example: 11.143.142.140 or my_domain.com
|
||||||
WEBHOOK_PORT=8443 # allowed: 80, 443, 8080, 8443
|
WEBHOOK_PORT=8443 # allowed: 80, 443, 8080, 8443
|
||||||
CUSTOM_CERT=true # use that if you don't set up your own domain and let's encrypt certificate
|
CUSTOM_CERT=true # use that if you don't set up your own domain and let's encrypt certificate
|
||||||
|
2
main.py
2
main.py
@ -47,7 +47,7 @@ def main():
|
|||||||
loop.run_until_complete(initialization())
|
loop.run_until_complete(initialization())
|
||||||
|
|
||||||
loop.create_task(dp.start_polling())
|
loop.create_task(dp.start_polling())
|
||||||
loop.create_task(server_main().start())
|
# loop.create_task(server_main().start())
|
||||||
|
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
||||||
|
9
migrate.py
Normal file
9
migrate.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from olgram.migrations.custom import migrate
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.get_event_loop().run_until_complete(migrate())
|
@ -12,7 +12,7 @@ async def delete_bot(bot: Bot, call: types.CallbackQuery):
|
|||||||
Пользователь решил удалить бота
|
Пользователь решил удалить бота
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
await unregister_token(bot.token)
|
await unregister_token(bot.decrypted_token())
|
||||||
except Unauthorized:
|
except Unauthorized:
|
||||||
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
|
# Вероятно пользователь сбросил токен или удалил бот, это уже не наши проблемы
|
||||||
pass
|
pass
|
||||||
|
@ -99,7 +99,7 @@ async def bot_added(message: types.Message, state: FSMContext):
|
|||||||
return await on_unknown_error()
|
return await on_unknown_error()
|
||||||
|
|
||||||
user, _ = await User.get_or_create(telegram_id=message.from_user.id)
|
user, _ = await User.get_or_create(telegram_id=message.from_user.id)
|
||||||
bot = Bot(token=token, owner=user, name=test_bot_info.username, super_chat_id=message.from_user.id)
|
bot = Bot(token=Bot.encrypted_token(token), owner=user, name=test_bot_info.username, super_chat_id=message.from_user.id)
|
||||||
try:
|
try:
|
||||||
await bot.save()
|
await bot.save()
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
|
35
olgram/migrations/custom.py
Normal file
35
olgram/migrations/custom.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"""Наши собственные миграции, которые нельзя описать на языке SQL и с которыми не справится TortoiseORM/Aerich"""
|
||||||
|
|
||||||
|
from tortoise import transactions, Tortoise
|
||||||
|
from olgram.settings import TORTOISE_ORM
|
||||||
|
from olgram.models.models import MetaInfo, Bot
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
async def upgrade_1():
|
||||||
|
"""Шифруем токены"""
|
||||||
|
meta_info = await MetaInfo.first()
|
||||||
|
if meta_info.version != 0:
|
||||||
|
logging.info("skip")
|
||||||
|
return
|
||||||
|
|
||||||
|
async with transactions.in_transaction():
|
||||||
|
bots = await Bot.all()
|
||||||
|
for bot in bots:
|
||||||
|
bot.token = bot.encrypted_token(bot.token)
|
||||||
|
await bot.save()
|
||||||
|
meta_info.version = 1
|
||||||
|
await meta_info.save()
|
||||||
|
logging.info("done")
|
||||||
|
|
||||||
|
# Не забудь добавить миграцию в этот лист!
|
||||||
|
_migrations = [upgrade_1]
|
||||||
|
|
||||||
|
|
||||||
|
async def migrate():
|
||||||
|
logging.info("Run custom migrations...")
|
||||||
|
await Tortoise.init(config=TORTOISE_ORM)
|
||||||
|
|
||||||
|
for migration in _migrations:
|
||||||
|
logging.info(f"Migration {migration.__name__}...")
|
||||||
|
await migration()
|
10
olgram/migrations/models/5_20210926185420_update.sql
Normal file
10
olgram/migrations/models/5_20210926185420_update.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
-- upgrade --
|
||||||
|
ALTER TABLE "bot" ALTER COLUMN "token" TYPE VARCHAR(200) USING "token"::VARCHAR(200);
|
||||||
|
CREATE TABLE IF NOT EXISTS "_custom_meta_info" (
|
||||||
|
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||||
|
"version" INT NOT NULL DEFAULT 0
|
||||||
|
);;
|
||||||
|
INSERT INTO _custom_meta_info (id, version) VALUES (0, 0) ON CONFLICT DO NOTHING;
|
||||||
|
-- downgrade --
|
||||||
|
ALTER TABLE "bot" ALTER COLUMN "token" TYPE VARCHAR(50) USING "token"::VARCHAR(50);
|
||||||
|
DROP TABLE IF EXISTS "_custom_meta_info";
|
@ -2,11 +2,27 @@ from tortoise.models import Model
|
|||||||
from tortoise import fields
|
from tortoise import fields
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
from olgram.settings import DatabaseSettings
|
||||||
|
|
||||||
|
|
||||||
|
class MetaInfo(Model):
|
||||||
|
id = fields.IntField(pk=True)
|
||||||
|
version = fields.IntField(default=0)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
# Кажется это единственный способ сделать single-instance модель в TortoiseORM :(
|
||||||
|
if "id" in kwargs:
|
||||||
|
kwargs["id"] = 0
|
||||||
|
self.id = 0
|
||||||
|
super(MetaInfo, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
table = '_custom_meta_info'
|
||||||
|
|
||||||
|
|
||||||
class Bot(Model):
|
class Bot(Model):
|
||||||
id = fields.IntField(pk=True)
|
id = fields.IntField(pk=True)
|
||||||
token = fields.CharField(max_length=50, unique=True)
|
token = fields.CharField(max_length=200, unique=True)
|
||||||
owner = fields.ForeignKeyField("models.User", related_name="bots")
|
owner = fields.ForeignKeyField("models.User", related_name="bots")
|
||||||
name = fields.CharField(max_length=33)
|
name = fields.CharField(max_length=33)
|
||||||
code = fields.UUIDField(default=uuid4, index=True)
|
code = fields.UUIDField(default=uuid4, index=True)
|
||||||
@ -22,6 +38,15 @@ class Bot(Model):
|
|||||||
on_delete=fields.relational.CASCADE,
|
on_delete=fields.relational.CASCADE,
|
||||||
null=True)
|
null=True)
|
||||||
|
|
||||||
|
def decrypted_token(self):
|
||||||
|
cryptor = DatabaseSettings.cryptor()
|
||||||
|
return cryptor.decrypt(self.token)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def encrypted_token(cls, token: str):
|
||||||
|
cryptor = DatabaseSettings.cryptor()
|
||||||
|
return cryptor.encrypt(token)
|
||||||
|
|
||||||
async def super_chat_id(self):
|
async def super_chat_id(self):
|
||||||
group_chat = await self.group_chat
|
group_chat = await self.group_chat
|
||||||
if group_chat:
|
if group_chat:
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
import os
|
import os
|
||||||
|
from olgram.utils.crypto import Cryptor
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: рефакторинг, использовать какой-нибудь lazy-config вместо своих костылей
|
||||||
|
|
||||||
class AbstractSettings(ABC):
|
class AbstractSettings(ABC):
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_env(cls, parameter: str, allow_none: bool = False) -> str:
|
def _get_env(cls, parameter: str, allow_none: bool = False) -> str:
|
||||||
parameter = os.getenv(parameter, None)
|
parameter_v = os.getenv(parameter, None)
|
||||||
if not parameter and not allow_none:
|
if not parameter_v and not allow_none:
|
||||||
raise ValueError(f"{parameter} not defined in ENV")
|
raise ValueError(f"{parameter} not defined in ENV")
|
||||||
return parameter
|
return parameter_v
|
||||||
|
|
||||||
|
|
||||||
class OlgramSettings(AbstractSettings):
|
class OlgramSettings(AbstractSettings):
|
||||||
@ -99,6 +103,12 @@ class DatabaseSettings(AbstractSettings):
|
|||||||
def host(cls) -> str:
|
def host(cls) -> str:
|
||||||
return cls._get_env("POSTGRES_HOST")
|
return cls._get_env("POSTGRES_HOST")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@lru_cache
|
||||||
|
def cryptor(cls) -> Cryptor:
|
||||||
|
password = cls._get_env("TOKEN_ENCRYPTION_KEY")
|
||||||
|
return Cryptor(password)
|
||||||
|
|
||||||
|
|
||||||
TORTOISE_ORM = {
|
TORTOISE_ORM = {
|
||||||
"connections": {"default": f'postgres://{DatabaseSettings.user()}:{DatabaseSettings.password()}'
|
"connections": {"default": f'postgres://{DatabaseSettings.user()}:{DatabaseSettings.password()}'
|
||||||
|
16
olgram/utils/crypto.py
Normal file
16
olgram/utils/crypto.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import base64
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
|
||||||
|
class Cryptor:
|
||||||
|
def __init__(self, password: str):
|
||||||
|
password = password.rjust(32)[:32]
|
||||||
|
self._cipher = AES.new(password.encode("utf-8"), AES.MODE_ECB)
|
||||||
|
|
||||||
|
def encrypt(self, data: str) -> str:
|
||||||
|
if data.startswith(" "):
|
||||||
|
raise ValueError("Data should not start with space!")
|
||||||
|
return base64.b64encode(self._cipher.encrypt(data.encode("utf-8").rjust(64))).decode("utf-8")
|
||||||
|
|
||||||
|
def decrypt(self, data: str) -> str:
|
||||||
|
return self._cipher.decrypt(base64.b64decode(data.encode("utf-8"))).decode("utf-8").lstrip()
|
@ -5,3 +5,4 @@ python-dotenv~=0.17.1
|
|||||||
aioredis==1.3.1
|
aioredis==1.3.1
|
||||||
aiocache
|
aiocache
|
||||||
aiohttp
|
aiohttp
|
||||||
|
pycrypto
|
@ -114,7 +114,7 @@ class CustomRequestHandler(WebhookRequestHandler):
|
|||||||
if not bot:
|
if not bot:
|
||||||
return None
|
return None
|
||||||
db_bot_instance.set(bot)
|
db_bot_instance.set(bot)
|
||||||
dp = Dispatcher(AioBot(bot.token))
|
dp = Dispatcher(AioBot(bot.decrypted_token()))
|
||||||
|
|
||||||
dp.register_message_handler(message_handler, content_types=[types.ContentType.TEXT,
|
dp.register_message_handler(message_handler, content_types=[types.ContentType.TEXT,
|
||||||
types.ContentType.CONTACT,
|
types.ContentType.CONTACT,
|
||||||
|
@ -26,9 +26,9 @@ async def register_token(bot: Bot) -> bool:
|
|||||||
:param bot: Бот
|
:param bot: Бот
|
||||||
:return: получилось ли
|
:return: получилось ли
|
||||||
"""
|
"""
|
||||||
await unregister_token(bot.token)
|
await unregister_token(bot.decrypted_token())
|
||||||
|
|
||||||
a_bot = AioBot(bot.token)
|
a_bot = AioBot(bot.decrypted_token())
|
||||||
certificate = None
|
certificate = None
|
||||||
if ServerSettings.use_custom_cert():
|
if ServerSettings.use_custom_cert():
|
||||||
certificate = open(ServerSettings.public_path(), 'rb')
|
certificate = open(ServerSettings.public_path(), 'rb')
|
||||||
|
Loading…
Reference in New Issue
Block a user