Skip to content

Commit f1b43b1

Browse files
committed
Rewrite authorization through Telegram.
1 parent 4b18eea commit f1b43b1

1 file changed

Lines changed: 33 additions & 147 deletions

File tree

auth_backend/auth_plugins/telegram.py

Lines changed: 33 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -24,61 +24,55 @@
2424
logger = logging.getLogger(__name__)
2525

2626

27-
class TelegramSettings(Settings):
27+
class TelegramSettings(Settings): # TODO: переписать раздел про ТГ в README.md
2828
TELEGRAM_REDIRECT_URL: str = "https://app.test.profcomff.com/auth"
29-
TELEGRAM_BOT_TOKEN: str | None = None
29+
TELEGRAM_BOT_TOKEN: str
3030

3131

3232
class TelegramAuth(OauthMeta):
33+
"""Вход в приложение 'Твой ФФ' через Telegram Login Widget."""
3334
prefix = '/telegram'
3435
tags = ['Telegram']
3536
settings = TelegramSettings()
3637

37-
class OauthResponseSchema(BaseModel):
38-
id_token: str | None = Field(default=None, help="Telegram JWT token identifier")
39-
id: str | None = None
40-
first_name: str | None = None
38+
class TGAuthResponseSchema(BaseModel):
39+
id: str
40+
first_name: str
4141
last_name: str | None = None
4242
username: str | None = None
4343
photo_url: str | None = None
44-
auth_date: str | None = None
45-
hash: str | None = None
46-
scopes: list[Scope] | None = None
47-
session_name: str | None = None
44+
auth_date: str
45+
hash: str
46+
scopes: list[Scope] | None = None # Телеграм не передает это поле. Осталось "исторически".
47+
session_name: str | None = None # Телеграм не передает это поле. Осталось "исторически".
4848

4949
@classmethod
5050
async def _register(
5151
cls,
52-
user_inp: OauthResponseSchema,
52+
user_inp: TGAuthResponseSchema,
5353
background_tasks: BackgroundTasks,
5454
user_session: UserSession = Depends(UnionAuth(auto_error=True, scopes=[], allow_none=True)),
5555
) -> Session:
56+
"""Добавление метода аутентификации через (виджет) Телеграма."""
5657
old_user = None
5758
new_user = {}
58-
telegram_user_id = None
59-
userinfo = None
6059

61-
if user_inp.id_token is None:
62-
userinfo = await cls._check(user_inp)
63-
telegram_user_id = user_inp.id
64-
logger.debug(userinfo)
65-
else:
66-
userinfo = jwt.decode(user_inp.id_token, cls.settings.ENCRYPTION_KEY, algorithms=["HS256"])
67-
telegram_user_id = userinfo['id']
68-
logger.debug(userinfo)
69-
70-
user = await cls._get_user('user_id', telegram_user_id, db_session=db.session)
60+
# Проверяем получение корректных данных
61+
userinfo = await cls._check(user_inp)
62+
tg_user_id = userinfo['id']
7163

64+
user = await cls._get_user('user_id', tg_user_id, db_session=db.session)
7265
if user is not None:
7366
raise AlreadyExists(User, user.id)
67+
7468
if user_session is None:
75-
user = await cls._create_user(db_session=db.session) if user_session is None else user_session.user
69+
user = await cls._create_user(db_session=db.session)
7670
else:
7771
user = user_session.user
7872
old_user = {'user_id': user.id}
7973
new_user["user_id"] = user.id
80-
tg_id = cls.create_auth_method_param('user_id', telegram_user_id, user.id, db_session=db.session)
81-
new_user[cls.get_name()] = {"user_id": tg_id.value}
74+
tg_auth = cls.create_auth_method_param('user_id', tg_user_id, user.id, db_session=db.session)
75+
new_user[cls.get_name()] = {"user_id": tg_auth.value}
8276
userdata = await TelegramAuth._convert_data_to_userdata_format(userinfo)
8377
background_tasks.add_task(
8478
get_kafka_producer().produce,
@@ -93,12 +87,12 @@ async def _register(
9387
db_session=db.session,
9488
session_name=user_inp.session_name,
9589
)
96-
90+
9791
@classmethod
98-
async def _login(cls, user_inp: OauthResponseSchema, background_tasks: BackgroundTasks) -> Session:
99-
"""Вход в пользователя с помощью аккаунта https://lk.msu.ru
92+
async def _login(cls, user_inp: TGAuthResponseSchema, background_tasks: BackgroundTasks) -> Session:
93+
"""Вход в пользователя с помощью аккаунта ТГ.
10094
101-
Производит вход, если находит пользователя по уникаотному идендификатору. Если аккаунт не
95+
Производит вход, если находит пользователя по id (из Телеграма). Если аккаунт не
10296
найден, возвращает ошибка.
10397
"""
10498

@@ -128,24 +122,23 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun
128122
)
129123

130124
@classmethod
131-
async def _redirect_url(cls):
125+
async def _redirect_url(cls): # А это вообще нужно, если мы используем виджет с атрибутом redirect_url (а не callback)?
132126
"""URL на который происходит редирект после завершения входа на стороне провайдера"""
133127
return OauthMeta.UrlSchema(url=cls.settings.TELEGRAM_REDIRECT_URL)
134-
128+
135129
@classmethod
136-
async def _auth_url(cls):
137-
"""URL на который происходит редирект из приложения для авторизации на стороне провайдера"""
138-
130+
async def _auth_url(cls): # А это вообще нужно, если в виджете ТГ уже прописан атрибут src и там скрипт?!
131+
"""URL на который происходит редирект из приложения, чтобы авторизоваться на стороне провайдера."""
139132
return OauthMeta.UrlSchema(
140133
url=f"https://oauth.telegram.org/auth?bot_id={cls.settings.TELEGRAM_BOT_TOKEN.split(':')[0]}&origin={quote(cls.settings.TELEGRAM_REDIRECT_URL)}&return_to={quote(cls.settings.TELEGRAM_REDIRECT_URL)}"
141134
)
142-
135+
143136
@classmethod
144137
async def _check(cls, user_inp):
145-
'''Проверка данных пользователя
138+
"""Проверка данных пользователя.
146139
147140
https://core.telegram.org/widgets/login#checking-authorization
148-
'''
141+
"""
149142
data_check = {
150143
'id': user_inp.id,
151144
'first_name': user_inp.first_name,
@@ -167,9 +160,10 @@ async def _check(cls, user_inp):
167160
return data_check
168161
else:
169162
raise OauthAuthFailed('Invalid user data from Telegram', 'Неправильные учетные данные')
170-
163+
171164
@classmethod
172165
async def _convert_data_to_userdata_format(cls, data: dict[str, Any]) -> UserLogin:
166+
"""Конвертация данных в формат для userdata-api."""
173167
first_name, last_name = '', ''
174168
if 'first_name' in data.keys() and data['first_name'] is not None:
175169
first_name = data['first_name']
@@ -185,111 +179,3 @@ async def _convert_data_to_userdata_format(cls, data: dict[str, Any]) -> UserLog
185179
]
186180
result = {"items": items, "source": cls.get_name()}
187181
return cls.userdata_process_empty_strings(UserLogin.model_validate(result))
188-
189-
190-
class NewTelegramSettings(Settings):
191-
TELEGRAM_REDIRECT_URL: str = "https://app.test.profcomff.com/auth"
192-
TELEGRAM_BOT_TOKEN: str | None = None # TODO: добавить сюда токен бота для тестов. В целом, брать его из .env.
193-
194-
195-
class NewTelegramAuth(OauthMeta):
196-
"""Вход в приложение 'Твой ФФ' через Telegram Login Widget."""
197-
prefix = '/telegram'
198-
tags = ['Telegram']
199-
settings = NewTelegramSettings()
200-
201-
class TGAuthResponseSchema(BaseModel):
202-
# id_token: str | None = Field(default=None, help="Telegram JWT token identifier")
203-
id: str
204-
first_name: str
205-
last_name: str | None = None
206-
username: str | None = None
207-
photo_url: str | None = None
208-
auth_date: str
209-
hash: str
210-
# scopes: list[Scope] | None = None
211-
# session_name: str | None = None
212-
213-
@classmethod
214-
async def _register(
215-
cls,
216-
user_inp: TGAuthResponseSchema,
217-
background_tasks: BackgroundTasks,
218-
user_session: UserSession = Depends(UnionAuth(auto_error=True, scopes=[], allow_none=True)),
219-
) -> Session:
220-
old_user = None
221-
new_user = {}
222-
223-
# Проверяем получение корректных данных
224-
userinfo = await cls._check(user_inp)
225-
tg_user_id = userinfo['id']
226-
227-
# if user_inp.id_token is None:
228-
# userinfo = await cls._check(user_inp)
229-
# telegram_user_id = user_inp.id
230-
# logger.debug(userinfo)
231-
# else:
232-
# userinfo = jwt.decode(user_inp.id_token, cls.settings.ENCRYPTION_KEY, algorithms=["HS256"])
233-
# telegram_user_id = userinfo['id']
234-
# logger.debug(userinfo)
235-
236-
# Проверяем, что этот тг-аккаунт уже не привязан к существующему пользователю! # TODO: уточнить, что это правда так работает...
237-
user = await cls._get_user('user_id', tg_user_id, db_session=db.session)
238-
if user is not None:
239-
raise AlreadyExists(User, user.id)
240-
241-
if user_session is None:
242-
user = await cls._create_user(db_session=db.session) # if user_session is None else user_session.user
243-
else:
244-
user = user_session.user
245-
old_user = {'user_id': user.id}
246-
new_user["user_id"] = user.id
247-
tg_auth = cls.create_auth_method_param('user_id', tg_user_id, user.id, db_session=db.session)
248-
new_user[cls.get_name()] = {"user_id": tg_auth.value}
249-
userdata = await TelegramAuth._convert_data_to_userdata_format(userinfo)
250-
background_tasks.add_task(
251-
get_kafka_producer().produce,
252-
cls.settings.KAFKA_USER_LOGIN_TOPIC_NAME,
253-
TelegramAuth.generate_kafka_key(user.id),
254-
userdata,
255-
)
256-
await AuthPluginMeta.user_updated(new_user, old_user)
257-
return await cls._create_session(
258-
user, # TODO: спросить, зачем (в google.py) отправляется новая сессия с user. user обновляется к этому моменту? Даже если меняется, то зачем именно здесь пользователю новая сессия?
259-
user_inp.scopes,
260-
db_session=db.session,
261-
session_name=user_inp.session_name,
262-
)
263-
264-
@classmethod
265-
async def _redirect_url(cls):
266-
"""URL на который происходит редирект после завершения входа на стороне провайдера"""
267-
return OauthMeta.UrlSchema(url=cls.settings.TELEGRAM_REDIRECT_URL)
268-
269-
@classmethod
270-
async def _check(cls, user_inp): # TODO: перепроверить, что написанное реально поддерживает проверку по доке тг!
271-
'''Проверка данных пользователя
272-
273-
https://core.telegram.org/widgets/login#checking-authorization
274-
'''
275-
data_check = {
276-
'id': user_inp.id,
277-
'first_name': user_inp.first_name,
278-
'last_name': user_inp.last_name,
279-
'username': user_inp.username,
280-
'photo_url': user_inp.photo_url,
281-
'auth_date': user_inp.auth_date,
282-
}
283-
check_hash = user_inp.hash
284-
data_check_string = ''
285-
for k, v in sorted(data_check.items()):
286-
if v is None:
287-
continue
288-
data_check_string += f'{unquote(k)}={unquote(v)}\n'
289-
data_check_string = data_check_string.rstrip('\n')
290-
secret_key = hashlib.sha256(str.encode(cls.settings.TELEGRAM_BOT_TOKEN)).digest()
291-
signing = hmac.new(secret_key, msg=str.encode(data_check_string), digestmod=hashlib.sha256).hexdigest()
292-
if signing == check_hash:
293-
return data_check
294-
else:
295-
raise OauthAuthFailed('Invalid user data from Telegram', 'Неправильные учетные данные')

0 commit comments

Comments
 (0)