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 (
|
from src.contract_check.api.deps import (
|
||||||
AsyncSessionDep,
|
AsyncSessionDep,
|
||||||
|
CurrentUserDep,
|
||||||
NotificationPublisherDep,
|
NotificationPublisherDep,
|
||||||
RefreshStoreDep,
|
RefreshStoreDep,
|
||||||
create_email_user,
|
create_email_user,
|
||||||
fetch_user_by_email,
|
fetch_user_by_email,
|
||||||
|
fetch_user_by_id_full,
|
||||||
)
|
)
|
||||||
from src.contract_check.api.routes.auth.support import (
|
from src.contract_check.api.routes.auth.support import (
|
||||||
_build_reset_link,
|
_build_reset_link,
|
||||||
|
|
@ -20,6 +22,7 @@ from src.contract_check.api.routes.auth.support import (
|
||||||
_require_web_auth_enabled,
|
_require_web_auth_enabled,
|
||||||
)
|
)
|
||||||
from src.contract_check.api.schemas import (
|
from src.contract_check.api.schemas import (
|
||||||
|
ChangePasswordRequest,
|
||||||
ForgotPasswordRequest,
|
ForgotPasswordRequest,
|
||||||
LoginRequest,
|
LoginRequest,
|
||||||
LogoutRequest,
|
LogoutRequest,
|
||||||
|
|
@ -225,3 +228,41 @@ async def reset_password(
|
||||||
|
|
||||||
log.info("user_password_reset", user_id=str(user_id))
|
log.info("user_password_reset", user_id=str(user_id))
|
||||||
return OkResponse(ok=True, detail="password updated")
|
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 (
|
from src.contract_check.api.schemas.auth import (
|
||||||
AuthResponse,
|
AuthResponse,
|
||||||
|
ChangePasswordRequest,
|
||||||
ForgotPasswordRequest,
|
ForgotPasswordRequest,
|
||||||
LoginRequest,
|
LoginRequest,
|
||||||
LogoutRequest,
|
LogoutRequest,
|
||||||
|
|
@ -106,6 +107,7 @@ __all__ = [
|
||||||
"LogoutRequest",
|
"LogoutRequest",
|
||||||
"ForgotPasswordRequest",
|
"ForgotPasswordRequest",
|
||||||
"ResetPasswordRequest",
|
"ResetPasswordRequest",
|
||||||
|
"ChangePasswordRequest",
|
||||||
"OkResponse",
|
"OkResponse",
|
||||||
"PasskeyRegistrationOptions",
|
"PasskeyRegistrationOptions",
|
||||||
"PasskeyAuthenticationOptions",
|
"PasskeyAuthenticationOptions",
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,11 @@ class ResetPasswordRequest(BaseModel):
|
||||||
password: str = Field(..., min_length=8, max_length=128)
|
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):
|
class OkResponse(BaseModel):
|
||||||
"""Generic `{ok: true, ...}` payload for state-mutating auth endpoints."""
|
"""Generic `{ok: true, ...}` payload for state-mutating auth endpoints."""
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue