fix: use scope constant in get_budget permission query (#2510)
Some checks are pending
Multiplatform Docker build & push / build (push) Waiting to run
Code quality - linting and typechecking / linting (push) Waiting to run
Backend testing with Postgres / test-postgres (push) Waiting to run

* fix: use scope constant in get_budget permission query

The get_budget controller filtered the app_permissions scope column with
models.PAY_INVOICE_METHOD, which only matched because the method and
scope constants share the same string value. Use
constants.PAY_INVOICE_SCOPE like every other scope lookup, and document
why the unchecked First result is safe.

Fixes #2503

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: return error from get_budget on unexpected permission query failure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-08-07 12:39:07 +07:00 committed by GitHub
parent d94f6933f5
commit bdce8fe8d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 1 deletions

View file

@ -2,10 +2,13 @@ package controllers
import (
"context"
"errors"
"github.com/getAlby/go-nostr"
"github.com/getAlby/hub/db/queries"
"gorm.io/gorm"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/nip47/models"
@ -26,8 +29,21 @@ func (controller *nip47Controller) HandleGetBudgetEvent(ctx context.Context, nip
}).Debug("Getting budget")
appPermission := db.AppPermission{}
controller.db.Where("app_id = ? AND scope = ?", app.ID, models.PAY_INVOICE_METHOD).First(&appPermission)
result := controller.db.Where("app_id = ? AND scope = ?", app.ID, constants.PAY_INVOICE_SCOPE).First(&appPermission)
if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
}).WithError(result.Error).Error("Failed to fetch pay_invoice permission")
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: mapNip47Error(result.Error),
}, nostr.Tags{})
return
}
// On ErrRecordNotFound appPermission stays zero-valued and maxAmountSat == 0,
// which returns the same empty "no budget" response as a permission with no
// budget set.
maxAmountSat := appPermission.MaxAmountSat
if maxAmountSat == 0 {
publishResponse(&models.Response{

View file

@ -207,6 +207,41 @@ func TestHandleGetBudgetEvent_NoBudget(t *testing.T) {
assert.Nil(t, publishedResponse.Error)
}
func TestHandleGetBudgetEvent_DatabaseError(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetBudgetJson), nip47Request)
assert.NoError(t, err)
app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)
dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)
// simulate a database failure that is not a record-not-found error
err = svc.DB.Exec("DROP TABLE app_permissions").Error
assert.NoError(t, err)
var publishedResponse *models.Response
publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}
NewTestNip47Controller(svc).
HandleGetBudgetEvent(ctx, nip47Request, dbRequestEvent.ID, app, publishResponse)
assert.Nil(t, publishedResponse.Result)
require.NotNil(t, publishedResponse.Error)
assert.Equal(t, constants.ERROR_INTERNAL, publishedResponse.Error.Code)
}
func TestHandleGetBudgetEvent_NoPayInvoicePermission(t *testing.T) {
ctx := context.TODO()
svc, err := tests.CreateTestService(t)