feat: event proposal and approval workflow #9

Closed
padreug wants to merge 38 commits from feat/event-approval-workflow into main
2 changed files with 14 additions and 17 deletions
Showing only changes of commit c1e66fbf7f - Show all commits

fix: use check_admin for approval endpoints, not require_admin_key
Some checks failed
lint.yml / fix: use check_admin for approval endpoints, not require_admin_key (pull_request) Failing after 0s

require_admin_key only checks that the API key is a wallet admin key,
which ANY user has. check_admin verifies the user is a LNbits admin
(super_user or lnbits_admin_users). JS updated to omit API key on
admin endpoints, relying on session cookie auth instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Padreug 2026-04-27 11:27:21 +02:00

View file

@ -122,8 +122,7 @@ window.app = Vue.createApp({
LNbits.api LNbits.api
.request( .request(
'PUT', 'PUT',
'/events/api/v1/events/' + eventId + '/approve', '/events/api/v1/events/' + eventId + '/approve'
this.g.user.wallets[0].adminkey
) )
.then(() => { .then(() => {
this.$q.notify({ this.$q.notify({
@ -145,8 +144,7 @@ window.app = Vue.createApp({
LNbits.api LNbits.api
.request( .request(
'PUT', 'PUT',
'/events/api/v1/events/' + eventId + '/reject', '/events/api/v1/events/' + eventId + '/reject'
this.g.user.wallets[0].adminkey
) )
.then(() => { .then(() => {
this.$q.notify({ this.$q.notify({
@ -207,8 +205,7 @@ window.app = Vue.createApp({
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/events/api/v1/events/all', '/events/api/v1/events/all'
this.g.user.wallets[0].adminkey
) )
.then(response => { .then(response => {
this.events = response.data.map(obj => { this.events = response.data.map(obj => {
@ -236,8 +233,7 @@ window.app = Vue.createApp({
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/events/api/v1/events/pending', '/events/api/v1/events/pending'
this.g.user.wallets[0].adminkey
) )
.then(response => { .then(response => {
this.pendingEvents = response.data.map(obj => { this.pendingEvents = response.data.map(obj => {

View file

@ -3,9 +3,10 @@ from http import HTTPStatus
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from lnbits.core.crud import get_standalone_payment, get_user from lnbits.core.crud import get_standalone_payment, get_user
from lnbits.core.models import WalletTypeInfo from lnbits.core.models import Account, WalletTypeInfo
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.decorators import ( from lnbits.decorators import (
check_admin,
require_admin_key, require_admin_key,
require_invoice_key, require_invoice_key,
) )
@ -65,9 +66,9 @@ async def api_events_public():
@events_api_router.get("/api/v1/events/all") @events_api_router.get("/api/v1/events/all")
async def api_events_all( async def api_events_all(
wallet: WalletTypeInfo = Depends(require_admin_key), admin: Account = Depends(check_admin),
): ):
"""Get all events across all wallets. Admin only.""" """Get all events across all wallets. LNbits admin only."""
from .crud import get_all_events from .crud import get_all_events
events = await get_all_events() events = await get_all_events()
@ -161,9 +162,9 @@ async def api_event_propose(
@events_api_router.get("/api/v1/events/pending") @events_api_router.get("/api/v1/events/pending")
async def api_events_pending( async def api_events_pending(
wallet: WalletTypeInfo = Depends(require_admin_key), admin: Account = Depends(check_admin),
): ):
"""Get all proposed events awaiting approval. Admin only.""" """Get all proposed events awaiting approval. LNbits admin only."""
events = await get_pending_events() events = await get_pending_events()
return [event.dict() for event in events] return [event.dict() for event in events]
@ -171,9 +172,9 @@ async def api_events_pending(
@events_api_router.put("/api/v1/events/{event_id}/approve") @events_api_router.put("/api/v1/events/{event_id}/approve")
async def api_event_approve( async def api_event_approve(
event_id: str, event_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key), admin: Account = Depends(check_admin),
): ):
"""Approve a proposed event. Admin only.""" """Approve a proposed event. LNbits admin only."""
event = await get_event(event_id) event = await get_event(event_id)
if not event: if not event:
raise HTTPException( raise HTTPException(
@ -192,9 +193,9 @@ async def api_event_approve(
@events_api_router.put("/api/v1/events/{event_id}/reject") @events_api_router.put("/api/v1/events/{event_id}/reject")
async def api_event_reject( async def api_event_reject(
event_id: str, event_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key), admin: Account = Depends(check_admin),
): ):
"""Reject a proposed event. Admin only.""" """Reject a proposed event. LNbits admin only."""
event = await get_event(event_id) event = await get_event(event_id)
if not event: if not event:
raise HTTPException( raise HTTPException(