mirror of
https://github.com/langgenius/dify.git
synced 2026-09-06 01:04:49 +08:00
149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
from flask import request
|
|
from flask_restx import Resource
|
|
from pydantic import BaseModel, Field
|
|
|
|
from controllers.common.schema import register_schema_models
|
|
from controllers.console import console_ns
|
|
from controllers.console.auth.error import (
|
|
EmailCodeError,
|
|
EmailPasswordResetLimitError,
|
|
InvalidEmailError,
|
|
InvalidTokenError,
|
|
PasswordMismatchError,
|
|
PasswordResetRateLimitExceededError,
|
|
)
|
|
from controllers.console.error import AccountNotFound, EmailSendIpLimitError
|
|
from controllers.console.wraps import email_password_login_enabled, model_validate, setup_required
|
|
from extensions.ext_application_services import application_services
|
|
from libs.helper import EmailStr, dump_response, extract_remote_ip
|
|
from services import account_errors
|
|
from services.entities.auth_entities import (
|
|
ForgotPasswordCheckPayload,
|
|
ForgotPasswordResetPayload,
|
|
ForgotPasswordSendPayload,
|
|
)
|
|
|
|
|
|
class ForgotPasswordEmailResponse(BaseModel):
|
|
result: str = Field(description="Operation result")
|
|
data: str | None = Field(default=None, description="Reset token")
|
|
|
|
|
|
class ForgotPasswordCheckResponse(BaseModel):
|
|
is_valid: bool = Field(description="Whether code is valid")
|
|
email: EmailStr = Field(description="Email address")
|
|
token: str = Field(description="New reset token")
|
|
|
|
|
|
class ForgotPasswordResetResponse(BaseModel):
|
|
result: str = Field(description="Operation result")
|
|
|
|
|
|
register_schema_models(
|
|
console_ns,
|
|
ForgotPasswordSendPayload,
|
|
ForgotPasswordCheckPayload,
|
|
ForgotPasswordResetPayload,
|
|
ForgotPasswordEmailResponse,
|
|
ForgotPasswordCheckResponse,
|
|
ForgotPasswordResetResponse,
|
|
)
|
|
|
|
|
|
@console_ns.route("/forgot-password")
|
|
class ForgotPasswordSendEmailApi(Resource):
|
|
@console_ns.doc("send_forgot_password_email")
|
|
@console_ns.doc(description="Send password reset email")
|
|
@console_ns.expect(console_ns.models[ForgotPasswordSendPayload.__name__])
|
|
@console_ns.response(
|
|
200,
|
|
"Email sent successfully",
|
|
console_ns.models[ForgotPasswordEmailResponse.__name__],
|
|
)
|
|
@console_ns.response(400, "Invalid email or rate limit exceeded")
|
|
@setup_required
|
|
@email_password_login_enabled
|
|
@model_validate(ForgotPasswordSendPayload)
|
|
def post(self, req_data: ForgotPasswordSendPayload):
|
|
ip_address = extract_remote_ip(request)
|
|
language = "zh-Hans" if req_data.language == "zh-Hans" else "en-US"
|
|
try:
|
|
token = application_services().accounts.forgot_password.send_code(
|
|
email=req_data.email,
|
|
language=language,
|
|
ip_address=ip_address,
|
|
)
|
|
except account_errors.ForgotPasswordSendIPLimitedError:
|
|
raise EmailSendIpLimitError() from None
|
|
except account_errors.ForgotPasswordSendRateLimitError as error:
|
|
raise PasswordResetRateLimitExceededError(error.retry_after_minutes) from None
|
|
|
|
return dump_response(ForgotPasswordEmailResponse, {"result": "success", "data": token})
|
|
|
|
|
|
@console_ns.route("/forgot-password/validity")
|
|
class ForgotPasswordCheckApi(Resource):
|
|
@console_ns.doc("check_forgot_password_code")
|
|
@console_ns.doc(description="Verify password reset code")
|
|
@console_ns.expect(console_ns.models[ForgotPasswordCheckPayload.__name__])
|
|
@console_ns.response(
|
|
200,
|
|
"Code verified successfully",
|
|
console_ns.models[ForgotPasswordCheckResponse.__name__],
|
|
)
|
|
@console_ns.response(400, "Invalid code or token")
|
|
@setup_required
|
|
@email_password_login_enabled
|
|
@model_validate(ForgotPasswordCheckPayload)
|
|
def post(self, req_data: ForgotPasswordCheckPayload):
|
|
try:
|
|
verification = application_services().accounts.forgot_password.verify_code(
|
|
email=req_data.email,
|
|
code=req_data.code,
|
|
token=req_data.token,
|
|
)
|
|
except account_errors.ForgotPasswordVerificationLimitError:
|
|
raise EmailPasswordResetLimitError() from None
|
|
except account_errors.InvalidForgotPasswordTokenError:
|
|
raise InvalidTokenError() from None
|
|
except account_errors.InvalidForgotPasswordEmailError:
|
|
raise InvalidEmailError() from None
|
|
except account_errors.InvalidForgotPasswordCodeError:
|
|
raise EmailCodeError() from None
|
|
|
|
return dump_response(
|
|
ForgotPasswordCheckResponse,
|
|
{"is_valid": True, "email": verification.email, "token": verification.token},
|
|
)
|
|
|
|
|
|
@console_ns.route("/forgot-password/resets")
|
|
class ForgotPasswordResetApi(Resource):
|
|
@console_ns.doc("reset_password")
|
|
@console_ns.doc(description="Reset password with verification token")
|
|
@console_ns.expect(console_ns.models[ForgotPasswordResetPayload.__name__])
|
|
@console_ns.response(
|
|
200,
|
|
"Password reset successfully",
|
|
console_ns.models[ForgotPasswordResetResponse.__name__],
|
|
)
|
|
@console_ns.response(400, "Invalid token or password mismatch")
|
|
@setup_required
|
|
@email_password_login_enabled
|
|
@model_validate(ForgotPasswordResetPayload)
|
|
def post(self, req_data: ForgotPasswordResetPayload):
|
|
try:
|
|
application_services().accounts.forgot_password.reset(
|
|
token=req_data.token,
|
|
new_password=req_data.new_password,
|
|
password_confirm=req_data.password_confirm,
|
|
)
|
|
except account_errors.ForgotPasswordMismatchError:
|
|
raise PasswordMismatchError() from None
|
|
except account_errors.InvalidForgotPasswordTokenError:
|
|
raise InvalidTokenError() from None
|
|
except account_errors.AccountNotFoundError:
|
|
raise AccountNotFound() from None
|
|
|
|
return dump_response(ForgotPasswordResetResponse, {"result": "success"})
|