Skip to content

Commit

Permalink
feat: support tg login
Browse files Browse the repository at this point in the history
  • Loading branch information
Ehco1996 committed Jan 13, 2024
1 parent ef268fc commit 474a313
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 40 deletions.
12 changes: 12 additions & 0 deletions apps/sspanel/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ class TGLoginForm(forms.Form):
),
)

tg_user_id = forms.CharField(
required=True,
label="TG ID",
widget=forms.TextInput(
attrs={
"class": "input is-primary",
"placeholder": "TG ID",
"readonly": "readonly",
}
),
)

def clean(self):
if not self.is_valid():
raise forms.ValidationError("用户名和密码为必填项")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 4.2.6 on 2024-01-13 13:46

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("sspanel", "0020_ticketmessage"),
]

operations = [
migrations.AlterModelOptions(
name="ticketmessage",
options={
"ordering": ("ticket", "created_at"),
"verbose_name": "工单回复",
"verbose_name_plural": "工单回复",
},
),
migrations.AlterUniqueTogether(
name="usersocialprofile",
unique_together=set(),
),
migrations.AddField(
model_name="usersocialprofile",
name="platform_user_id",
field=models.CharField(
blank=True, max_length=32, null=True, verbose_name="用户ID"
),
),
migrations.AlterUniqueTogether(
name="usersocialprofile",
unique_together={
("platform", "platform_user_id", "user_id"),
("platform", "platform_user_id"),
},
),
]
19 changes: 12 additions & 7 deletions apps/sspanel/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class UserSocialProfile(models.Model, UserMixin):
"平台", default=TYPE_TG, choices=TYPE_CHOICES, max_length=32
)
platform_username = models.CharField("用户名", max_length=32)
platform_user_id = models.CharField("用户ID", max_length=32, null=True, blank=True)
created_at = models.DateTimeField(
auto_now_add=True, db_index=True, help_text="创建时间", verbose_name="创建时间"
)
Expand All @@ -309,16 +310,18 @@ class Meta:
verbose_name = "用户社交资料"
verbose_name_plural = "用户社交资料"
unique_together = [
["platform", "platform_username"],
["platform", "platform_username", "user_id"],
["platform", "platform_user_id"],
["platform", "platform_user_id", "user_id"],
]

@classmethod
def get_or_create_and_update_info(cls, platform, username, data):
def get_or_create_and_update_info(
cls, platform, platform_user_id, platform_username, data
):
usp, _ = cls.objects.get_or_create(
platform=platform,
platform_username=username,
defaults={"raw_auth_data": data},
platform_user_id=platform_user_id,
defaults={"raw_auth_data": data, "platform_username": platform_username},
)
# update auth info
usp.raw_auth_data = data
Expand All @@ -330,8 +333,10 @@ def list_by_user_id(cls, user_id):
return cls.objects.filter(user_id=user_id)

@classmethod
def get_by_platform(cls, platform, username):
return cls.objects.filter(platform=platform, platform_username=username).first()
def get_by_platform_user_id(cls, platform, platform_user_id):
return cls.objects.filter(
platform=platform, platform_user_id=platform_user_id
).first()

def bind(self, user):
self.user_id = user.id
Expand Down
73 changes: 42 additions & 31 deletions apps/sspanel/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,15 @@ def get(self, request):


class TelegramLoginView(View):
def _find_tg_username(self, auth_data):
for key in ["username", "first_name", "last_name"]:
if auth_data.get(key):
return auth_data[key]
return ""

def get(self, request):
try:
result = verify_telegram_authentication(
auth_data = verify_telegram_authentication(
bot_token=settings.TELEGRAM_BOT_TOKEN, request_data=request.GET
)
except TelegramDataIsOutdatedError:
Expand All @@ -117,52 +123,57 @@ def get(self, request):
return HttpResponseBadRequest("The data is not related to Telegram!")
except Exception as e:
return HttpResponseBadRequest(str(e))

if "username" in result:
tg_username = result["username"]
else:
tg_username = (
result.get("first_name", "") + " " + result.get("last_name", "")
)

try:
tg_user_id = auth_data["id"]
except KeyError:
return HttpResponseBadRequest(f"The data is not valid {auth_data}")
tg_username = self._find_tg_username(auth_data)
# 已经绑定过了
usp = UserSocialProfile.get_or_create_and_update_info(
UserSocialProfile.TYPE_TG, tg_username, result
UserSocialProfile.TYPE_TG, tg_user_id, tg_username, auth_data
)
if usp.user_id:
if usp.user_id and usp.user.is_active:
login(
request,
usp.user,
backend="django.contrib.auth.backends.ModelBackend",
)
messages.success(request, "自动跳转到用户中心", extra_tags="登录成功!")
return HttpResponseRedirect(reverse("sspanel:userinfo"))
# 需要渲染绑定页面
# user not found 渲染绑定页面
context = {
"form": TGLoginForm(initial={"tg_username": tg_username}),
"form": TGLoginForm(
initial={"tg_username": tg_username, "tg_user_id": tg_user_id}
),
}
return render(request, "web/telegram_login.html", context)

def post(self, request):
form = TGLoginForm(request.POST)
if form.is_valid():
user = authenticate(
username=form.cleaned_data["username"],
password=form.cleaned_data["password"],
)
usp = UserSocialProfile.get_by_platform(
UserSocialProfile.TYPE_TG, form.cleaned_data["tg_username"]
)
if user and user.is_active and usp:
with transaction.atomic():
login(request, user)
usp.bind(user)
messages.success(request, "自动跳转到用户中心", extra_tags="绑定成功!")
return HttpResponseRedirect(reverse("sspanel:userinfo"))
else:
messages.error(request, "账户不存在(请先注册)/密码不正确!", extra_tags="绑定失败!")

return HttpResponseRedirect(reverse("sspanel:login"))
if not form.is_valid():
return HttpResponseBadRequest("表单数据不合法")
user = authenticate(
username=form.cleaned_data["username"],
password=form.cleaned_data["password"],
)
if not user:
messages.error(request, "账户不存在(请先注册)/密码不正确!", extra_tags="绑定失败!")
return HttpResponseRedirect(reverse("sspanel:login"))
usp = UserSocialProfile.get_by_platform_user_id(
UserSocialProfile.TYPE_TG, form.cleaned_data["tg_user_id"]
)
if not usp:
messages.error(request, "请先登录TG账号!", extra_tags="绑定失败!")
return HttpResponseRedirect(reverse("sspanel:login"))
elif usp.user_id:
messages.error(request, "该TG账号已经绑定过了!", extra_tags="绑定失败!")
return HttpResponseRedirect(reverse("sspanel:login"))
else:
with transaction.atomic():
login(request, user)
usp.bind(user)
messages.success(request, "自动跳转到用户中心", extra_tags="绑定成功!")
return HttpResponseRedirect(reverse("sspanel:userinfo"))


class UserLogOutView(View):
Expand Down
1 change: 1 addition & 0 deletions configs/default/sites.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
SITE_HOST = os.getenv("SITE_HOST", "http://127.0.0.1:8000")
CORS_ALLOWED_ORIGINS = [SITE_HOST] # django-cors-headers
CSRF_TRUSTED_ORIGINS = [SITE_HOST] # django built-in
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin-allow-popups"

# 网站密钥
SECRET_KEY = os.getenv("SECRET_KEY", "aasdasdas")
Expand Down
2 changes: 1 addition & 1 deletion templates/web/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ <h3 class="title">登录:</h3>

{% settings_value "TELEGRAM_BOT_NAME" as tg_bot_name %}
{% settings_value "TELEGRAM_LOGIN_REDIRECT_URL" as tg_redirect_url %}
{% if tg_bot_name %}
{% if tg_bot_name and request.get_host in tg_redirect_url %}
<p class="control">
<script async src="https://telegram.org/js/telegram-widget.js?22"
data-telegram-login="{{ tg_bot_name }}" data-size="large" data-auth-url="{{ tg_redirect_url }}"
Expand Down
2 changes: 1 addition & 1 deletion templates/web/telegram_login.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<div class="hero-body">
<div class="container">
<h1 class="title">
绑定 Telegram 账号
第一次需要绑定 Telegram 账号
</h1>
<h2 class="subtitle">
绑定操作只需要执行一次
Expand Down

0 comments on commit 474a313

Please sign in to comment.