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)):
|
||||
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 ============
|
||||
def get_excel_path(section: str) -> str:
|
||||
"""Get path to the stored Excel file for a section"""
|
||||
|
||||
Reference in New Issue
Block a user