feat(auth): add admin-only /api/auth/reset-password endpoint
POST /api/auth/reset-password
Body: { email, new_password }
Auth: requires admin role (get_current_admin)
Response: { ok, email, id, reset_by }
- Validates new_password >= 6 chars
- Returns 404 if target email not found
- Logs admin_id + target_user_id for audit trail
- Fixes passlib sha256_crypt cross-version verify bug by allowing
admin to rehash any user's password on demand
This commit is contained in:
@@ -266,6 +266,45 @@ async def login(form_data: LoginRequest, db: Session = Depends(get_db)):
|
|||||||
async def get_me(current_user: User = Depends(get_current_user)):
|
async def get_me(current_user: User = Depends(get_current_user)):
|
||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
|
class ResetPasswordRequest(BaseModel):
|
||||||
|
email: str
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/auth/reset-password")
|
||||||
|
async def reset_password(
|
||||||
|
payload: ResetPasswordRequest,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: User = Depends(get_current_admin),
|
||||||
|
):
|
||||||
|
"""Admin-only: reset another user's password.
|
||||||
|
|
||||||
|
Body: {"email": "<target email>", "new_password": "<new plaintext>"}
|
||||||
|
"""
|
||||||
|
if not payload.email or not payload.new_password:
|
||||||
|
raise HTTPException(status_code=400, detail="email and new_password are required")
|
||||||
|
if len(payload.new_password) < 6:
|
||||||
|
raise HTTPException(status_code=400, detail="new_password must be at least 6 characters")
|
||||||
|
|
||||||
|
target = db.query(User).filter(User.email == payload.email).first()
|
||||||
|
if not target:
|
||||||
|
raise HTTPException(status_code=404, detail=f"User not found: {payload.email}")
|
||||||
|
|
||||||
|
target.password_hash = get_password_hash(payload.new_password)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(target)
|
||||||
|
logger.info(
|
||||||
|
"password reset by admin",
|
||||||
|
extra={"context": {"admin_id": current_user.id, "target_user_id": target.id, "email": payload.email}},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"email": target.email,
|
||||||
|
"id": target.id,
|
||||||
|
"reset_by": current_user.email,
|
||||||
|
}
|
||||||
|
|
||||||
# ============ Excel File Storage ============
|
# ============ Excel File Storage ============
|
||||||
def get_excel_path(section: str) -> str:
|
def get_excel_path(section: str) -> str:
|
||||||
"""Get path to the stored Excel file for a section"""
|
"""Get path to the stored Excel file for a section"""
|
||||||
|
|||||||
Reference in New Issue
Block a user