Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
233727e
feat(#884): Add password reset functionality
bitloi Jan 19, 2026
2758d1c
Merge branch 'main' into feature/password-reset-884
bitloi Jan 19, 2026
b73fcf1
Merge branch 'main' into feature/password-reset-884
LuoPengcheng12138 Jan 21, 2026
58a8af6
refactor(#884): Update password reset for local vs cloud deployment
bitloi Jan 21, 2026
e5c2d2e
refactor(#884): Use Pydantic validation for password reset models
bitloi Jan 21, 2026
484455d
Merge branch 'main' into feature/password-reset-884
bitloi Jan 21, 2026
41fe1cd
fix(#884): Remove sensitive email data from log messages
bitloi Jan 21, 2026
ed2c763
fix(security): avoid logging email in password reset flow
bitloi Jan 26, 2026
d29417d
Merge branch 'main' into feature/password-reset-884
bitloi Jan 26, 2026
3ad5d0b
fix: resolve router logging conflict and keep password reset router
bitloi Jan 26, 2026
2285019
refactor(#884): Simplify Electron backend password reset to only rese…
bitloi Jan 27, 2026
210670e
Merge branch 'main' into feature/password-reset-884
LuoPengcheng12138 Jan 27, 2026
52f2d80
minor log update
LuoPengcheng12138 Jan 27, 2026
146658f
fix(#884): Remove traceroot_wrapper, simplify to only reset_password_…
bitloi Jan 27, 2026
9bf12b6
feat(#884): Implement proxy to server backend for password reset
bitloi Jan 27, 2026
a37680f
minor log update & remove redundant func
LuoPengcheng12138 Jan 27, 2026
2775963
Merge branch 'pr-958' into feature/password-reset-884
LuoPengcheng12138 Jan 27, 2026
494e390
remove redundant file
LuoPengcheng12138 Jan 27, 2026
4c489df
minor lint update
LuoPengcheng12138 Jan 27, 2026
f639025
Merge branch 'main' into feature/password-reset-884
bitloi Jan 29, 2026
da85939
Merge branch 'main' into feature/password-reset-884
bitloi Feb 1, 2026
89535c4
Merge upstream/main into feature/password-reset-884, resolve conflict…
bitloi Feb 1, 2026
8c713f0
Merge branch 'main' into feature/password-reset-884
bitloi Feb 1, 2026
8e3a379
Merge branch 'main' into feature/password-reset-884
bitloi Feb 2, 2026
73f03c7
Merge branch 'main' into feature/password-reset-884
bitloi Feb 3, 2026
049caf0
Merge branch 'main' into feature/password-reset-884
bitloi Feb 3, 2026
ca43f7e
Merge branch 'main' into feature/password-reset-884
bitloi Feb 3, 2026
4ca6914
Merge upstream/main into feature/password-reset-884, resolve conflicts
bitloi Feb 4, 2026
95194fe
Merge branch 'main' into feature/password-reset-884
bitloi Feb 4, 2026
af087cc
Merge branch 'main' into feature/password-reset-884
bitloi Feb 4, 2026
0dc8c28
Merge branch 'main' into feature/password-reset-884
bitloi Feb 4, 2026
aaa8626
Merge branch 'main' into feature/password-reset-884
bitloi Feb 4, 2026
235baec
Merge branch 'main' into feature/password-reset-884
bitloi Feb 5, 2026
126b23a
Merge branch 'main' into feature/password-reset-884
bitloi Feb 5, 2026
0d7b68e
Merge branch 'main' into feature/password-reset-884
bitloi Feb 5, 2026
06610a3
Merge branch 'main' into feature/password-reset-884
bitloi Feb 6, 2026
6322cbe
Merge branch 'main' into feature/password-reset-884
bitloi Feb 7, 2026
9003537
Merge branch 'main' into feature/password-reset-884
bitloi Feb 7, 2026
5f6a4e7
Merge branch 'main' into feature/password-reset-884
bitloi Feb 9, 2026
bc7ed35
Merge branch 'main' into feature/password-reset-884
bitloi Feb 15, 2026
fdf1227
Merge branch 'main' into feature/password-reset-884
bitloi Feb 15, 2026
a3af082
Merge branch 'main' into feature/password-reset-884
bitloi Feb 17, 2026
7484820
Merge branch 'main' into feature/password-reset-884
bitloi Feb 18, 2026
ad97398
Merge upstream/main: resolve i18n conflicts, keep password reset and …
bitloi Feb 19, 2026
90d4d50
Merge branch 'main' into feature/password-reset-884
bitloi Feb 19, 2026
da7df87
Merge branch 'main' into feature/password-reset-884
bitloi Feb 20, 2026
2d273f7
Merge branch 'main' into feature/password-reset-884
bitloi Feb 21, 2026
68095ad
Merge branch 'main' into feature/password-reset-884
bitloi Feb 22, 2026
522b026
Merge branch 'main' into feature/password-reset-884
bitloi Feb 22, 2026
c610b53
Merge upstream/main: resolve i18n conflicts, keep CDP browser and pas…
bitloi Feb 24, 2026
f3f25a0
Merge branch 'main' into feature/password-reset-884
bitloi Feb 25, 2026
55674eb
Merge branch 'main' into feature/password-reset-884
bitloi Mar 4, 2026
3729285
Merge branch 'main' into feature/password-reset-884
bitloi Mar 6, 2026
d0619a8
Merge branch 'main' into feature/password-reset-884
bitloi Mar 8, 2026
26d7e9d
refactor: remove token-based password reset flow, redirect to /signin
bitloi Mar 12, 2026
a7fd245
Merge upstream/main into feature/password-reset-884
bitloi Mar 18, 2026
dec1d19
Merge branch 'main' into feature/password-reset-884
fengju0213 Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========

"""add_password_reset_token_table

Revision ID: add_password_reset_token
Revises: add_timestamp_to_chat_step
Create Date: 2026-01-19 12:00:00.000000

"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "add_password_reset_token"
down_revision: Union[str, None] = "add_timestamp_to_chat_step"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
"""No-op.

The password_reset_token flow is no longer used.
"""
pass


def downgrade() -> None:
"""No-op."""
pass
65 changes: 65 additions & 0 deletions server/app/controller/user/password_reset_controller.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========

import logging

from fastapi import APIRouter, Depends
from fastapi_babel import _
from sqlmodel import Session, col

from app.component import code
from app.component.database import session
from app.component.encrypt import password_hash
from app.exception.exception import UserException
from app.model.user.password_reset import (
DirectResetPasswordRequest,
)
from app.model.user.user import User

logger = logging.getLogger("server_password_reset_controller")

router = APIRouter(tags=["Password Reset"])


@router.post("/reset-password-direct", name="reset password directly")
async def reset_password_direct(
data: DirectResetPasswordRequest,
session: Session = Depends(session),
):
"""
Reset password directly without token verification.
This endpoint is for Full Local Deployment only where email verification is not needed.
The password is updated directly in the local Docker database.
Password validation is handled by Pydantic model.
"""
# Find the user by email
user = User.by(User.email == data.email, col(User.deleted_at).is_(None), s=session).one_or_none()

if not user:
logger.warning("Direct password reset failed: user not found")
raise UserException(code.error, _("User with this email not found"))

# Update password
user.password = password_hash(data.new_password)
user.save(session)

logger.info(
"Direct password reset successful",
extra={"user_id": user.id}
)

return {
"status": "success",
"message": "Password has been reset successfully. You can now log in with your new password.",
}
46 changes: 46 additions & 0 deletions server/app/model/user/password_reset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========

from pydantic import BaseModel, EmailStr, field_validator, model_validator


class DirectResetPasswordRequest(BaseModel):
"""Request model for direct password reset (local deployment only)."""
email: EmailStr
new_password: str
confirm_password: str

@field_validator("new_password")
@classmethod
def validate_password_strength(cls, v: str) -> str:
"""Validate password meets strength requirements."""
if len(v) < 8:
raise ValueError("Password must be at least 8 characters long")

has_letter = any(c.isalpha() for c in v)
has_number = any(c.isdigit() for c in v)

if not has_letter:
raise ValueError("Password must contain at least one letter")
if not has_number:
raise ValueError("Password must contain at least one number")

return v

@model_validator(mode="after")
def validate_passwords_match(self):
"""Validate that new_password and confirm_password match."""
if self.new_password != self.confirm_password:
raise ValueError("Passwords do not match")
return self
Loading