add password reset via admin-generated one-time code
db.py: reset_codes table (code, handle, created_by, created_at,
expires_at, used_at); create_reset_code() invalidates any prior unused
code for the same handle; use_reset_code() validates handle match,
expiry (24 h), and single-use; change_password() updates the hash.
server.py: POST /api/admin/users/{handle}/reset-password-code (admin)
returns a code; POST /api/auth/reset-password (public) validates the
code + handle and sets the new password.
Admin page: "Reset pwd" button per user — shows the code inline on
click (monospace, click-to-copy).
/reset-password/ page: handle + code + new password form.
Login page: "Forgot password?" link.
This commit is contained in:
@@ -346,6 +346,25 @@ async def logout(bincio_session: Optional[str] = Cookie(default=None)) -> JSONRe
|
||||
return resp
|
||||
|
||||
|
||||
@app.post("/api/auth/reset-password")
|
||||
async def reset_password(request: Request) -> JSONResponse:
|
||||
"""Validate a reset code and set a new password. Public endpoint."""
|
||||
from bincio.serve.db import use_reset_code, change_password
|
||||
body = await request.json()
|
||||
handle = (body.get("handle") or "").strip().lower()
|
||||
code = (body.get("code") or "").strip().upper()
|
||||
new_pw = body.get("password") or ""
|
||||
if not handle or not code or not new_pw:
|
||||
raise HTTPException(400, "handle, code, and password are required")
|
||||
if len(new_pw) < 8:
|
||||
raise HTTPException(400, "Password must be at least 8 characters")
|
||||
db = _get_db()
|
||||
if not use_reset_code(db, code, handle):
|
||||
raise HTTPException(400, "Invalid or expired reset code")
|
||||
change_password(db, handle, new_pw)
|
||||
return JSONResponse({"ok": True})
|
||||
|
||||
|
||||
# ── Registration ──────────────────────────────────────────────────────────────
|
||||
|
||||
@app.post("/api/register")
|
||||
@@ -503,6 +522,21 @@ async def admin_disk(bincio_session: Optional[str] = Cookie(default=None)) -> JS
|
||||
})
|
||||
|
||||
|
||||
@app.post("/api/admin/users/{handle}/reset-password-code")
|
||||
async def admin_reset_password_code(
|
||||
handle: str,
|
||||
bincio_session: Optional[str] = Cookie(default=None),
|
||||
) -> JSONResponse:
|
||||
"""Generate a one-time password reset code for a user. Admin only."""
|
||||
from bincio.serve.db import create_reset_code
|
||||
admin = _require_admin(bincio_session)
|
||||
db = _get_db()
|
||||
if not get_user(db, handle):
|
||||
raise HTTPException(404, f"User '{handle}' not found")
|
||||
code = create_reset_code(db, handle, admin.handle)
|
||||
return JSONResponse({"ok": True, "code": code, "expires_in_hours": 24})
|
||||
|
||||
|
||||
@app.post("/api/admin/users/{handle}/rebuild")
|
||||
async def admin_rebuild(
|
||||
handle: str,
|
||||
|
||||
Reference in New Issue
Block a user