mirror of
https://github.com/openmax-server/server.git
synced 2026-05-23 12:01:43 +03:00
Fix OpenMAX mobile compatibility and Telegram auth fallback (#30)
* Fix OpenMAX mobile compatibility and Telegram auth fallback * Common: Убрал скобку в конфиге --------- Co-authored-by: Alexey Polyakov <starwear3000@mail.ru>
This commit is contained in:
committed by
GitHub
parent
d9cbafc4e3
commit
d9798a6fc6
@@ -1,10 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from common.config import ServerConfig
|
from common.config import ServerConfig
|
||||||
from common.static import Static
|
from common.opcodes import Opcodes
|
||||||
from common.tools import Tools
|
|
||||||
from common.proto_tcp import MobileProto
|
from common.proto_tcp import MobileProto
|
||||||
from common.proto_web import WebProto
|
from common.proto_web import WebProto
|
||||||
from common.opcodes import Opcodes
|
from common.static import Static
|
||||||
|
from common.tools import Tools
|
||||||
|
|
||||||
|
|
||||||
class BaseProcessor:
|
class BaseProcessor:
|
||||||
def __init__(self, db_pool=None, clients=None, send_event=None, type="socket"):
|
def __init__(self, db_pool=None, clients=None, send_event=None, type="socket"):
|
||||||
@@ -21,7 +23,7 @@ class BaseProcessor:
|
|||||||
self.event = send_event
|
self.event = send_event
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.type = type
|
self.type = "mobile" if type == "socket" else type
|
||||||
|
|
||||||
if type == "socket":
|
if type == "socket":
|
||||||
self.proto = MobileProto()
|
self.proto = MobileProto()
|
||||||
@@ -31,7 +33,7 @@ class BaseProcessor:
|
|||||||
async def _send(self, writer, packet):
|
async def _send(self, writer, packet):
|
||||||
try:
|
try:
|
||||||
# Если объектом является вебсокет, то используем функцию send для отправки
|
# Если объектом является вебсокет, то используем функцию send для отправки
|
||||||
if hasattr(writer, 'send'):
|
if hasattr(writer, "send"):
|
||||||
await writer.send(packet)
|
await writer.send(packet)
|
||||||
else: # В ином случае отправляем как в обычный сокет
|
else: # В ином случае отправляем как в обычный сокет
|
||||||
writer.write(packet)
|
writer.write(packet)
|
||||||
@@ -40,12 +42,15 @@ class BaseProcessor:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _send_error(self, seq, opcode, error_type, writer):
|
async def _send_error(self, seq, opcode, error_type, writer):
|
||||||
payload = self.static.ERROR_TYPES.get(error_type, {
|
payload = self.static.ERROR_TYPES.get(
|
||||||
|
error_type,
|
||||||
|
{
|
||||||
"localizedMessage": "Неизвестная ошибка",
|
"localizedMessage": "Неизвестная ошибка",
|
||||||
"error": "unknown.error",
|
"error": "unknown.error",
|
||||||
"message": "Unknown error",
|
"message": "Unknown error",
|
||||||
"title": "Неизвестная ошибка"
|
"title": "Неизвестная ошибка",
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_ERR, seq=seq, opcode=opcode, payload=payload
|
cmd=self.proto.CMD_ERR, seq=seq, opcode=opcode, payload=payload
|
||||||
|
|||||||
@@ -50,4 +50,4 @@ class ServerConfig:
|
|||||||
origins = [x.strip() for x in os.getenv("origins", "").split(",") if x.strip()] if os.getenv("origins") else None
|
origins = [x.strip() for x in os.getenv("origins", "").split(",") if x.strip()] if os.getenv("origins") else None
|
||||||
|
|
||||||
### sms шлюз
|
### sms шлюз
|
||||||
sms_gateway_url = os.getenv("sms_gateway_url") or "http://127.0.0.1/sms-gateway"
|
sms_gateway_url = os.getenv("sms_gateway_url", "")
|
||||||
|
|||||||
@@ -96,10 +96,12 @@ class Tools:
|
|||||||
"""Генерация чата"""
|
"""Генерация чата"""
|
||||||
# Генерируем список участников
|
# Генерируем список участников
|
||||||
if isinstance(participants, dict):
|
if isinstance(participants, dict):
|
||||||
result_participants = {str(k): v for k, v in participants.items()}
|
result_participants = {
|
||||||
|
int(k): int(v) if v is not None else 0 for k, v in participants.items()
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
# assume list
|
# assume list
|
||||||
result_participants = {str(participant): 0 for participant in participants}
|
result_participants = {int(participant): 0 for participant in participants}
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
@@ -125,7 +127,14 @@ class Tools:
|
|||||||
# Возвращаем
|
# Возвращаем
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def generate_chats(self, chatIds, db_pool, senderId, include_favourites=True, protocol_type='mobile'):
|
async def generate_chats(
|
||||||
|
self,
|
||||||
|
chatIds,
|
||||||
|
db_pool,
|
||||||
|
senderId,
|
||||||
|
include_favourites=True,
|
||||||
|
protocol_type="mobile",
|
||||||
|
):
|
||||||
"""Генерирует чаты для отдачи клиенту"""
|
"""Генерирует чаты для отдачи клиенту"""
|
||||||
# Готовый список с чатами
|
# Готовый список с чатами
|
||||||
chats = []
|
chats = []
|
||||||
@@ -188,12 +197,14 @@ class Tools:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Получаем ID предыдущего сообщения для избранного (чат ID = senderId)
|
# Получаем ID предыдущего сообщения для избранного (чат ID = senderId)
|
||||||
prevMessageId = await self.get_previous_message_id(senderId, db_pool, protocol_type=protocol_type)
|
prevMessageId = await self.get_previous_message_id(
|
||||||
|
senderId, db_pool, protocol_type=protocol_type
|
||||||
|
)
|
||||||
|
|
||||||
# Хардкодим в лист чатов избранное
|
# Хардкодим в лист чатов избранное
|
||||||
chats.append(
|
chats.append(
|
||||||
self.generate_chat(
|
self.generate_chat(
|
||||||
chatId if protocol_type == 'mobile' else str(chatId),
|
chatId if protocol_type == "mobile" else str(chatId),
|
||||||
senderId,
|
senderId,
|
||||||
"DIALOG",
|
"DIALOG",
|
||||||
participants,
|
participants,
|
||||||
@@ -241,7 +252,7 @@ class Tools:
|
|||||||
# Возвращаем айдишки
|
# Возвращаем айдишки
|
||||||
return int(message_id), int(last_message_id), message_time
|
return int(message_id), int(last_message_id), message_time
|
||||||
|
|
||||||
async def get_last_message(self, chatId, db_pool, protocol_type='mobile'):
|
async def get_last_message(self, chatId, db_pool, protocol_type="mobile"):
|
||||||
"""Получение последнего сообщения в чате"""
|
"""Получение последнего сообщения в чате"""
|
||||||
async with db_pool.acquire() as db_connection:
|
async with db_pool.acquire() as db_connection:
|
||||||
async with db_connection.cursor() as cursor:
|
async with db_connection.cursor() as cursor:
|
||||||
@@ -259,7 +270,9 @@ class Tools:
|
|||||||
|
|
||||||
# Собираем сообщение
|
# Собираем сообщение
|
||||||
message = {
|
message = {
|
||||||
"id": row.get("id") if protocol_type == 'mobile' else str(row.get('id')),
|
"id": row.get("id")
|
||||||
|
if protocol_type == "mobile"
|
||||||
|
else str(row.get("id")),
|
||||||
"time": int(row.get("time")),
|
"time": int(row.get("time")),
|
||||||
"type": row.get("type"),
|
"type": row.get("type"),
|
||||||
"sender": row.get("sender"),
|
"sender": row.get("sender"),
|
||||||
@@ -273,7 +286,7 @@ class Tools:
|
|||||||
# Возвращаем
|
# Возвращаем
|
||||||
return message, int(row.get("time"))
|
return message, int(row.get("time"))
|
||||||
|
|
||||||
async def get_previous_message_id(self, chatId, db_pool, protocol_type='mobile'):
|
async def get_previous_message_id(self, chatId, db_pool, protocol_type="mobile"):
|
||||||
"""Получение ID предыдущего сообщения (второго с конца) в чате."""
|
"""Получение ID предыдущего сообщения (второго с конца) в чате."""
|
||||||
async with db_pool.acquire() as db_connection:
|
async with db_pool.acquire() as db_connection:
|
||||||
async with db_connection.cursor() as cursor:
|
async with db_connection.cursor() as cursor:
|
||||||
@@ -285,10 +298,14 @@ class Tools:
|
|||||||
|
|
||||||
# Если результат есть, возвращаем его
|
# Если результат есть, возвращаем его
|
||||||
if row:
|
if row:
|
||||||
return row.get("id") if protocol_type == 'mobile' else str(row.get('id'))
|
return (
|
||||||
|
row.get("id")
|
||||||
|
if protocol_type == "mobile"
|
||||||
|
else str(row.get("id"))
|
||||||
|
)
|
||||||
|
|
||||||
# В ином случае возвращаем 0
|
# В ином случае возвращаем 0
|
||||||
return 0 if protocol_type == 'mobile' else "0"
|
return 0 if protocol_type == "mobile" else "0"
|
||||||
|
|
||||||
async def get_participant_last_activity(self, chatId, participant_ids, db_pool):
|
async def get_participant_last_activity(self, chatId, participant_ids, db_pool):
|
||||||
"""Возвращает словарь {participant_id: last_activity_time} для участников чата."""
|
"""Возвращает словарь {participant_id: last_activity_time} для участников чата."""
|
||||||
@@ -310,11 +327,11 @@ class Tools:
|
|||||||
rows = await cursor.fetchall()
|
rows = await cursor.fetchall()
|
||||||
|
|
||||||
# Собираем список участников без времени последней активности в чате
|
# Собираем список участников без времени последней активности в чате
|
||||||
result = {str(pid): 0 for pid in participant_ids}
|
result = {int(pid): 0 for pid in participant_ids}
|
||||||
|
|
||||||
# Обновляем для каждого участника время последней активности в чате
|
# Обновляем для каждого участника время последней активности в чате
|
||||||
for row in rows:
|
for row in rows:
|
||||||
sender = str(row["sender"])
|
sender = int(row["sender"])
|
||||||
last_time = row["last_time"]
|
last_time = row["last_time"]
|
||||||
if last_time is not None:
|
if last_time is not None:
|
||||||
result[sender] = int(last_time)
|
result[sender] = int(last_time)
|
||||||
@@ -330,7 +347,7 @@ class Tools:
|
|||||||
(chatId,),
|
(chatId,),
|
||||||
)
|
)
|
||||||
rows = await cursor.fetchall()
|
rows = await cursor.fetchall()
|
||||||
return [row["user_id"] for row in rows]
|
return [int(row["user_id"]) for row in rows]
|
||||||
|
|
||||||
async def auth_required(self, userPhone, coro, *args):
|
async def auth_required(self, userPhone, coro, *args):
|
||||||
if userPhone:
|
if userPhone:
|
||||||
|
|||||||
69
src/main.py
69
src/main.py
@@ -12,6 +12,70 @@ from telegrambot.controller import TelegramBotController
|
|||||||
server_config = ServerConfig()
|
server_config = ServerConfig()
|
||||||
|
|
||||||
|
|
||||||
|
class SQLiteCursorCompat:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
self.cursor = None
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
self.cursor = await self.connection.cursor()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
|
if self.cursor is not None:
|
||||||
|
await self.cursor.close()
|
||||||
|
self.cursor = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def lastrowid(self):
|
||||||
|
return None if self.cursor is None else self.cursor.lastrowid
|
||||||
|
|
||||||
|
def _normalize_query(self, query):
|
||||||
|
return query.replace("%s", "?").replace(
|
||||||
|
"UNIX_TIMESTAMP()", "CAST(strftime('%s','now') AS INTEGER)"
|
||||||
|
)
|
||||||
|
|
||||||
|
async def execute(self, query, params=()):
|
||||||
|
normalized_query = self._normalize_query(query)
|
||||||
|
if params is None:
|
||||||
|
params = ()
|
||||||
|
elif not isinstance(params, (tuple, list, dict)):
|
||||||
|
params = (params,)
|
||||||
|
await self.cursor.execute(normalized_query, params)
|
||||||
|
|
||||||
|
async def fetchone(self):
|
||||||
|
row = await self.cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
return dict(row)
|
||||||
|
|
||||||
|
async def fetchall(self):
|
||||||
|
rows = await self.cursor.fetchall()
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
class SQLiteConnectionCompat:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def cursor(self):
|
||||||
|
return SQLiteCursorCompat(self.connection)
|
||||||
|
|
||||||
|
|
||||||
|
class SQLitePoolCompat:
|
||||||
|
def __init__(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
|
||||||
|
def acquire(self):
|
||||||
|
return SQLiteConnectionCompat(self.connection)
|
||||||
|
|
||||||
|
|
||||||
async def init_db():
|
async def init_db():
|
||||||
"""Инициализация базы данных"""
|
"""Инициализация базы данных"""
|
||||||
|
|
||||||
@@ -32,8 +96,9 @@ async def init_db():
|
|||||||
elif server_config.db_type == "sqlite":
|
elif server_config.db_type == "sqlite":
|
||||||
import aiosqlite
|
import aiosqlite
|
||||||
|
|
||||||
raw_db = await aiosqlite.connect(server_config.db_file)
|
raw_db = await aiosqlite.connect(server_config.db_file, isolation_level=None)
|
||||||
db["acquire"] = lambda: raw_db
|
raw_db.row_factory = aiosqlite.Row
|
||||||
|
db = SQLitePoolCompat(raw_db)
|
||||||
|
|
||||||
# Возвращаем
|
# Возвращаем
|
||||||
return db
|
return db
|
||||||
|
|||||||
@@ -1,22 +1,31 @@
|
|||||||
import json
|
|
||||||
import secrets
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import secrets
|
||||||
|
import time
|
||||||
|
|
||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
from classes.baseprocessor import BaseProcessor
|
from classes.baseprocessor import BaseProcessor
|
||||||
|
from common.sms import send_sms_code
|
||||||
|
from oneme.config import OnemeConfig
|
||||||
from oneme.models import (
|
from oneme.models import (
|
||||||
RequestCodePayloadModel,
|
|
||||||
VerifyCodePayloadModel,
|
|
||||||
AuthConfirmRegisterPayloadModel,
|
AuthConfirmRegisterPayloadModel,
|
||||||
LoginPayloadModel,
|
LoginPayloadModel,
|
||||||
|
RequestCodePayloadModel,
|
||||||
|
VerifyCodePayloadModel,
|
||||||
)
|
)
|
||||||
from oneme.config import OnemeConfig
|
|
||||||
from common.sms import send_sms_code
|
|
||||||
|
|
||||||
|
|
||||||
class AuthProcessors(BaseProcessor):
|
class AuthProcessors(BaseProcessor):
|
||||||
def __init__(self, db_pool=None, clients=None, send_event=None, telegram_bot=None, type="socket"):
|
def __init__(
|
||||||
|
self,
|
||||||
|
db_pool=None,
|
||||||
|
clients=None,
|
||||||
|
send_event=None,
|
||||||
|
telegram_bot=None,
|
||||||
|
type="socket",
|
||||||
|
):
|
||||||
super().__init__(db_pool, clients, send_event, type)
|
super().__init__(db_pool, clients, send_event, type)
|
||||||
self.server_config = OnemeConfig().SERVER_CONFIG
|
self.server_config = OnemeConfig().SERVER_CONFIG
|
||||||
self.telegram_bot = telegram_bot
|
self.telegram_bot = telegram_bot
|
||||||
@@ -27,7 +36,9 @@ class AuthProcessors(BaseProcessor):
|
|||||||
RequestCodePayloadModel.model_validate(payload)
|
RequestCodePayloadModel.model_validate(payload)
|
||||||
except pydantic.ValidationError as error:
|
except pydantic.ValidationError as error:
|
||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
await self._send_error(seq, self.opcodes.AUTH_REQUEST, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.AUTH_REQUEST, self.error_types.INVALID_PAYLOAD, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Извлекаем телефон из пакета
|
# Извлекаем телефон из пакета
|
||||||
@@ -49,13 +60,16 @@ class AuthProcessors(BaseProcessor):
|
|||||||
user = await cursor.fetchone()
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
# Получаем код через SMS шлюз или генерируем локально (безопасность прежде всего)
|
# Получаем код через SMS шлюз или генерируем локально (безопасность прежде всего)
|
||||||
|
local_fallback_code = False
|
||||||
if self.config.sms_gateway_url:
|
if self.config.sms_gateway_url:
|
||||||
code = await send_sms_code(self.config.sms_gateway_url, phone)
|
code = await send_sms_code(self.config.sms_gateway_url, phone)
|
||||||
|
|
||||||
if code is None:
|
if code is None:
|
||||||
code = str(secrets.randbelow(900000) + 100000)
|
code = str(secrets.randbelow(900000) + 100000)
|
||||||
|
local_fallback_code = True
|
||||||
else:
|
else:
|
||||||
code = str(secrets.randbelow(900000) + 100000)
|
code = str(secrets.randbelow(900000) + 100000)
|
||||||
|
local_fallback_code = True
|
||||||
|
|
||||||
# Хешируем
|
# Хешируем
|
||||||
code_hash = hashlib.sha256(code.encode()).hexdigest()
|
code_hash = hashlib.sha256(code.encode()).hexdigest()
|
||||||
@@ -68,18 +82,35 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Сохраняем токен
|
# Сохраняем токен
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires) VALUES (%s, %s, %s, %s)",
|
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires) VALUES (%s, %s, %s, %s)",
|
||||||
(phone, token_hash, code_hash, expires,)
|
(
|
||||||
|
phone,
|
||||||
|
token_hash,
|
||||||
|
code_hash,
|
||||||
|
expires,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Если тг бот включен, и тг привязан к аккаунту - отправляем туда сообщение
|
# Если код был сгенерирован локально, а тг привязан к аккаунту - отправляем туда сообщение
|
||||||
if not self.config.sms_gateway_url and self.telegram_bot and user.get("telegram_id"):
|
if (
|
||||||
await self.telegram_bot.send_code(chat_id=int(user.get("telegram_id")), phone=phone, code=code)
|
local_fallback_code
|
||||||
|
and self.telegram_bot
|
||||||
|
and user.get("telegram_id")
|
||||||
|
):
|
||||||
|
await self.telegram_bot.send_code(
|
||||||
|
chat_id=int(user.get("telegram_id")), phone=phone, code=code
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Пользователь не найден - сохраняем токен со state='register'
|
# Пользователь не найден - сохраняем токен со state='register'
|
||||||
# чтобы после верификации кода направить на экран регистрации
|
# чтобы после верификации кода направить на экран регистрации
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)",
|
"INSERT INTO auth_tokens (phone, token_hash, code_hash, expires, state) VALUES (%s, %s, %s, %s, %s)",
|
||||||
(phone, token_hash, code_hash, expires, "register",)
|
(
|
||||||
|
phone,
|
||||||
|
token_hash,
|
||||||
|
code_hash,
|
||||||
|
expires,
|
||||||
|
"register",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Данные пакета
|
# Данные пакета
|
||||||
@@ -88,12 +119,15 @@ class AuthProcessors(BaseProcessor):
|
|||||||
"codeLength": 6,
|
"codeLength": 6,
|
||||||
"requestMaxDuration": 60000,
|
"requestMaxDuration": 60000,
|
||||||
"requestCountLeft": 10,
|
"requestCountLeft": 10,
|
||||||
"altActionDuration": 60000
|
"altActionDuration": 60000,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Собираем пакет
|
# Собираем пакет
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH_REQUEST, payload=payload
|
cmd=self.proto.CMD_OK,
|
||||||
|
seq=seq,
|
||||||
|
opcode=self.opcodes.AUTH_REQUEST,
|
||||||
|
payload=payload,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Отправляем
|
# Отправляем
|
||||||
@@ -106,7 +140,9 @@ class AuthProcessors(BaseProcessor):
|
|||||||
VerifyCodePayloadModel.model_validate(payload)
|
VerifyCodePayloadModel.model_validate(payload)
|
||||||
except pydantic.ValidationError as error:
|
except pydantic.ValidationError as error:
|
||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
await self._send_error(seq, self.opcodes.AUTH, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.AUTH, self.error_types.INVALID_PAYLOAD, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Извлекаем данные из пакета
|
# Извлекаем данные из пакета
|
||||||
@@ -127,18 +163,22 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Ищем токен
|
# Ищем токен
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP()",
|
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP()",
|
||||||
(hashed_token,)
|
(hashed_token,),
|
||||||
)
|
)
|
||||||
stored_token = await cursor.fetchone()
|
stored_token = await cursor.fetchone()
|
||||||
|
|
||||||
# Если токен просрочен, или его нет - отправляем ошибку
|
# Если токен просрочен, или его нет - отправляем ошибку
|
||||||
if stored_token is None:
|
if stored_token is None:
|
||||||
await self._send_error(seq, self.opcodes.AUTH, self.error_types.CODE_EXPIRED, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.AUTH, self.error_types.CODE_EXPIRED, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Проверяем код
|
# Проверяем код
|
||||||
if stored_token.get("code_hash") != hashed_code:
|
if stored_token.get("code_hash") != hashed_code:
|
||||||
await self._send_error(seq, self.opcodes.AUTH, self.error_types.INVALID_CODE, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.AUTH, self.error_types.INVALID_CODE, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Если это новый пользователь - переводим токен в state='verified'
|
# Если это новый пользователь - переводим токен в state='verified'
|
||||||
@@ -146,48 +186,60 @@ class AuthProcessors(BaseProcessor):
|
|||||||
if stored_token.get("state") == "register":
|
if stored_token.get("state") == "register":
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"UPDATE auth_tokens SET state = %s WHERE token_hash = %s",
|
"UPDATE auth_tokens SET state = %s WHERE token_hash = %s",
|
||||||
("verified", hashed_token,)
|
(
|
||||||
|
"verified",
|
||||||
|
hashed_token,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH,
|
cmd=self.proto.CMD_OK,
|
||||||
|
seq=seq,
|
||||||
|
opcode=self.opcodes.AUTH,
|
||||||
payload={
|
payload={
|
||||||
"tokenAttrs": {
|
"tokenAttrs": {"REGISTER": {"token": token}},
|
||||||
"REGISTER": {
|
"presetAvatars": [],
|
||||||
"token": token
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"presetAvatars": []
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
await self._send(writer, packet)
|
await self._send(writer, packet)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ищем аккаунт
|
# Ищем аккаунт
|
||||||
await cursor.execute("SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),))
|
await cursor.execute(
|
||||||
|
"SELECT * FROM users WHERE phone = %s", (stored_token.get("phone"),)
|
||||||
|
)
|
||||||
account = await cursor.fetchone()
|
account = await cursor.fetchone()
|
||||||
|
|
||||||
# Удаляем токен
|
# Удаляем токен
|
||||||
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,))
|
await cursor.execute(
|
||||||
|
"DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)
|
||||||
|
)
|
||||||
|
|
||||||
# Создаем сессию
|
# Создаем сессию
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
||||||
(stored_token.get("phone"), hashed_login, deviceType, deviceName, "Little Saint James Island", int(time.time()),) # весь покрытый зеленью, абсолютно весь, остров невезения в океане есть
|
(
|
||||||
|
stored_token.get("phone"),
|
||||||
|
hashed_login,
|
||||||
|
deviceType,
|
||||||
|
deviceName,
|
||||||
|
"Little Saint James Island",
|
||||||
|
int(time.time()),
|
||||||
|
), # весь покрытый зеленью, абсолютно весь, остров невезения в океане есть
|
||||||
)
|
)
|
||||||
|
|
||||||
# Генерируем профиль
|
# Генерируем профиль
|
||||||
# Аватарка с биографией
|
# Аватарка с биографией
|
||||||
photoId = None if not account.get("avatar_id") else int(account.get("avatar_id"))
|
photoId = (
|
||||||
|
None if not account.get("avatar_id") else int(account.get("avatar_id"))
|
||||||
|
)
|
||||||
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
avatar_url = None if not photoId else self.config.avatar_base_url + photoId
|
||||||
description = None if not account.get("description") else account.get("description")
|
description = (
|
||||||
|
None if not account.get("description") else account.get("description")
|
||||||
|
)
|
||||||
|
|
||||||
# Собираем данные пакета
|
# Собираем данные пакета
|
||||||
payload = {
|
payload = {
|
||||||
"tokenAttrs": {
|
"tokenAttrs": {"LOGIN": {"token": login}},
|
||||||
"LOGIN": {
|
|
||||||
"token": login
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"profile": self.tools.generate_profile(
|
"profile": self.tools.generate_profile(
|
||||||
id=account.get("id"),
|
id=account.get("id"),
|
||||||
phone=int(account.get("phone")),
|
phone=int(account.get("phone")),
|
||||||
@@ -201,8 +253,8 @@ class AuthProcessors(BaseProcessor):
|
|||||||
accountStatus=int(account.get("accountstatus")),
|
accountStatus=int(account.get("accountstatus")),
|
||||||
profileOptions=json.loads(account.get("profileoptions")),
|
profileOptions=json.loads(account.get("profileoptions")),
|
||||||
includeProfileOptions=True,
|
includeProfileOptions=True,
|
||||||
username=account.get("username")
|
username=account.get("username"),
|
||||||
)
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Создаем пакет
|
# Создаем пакет
|
||||||
@@ -220,7 +272,9 @@ class AuthProcessors(BaseProcessor):
|
|||||||
AuthConfirmRegisterPayloadModel.model_validate(payload)
|
AuthConfirmRegisterPayloadModel.model_validate(payload)
|
||||||
except pydantic.ValidationError as error:
|
except pydantic.ValidationError as error:
|
||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
await self._send_error(seq, self.opcodes.AUTH_CONFIRM, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.AUTH_CONFIRM, self.error_types.INVALID_PAYLOAD, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Извлекаем данные из пакета
|
# Извлекаем данные из пакета
|
||||||
@@ -240,13 +294,21 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Ищем токен - он должен быть в state='verified'
|
# Ищем токен - он должен быть в state='verified'
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP() AND state = %s",
|
"SELECT * FROM auth_tokens WHERE token_hash = %s AND expires > UNIX_TIMESTAMP() AND state = %s",
|
||||||
(hashed_token, "verified",)
|
(
|
||||||
|
hashed_token,
|
||||||
|
"verified",
|
||||||
|
),
|
||||||
)
|
)
|
||||||
stored_token = await cursor.fetchone()
|
stored_token = await cursor.fetchone()
|
||||||
|
|
||||||
# Если токен не найден или просрочен - отправляем ошибку
|
# Если токен не найден или просрочен - отправляем ошибку
|
||||||
if stored_token is None:
|
if stored_token is None:
|
||||||
await self._send_error(seq, self.opcodes.AUTH_CONFIRM, self.error_types.CODE_EXPIRED, writer)
|
await self._send_error(
|
||||||
|
seq,
|
||||||
|
self.opcodes.AUTH_CONFIRM,
|
||||||
|
self.error_types.CODE_EXPIRED,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
phone = stored_token.get("phone")
|
phone = stored_token.get("phone")
|
||||||
@@ -254,7 +316,12 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Проверяем что пользователь с таким телефоном ещё не существует
|
# Проверяем что пользователь с таким телефоном ещё не существует
|
||||||
await cursor.execute("SELECT id FROM users WHERE phone = %s", (phone,))
|
await cursor.execute("SELECT id FROM users WHERE phone = %s", (phone,))
|
||||||
if await cursor.fetchone():
|
if await cursor.fetchone():
|
||||||
await self._send_error(seq, self.opcodes.AUTH_CONFIRM, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(
|
||||||
|
seq,
|
||||||
|
self.opcodes.AUTH_CONFIRM,
|
||||||
|
self.error_types.INVALID_PAYLOAD,
|
||||||
|
writer,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
now_ms = int(time.time() * 1000)
|
now_ms = int(time.time() * 1000)
|
||||||
@@ -269,10 +336,18 @@ class AuthProcessors(BaseProcessor):
|
|||||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
self.tools.generate_id(), phone, None, first_name, last_name, None,
|
self.tools.generate_id(),
|
||||||
json.dumps([]), json.dumps(["TT", "ONEME"]),
|
phone,
|
||||||
0, str(now_ms), str(now_s),
|
None,
|
||||||
)
|
first_name,
|
||||||
|
last_name,
|
||||||
|
None,
|
||||||
|
json.dumps([]),
|
||||||
|
json.dumps(["TT", "ONEME"]),
|
||||||
|
0,
|
||||||
|
str(now_ms),
|
||||||
|
str(now_s),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
user_id = cursor.lastrowid
|
user_id = cursor.lastrowid
|
||||||
@@ -290,16 +365,25 @@ class AuthProcessors(BaseProcessor):
|
|||||||
json.dumps(self.static.USER_FOLDERS),
|
json.dumps(self.static.USER_FOLDERS),
|
||||||
json.dumps(self.static.USER_SETTINGS),
|
json.dumps(self.static.USER_SETTINGS),
|
||||||
json.dumps({}),
|
json.dumps({}),
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Удаляем токен
|
# Удаляем токен
|
||||||
await cursor.execute("DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,))
|
await cursor.execute(
|
||||||
|
"DELETE FROM auth_tokens WHERE token_hash = %s", (hashed_token,)
|
||||||
|
)
|
||||||
|
|
||||||
# Создаем сессию
|
# Создаем сессию
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
"INSERT INTO tokens (phone, token_hash, device_type, device_name, location, time) VALUES (%s, %s, %s, %s, %s, %s)",
|
||||||
(phone, hashed_login, deviceType or "ANDROID", deviceName or "Unknown", "Little Saint James Island", now_s,)
|
(
|
||||||
|
phone,
|
||||||
|
hashed_login,
|
||||||
|
deviceType or "ANDROID",
|
||||||
|
deviceName or "Unknown",
|
||||||
|
"Little Saint James Island",
|
||||||
|
now_s,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Генерируем профиль
|
# Генерируем профиль
|
||||||
@@ -316,7 +400,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
accountStatus=0,
|
accountStatus=0,
|
||||||
profileOptions=[],
|
profileOptions=[],
|
||||||
includeProfileOptions=True,
|
includeProfileOptions=True,
|
||||||
username=None
|
username=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Собираем данные пакета
|
# Собираем данные пакета
|
||||||
@@ -324,17 +408,22 @@ class AuthProcessors(BaseProcessor):
|
|||||||
"userToken": "0",
|
"userToken": "0",
|
||||||
"profile": profile,
|
"profile": profile,
|
||||||
"tokenType": "LOGIN",
|
"tokenType": "LOGIN",
|
||||||
"token": login
|
"token": login,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Создаем пакет
|
# Создаем пакет
|
||||||
packet = self.proto.pack_packet(
|
packet = self.proto.pack_packet(
|
||||||
cmd=self.proto.CMD_OK, seq=seq, opcode=self.opcodes.AUTH_CONFIRM, payload=payload
|
cmd=self.proto.CMD_OK,
|
||||||
|
seq=seq,
|
||||||
|
opcode=self.opcodes.AUTH_CONFIRM,
|
||||||
|
payload=payload,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Отправляем
|
# Отправляем
|
||||||
await self._send(writer, packet)
|
await self._send(writer, packet)
|
||||||
self.logger.info(f"Новый пользователь зарегистрирован: phone={phone} id={user_id} name={first_name} {last_name}")
|
self.logger.info(
|
||||||
|
f"Новый пользователь зарегистрирован: phone={phone} id={user_id} name={first_name} {last_name}"
|
||||||
|
)
|
||||||
|
|
||||||
async def login(self, payload, seq, writer):
|
async def login(self, payload, seq, writer):
|
||||||
"""Обработчик авторизации клиента на сервере"""
|
"""Обработчик авторизации клиента на сервере"""
|
||||||
@@ -343,7 +432,9 @@ class AuthProcessors(BaseProcessor):
|
|||||||
LoginPayloadModel.model_validate(payload)
|
LoginPayloadModel.model_validate(payload)
|
||||||
except pydantic.ValidationError as error:
|
except pydantic.ValidationError as error:
|
||||||
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
self.logger.error(f"Возникли ошибки при валидации пакета: {error}")
|
||||||
await self._send_error(seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.LOGIN, self.error_types.INVALID_PAYLOAD, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Чаты, где состоит пользователь
|
# Чаты, где состоит пользователь
|
||||||
@@ -358,33 +449,40 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Ищем токен в бд
|
# Ищем токен в бд
|
||||||
async with self.db_pool.acquire() as conn:
|
async with self.db_pool.acquire() as conn:
|
||||||
async with conn.cursor() as cursor:
|
async with conn.cursor() as cursor:
|
||||||
await cursor.execute("SELECT * FROM tokens WHERE token_hash = %s", (hashed_token,))
|
await cursor.execute(
|
||||||
|
"SELECT * FROM tokens WHERE token_hash = %s", (hashed_token,)
|
||||||
|
)
|
||||||
token_data = await cursor.fetchone()
|
token_data = await cursor.fetchone()
|
||||||
|
|
||||||
# Если токен не найден, отправляем ошибку
|
# Если токен не найден, отправляем ошибку
|
||||||
if token_data is None:
|
if token_data is None:
|
||||||
await self._send_error(seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer)
|
await self._send_error(
|
||||||
|
seq, self.opcodes.LOGIN, self.error_types.INVALID_TOKEN, writer
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ищем аккаунт пользователя в бд
|
# Ищем аккаунт пользователя в бд
|
||||||
await cursor.execute("SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),))
|
await cursor.execute(
|
||||||
|
"SELECT * FROM users WHERE phone = %s", (token_data.get("phone"),)
|
||||||
|
)
|
||||||
user = await cursor.fetchone()
|
user = await cursor.fetchone()
|
||||||
|
|
||||||
# Ищем данные пользователя в бд
|
# Ищем данные пользователя в бд
|
||||||
await cursor.execute("SELECT * FROM user_data WHERE phone = %s", (token_data.get("phone"),))
|
await cursor.execute(
|
||||||
|
"SELECT * FROM user_data WHERE phone = %s",
|
||||||
|
(token_data.get("phone"),),
|
||||||
|
)
|
||||||
user_data = await cursor.fetchone()
|
user_data = await cursor.fetchone()
|
||||||
|
|
||||||
# Ищем все чаты, где состоит пользователь
|
# Ищем все чаты, где состоит пользователь
|
||||||
await cursor.execute(
|
await cursor.execute(
|
||||||
"SELECT * FROM chat_participants WHERE user_id = %s",
|
"SELECT * FROM chat_participants WHERE user_id = %s",
|
||||||
(user.get('id'))
|
(user.get("id")),
|
||||||
)
|
)
|
||||||
user_chats = await cursor.fetchall()
|
user_chats = await cursor.fetchall()
|
||||||
|
|
||||||
for chat in user_chats:
|
for chat in user_chats:
|
||||||
chats.append(
|
chats.append(chat.get("chat_id"))
|
||||||
chat.get("chat_id")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Аватарка с биографией
|
# Аватарка с биографией
|
||||||
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
photoId = None if not user.get("avatar_id") else int(user.get("avatar_id"))
|
||||||
@@ -405,7 +503,7 @@ class AuthProcessors(BaseProcessor):
|
|||||||
accountStatus=int(user.get("accountstatus")),
|
accountStatus=int(user.get("accountstatus")),
|
||||||
profileOptions=json.loads(user.get("profileoptions")),
|
profileOptions=json.loads(user.get("profileoptions")),
|
||||||
includeProfileOptions=True,
|
includeProfileOptions=True,
|
||||||
username=user.get("username")
|
username=user.get("username"),
|
||||||
)
|
)
|
||||||
|
|
||||||
chats = await self.tools.generate_chats(
|
chats = await self.tools.generate_chats(
|
||||||
@@ -422,11 +520,11 @@ class AuthProcessors(BaseProcessor):
|
|||||||
"presence": {},
|
"presence": {},
|
||||||
"config": {
|
"config": {
|
||||||
"server": self.server_config,
|
"server": self.server_config,
|
||||||
"user": json.loads(user_data.get("user_config"))
|
"user": json.loads(user_data.get("user_config")),
|
||||||
},
|
},
|
||||||
"token": token,
|
"token": token,
|
||||||
"videoChatHistory": False,
|
"videoChatHistory": False,
|
||||||
"time": int(time.time() * 1000)
|
"time": int(time.time() * 1000),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Собираем пакет
|
# Собираем пакет
|
||||||
@@ -443,7 +541,9 @@ class AuthProcessors(BaseProcessor):
|
|||||||
# Удаляем токен из бд
|
# Удаляем токен из бд
|
||||||
async with self.db_pool.acquire() as conn:
|
async with self.db_pool.acquire() as conn:
|
||||||
async with conn.cursor() as cursor:
|
async with conn.cursor() as cursor:
|
||||||
await cursor.execute("DELETE FROM tokens WHERE token_hash = %s", (hashedToken,))
|
await cursor.execute(
|
||||||
|
"DELETE FROM tokens WHERE token_hash = %s", (hashedToken,)
|
||||||
|
)
|
||||||
|
|
||||||
# Создаем пакет
|
# Создаем пакет
|
||||||
response = self.proto.pack_packet(
|
response = self.proto.pack_packet(
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class TelegramBot:
|
|||||||
tg_id = str(message.from_user.id)
|
tg_id = str(message.from_user.id)
|
||||||
|
|
||||||
# Проверка ID на наличие в белом списке
|
# Проверка ID на наличие в белом списке
|
||||||
if tg_id not in self.whitelist_ids:
|
if self.whitelist_ids and tg_id not in self.whitelist_ids:
|
||||||
await message.answer(
|
await message.answer(
|
||||||
self.get_bot_message(self.msg_types.ID_NOT_WHITELISTED)
|
self.get_bot_message(self.msg_types.ID_NOT_WHITELISTED)
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user