From ea61420441b9e1141ab6f4120bc1ca6b57fd7962 Mon Sep 17 00:00:00 2001 From: zyssyz123 <916125788@qq.com> Date: Mon, 8 Sep 2025 19:20:09 +0800 Subject: [PATCH] Revert "feat: email register refactor" (#25367) --- api/.env.example | 1 - api/configs/feature/__init__.py | 11 -- api/controllers/console/__init__.py | 11 +- .../console/auth/email_register.py | 154 ------------------ api/controllers/console/auth/error.py | 12 -- .../console/auth/forgot_password.py | 39 ++++- api/controllers/console/auth/login.py | 28 +++- api/controllers/console/wraps.py | 13 -- api/libs/email_i18n.py | 52 ------ api/services/account_service.py | 111 +------------ api/tasks/mail_register_task.py | 86 ---------- api/tasks/mail_reset_password_task.py | 45 ----- .../register_email_template_en-US.html | 87 ---------- .../register_email_template_zh-CN.html | 87 ---------- ...ail_when_account_exist_template_en-US.html | 94 ----------- ...ail_when_account_exist_template_zh-CN.html | 95 ----------- ..._not_exist_no_register_template_en-US.html | 85 ---------- ..._not_exist_no_register_template_zh-CN.html | 84 ---------- ...when_account_not_exist_template_en-US.html | 89 ---------- ...when_account_not_exist_template_zh-CN.html | 89 ---------- .../register_email_template_en-US.html | 83 ---------- .../register_email_template_zh-CN.html | 83 ---------- ...ail_when_account_exist_template_en-US.html | 90 ---------- ...ail_when_account_exist_template_zh-CN.html | 91 ----------- ..._not_exist_no_register_template_en-US.html | 81 --------- ..._not_exist_no_register_template_zh-CN.html | 81 --------- ...when_account_not_exist_template_en-US.html | 85 ---------- ...when_account_not_exist_template_zh-CN.html | 85 ---------- api/tests/integration_tests/.env.example | 1 - .../services/test_account_service.py | 3 +- .../auth/test_authentication_security.py | 34 ++-- .../services/test_account_service.py | 3 +- docker/.env.example | 1 - docker/docker-compose.yaml | 1 - 34 files changed, 79 insertions(+), 1916 deletions(-) delete mode 100644 api/controllers/console/auth/email_register.py delete mode 100644 api/tasks/mail_register_task.py delete mode 100644 api/templates/register_email_template_en-US.html delete mode 100644 api/templates/register_email_template_zh-CN.html delete mode 100644 api/templates/register_email_when_account_exist_template_en-US.html delete mode 100644 api/templates/register_email_when_account_exist_template_zh-CN.html delete mode 100644 api/templates/reset_password_mail_when_account_not_exist_no_register_template_en-US.html delete mode 100644 api/templates/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html delete mode 100644 api/templates/reset_password_mail_when_account_not_exist_template_en-US.html delete mode 100644 api/templates/reset_password_mail_when_account_not_exist_template_zh-CN.html delete mode 100644 api/templates/without-brand/register_email_template_en-US.html delete mode 100644 api/templates/without-brand/register_email_template_zh-CN.html delete mode 100644 api/templates/without-brand/register_email_when_account_exist_template_en-US.html delete mode 100644 api/templates/without-brand/register_email_when_account_exist_template_zh-CN.html delete mode 100644 api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_en-US.html delete mode 100644 api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html delete mode 100644 api/templates/without-brand/reset_password_mail_when_account_not_exist_template_en-US.html delete mode 100644 api/templates/without-brand/reset_password_mail_when_account_not_exist_template_zh-CN.html diff --git a/api/.env.example b/api/.env.example index 2986402e9e..8d783af134 100644 --- a/api/.env.example +++ b/api/.env.example @@ -530,7 +530,6 @@ ENDPOINT_URL_TEMPLATE=http://localhost:5002/e/{hook_id} # Reset password token expiry minutes RESET_PASSWORD_TOKEN_EXPIRY_MINUTES=5 -EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES=5 CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES=5 OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES=5 diff --git a/api/configs/feature/__init__.py b/api/configs/feature/__init__.py index 0d6f4e416e..899fecea7c 100644 --- a/api/configs/feature/__init__.py +++ b/api/configs/feature/__init__.py @@ -31,12 +31,6 @@ class SecurityConfig(BaseSettings): description="Duration in minutes for which a password reset token remains valid", default=5, ) - - EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES: PositiveInt = Field( - description="Duration in minutes for which a email register token remains valid", - default=5, - ) - CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES: PositiveInt = Field( description="Duration in minutes for which a change email token remains valid", default=5, @@ -645,11 +639,6 @@ class AuthConfig(BaseSettings): default=86400, ) - EMAIL_REGISTER_LOCKOUT_DURATION: PositiveInt = Field( - description="Time (in seconds) a user must wait before retrying email register after exceeding the rate limit.", - default=86400, - ) - class ModerationConfig(BaseSettings): """ diff --git a/api/controllers/console/__init__.py b/api/controllers/console/__init__.py index 9634f3ca17..5ad7645969 100644 --- a/api/controllers/console/__init__.py +++ b/api/controllers/console/__init__.py @@ -70,16 +70,7 @@ from .app import ( ) # Import auth controllers -from .auth import ( - activate, - data_source_bearer_auth, - data_source_oauth, - email_register, - forgot_password, - login, - oauth, - oauth_server, -) +from .auth import activate, data_source_bearer_auth, data_source_oauth, forgot_password, login, oauth, oauth_server # Import billing controllers from .billing import billing, compliance diff --git a/api/controllers/console/auth/email_register.py b/api/controllers/console/auth/email_register.py deleted file mode 100644 index 458e70c8de..0000000000 --- a/api/controllers/console/auth/email_register.py +++ /dev/null @@ -1,154 +0,0 @@ -from flask import request -from flask_restx import Resource, reqparse -from sqlalchemy import select -from sqlalchemy.orm import Session - -from constants.languages import languages -from controllers.console import api -from controllers.console.auth.error import ( - EmailAlreadyInUseError, - EmailCodeError, - EmailRegisterLimitError, - InvalidEmailError, - InvalidTokenError, - PasswordMismatchError, -) -from controllers.console.error import AccountInFreezeError, EmailSendIpLimitError -from controllers.console.wraps import email_password_login_enabled, email_register_enabled, setup_required -from extensions.ext_database import db -from libs.helper import email, extract_remote_ip -from libs.password import valid_password -from models.account import Account -from services.account_service import AccountService -from services.errors.account import AccountRegisterError -from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError - - -class EmailRegisterSendEmailApi(Resource): - @setup_required - @email_password_login_enabled - @email_register_enabled - def post(self): - parser = reqparse.RequestParser() - parser.add_argument("email", type=email, required=True, location="json") - parser.add_argument("language", type=str, required=False, location="json") - args = parser.parse_args() - - ip_address = extract_remote_ip(request) - if AccountService.is_email_send_ip_limit(ip_address): - raise EmailSendIpLimitError() - - if args["language"] is not None and args["language"] == "zh-Hans": - language = "zh-Hans" - else: - language = "en-US" - - with Session(db.engine) as session: - account = session.execute(select(Account).filter_by(email=args["email"])).scalar_one_or_none() - token = None - token = AccountService.send_email_register_email(email=args["email"], account=account, language=language) - return {"result": "success", "data": token} - - -class EmailRegisterCheckApi(Resource): - @setup_required - @email_password_login_enabled - @email_register_enabled - def post(self): - parser = reqparse.RequestParser() - parser.add_argument("email", type=str, required=True, location="json") - parser.add_argument("code", type=str, required=True, location="json") - parser.add_argument("token", type=str, required=True, nullable=False, location="json") - args = parser.parse_args() - - user_email = args["email"] - - is_email_register_error_rate_limit = AccountService.is_email_register_error_rate_limit(args["email"]) - if is_email_register_error_rate_limit: - raise EmailRegisterLimitError() - - token_data = AccountService.get_email_register_data(args["token"]) - if token_data is None: - raise InvalidTokenError() - - if user_email != token_data.get("email"): - raise InvalidEmailError() - - if args["code"] != token_data.get("code"): - AccountService.add_email_register_error_rate_limit(args["email"]) - raise EmailCodeError() - - # Verified, revoke the first token - AccountService.revoke_email_register_token(args["token"]) - - # Refresh token data by generating a new token - _, new_token = AccountService.generate_email_register_token( - user_email, code=args["code"], additional_data={"phase": "register"} - ) - - AccountService.reset_email_register_error_rate_limit(args["email"]) - return {"is_valid": True, "email": token_data.get("email"), "token": new_token} - - -class EmailRegisterResetApi(Resource): - @setup_required - @email_password_login_enabled - @email_register_enabled - def post(self): - parser = reqparse.RequestParser() - parser.add_argument("token", type=str, required=True, nullable=False, location="json") - parser.add_argument("new_password", type=valid_password, required=True, nullable=False, location="json") - parser.add_argument("password_confirm", type=valid_password, required=True, nullable=False, location="json") - args = parser.parse_args() - - # Validate passwords match - if args["new_password"] != args["password_confirm"]: - raise PasswordMismatchError() - - # Validate token and get register data - register_data = AccountService.get_email_register_data(args["token"]) - if not register_data: - raise InvalidTokenError() - # Must use token in reset phase - if register_data.get("phase", "") != "register": - raise InvalidTokenError() - - # Revoke token to prevent reuse - AccountService.revoke_email_register_token(args["token"]) - - email = register_data.get("email", "") - - with Session(db.engine) as session: - account = session.execute(select(Account).filter_by(email=email)).scalar_one_or_none() - - if account: - raise EmailAlreadyInUseError() - else: - account = self._create_new_account(email, args["password_confirm"]) - token_pair = AccountService.login(account=account, ip_address=extract_remote_ip(request)) - AccountService.reset_login_error_rate_limit(email) - - return {"result": "success", "data": token_pair.model_dump()} - - def _create_new_account(self, email, password): - # Create new account if allowed - try: - account = AccountService.create_account_and_tenant( - email=email, - name=email, - password=password, - interface_language=languages[0], - ) - except WorkSpaceNotAllowedCreateError: - pass - except WorkspacesLimitExceededError: - pass - except AccountRegisterError: - raise AccountInFreezeError() - - return account - - -api.add_resource(EmailRegisterSendEmailApi, "/email-register/send-email") -api.add_resource(EmailRegisterCheckApi, "/email-register/validity") -api.add_resource(EmailRegisterResetApi, "/email-register") diff --git a/api/controllers/console/auth/error.py b/api/controllers/console/auth/error.py index 9cda8c90b1..7853bef917 100644 --- a/api/controllers/console/auth/error.py +++ b/api/controllers/console/auth/error.py @@ -31,12 +31,6 @@ class PasswordResetRateLimitExceededError(BaseHTTPException): code = 429 -class EmailRegisterRateLimitExceededError(BaseHTTPException): - error_code = "email_register_rate_limit_exceeded" - description = "Too many email register emails have been sent. Please try again in 1 minute." - code = 429 - - class EmailChangeRateLimitExceededError(BaseHTTPException): error_code = "email_change_rate_limit_exceeded" description = "Too many email change emails have been sent. Please try again in 1 minute." @@ -91,12 +85,6 @@ class EmailPasswordResetLimitError(BaseHTTPException): code = 429 -class EmailRegisterLimitError(BaseHTTPException): - error_code = "email_register_limit" - description = "Too many failed email register attempts. Please try again in 24 hours." - code = 429 - - class EmailChangeLimitError(BaseHTTPException): error_code = "email_change_limit" description = "Too many failed email change attempts. Please try again in 24 hours." diff --git a/api/controllers/console/auth/forgot_password.py b/api/controllers/console/auth/forgot_password.py index d7558e0f67..ede0696854 100644 --- a/api/controllers/console/auth/forgot_password.py +++ b/api/controllers/console/auth/forgot_password.py @@ -6,6 +6,7 @@ from flask_restx import Resource, reqparse from sqlalchemy import select from sqlalchemy.orm import Session +from constants.languages import languages from controllers.console import api from controllers.console.auth.error import ( EmailCodeError, @@ -14,7 +15,7 @@ from controllers.console.auth.error import ( InvalidTokenError, PasswordMismatchError, ) -from controllers.console.error import AccountNotFound, EmailSendIpLimitError +from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError from controllers.console.wraps import email_password_login_enabled, setup_required from events.tenant_event import tenant_was_created from extensions.ext_database import db @@ -22,6 +23,8 @@ from libs.helper import email, extract_remote_ip from libs.password import hash_password, valid_password from models.account import Account from services.account_service import AccountService, TenantService +from services.errors.account import AccountRegisterError +from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError from services.feature_service import FeatureService @@ -45,13 +48,15 @@ class ForgotPasswordSendEmailApi(Resource): with Session(db.engine) as session: account = session.execute(select(Account).filter_by(email=args["email"])).scalar_one_or_none() - - token = AccountService.send_reset_password_email( - account=account, - email=args["email"], - language=language, - is_allow_register=FeatureService.get_system_features().is_allow_register, - ) + token = None + if account is None: + if FeatureService.get_system_features().is_allow_register: + token = AccountService.send_reset_password_email(email=args["email"], language=language) + return {"result": "fail", "data": token, "code": "account_not_found"} + else: + raise AccountNotFound() + else: + token = AccountService.send_reset_password_email(account=account, email=args["email"], language=language) return {"result": "success", "data": token} @@ -132,7 +137,7 @@ class ForgotPasswordResetApi(Resource): if account: self._update_existing_account(account, password_hashed, salt, session) else: - raise AccountNotFound() + self._create_new_account(email, args["password_confirm"]) return {"result": "success"} @@ -152,6 +157,22 @@ class ForgotPasswordResetApi(Resource): account.current_tenant = tenant tenant_was_created.send(tenant) + def _create_new_account(self, email, password): + # Create new account if allowed + try: + AccountService.create_account_and_tenant( + email=email, + name=email, + password=password, + interface_language=languages[0], + ) + except WorkSpaceNotAllowedCreateError: + pass + except WorkspacesLimitExceededError: + pass + except AccountRegisterError: + raise AccountInFreezeError() + api.add_resource(ForgotPasswordSendEmailApi, "/forgot-password") api.add_resource(ForgotPasswordCheckApi, "/forgot-password/validity") diff --git a/api/controllers/console/auth/login.py b/api/controllers/console/auth/login.py index 3b35ab3c23..b11bc0c6ac 100644 --- a/api/controllers/console/auth/login.py +++ b/api/controllers/console/auth/login.py @@ -26,6 +26,7 @@ from controllers.console.error import ( from controllers.console.wraps import email_password_login_enabled, setup_required from events.tenant_event import tenant_was_created from libs.helper import email, extract_remote_ip +from libs.password import valid_password from models.account import Account from services.account_service import AccountService, RegisterService, TenantService from services.billing_service import BillingService @@ -43,9 +44,10 @@ class LoginApi(Resource): """Authenticate user and login.""" parser = reqparse.RequestParser() parser.add_argument("email", type=email, required=True, location="json") - parser.add_argument("password", type=str, required=True, location="json") + parser.add_argument("password", type=valid_password, required=True, location="json") parser.add_argument("remember_me", type=bool, required=False, default=False, location="json") parser.add_argument("invite_token", type=str, required=False, default=None, location="json") + parser.add_argument("language", type=str, required=False, default="en-US", location="json") args = parser.parse_args() if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(args["email"]): @@ -59,6 +61,11 @@ class LoginApi(Resource): if invitation: invitation = RegisterService.get_invitation_if_token_valid(None, args["email"], invitation) + if args["language"] is not None and args["language"] == "zh-Hans": + language = "zh-Hans" + else: + language = "en-US" + try: if invitation: data = invitation.get("data", {}) @@ -73,6 +80,12 @@ class LoginApi(Resource): except services.errors.account.AccountPasswordError: AccountService.add_login_error_rate_limit(args["email"]) raise AuthenticationFailedError() + except services.errors.account.AccountNotFoundError: + if FeatureService.get_system_features().is_allow_register: + token = AccountService.send_reset_password_email(email=args["email"], language=language) + return {"result": "fail", "data": token, "code": "account_not_found"} + else: + raise AccountNotFound() # SELF_HOSTED only have one workspace tenants = TenantService.get_join_tenants(account) if len(tenants) == 0: @@ -120,12 +133,13 @@ class ResetPasswordSendEmailApi(Resource): except AccountRegisterError: raise AccountInFreezeError() - token = AccountService.send_reset_password_email( - email=args["email"], - account=account, - language=language, - is_allow_register=FeatureService.get_system_features().is_allow_register, - ) + if account is None: + if FeatureService.get_system_features().is_allow_register: + token = AccountService.send_reset_password_email(email=args["email"], language=language) + else: + raise AccountNotFound() + else: + token = AccountService.send_reset_password_email(account=account, language=language) return {"result": "success", "data": token} diff --git a/api/controllers/console/wraps.py b/api/controllers/console/wraps.py index 092071481e..e375fe285b 100644 --- a/api/controllers/console/wraps.py +++ b/api/controllers/console/wraps.py @@ -242,19 +242,6 @@ def email_password_login_enabled(view: Callable[P, R]): return decorated -def email_register_enabled(view): - @wraps(view) - def decorated(*args, **kwargs): - features = FeatureService.get_system_features() - if features.is_allow_register: - return view(*args, **kwargs) - - # otherwise, return 403 - abort(403) - - return decorated - - def enable_change_email(view: Callable[P, R]): @wraps(view) def decorated(*args: P.args, **kwargs: P.kwargs): diff --git a/api/libs/email_i18n.py b/api/libs/email_i18n.py index 9dde87d800..3c039dff53 100644 --- a/api/libs/email_i18n.py +++ b/api/libs/email_i18n.py @@ -21,7 +21,6 @@ class EmailType(Enum): """Enumeration of supported email types.""" RESET_PASSWORD = "reset_password" - RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST = "reset_password_when_account_not_exist" INVITE_MEMBER = "invite_member" EMAIL_CODE_LOGIN = "email_code_login" CHANGE_EMAIL_OLD = "change_email_old" @@ -35,9 +34,6 @@ class EmailType(Enum): ENTERPRISE_CUSTOM = "enterprise_custom" QUEUE_MONITOR_ALERT = "queue_monitor_alert" DOCUMENT_CLEAN_NOTIFY = "document_clean_notify" - EMAIL_REGISTER = "email_register" - EMAIL_REGISTER_WHEN_ACCOUNT_EXIST = "email_register_when_account_exist" - RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST_NO_REGISTER = "reset_password_when_account_not_exist_no_register" class EmailLanguage(Enum): @@ -445,54 +441,6 @@ def create_default_email_config() -> EmailI18nConfig: branded_template_path="clean_document_job_mail_template_zh-CN.html", ), }, - EmailType.EMAIL_REGISTER: { - EmailLanguage.EN_US: EmailTemplate( - subject="Register Your {application_title} Account", - template_path="register_email_template_en-US.html", - branded_template_path="without-brand/register_email_template_en-US.html", - ), - EmailLanguage.ZH_HANS: EmailTemplate( - subject="注册您的 {application_title} 账户", - template_path="register_email_template_zh-CN.html", - branded_template_path="without-brand/register_email_template_zh-CN.html", - ), - }, - EmailType.EMAIL_REGISTER_WHEN_ACCOUNT_EXIST: { - EmailLanguage.EN_US: EmailTemplate( - subject="Register Your {application_title} Account", - template_path="register_email_when_account_exist_template_en-US.html", - branded_template_path="without-brand/register_email_when_account_exist_template_en-US.html", - ), - EmailLanguage.ZH_HANS: EmailTemplate( - subject="注册您的 {application_title} 账户", - template_path="register_email_when_account_exist_template_zh-CN.html", - branded_template_path="without-brand/register_email_when_account_exist_template_zh-CN.html", - ), - }, - EmailType.RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST: { - EmailLanguage.EN_US: EmailTemplate( - subject="Reset Your {application_title} Password", - template_path="reset_password_mail_when_account_not_exist_template_en-US.html", - branded_template_path="without-brand/reset_password_mail_when_account_not_exist_template_en-US.html", - ), - EmailLanguage.ZH_HANS: EmailTemplate( - subject="重置您的 {application_title} 密码", - template_path="reset_password_mail_when_account_not_exist_template_zh-CN.html", - branded_template_path="without-brand/reset_password_mail_when_account_not_exist_template_zh-CN.html", - ), - }, - EmailType.RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST_NO_REGISTER: { - EmailLanguage.EN_US: EmailTemplate( - subject="Reset Your {application_title} Password", - template_path="reset_password_mail_when_account_not_exist_no_register_template_en-US.html", - branded_template_path="without-brand/reset_password_mail_when_account_not_exist_no_register_template_en-US.html", - ), - EmailLanguage.ZH_HANS: EmailTemplate( - subject="重置您的 {application_title} 密码", - template_path="reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html", - branded_template_path="without-brand/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html", - ), - }, } return EmailI18nConfig(templates=templates) diff --git a/api/services/account_service.py b/api/services/account_service.py index 8438423f2e..a76792f88e 100644 --- a/api/services/account_service.py +++ b/api/services/account_service.py @@ -37,6 +37,7 @@ from services.billing_service import BillingService from services.errors.account import ( AccountAlreadyInTenantError, AccountLoginError, + AccountNotFoundError, AccountNotLinkTenantError, AccountPasswordError, AccountRegisterError, @@ -64,11 +65,7 @@ from tasks.mail_owner_transfer_task import ( send_old_owner_transfer_notify_email_task, send_owner_transfer_confirm_task, ) -from tasks.mail_register_task import send_email_register_mail_task, send_email_register_mail_task_when_account_exist -from tasks.mail_reset_password_task import ( - send_reset_password_mail_task, - send_reset_password_mail_task_when_account_not_exist, -) +from tasks.mail_reset_password_task import send_reset_password_mail_task logger = logging.getLogger(__name__) @@ -85,7 +82,6 @@ REFRESH_TOKEN_EXPIRY = timedelta(days=dify_config.REFRESH_TOKEN_EXPIRE_DAYS) class AccountService: reset_password_rate_limiter = RateLimiter(prefix="reset_password_rate_limit", max_attempts=1, time_window=60 * 1) - email_register_rate_limiter = RateLimiter(prefix="email_register_rate_limit", max_attempts=1, time_window=60 * 1) email_code_login_rate_limiter = RateLimiter( prefix="email_code_login_rate_limit", max_attempts=1, time_window=60 * 1 ) @@ -99,7 +95,6 @@ class AccountService: FORGOT_PASSWORD_MAX_ERROR_LIMITS = 5 CHANGE_EMAIL_MAX_ERROR_LIMITS = 5 OWNER_TRANSFER_MAX_ERROR_LIMITS = 5 - EMAIL_REGISTER_MAX_ERROR_LIMITS = 5 @staticmethod def _get_refresh_token_key(refresh_token: str) -> str: @@ -176,7 +171,7 @@ class AccountService: account = db.session.query(Account).filter_by(email=email).first() if not account: - raise AccountPasswordError("Invalid email or password.") + raise AccountNotFoundError() if account.status == AccountStatus.BANNED.value: raise AccountLoginError("Account is banned.") @@ -438,7 +433,6 @@ class AccountService: account: Optional[Account] = None, email: Optional[str] = None, language: str = "en-US", - is_allow_register: bool = False, ): account_email = account.email if account else email if account_email is None: @@ -451,54 +445,14 @@ class AccountService: code, token = cls.generate_reset_password_token(account_email, account) - if account: - send_reset_password_mail_task.delay( - language=language, - to=account_email, - code=code, - ) - else: - send_reset_password_mail_task_when_account_not_exist.delay( - language=language, - to=account_email, - is_allow_register=is_allow_register, - ) + send_reset_password_mail_task.delay( + language=language, + to=account_email, + code=code, + ) cls.reset_password_rate_limiter.increment_rate_limit(account_email) return token - @classmethod - def send_email_register_email( - cls, - account: Optional[Account] = None, - email: Optional[str] = None, - language: str = "en-US", - ): - account_email = account.email if account else email - if account_email is None: - raise ValueError("Email must be provided.") - - if cls.email_register_rate_limiter.is_rate_limited(account_email): - from controllers.console.auth.error import EmailRegisterRateLimitExceededError - - raise EmailRegisterRateLimitExceededError() - - code, token = cls.generate_email_register_token(account_email) - - if account: - send_email_register_mail_task_when_account_exist.delay( - language=language, - to=account_email, - ) - - else: - send_email_register_mail_task.delay( - language=language, - to=account_email, - code=code, - ) - cls.email_register_rate_limiter.increment_rate_limit(account_email) - return token - @classmethod def send_change_email_email( cls, @@ -631,19 +585,6 @@ class AccountService: ) return code, token - @classmethod - def generate_email_register_token( - cls, - email: str, - code: Optional[str] = None, - additional_data: dict[str, Any] = {}, - ): - if not code: - code = "".join([str(secrets.randbelow(exclusive_upper_bound=10)) for _ in range(6)]) - additional_data["code"] = code - token = TokenManager.generate_token(email=email, token_type="email_register", additional_data=additional_data) - return code, token - @classmethod def generate_change_email_token( cls, @@ -682,10 +623,6 @@ class AccountService: def revoke_reset_password_token(cls, token: str): TokenManager.revoke_token(token, "reset_password") - @classmethod - def revoke_email_register_token(cls, token: str): - TokenManager.revoke_token(token, "email_register") - @classmethod def revoke_change_email_token(cls, token: str): TokenManager.revoke_token(token, "change_email") @@ -698,10 +635,6 @@ class AccountService: def get_reset_password_data(cls, token: str) -> Optional[dict[str, Any]]: return TokenManager.get_token_data(token, "reset_password") - @classmethod - def get_email_register_data(cls, token: str) -> Optional[dict[str, Any]]: - return TokenManager.get_token_data(token, "email_register") - @classmethod def get_change_email_data(cls, token: str) -> Optional[dict[str, Any]]: return TokenManager.get_token_data(token, "change_email") @@ -809,16 +742,6 @@ class AccountService: count = int(count) + 1 redis_client.setex(key, dify_config.FORGOT_PASSWORD_LOCKOUT_DURATION, count) - @staticmethod - @redis_fallback(default_return=None) - def add_email_register_error_rate_limit(email: str) -> None: - key = f"email_register_error_rate_limit:{email}" - count = redis_client.get(key) - if count is None: - count = 0 - count = int(count) + 1 - redis_client.setex(key, dify_config.EMAIL_REGISTER_LOCKOUT_DURATION, count) - @staticmethod @redis_fallback(default_return=False) def is_forgot_password_error_rate_limit(email: str) -> bool: @@ -838,24 +761,6 @@ class AccountService: key = f"forgot_password_error_rate_limit:{email}" redis_client.delete(key) - @staticmethod - @redis_fallback(default_return=False) - def is_email_register_error_rate_limit(email: str) -> bool: - key = f"email_register_error_rate_limit:{email}" - count = redis_client.get(key) - if count is None: - return False - count = int(count) - if count > AccountService.EMAIL_REGISTER_MAX_ERROR_LIMITS: - return True - return False - - @staticmethod - @redis_fallback(default_return=None) - def reset_email_register_error_rate_limit(email: str): - key = f"email_register_error_rate_limit:{email}" - redis_client.delete(key) - @staticmethod @redis_fallback(default_return=None) def add_change_email_error_rate_limit(email: str): diff --git a/api/tasks/mail_register_task.py b/api/tasks/mail_register_task.py deleted file mode 100644 index acf2852649..0000000000 --- a/api/tasks/mail_register_task.py +++ /dev/null @@ -1,86 +0,0 @@ -import logging -import time - -import click -from celery import shared_task - -from configs import dify_config -from extensions.ext_mail import mail -from libs.email_i18n import EmailType, get_email_i18n_service - -logger = logging.getLogger(__name__) - - -@shared_task(queue="mail") -def send_email_register_mail_task(language: str, to: str, code: str) -> None: - """ - Send email register email with internationalization support. - - Args: - language: Language code for email localization - to: Recipient email address - code: Email register code - """ - if not mail.is_inited(): - return - - logger.info(click.style(f"Start email register mail to {to}", fg="green")) - start_at = time.perf_counter() - - try: - email_service = get_email_i18n_service() - email_service.send_email( - email_type=EmailType.EMAIL_REGISTER, - language_code=language, - to=to, - template_context={ - "to": to, - "code": code, - }, - ) - - end_at = time.perf_counter() - logger.info( - click.style(f"Send email register mail to {to} succeeded: latency: {end_at - start_at}", fg="green") - ) - except Exception: - logger.exception("Send email register mail to %s failed", to) - - -@shared_task(queue="mail") -def send_email_register_mail_task_when_account_exist(language: str, to: str) -> None: - """ - Send email register email with internationalization support when account exist. - - Args: - language: Language code for email localization - to: Recipient email address - """ - if not mail.is_inited(): - return - - logger.info(click.style(f"Start email register mail to {to}", fg="green")) - start_at = time.perf_counter() - - try: - login_url = f"{dify_config.CONSOLE_WEB_URL}/signin" - reset_password_url = f"{dify_config.CONSOLE_WEB_URL}/reset-password" - - email_service = get_email_i18n_service() - email_service.send_email( - email_type=EmailType.EMAIL_REGISTER_WHEN_ACCOUNT_EXIST, - language_code=language, - to=to, - template_context={ - "to": to, - "login_url": login_url, - "reset_password_url": reset_password_url, - }, - ) - - end_at = time.perf_counter() - logger.info( - click.style(f"Send email register mail to {to} succeeded: latency: {end_at - start_at}", fg="green") - ) - except Exception: - logger.exception("Send email register mail to %s failed", to) diff --git a/api/tasks/mail_reset_password_task.py b/api/tasks/mail_reset_password_task.py index 1739562588..545db84fde 100644 --- a/api/tasks/mail_reset_password_task.py +++ b/api/tasks/mail_reset_password_task.py @@ -4,7 +4,6 @@ import time import click from celery import shared_task -from configs import dify_config from extensions.ext_mail import mail from libs.email_i18n import EmailType, get_email_i18n_service @@ -45,47 +44,3 @@ def send_reset_password_mail_task(language: str, to: str, code: str): ) except Exception: logger.exception("Send password reset mail to %s failed", to) - - -@shared_task(queue="mail") -def send_reset_password_mail_task_when_account_not_exist(language: str, to: str, is_allow_register: bool) -> None: - """ - Send reset password email with internationalization support when account not exist. - - Args: - language: Language code for email localization - to: Recipient email address - """ - if not mail.is_inited(): - return - - logger.info(click.style(f"Start password reset mail to {to}", fg="green")) - start_at = time.perf_counter() - - try: - if is_allow_register: - sign_up_url = f"{dify_config.CONSOLE_WEB_URL}/signup" - email_service = get_email_i18n_service() - email_service.send_email( - email_type=EmailType.RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST, - language_code=language, - to=to, - template_context={ - "to": to, - "sign_up_url": sign_up_url, - }, - ) - else: - email_service = get_email_i18n_service() - email_service.send_email( - email_type=EmailType.RESET_PASSWORD_WHEN_ACCOUNT_NOT_EXIST_NO_REGISTER, - language_code=language, - to=to, - ) - - end_at = time.perf_counter() - logger.info( - click.style(f"Send password reset mail to {to} succeeded: latency: {end_at - start_at}", fg="green") - ) - except Exception: - logger.exception("Send password reset mail to %s failed", to) diff --git a/api/templates/register_email_template_en-US.html b/api/templates/register_email_template_en-US.html deleted file mode 100644 index e0fec59100..0000000000 --- a/api/templates/register_email_template_en-US.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

Dify Sign-up Code

-

Your sign-up code for Dify - - Copy and paste this code, this code will only be valid for the next 5 minutes.

-
- {{code}} -
-

If you didn't request this code, don't worry. You can safely ignore this email.

-
- - - \ No newline at end of file diff --git a/api/templates/register_email_template_zh-CN.html b/api/templates/register_email_template_zh-CN.html deleted file mode 100644 index 3b507290f0..0000000000 --- a/api/templates/register_email_template_zh-CN.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

Dify 注册验证码

-

您的 Dify 注册验证码 - - 复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。

-
- {{code}} -
-

如果您没有请求,请不要担心。您可以安全地忽略此电子邮件。

-
- - - \ No newline at end of file diff --git a/api/templates/register_email_when_account_exist_template_en-US.html b/api/templates/register_email_when_account_exist_template_en-US.html deleted file mode 100644 index 967f97a1b8..0000000000 --- a/api/templates/register_email_when_account_exist_template_en-US.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

It looks like you’re signing up with an existing account

-

Hi, - We noticed you tried to sign up, but this email is already registered with an existing account. - - Please log in here:

-

- Log In -

-

- If you forgot your password, you can reset it here:

-

- Reset Password -

-

If you didn’t request this action, you can safely ignore this email. - Need help? Feel free to contact us at support@dify.ai.

-
- - - \ No newline at end of file diff --git a/api/templates/register_email_when_account_exist_template_zh-CN.html b/api/templates/register_email_when_account_exist_template_zh-CN.html deleted file mode 100644 index 7d63ca06e8..0000000000 --- a/api/templates/register_email_when_account_exist_template_zh-CN.html +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

您似乎正在使用现有账户注册

-

Hi, - 我们注意到您尝试注册,但此电子邮件已与现有账户注册。 - - 请在此登录:

-

- 登录 -

-

- 如果您忘记了密码,可以在此重置:

-

- 重置密码 -

-

如果您没有请求此操作,您可以安全地忽略此电子邮件。 - - 需要帮助?随时联系我们 at support@dify.ai。

-
- - - \ No newline at end of file diff --git a/api/templates/reset_password_mail_when_account_not_exist_no_register_template_en-US.html b/api/templates/reset_password_mail_when_account_not_exist_no_register_template_en-US.html deleted file mode 100644 index c849057519..0000000000 --- a/api/templates/reset_password_mail_when_account_not_exist_no_register_template_en-US.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

It looks like you’re resetting a password with an unregistered email

-

Hi, - We noticed you tried to reset your password, but this email is not associated with any account. -

-

If you didn’t request this action, you can safely ignore this email. - Need help? Feel free to contact us at support@dify.ai.

-
- - - \ No newline at end of file diff --git a/api/templates/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html b/api/templates/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html deleted file mode 100644 index 51ed79cfbb..0000000000 --- a/api/templates/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

看起来您正在使用未注册的电子邮件重置密码

-

Hi, - 我们注意到您尝试重置密码,但此电子邮件未与任何账户关联。

-

如果您没有请求此操作,您可以安全地忽略此电子邮件。 - 需要帮助?随时联系我们 at support@dify.ai。

-
- - - \ No newline at end of file diff --git a/api/templates/reset_password_mail_when_account_not_exist_template_en-US.html b/api/templates/reset_password_mail_when_account_not_exist_template_en-US.html deleted file mode 100644 index 4ad82a2ccd..0000000000 --- a/api/templates/reset_password_mail_when_account_not_exist_template_en-US.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

It looks like you’re resetting a password with an unregistered email

-

Hi, - We noticed you tried to reset your password, but this email is not associated with any account. - - Please sign up here:

-

- [Sign Up] -

-

If you didn’t request this action, you can safely ignore this email. - Need help? Feel free to contact us at support@dify.ai.

-
- - - \ No newline at end of file diff --git a/api/templates/reset_password_mail_when_account_not_exist_template_zh-CN.html b/api/templates/reset_password_mail_when_account_not_exist_template_zh-CN.html deleted file mode 100644 index 284d700485..0000000000 --- a/api/templates/reset_password_mail_when_account_not_exist_template_zh-CN.html +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - -
-
- - Dify Logo -
-

看起来您正在使用未注册的电子邮件重置密码

-

Hi, - 我们注意到您尝试重置密码,但此电子邮件未与任何账户关联。 - - 请在此注册:

-

- [注册] -

-

如果您没有请求此操作,您可以安全地忽略此电子邮件。 - 需要帮助?随时联系我们 at support@dify.ai。

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/register_email_template_en-US.html b/api/templates/without-brand/register_email_template_en-US.html deleted file mode 100644 index 65e179ef18..0000000000 --- a/api/templates/without-brand/register_email_template_en-US.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - -
-

{{application_title}} Sign-up Code

-

Your sign-up code for Dify - - Copy and paste this code, this code will only be valid for the next 5 minutes.

-
- {{code}} -
-

If you didn't request this code, don't worry. You can safely ignore this email.

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/register_email_template_zh-CN.html b/api/templates/without-brand/register_email_template_zh-CN.html deleted file mode 100644 index 26df4760aa..0000000000 --- a/api/templates/without-brand/register_email_template_zh-CN.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - -
-

{{application_title}} 注册验证码

-

您的 {{application_title}} 注册验证码 - - 复制并粘贴此验证码,注意验证码仅在接下来的 5 分钟内有效。

-
- {{code}} -
-

如果您没有请求此验证码,请不要担心。您可以安全地忽略此电子邮件。

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/register_email_when_account_exist_template_en-US.html b/api/templates/without-brand/register_email_when_account_exist_template_en-US.html deleted file mode 100644 index 063d0de34c..0000000000 --- a/api/templates/without-brand/register_email_when_account_exist_template_en-US.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - -
-

It looks like you’re signing up with an existing account

-

Hi, - We noticed you tried to sign up, but this email is already registered with an existing account. - - Please log in here:

-

- Log In -

-

- If you forgot your password, you can reset it here:

-

- Reset Password -

-

If you didn’t request this action, you can safely ignore this email. - Need help? Feel free to contact us at support@dify.ai.

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/register_email_when_account_exist_template_zh-CN.html b/api/templates/without-brand/register_email_when_account_exist_template_zh-CN.html deleted file mode 100644 index 3edbd25e87..0000000000 --- a/api/templates/without-brand/register_email_when_account_exist_template_zh-CN.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - -
-

您似乎正在使用现有账户注册

-

Hi, - 我们注意到您尝试注册,但此电子邮件已与现有账户注册。 - - 请在此登录:

-

- 登录 -

-

- 如果您忘记了密码,可以在此重置:

-

- 重置密码 -

-

如果您没有请求此操作,您可以安全地忽略此电子邮件。 - - 需要帮助?随时联系我们 at support@dify.ai。

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_en-US.html b/api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_en-US.html deleted file mode 100644 index 5e6d2f1671..0000000000 --- a/api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_en-US.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - -
-

It looks like you’re resetting a password with an unregistered email

-

Hi, - We noticed you tried to reset your password, but this email is not associated with any account. -

-

If you didn’t request this action, you can safely ignore this email. - Need help? Feel free to contact us at support@dify.ai.

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html b/api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html deleted file mode 100644 index fd53becef6..0000000000 --- a/api/templates/without-brand/reset_password_mail_when_account_not_exist_no_register_template_zh-CN.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - -
-

看起来您正在使用未注册的电子邮件重置密码

-

Hi, - 我们注意到您尝试重置密码,但此电子邮件未与任何账户关联。 -

-

如果您没有请求此操作,您可以安全地忽略此电子邮件。 - 需要帮助?随时联系我们 at support@dify.ai。

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/reset_password_mail_when_account_not_exist_template_en-US.html b/api/templates/without-brand/reset_password_mail_when_account_not_exist_template_en-US.html deleted file mode 100644 index c67400593f..0000000000 --- a/api/templates/without-brand/reset_password_mail_when_account_not_exist_template_en-US.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - -
-

It looks like you’re resetting a password with an unregistered email

-

Hi, - We noticed you tried to reset your password, but this email is not associated with any account. - - Please sign up here:

-

- [Sign Up] -

-

If you didn’t request this action, you can safely ignore this email. - Need help? Feel free to contact us at support@dify.ai.

-
- - - \ No newline at end of file diff --git a/api/templates/without-brand/reset_password_mail_when_account_not_exist_template_zh-CN.html b/api/templates/without-brand/reset_password_mail_when_account_not_exist_template_zh-CN.html deleted file mode 100644 index bfd0272831..0000000000 --- a/api/templates/without-brand/reset_password_mail_when_account_not_exist_template_zh-CN.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - -
-

看起来您正在使用未注册的电子邮件重置密码

-

Hi, - 我们注意到您尝试重置密码,但此电子邮件未与任何账户关联。 - - 请在此注册:

-

- [注册] -

-

如果您没有请求此操作,您可以安全地忽略此电子邮件。 - 需要帮助?随时联系我们 at support@dify.ai。

-
- - - \ No newline at end of file diff --git a/api/tests/integration_tests/.env.example b/api/tests/integration_tests/.env.example index 92df93fb13..2e98dec964 100644 --- a/api/tests/integration_tests/.env.example +++ b/api/tests/integration_tests/.env.example @@ -203,7 +203,6 @@ ENDPOINT_URL_TEMPLATE=http://localhost:5002/e/{hook_id} # Reset password token expiry minutes RESET_PASSWORD_TOKEN_EXPIRY_MINUTES=5 -EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES=5 CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES=5 OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES=5 diff --git a/api/tests/test_containers_integration_tests/services/test_account_service.py b/api/tests/test_containers_integration_tests/services/test_account_service.py index fef353b0e2..415e65ce51 100644 --- a/api/tests/test_containers_integration_tests/services/test_account_service.py +++ b/api/tests/test_containers_integration_tests/services/test_account_service.py @@ -13,6 +13,7 @@ from services.account_service import AccountService, RegisterService, TenantServ from services.errors.account import ( AccountAlreadyInTenantError, AccountLoginError, + AccountNotFoundError, AccountPasswordError, AccountRegisterError, CurrentPasswordIncorrectError, @@ -138,7 +139,7 @@ class TestAccountService: fake = Faker() email = fake.email() password = fake.password(length=12) - with pytest.raises(AccountPasswordError): + with pytest.raises(AccountNotFoundError): AccountService.authenticate(email, password) def test_authenticate_banned_account(self, db_session_with_containers, mock_external_service_dependencies): diff --git a/api/tests/unit_tests/controllers/console/auth/test_authentication_security.py b/api/tests/unit_tests/controllers/console/auth/test_authentication_security.py index b6697ac5d4..aefb4bf8b0 100644 --- a/api/tests/unit_tests/controllers/console/auth/test_authentication_security.py +++ b/api/tests/unit_tests/controllers/console/auth/test_authentication_security.py @@ -9,6 +9,7 @@ from flask_restx import Api import services.errors.account from controllers.console.auth.error import AuthenticationFailedError from controllers.console.auth.login import LoginApi +from controllers.console.error import AccountNotFound class TestAuthenticationSecurity: @@ -26,33 +27,31 @@ class TestAuthenticationSecurity: @patch("controllers.console.auth.login.FeatureService.get_system_features") @patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit") @patch("controllers.console.auth.login.AccountService.authenticate") - @patch("controllers.console.auth.login.AccountService.add_login_error_rate_limit") + @patch("controllers.console.auth.login.AccountService.send_reset_password_email") @patch("controllers.console.auth.login.dify_config.BILLING_ENABLED", False) @patch("controllers.console.auth.login.RegisterService.get_invitation_if_token_valid") def test_login_invalid_email_with_registration_allowed( - self, mock_get_invitation, mock_add_rate_limit, mock_authenticate, mock_is_rate_limit, mock_features, mock_db + self, mock_get_invitation, mock_send_email, mock_authenticate, mock_is_rate_limit, mock_features, mock_db ): - """Test that invalid email raises AuthenticationFailedError when account not found.""" + """Test that invalid email sends reset password email when registration is allowed.""" # Arrange mock_is_rate_limit.return_value = False mock_get_invitation.return_value = None - mock_authenticate.side_effect = services.errors.account.AccountPasswordError("Invalid email or password.") + mock_authenticate.side_effect = services.errors.account.AccountNotFoundError("Account not found") mock_db.session.query.return_value.first.return_value = MagicMock() # Mock setup exists mock_features.return_value.is_allow_register = True + mock_send_email.return_value = "token123" # Act with self.app.test_request_context( "/login", method="POST", json={"email": "nonexistent@example.com", "password": "WrongPass123!"} ): login_api = LoginApi() + result = login_api.post() - # Assert - with pytest.raises(AuthenticationFailedError) as exc_info: - login_api.post() - - assert exc_info.value.error_code == "authentication_failed" - assert exc_info.value.description == "Invalid email or password." - mock_add_rate_limit.assert_called_once_with("nonexistent@example.com") + # Assert + assert result == {"result": "fail", "data": "token123", "code": "account_not_found"} + mock_send_email.assert_called_once_with(email="nonexistent@example.com", language="en-US") @patch("controllers.console.wraps.db") @patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit") @@ -88,17 +87,16 @@ class TestAuthenticationSecurity: @patch("controllers.console.auth.login.FeatureService.get_system_features") @patch("controllers.console.auth.login.AccountService.is_login_error_rate_limit") @patch("controllers.console.auth.login.AccountService.authenticate") - @patch("controllers.console.auth.login.AccountService.add_login_error_rate_limit") @patch("controllers.console.auth.login.dify_config.BILLING_ENABLED", False) @patch("controllers.console.auth.login.RegisterService.get_invitation_if_token_valid") def test_login_invalid_email_with_registration_disabled( - self, mock_get_invitation, mock_add_rate_limit, mock_authenticate, mock_is_rate_limit, mock_features, mock_db + self, mock_get_invitation, mock_authenticate, mock_is_rate_limit, mock_features, mock_db ): - """Test that invalid email raises AuthenticationFailedError when account not found.""" + """Test that invalid email raises AccountNotFound when registration is disabled.""" # Arrange mock_is_rate_limit.return_value = False mock_get_invitation.return_value = None - mock_authenticate.side_effect = services.errors.account.AccountPasswordError("Invalid email or password.") + mock_authenticate.side_effect = services.errors.account.AccountNotFoundError("Account not found") mock_db.session.query.return_value.first.return_value = MagicMock() # Mock setup exists mock_features.return_value.is_allow_register = False @@ -109,12 +107,10 @@ class TestAuthenticationSecurity: login_api = LoginApi() # Assert - with pytest.raises(AuthenticationFailedError) as exc_info: + with pytest.raises(AccountNotFound) as exc_info: login_api.post() - assert exc_info.value.error_code == "authentication_failed" - assert exc_info.value.description == "Invalid email or password." - mock_add_rate_limit.assert_called_once_with("nonexistent@example.com") + assert exc_info.value.error_code == "account_not_found" @patch("controllers.console.wraps.db") @patch("controllers.console.auth.login.FeatureService.get_system_features") diff --git a/api/tests/unit_tests/services/test_account_service.py b/api/tests/unit_tests/services/test_account_service.py index ed70a7b0de..442839e44e 100644 --- a/api/tests/unit_tests/services/test_account_service.py +++ b/api/tests/unit_tests/services/test_account_service.py @@ -10,6 +10,7 @@ from services.account_service import AccountService, RegisterService, TenantServ from services.errors.account import ( AccountAlreadyInTenantError, AccountLoginError, + AccountNotFoundError, AccountPasswordError, AccountRegisterError, CurrentPasswordIncorrectError, @@ -194,7 +195,7 @@ class TestAccountService: # Execute test and verify exception self._assert_exception_raised( - AccountPasswordError, AccountService.authenticate, "notfound@example.com", "password" + AccountNotFoundError, AccountService.authenticate, "notfound@example.com", "password" ) def test_authenticate_account_banned(self, mock_db_dependencies): diff --git a/docker/.env.example b/docker/.env.example index 92347a6e76..9a0a5a9622 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -843,7 +843,6 @@ INVITE_EXPIRY_HOURS=72 # Reset password token valid time (minutes), RESET_PASSWORD_TOKEN_EXPIRY_MINUTES=5 -EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES=5 CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES=5 OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES=5 diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 193157b54f..3f19dc7f63 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -372,7 +372,6 @@ x-shared-env: &shared-api-worker-env INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-4000} INVITE_EXPIRY_HOURS: ${INVITE_EXPIRY_HOURS:-72} RESET_PASSWORD_TOKEN_EXPIRY_MINUTES: ${RESET_PASSWORD_TOKEN_EXPIRY_MINUTES:-5} - EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES: ${EMAIL_REGISTER_TOKEN_EXPIRY_MINUTES:-5} CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES: ${CHANGE_EMAIL_TOKEN_EXPIRY_MINUTES:-5} OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES: ${OWNER_TRANSFER_TOKEN_EXPIRY_MINUTES:-5} CODE_EXECUTION_ENDPOINT: ${CODE_EXECUTION_ENDPOINT:-http://sandbox:8194}