|
6 | 6 | @date:2025/4/14 19:18 |
7 | 7 | @desc: |
8 | 8 | """ |
| 9 | +import datetime |
| 10 | +import os |
| 11 | +import random |
9 | 12 | import re |
10 | 13 | from collections import defaultdict |
11 | 14 | from itertools import product |
12 | | - |
| 15 | +from django.core.mail.backends.smtp import EmailBackend |
13 | 16 | from django.db import transaction |
14 | 17 | from django.db.models import Q, QuerySet |
15 | 18 | from rest_framework import serializers |
|
20 | 23 | from common.db.search import page_search |
21 | 24 | from common.exception.app_exception import AppApiException |
22 | 25 | from common.utils.common import valid_license, password_encrypt |
| 26 | +from maxkb.conf import PROJECT_DIR |
| 27 | +from system_manage.models import SystemSetting, SettingType |
23 | 28 | from users.models import User |
24 | | -from django.utils.translation import gettext_lazy as _ |
| 29 | +from django.utils.translation import gettext_lazy as _, to_locale |
25 | 30 | from django.core import validators |
| 31 | +from django.core.mail import send_mail |
| 32 | +from django.utils.translation import get_language |
26 | 33 |
|
27 | 34 | PASSWORD_REGEX = re.compile( |
28 | 35 | r"^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z_!@#$%^&*`~.()-+=]+$)(?![a-z0-9]+$)(?![a-z_!@#$%^&*`~()-+=]+$)" |
@@ -441,3 +448,169 @@ def update_user_role(instance, user): |
441 | 448 | workspace_id=workspace_id, |
442 | 449 | user_id=user.id |
443 | 450 | ) |
| 451 | + |
| 452 | + |
| 453 | +class RePasswordSerializer(serializers.Serializer): |
| 454 | + email = serializers.EmailField( |
| 455 | + required=True, |
| 456 | + label=_("Email"), |
| 457 | + validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, |
| 458 | + code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) |
| 459 | + |
| 460 | + code = serializers.CharField(required=True, label=_("Verification code")) |
| 461 | + |
| 462 | + password = serializers.CharField(required=True, label=_("Password"), |
| 463 | + validators=[validators.RegexValidator(regex=re.compile( |
| 464 | + "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z_!@#$%^&*`~.()-+=]+$)(?![a-z0-9]+$)(?![a-z_!@#$%^&*`~()-+=]+$)" |
| 465 | + "(?![0-9_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9_!@#$%^&*`~.()-+=]{6,20}$") |
| 466 | + , message=_( |
| 467 | + "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."))]) |
| 468 | + |
| 469 | + re_password = serializers.CharField(required=True, label=_("Confirm Password"), |
| 470 | + validators=[validators.RegexValidator(regex=re.compile( |
| 471 | + "^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z_!@#$%^&*`~.()-+=]+$)(?![a-z0-9]+$)(?![a-z_!@#$%^&*`~()-+=]+$)" |
| 472 | + "(?![0-9_!@#$%^&*`~()-+=]+$)[a-zA-Z0-9_!@#$%^&*`~.()-+=]{6,20}$") |
| 473 | + , message=_( |
| 474 | + "The confirmation password must be 6-20 characters long and must be a combination of letters, numbers, and special characters."))] |
| 475 | + ) |
| 476 | + |
| 477 | + class Meta: |
| 478 | + model = User |
| 479 | + fields = '__all__' |
| 480 | + |
| 481 | + def is_valid(self, *, raise_exception=False): |
| 482 | + super().is_valid(raise_exception=True) |
| 483 | + email = self.data.get("email") |
| 484 | + # TODO 删除缓存 |
| 485 | + # cache_code = user_cache.get(email + ':reset_password') |
| 486 | + if self.data.get('password') != self.data.get('re_password'): |
| 487 | + raise AppApiException(ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.code, |
| 488 | + ExceptionCodeConstants.PASSWORD_NOT_EQ_RE_PASSWORD.value.message) |
| 489 | + # if cache_code != self.data.get('code'): |
| 490 | + # raise AppApiException(ExceptionCodeConstants.CODE_ERROR.value.code, |
| 491 | + # ExceptionCodeConstants.CODE_ERROR.value.message) |
| 492 | + return True |
| 493 | + |
| 494 | + def reset_password(self): |
| 495 | + """ |
| 496 | + 修改密码 |
| 497 | + :return: 是否成功 |
| 498 | + """ |
| 499 | + if self.is_valid(): |
| 500 | + email = self.data.get("email") |
| 501 | + QuerySet(User).filter(email=email).update( |
| 502 | + password=password_encrypt(self.data.get('password'))) |
| 503 | + code_cache_key = email + ":reset_password" |
| 504 | + # 删除验证码缓存 |
| 505 | + # user_cache.delete(code_cache_key) |
| 506 | + return True |
| 507 | + |
| 508 | + |
| 509 | +class SendEmailSerializer(serializers.Serializer): |
| 510 | + email = serializers.EmailField( |
| 511 | + required=True |
| 512 | + , label=_("Email"), |
| 513 | + validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, |
| 514 | + code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) |
| 515 | + |
| 516 | + type = serializers.CharField(required=True, label=_("Type"), validators=[ |
| 517 | + validators.RegexValidator(regex=re.compile("^register|reset_password$"), |
| 518 | + message=_("The type only supports register|reset_password"), code=500) |
| 519 | + ]) |
| 520 | + |
| 521 | + class Meta: |
| 522 | + model = User |
| 523 | + fields = '__all__' |
| 524 | + |
| 525 | + def is_valid(self, *, raise_exception=False): |
| 526 | + super().is_valid(raise_exception=raise_exception) |
| 527 | + user_exists = QuerySet(User).filter(email=self.data.get('email')).exists() |
| 528 | + if not user_exists and self.data.get('type') == 'reset_password': |
| 529 | + raise ExceptionCodeConstants.EMAIL_IS_NOT_EXIST.value.to_app_api_exception() |
| 530 | + elif user_exists and self.data.get('type') == 'register': |
| 531 | + raise ExceptionCodeConstants.EMAIL_IS_EXIST.value.to_app_api_exception() |
| 532 | + code_cache_key = self.data.get('email') + ":" + self.data.get("type") |
| 533 | + code_cache_key_lock = code_cache_key + "_lock" |
| 534 | + ttl = None # user_cache.ttl(code_cache_key_lock) |
| 535 | + if ttl is not None: |
| 536 | + raise AppApiException(500, _("Do not send emails again within {seconds} seconds").format( |
| 537 | + seconds=int(ttl.total_seconds()))) |
| 538 | + return True |
| 539 | + |
| 540 | + def send(self): |
| 541 | + """ |
| 542 | + 发送邮件 |
| 543 | + :return: 是否发送成功 |
| 544 | + :exception 发送失败异常 |
| 545 | + """ |
| 546 | + email = self.data.get("email") |
| 547 | + state = self.data.get("type") |
| 548 | + # 生成随机验证码 |
| 549 | + code = "".join(list(map(lambda i: random.choice(['1', '2', '3', '4', '5', '6', '7', '8', '9', '0' |
| 550 | + ]), range(6)))) |
| 551 | + # 获取邮件模板 |
| 552 | + language = get_language() |
| 553 | + file = open( |
| 554 | + os.path.join(PROJECT_DIR, "apps", "common", 'template', f'email_template_{to_locale(language)}.html'), "r", |
| 555 | + encoding='utf-8') |
| 556 | + content = file.read() |
| 557 | + file.close() |
| 558 | + code_cache_key = email + ":" + state |
| 559 | + code_cache_key_lock = code_cache_key + "_lock" |
| 560 | + # 设置缓存 |
| 561 | + # user_cache.set(code_cache_key_lock, code, timeout=datetime.timedelta(minutes=1)) |
| 562 | + system_setting = QuerySet(SystemSetting).filter(type=SettingType.EMAIL.value).first() |
| 563 | + if system_setting is None: |
| 564 | + # user_cache.delete(code_cache_key_lock) |
| 565 | + raise AppApiException(1004, |
| 566 | + _("The email service has not been set up. Please contact the administrator to set up the email service in [Email Settings].")) |
| 567 | + try: |
| 568 | + connection = EmailBackend(system_setting.meta.get("email_host"), |
| 569 | + system_setting.meta.get('email_port'), |
| 570 | + system_setting.meta.get('email_host_user'), |
| 571 | + system_setting.meta.get('email_host_password'), |
| 572 | + system_setting.meta.get('email_use_tls'), |
| 573 | + False, |
| 574 | + system_setting.meta.get('email_use_ssl') |
| 575 | + ) |
| 576 | + # 发送邮件 |
| 577 | + send_mail(_('【Intelligent knowledge base question and answer system-{action}】').format( |
| 578 | + action=_('User registration') if state == 'register' else _('Change password')), |
| 579 | + '', |
| 580 | + html_message=f'{content.replace("${code}", code)}', |
| 581 | + from_email=system_setting.meta.get('from_email'), |
| 582 | + recipient_list=[email], fail_silently=False, connection=connection) |
| 583 | + except Exception as e: |
| 584 | + # user_cache.delete(code_cache_key_lock) |
| 585 | + raise AppApiException(500, f"{str(e)}" + _("Email sending failed")) |
| 586 | + # user_cache.set(code_cache_key, code, timeout=datetime.timedelta(minutes=30)) |
| 587 | + return True |
| 588 | + |
| 589 | + |
| 590 | +class CheckCodeSerializer(serializers.Serializer): |
| 591 | + """ |
| 592 | + 校验验证码 |
| 593 | + """ |
| 594 | + email = serializers.EmailField( |
| 595 | + required=True, |
| 596 | + label=_("Email"), |
| 597 | + validators=[validators.EmailValidator(message=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.message, |
| 598 | + code=ExceptionCodeConstants.EMAIL_FORMAT_ERROR.value.code)]) |
| 599 | + code = serializers.CharField(required=True, label=_("Verification code")) |
| 600 | + |
| 601 | + type = serializers.CharField(required=True, |
| 602 | + label=_("Type"), |
| 603 | + validators=[ |
| 604 | + validators.RegexValidator(regex=re.compile("^register|reset_password$"), |
| 605 | + message=_( |
| 606 | + "The type only supports register|reset_password"), |
| 607 | + code=500) |
| 608 | + ]) |
| 609 | + |
| 610 | + def is_valid(self, *, raise_exception=False): |
| 611 | + super().is_valid() |
| 612 | + #TODO 这里的缓存 需要重新设计 |
| 613 | + value = None#user_cache.get(self.data.get("email") + ":" + self.data.get("type")) |
| 614 | + if value is None or value != self.data.get("code"): |
| 615 | + raise ExceptionCodeConstants.CODE_ERROR.value.to_app_api_exception() |
| 616 | + return True |
0 commit comments