diff --git a/gvsds/apps/gerp/views/mail.py b/gvsds/apps/gerp/views/mail.py index 780b71f5..eadd5cbf 100644 --- a/gvsds/apps/gerp/views/mail.py +++ b/gvsds/apps/gerp/views/mail.py @@ -41,6 +41,8 @@ _MAIL_TOKEN_TTL = 24 * 3600 # token 有效期 24 小时 _MAIL_TOKEN_STORE = {} # token -> {"email": str, "password": str, "exp": float} _MAIL_TOKEN_LOCK = threading.Lock() +_MAILBOX_LIMIT = 5 # 每个用户最多申请的企业邮箱数 + def _PurgeMailTokens(): """清理已过期 token。""" @@ -218,9 +220,9 @@ class MailApplyView(ELTAuthenticatedAPIView): if not user_bytes: return Response({'code': 1, 'msg': '无法识别当前用户'}, status=401) - # 每个用户最多一个邮箱 - if UserMailbox.objects.filter(UserUUID=request.user).exists(): - return Response({'code': 1, 'msg': '您已拥有邮箱,一个用户只能申请一个'}, status=400) + # 每个用户最多申请 _MAILBOX_LIMIT 个邮箱 + if UserMailbox.objects.filter(UserUUID=request.user).count() >= _MAILBOX_LIMIT: + return Response({'code': 1, 'msg': f'邮箱数量已达上限(最多 {_MAILBOX_LIMIT} 个)'}, status=400) mailbox_name = (request.data.get('MailboxName') or '').strip() password = request.data.get('Password') or '' @@ -291,9 +293,11 @@ class MailChangePasswordView(ELTAuthenticatedAPIView): permission_classes = [IsAuthenticated] def post(self, request): - mailbox = UserMailbox.objects.filter(UserUUID=request.user, Status=1).first() + mailbox_name = (request.data.get('MailboxName') or '').strip() + queryset = UserMailbox.objects.filter(UserUUID=request.user, Status=1) + mailbox = queryset.filter(MailboxName=mailbox_name).first() if mailbox_name else queryset.first() if mailbox is None: - return Response({'code': 1, 'msg': '您尚未申请邮箱'}, status=404) + return Response({'code': 1, 'msg': '未找到该邮箱' if mailbox_name else '您尚未申请邮箱'}, status=404) # 安全:改密必须先验证当前邮箱密码,防止已登录会话被劫持后直接改密 old_password = request.data.get('OldPassword') or '' if not old_password: @@ -315,27 +319,23 @@ class MailChangePasswordView(ELTAuthenticatedAPIView): class MailMyMailboxView(ELTAuthenticatedAPIView): - """我的邮箱信息 + JMAP 连接配置(供浏览器直连)。""" + """我的邮箱列表 + JMAP 连接配置(供浏览器直连)。""" permission_classes = [IsAuthenticated] def get(self, request): - mailbox = UserMailbox.objects.filter(UserUUID=request.user).first() session = StalwartMailManager.SessionConfig() - if mailbox is None: - return Response({'code': 0, 'data': {'Mailbox': None, 'Session': session}}) - return Response({ - 'code': 0, - 'data': { - 'Mailbox': { - 'MailboxName': mailbox.MailboxName, - 'QuotaMB': mailbox.QuotaMB, - 'Status': mailbox.Status, - 'CreateTime': mailbox.CreateTime.isoformat() if mailbox.CreateTime else None, - }, - 'Session': session, - }, - }) + rows = list(UserMailbox.objects.filter(UserUUID=request.user).order_by('-CreateTime')) + mailbox_list = [ + { + 'MailboxName': m.MailboxName, + 'QuotaMB': m.QuotaMB, + 'Status': m.Status, + 'CreateTime': m.CreateTime.isoformat() if m.CreateTime else None, + } + for m in rows + ] + return Response({'code': 0, 'data': {'MailboxList': mailbox_list, 'Session': session}}) class MailAdminListView(ELTAuthenticatedAPIView): @@ -403,16 +403,18 @@ class MailLoginView(ELTAuthenticatedAPIView): 密码仅在本次请求明文传输一次;后端校验通过后把凭据存入进程内存, 返回 token,后续 JMAP 调用只携带 token(见 MailJmapView)。 - 请求体:Password + 请求体:Password, MailboxName(可选,多邮箱时指定登录哪个;缺省取第一个) 响应:{code: 0, data: {Token, MailboxName}} """ permission_classes = [IsAuthenticated] def post(self, request): - mailbox = UserMailbox.objects.filter(UserUUID=request.user, Status=1).first() + mailbox_name = (request.data.get('MailboxName') or '').strip() + queryset = UserMailbox.objects.filter(UserUUID=request.user, Status=1) + mailbox = queryset.filter(MailboxName=mailbox_name).first() if mailbox_name else queryset.first() if mailbox is None: - return Response({'code': 1, 'msg': '您尚未申请邮箱'}, status=404) + return Response({'code': 1, 'msg': '未找到该邮箱' if mailbox_name else '您尚未申请邮箱'}, status=404) password = request.data.get('Password') or '' if not password: return Response({'code': 1, 'msg': '请输入邮箱密码'}, status=400)