fix: gate event edits through the approval workflow
The PUT /events/{id} endpoint blindly copied every field from the
request body onto the existing event, including `status`. A non-admin
owner with auto_approve=false could PUT {"status": "approved", ...}
and self-approve, bypassing review entirely.
Replace the blanket setattr loop with an explicit field list (status
omitted) and derive the new status from the same admin / auto_approve
gate that api_event_create uses. Reconcile Nostr against the status
transition:
approved → approved : re-publish the replaceable NIP-52 event
proposed → approved : fresh publish
approved → proposed : NIP-09 delete so the public feed drops it
until the edit is re-approved
proposed → proposed : no-op
Also apply the same end/closing-date defaulting as create_event so an
edit that omits those fields doesn't wipe them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d7a25e9bb3
commit
48f3c11f88
1 changed files with 52 additions and 4 deletions
56
views_api.py
56
views_api.py
|
|
@ -203,6 +203,18 @@ async def api_event_update(
|
|||
data: CreateEvent,
|
||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> Event:
|
||||
"""Update an event. The owner can edit any mutable field; the status
|
||||
is derived (admin / `auto_approve` ⇒ approved, otherwise proposed)
|
||||
and is NEVER taken from the request body — that would let owners
|
||||
self-approve.
|
||||
|
||||
Nostr is reconciled against the status transition:
|
||||
approved → approved : re-publish the replaceable NIP-52 event
|
||||
proposed → approved : fresh publish
|
||||
approved → proposed : NIP-09 delete so the public feed drops it
|
||||
until the edit is re-approved
|
||||
proposed → proposed : no-op
|
||||
"""
|
||||
event = await get_event(event_id)
|
||||
if not event:
|
||||
raise HTTPException(
|
||||
|
|
@ -210,13 +222,49 @@ async def api_event_update(
|
|||
)
|
||||
if event.wallet != wallet.wallet.id:
|
||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your event.")
|
||||
for k, v in data.dict().items():
|
||||
setattr(event, k, v)
|
||||
|
||||
from lnbits.settings import settings
|
||||
|
||||
ext_settings = await get_settings()
|
||||
user_id = wallet.wallet.user
|
||||
is_admin = user_id == settings.super_user or user_id in settings.lnbits_admin_users
|
||||
|
||||
previous_status = event.status
|
||||
|
||||
# Same defaulting as create_event: optional end/closing dates fall
|
||||
# back to start_date when omitted, so an edit that doesn't restate
|
||||
# them doesn't wipe them.
|
||||
if not data.event_end_date:
|
||||
data.event_end_date = data.event_start_date
|
||||
if not data.closing_date:
|
||||
data.closing_date = data.event_end_date
|
||||
|
||||
# Explicit field list — never copy `status` from the request body.
|
||||
for field in (
|
||||
"name",
|
||||
"info",
|
||||
"closing_date",
|
||||
"event_start_date",
|
||||
"event_end_date",
|
||||
"currency",
|
||||
"amount_tickets",
|
||||
"price_per_ticket",
|
||||
"banner",
|
||||
"location",
|
||||
"categories",
|
||||
"extra",
|
||||
):
|
||||
setattr(event, field, getattr(data, field))
|
||||
|
||||
event.status = "approved" if (is_admin or ext_settings.auto_approve) else "proposed"
|
||||
|
||||
event = await update_event(event)
|
||||
|
||||
# Re-publish the replaceable NIP-52 event if we already announced it.
|
||||
if event.status == "approved" and event.nostr_event_id:
|
||||
if event.status == "approved":
|
||||
await publish_or_delete_nostr_event(event)
|
||||
elif previous_status == "approved":
|
||||
# Take it down from the public feed while it waits for re-approval.
|
||||
await publish_or_delete_nostr_event(event, delete=True)
|
||||
|
||||
return event
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue