Change password route was added.
This commit is contained in:
parent
73187742f2
commit
6ae7edc8f5
3 changed files with 48 additions and 0 deletions
|
|
@ -8,10 +8,12 @@ from fastapi import APIRouter, HTTPException, status
|
|||
|
||||
from src.contract_check.api.deps import (
|
||||
AsyncSessionDep,
|
||||
CurrentUserDep,
|
||||
NotificationPublisherDep,
|
||||
RefreshStoreDep,
|
||||
create_email_user,
|
||||
fetch_user_by_email,
|
||||
fetch_user_by_id_full,
|
||||
)
|
||||
from src.contract_check.api.routes.auth.support import (
|
||||
_build_reset_link,
|
||||
|
|
@ -20,6 +22,7 @@ from src.contract_check.api.routes.auth.support import (
|
|||
_require_web_auth_enabled,
|
||||
)
|
||||
from src.contract_check.api.schemas import (
|
||||
ChangePasswordRequest,
|
||||
ForgotPasswordRequest,
|
||||
LoginRequest,
|
||||
LogoutRequest,
|
||||
|
|
@ -225,3 +228,41 @@ async def reset_password(
|
|||
|
||||
log.info("user_password_reset", user_id=str(user_id))
|
||||
return OkResponse(ok=True, detail="password updated")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/change-password",
|
||||
response_model=OkResponse,
|
||||
status_code=status.HTTP_200_OK,
|
||||
)
|
||||
async def change_password(
|
||||
session: AsyncSessionDep,
|
||||
refresh_store: RefreshStoreDep,
|
||||
user: CurrentUserDep,
|
||||
body: ChangePasswordRequest,
|
||||
) -> OkResponse:
|
||||
"""Rotate the password of the authenticated user.
|
||||
|
||||
Requires the current password. On success all active refresh tokens are
|
||||
revoked, forcing re-login on other devices.
|
||||
"""
|
||||
_require_web_auth_enabled()
|
||||
|
||||
db_user = await fetch_user_by_id_full(session, user.user_id)
|
||||
if db_user is None or not db_user.password_hash:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="no password set")
|
||||
if not verify_password(body.current_password, db_user.password_hash):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid credentials")
|
||||
|
||||
await UserRepository(session).set_password(user.user_id, hash_password(body.new_password))
|
||||
await session.commit()
|
||||
|
||||
# Best-effort revoke of existing sessions; ignore Redis hiccups so the
|
||||
# password itself is still rotated.
|
||||
try:
|
||||
await refresh_store.revoke_all(user.user_id)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
log.warning("change_password_revoke_failed", user_id=str(user.user_id), error=str(exc))
|
||||
|
||||
log.info("user_password_changed", user_id=str(user.user_id))
|
||||
return OkResponse(ok=True, detail="password updated")
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from src.contract_check.api.schemas.analysis_result import (
|
|||
)
|
||||
from src.contract_check.api.schemas.auth import (
|
||||
AuthResponse,
|
||||
ChangePasswordRequest,
|
||||
ForgotPasswordRequest,
|
||||
LoginRequest,
|
||||
LogoutRequest,
|
||||
|
|
@ -106,6 +107,7 @@ __all__ = [
|
|||
"LogoutRequest",
|
||||
"ForgotPasswordRequest",
|
||||
"ResetPasswordRequest",
|
||||
"ChangePasswordRequest",
|
||||
"OkResponse",
|
||||
"PasskeyRegistrationOptions",
|
||||
"PasskeyAuthenticationOptions",
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ class ResetPasswordRequest(BaseModel):
|
|||
password: str = Field(..., min_length=8, max_length=128)
|
||||
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
current_password: str = Field(..., min_length=1, max_length=128)
|
||||
new_password: str = Field(..., min_length=8, max_length=128)
|
||||
|
||||
|
||||
class OkResponse(BaseModel):
|
||||
"""Generic `{ok: true, ...}` payload for state-mutating auth endpoints."""
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue