refactor: 移除邮件功能
发信能力整体下线,唯一的调用方(忘记密码)前端也从来没接过。 - utils/shortcuts.py 删 send_email,连带四个 django.core.mail 相关 import - account/tasks.py 整个删除,里面只有 send_email_async 一个 actor - 删 ApplyResetPasswordAPI / ResetPasswordAPI 及其路由、serializer, User.reset_password_token 和 reset_password_token_expire_time 两个 字段(account/0010) - 删 SMTPAPI / SMTPTestAPI 及其路由、三个 SMTP serializer、 SysOptions.smtp_config - account/templates/reset_password_email.html options/0003 数据迁移删掉库里的 smtp_config 行:_init_option 会为每个 OptionKey 建行,key 从代码里去掉后它会变成孤儿,而且配过 SMTP 的话 value 里存着明文密码。 保留 ResetUserPasswordAPI(管理端直接重置密码,不发信,前端在用)和 User.email(注册要填)。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 6.0.4 on 2026-08-06 05:15
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('account', '0009_remove_user_tfa_token_remove_user_two_factor_auth'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='reset_password_token',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='user',
|
||||
name='reset_password_token_expire_time',
|
||||
),
|
||||
]
|
||||
@@ -36,8 +36,6 @@ class User(AbstractBaseUser):
|
||||
# One of UserType
|
||||
admin_type = models.TextField(default=AdminType.REGULAR_USER, choices=AdminType.choices)
|
||||
problem_permission = models.TextField(default=ProblemPermission.NONE, choices=ProblemPermission.choices)
|
||||
reset_password_token = models.TextField(null=True)
|
||||
reset_password_token_expire_time = models.DateTimeField(null=True)
|
||||
# SSO auth token
|
||||
auth_token = models.TextField(null=True)
|
||||
session_keys = JSONField(default=list, db_default=models.Value([], output_field=models.JSONField()))
|
||||
|
||||
@@ -131,17 +131,6 @@ class EditUserProfileSerializer(serializers.Serializer):
|
||||
language = serializers.CharField(max_length=32, allow_blank=True, required=False)
|
||||
|
||||
|
||||
class ApplyResetPasswordSerializer(serializers.Serializer):
|
||||
email = serializers.EmailField()
|
||||
captcha = serializers.CharField()
|
||||
|
||||
|
||||
class ResetPasswordSerializer(serializers.Serializer):
|
||||
token = serializers.CharField()
|
||||
password = serializers.CharField(min_length=6)
|
||||
captcha = serializers.CharField()
|
||||
|
||||
|
||||
class SSOSerializer(serializers.Serializer):
|
||||
token = serializers.CharField()
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import logging
|
||||
|
||||
import dramatiq
|
||||
|
||||
from options.options import SysOptions
|
||||
from utils.shortcuts import DRAMATIQ_WORKER_ARGS, send_email
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dramatiq.actor(**DRAMATIQ_WORKER_ARGS(max_retries=3))
|
||||
def send_email_async(from_name, to_email, to_name, subject, content):
|
||||
if not SysOptions.smtp_config:
|
||||
return
|
||||
try:
|
||||
send_email(smtp_config=SysOptions.smtp_config, from_name=from_name, to_email=to_email, to_name=to_name, subject=subject, content=content)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
@@ -1,31 +0,0 @@
|
||||
<div>
|
||||
<table cellpadding="0" align="center"
|
||||
style="overflow:hidden;background:#fff;margin:0 auto;text-align:left;position:relative;font-size:14px; font-family:'lucida Grande',Verdana;line-height:1.5;box-shadow:0 0 3px #ccc;border:1px solid #ccc;border-radius:5px;border-collapse:collapse;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th valign="middle"
|
||||
style="height:38px;color:#fff; font-size:14px;line-height:38px; font-weight:bold;text-align:left;padding:10px 24px 6px; border-bottom:1px solid #467ec3;background:#518bcb;border-radius:5px 5px 0 0;">
|
||||
{{ website_name }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div style="padding:20px 35px 40px;">
|
||||
<h2 style="font-weight:bold;margin-bottom:5px;font-size:14px;">Hello, {{ username }}:</h2>
|
||||
<p style="margin-top:20px">
|
||||
Please click <a href="{{ link }}">{{ link }}</a> to reset your password in 20 minutes.
|
||||
</p>
|
||||
<p style="margin-top:20px">
|
||||
To protect your account, please do not use simple passwords.
|
||||
</p>
|
||||
<p style="margin-top:20px">
|
||||
If you still have any questions, please contract system administrator.
|
||||
</p>
|
||||
<p style="margin-left:2em;"></p>
|
||||
<p style="text-indent:0;text-align:right;">{{ website_name }}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -4,12 +4,10 @@ from utils.captcha.views import CaptchaAPIView
|
||||
|
||||
from ..views.oj import (
|
||||
SSOAPI,
|
||||
ApplyResetPasswordAPI,
|
||||
AvatarUploadAPI,
|
||||
Metrics,
|
||||
OpenAPIAppkeyAPI,
|
||||
ProfileProblemDisplayIDRefreshAPI,
|
||||
ResetPasswordAPI,
|
||||
SessionManagementAPI,
|
||||
UserActivityRankAPI,
|
||||
UserChangeEmailAPI,
|
||||
@@ -29,8 +27,6 @@ urlpatterns = [
|
||||
path("register", UserRegisterAPI.as_view()),
|
||||
path("change_password", UserChangePasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
||||
path("change_email", UserChangeEmailAPI.as_view()), # DEPRECATED: 前端未调用
|
||||
path("apply_reset_password", ApplyResetPasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
||||
path("reset_password", ResetPasswordAPI.as_view()), # DEPRECATED: 前端未调用
|
||||
path("captcha", CaptchaAPIView.as_view()),
|
||||
path("check_username_or_email", UsernameOrEmailCheck.as_view()), # DEPRECATED: 前端未调用
|
||||
path("profile", UserProfileAPI.as_view(), name="user_profile_api"),
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import asyncio
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.db.models import Count, Q
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils import timezone
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
|
||||
|
||||
from options.options import SysOptions
|
||||
@@ -24,11 +21,9 @@ from utils.shortcuts import datetime2str, rand_str
|
||||
from ..decorators import login_required
|
||||
from ..models import AdminType, User, UserProfile
|
||||
from ..serializers import (
|
||||
ApplyResetPasswordSerializer,
|
||||
EditUserProfileSerializer,
|
||||
ImageUploadForm,
|
||||
RankInfoSerializer,
|
||||
ResetPasswordSerializer,
|
||||
SSOSerializer,
|
||||
UserChangeEmailSerializer,
|
||||
UserChangePasswordSerializer,
|
||||
@@ -37,7 +32,6 @@ from ..serializers import (
|
||||
UserProfileSerializer,
|
||||
UserRegisterSerializer,
|
||||
)
|
||||
from ..tasks import send_email_async
|
||||
|
||||
|
||||
class UserProfileAPI(AsyncAPIView):
|
||||
@@ -216,61 +210,6 @@ class UserChangePasswordAPI(APIView):
|
||||
return self.error("Invalid old password")
|
||||
|
||||
|
||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
||||
class ApplyResetPasswordAPI(APIView):
|
||||
@validate_serializer(ApplyResetPasswordSerializer)
|
||||
def post(self, request):
|
||||
if request.user.is_authenticated:
|
||||
return self.error("You have already logged in, are you kidding me? ")
|
||||
data = request.data
|
||||
captcha = Captcha(request)
|
||||
if not captcha.check(data["captcha"]):
|
||||
return self.error("Invalid captcha")
|
||||
try:
|
||||
user = User.objects.get(email__iexact=data["email"])
|
||||
except User.DoesNotExist:
|
||||
return self.error("User does not exist")
|
||||
if user.reset_password_token_expire_time and 0 < int((user.reset_password_token_expire_time - now()).total_seconds()) < 20 * 60:
|
||||
return self.error("You can only reset password once per 20 minutes")
|
||||
user.reset_password_token = rand_str()
|
||||
user.reset_password_token_expire_time = now() + timedelta(minutes=20)
|
||||
user.save()
|
||||
render_data = {
|
||||
"username": user.username,
|
||||
"website_name": SysOptions.website_name,
|
||||
"link": f"{SysOptions.website_base_url}/reset-password/{user.reset_password_token}",
|
||||
}
|
||||
email_html = render_to_string("reset_password_email.html", render_data)
|
||||
send_email_async.send(
|
||||
from_name=SysOptions.website_name_shortcut,
|
||||
to_email=user.email,
|
||||
to_name=user.username,
|
||||
subject="Reset your password",
|
||||
content=email_html,
|
||||
)
|
||||
return self.success("Succeeded")
|
||||
|
||||
|
||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
||||
class ResetPasswordAPI(APIView):
|
||||
@validate_serializer(ResetPasswordSerializer)
|
||||
def post(self, request):
|
||||
data = request.data
|
||||
captcha = Captcha(request)
|
||||
if not captcha.check(data["captcha"]):
|
||||
return self.error("Invalid captcha")
|
||||
try:
|
||||
user = User.objects.get(reset_password_token=data["token"])
|
||||
except User.DoesNotExist:
|
||||
return self.error("Token does not exist")
|
||||
if user.reset_password_token_expire_time < now():
|
||||
return self.error("Token has expired")
|
||||
user.reset_password_token = None
|
||||
user.set_password(data["password"])
|
||||
user.save()
|
||||
return self.success("Succeeded")
|
||||
|
||||
|
||||
# DEPRECATED: 前端未调用 (2026-05-26)
|
||||
class SessionManagementAPI(APIView):
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user