Skip to content

Commit 9fc6f94

Browse files
authored
Issue 229. Авторизация в ТГ через виджет (#240)
1 parent 0e9da5f commit 9fc6f94

1 file changed

Lines changed: 36 additions & 37 deletions

File tree

auth_backend/auth_plugins/telegram.py

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
import hmac
33
import logging
44
from typing import Any
5-
from urllib.parse import quote, unquote
5+
from urllib.parse import unquote
66

77
import jwt
88
from event_schema.auth import UserLogin
99
from fastapi import Depends
1010
from fastapi.background import BackgroundTasks
1111
from fastapi_sqlalchemy import db
12-
from pydantic import BaseModel, Field
12+
from pydantic import BaseModel
1313

1414
from auth_backend.auth_method import AuthPluginMeta, OauthMeta, Session
1515
from auth_backend.exceptions import AlreadyExists, OauthAuthFailed
@@ -26,59 +26,54 @@
2626

2727
class TelegramSettings(Settings):
2828
TELEGRAM_REDIRECT_URL: str = "https://app.test.profcomff.com/auth"
29-
TELEGRAM_BOT_TOKEN: str | None = None
29+
TELEGRAM_BOT_TOKEN: str | None = None # Сделал так для тестов, однако токен нужен для работы авторизации!
3030

3131

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

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
39+
class TGAuthResponseSchema(BaseModel):
40+
id: str
41+
first_name: str
4142
last_name: str | None = None
4243
username: str | None = None
4344
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
45+
auth_date: str
46+
hash: str
47+
scopes: list[Scope] | None = None # Телеграм не передает это поле. Осталось "исторически".
48+
session_name: str | None = None # Телеграм не передает это поле. Осталось "исторически".
4849

4950
@classmethod
5051
async def _register(
5152
cls,
52-
user_inp: OauthResponseSchema,
53+
user_inp: TGAuthResponseSchema,
5354
background_tasks: BackgroundTasks,
5455
user_session: UserSession = Depends(UnionAuth(auto_error=True, scopes=[], allow_none=True)),
5556
) -> Session:
57+
"""Добавление метода аутентификации через (виджет) Телеграма."""
5658
old_user = None
5759
new_user = {}
58-
telegram_user_id = None
59-
userinfo = None
60-
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)
6960

70-
user = await cls._get_user('user_id', telegram_user_id, db_session=db.session)
61+
# Проверяем получение корректных данных
62+
userinfo = await cls._check(user_inp)
63+
tg_user_id = userinfo['id']
7164

65+
user = await cls._get_user('user_id', tg_user_id, db_session=db.session)
7266
if user is not None:
7367
raise AlreadyExists(User, user.id)
68+
7469
if user_session is None:
75-
user = await cls._create_user(db_session=db.session) if user_session is None else user_session.user
70+
user = await cls._create_user(db_session=db.session)
7671
else:
7772
user = user_session.user
7873
old_user = {'user_id': user.id}
7974
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}
75+
tg_auth = cls.create_auth_method_param('user_id', tg_user_id, user.id, db_session=db.session)
76+
new_user[cls.get_name()] = {"user_id": tg_auth.value}
8277
userdata = await TelegramAuth._convert_data_to_userdata_format(userinfo)
8378
background_tasks.add_task(
8479
get_kafka_producer().produce,
@@ -95,10 +90,10 @@ async def _register(
9590
)
9691

9792
@classmethod
98-
async def _login(cls, user_inp: OauthResponseSchema, background_tasks: BackgroundTasks) -> Session:
99-
"""Вход в пользователя с помощью аккаунта https://lk.msu.ru
93+
async def _login(cls, user_inp: TGAuthResponseSchema, background_tasks: BackgroundTasks) -> Session:
94+
"""Вход в пользователя с помощью аккаунта ТГ.
10095
101-
Производит вход, если находит пользователя по уникаотному идендификатору. Если аккаунт не
96+
Производит вход, если находит пользователя по id (из Телеграма). Если аккаунт не
10297
найден, возвращает ошибка.
10398
"""
10499

@@ -129,23 +124,26 @@ async def _login(cls, user_inp: OauthResponseSchema, background_tasks: Backgroun
129124

130125
@classmethod
131126
async def _redirect_url(cls):
132-
"""URL на который происходит редирект после завершения входа на стороне провайдера"""
127+
"""URL на который происходит редирект после завершения входа на стороне провайдера.
128+
129+
В данном случае не предполагается к использованию, т.к. данный URL вшит в виджет.
130+
"""
133131
return OauthMeta.UrlSchema(url=cls.settings.TELEGRAM_REDIRECT_URL)
134132

135133
@classmethod
136134
async def _auth_url(cls):
137-
"""URL на который происходит редирект из приложения для авторизации на стороне провайдера"""
135+
"""URL на который происходит редирект из приложения, чтобы авторизоваться на стороне провайдера.
138136
139-
return OauthMeta.UrlSchema(
140-
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)}"
141-
)
137+
В данном случае не предполагается, т.к. URL вшит в виджет. Отдается атрибут src виджета.
138+
"""
139+
return OauthMeta.UrlSchema(url='https://telegram.org/js/telegram-widget.js?22')
142140

143141
@classmethod
144142
async def _check(cls, user_inp):
145-
'''Проверка данных пользователя
143+
"""Проверка данных пользователя.
146144
147145
https://core.telegram.org/widgets/login#checking-authorization
148-
'''
146+
"""
149147
data_check = {
150148
'id': user_inp.id,
151149
'first_name': user_inp.first_name,
@@ -170,6 +168,7 @@ async def _check(cls, user_inp):
170168

171169
@classmethod
172170
async def _convert_data_to_userdata_format(cls, data: dict[str, Any]) -> UserLogin:
171+
"""Конвертация данных в формат для userdata-api."""
173172
first_name, last_name = '', ''
174173
if 'first_name' in data.keys() and data['first_name'] is not None:
175174
first_name = data['first_name']

0 commit comments

Comments
 (0)