Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
feat: split scopes from request methods
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Jun 30, 2024
1 parent 70d62a2 commit 7d1fefb
Show file tree
Hide file tree
Showing 15 changed files with 201 additions and 132 deletions.
74 changes: 28 additions & 46 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,11 @@ func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppRespons
return nil, fmt.Errorf("invalid expiresAt: %v", err)
}

// request methods are a space separated list of known request kinds TODO: it should be a string array in the API
requestMethods := strings.Split(createAppRequest.RequestMethods, " ")
if len(requestMethods) == 0 {
return nil, fmt.Errorf("won't create an app without request methods")
if len(createAppRequest.Scopes) == 0 {
return nil, fmt.Errorf("won't create an app without scopes")
}

for _, m := range requestMethods {
//if we don't support this method, we return an error
if !strings.Contains(api.svc.GetLNClient().GetSupportedNIP47Methods(), m) {
return nil, fmt.Errorf("did not recognize request method: %s", m)
}
}

for _, m := range notificationTypes {
//if we don't support this method, we return an error
if !strings.Contains(api.svc.GetLNClient().GetSupportedNIP47Methods(), m) {
return nil, fmt.Errorf("did not recognize request method: %s", m)
}
}

app, pairingSecretKey, err := api.dbSvc.CreateApp(createAppRequest.Name, createAppRequest.Pubkey, createAppRequest.MaxAmount, createAppRequest.BudgetRenewal, expiresAt, requestMethods)
app, pairingSecretKey, err := api.dbSvc.CreateApp(createAppRequest.Name, createAppRequest.Pubkey, createAppRequest.MaxAmount, createAppRequest.BudgetRenewal, expiresAt, createAppRequest.Scopes)

if err != nil {
return nil, err
Expand Down Expand Up @@ -116,11 +100,10 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
maxAmount := updateAppRequest.MaxAmount
budgetRenewal := updateAppRequest.BudgetRenewal

requestMethods := updateAppRequest.RequestMethods
if requestMethods == "" {
if len(updateAppRequest.Scopes) == 0 {
return fmt.Errorf("won't update an app to have no request methods")
}
newRequestMethods := strings.Split(requestMethods, " ")
newScopes := updateAppRequest.Scopes

expiresAt, err := api.parseExpiresAt(updateAppRequest.ExpiresAt)
if err != nil {
Expand All @@ -143,17 +126,17 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
return err
}

existingMethodMap := make(map[string]bool)
existingScopeMap := make(map[string]bool)
for _, perm := range existingPermissions {
existingMethodMap[perm.RequestMethod] = true
existingScopeMap[perm.Scope] = true
}

// Add new permissions
for _, method := range newRequestMethods {
if !existingMethodMap[method] {
for _, method := range newScopes {
if !existingScopeMap[method] {
perm := db.AppPermission{
App: *userApp,
RequestMethod: method,
Scope: method,
ExpiresAt: expiresAt,
MaxAmount: int(maxAmount),
BudgetRenewal: budgetRenewal,
Expand All @@ -162,12 +145,12 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
return err
}
}
delete(existingMethodMap, method)
delete(existingScopeMap, method)
}

// Remove old permissions
for method := range existingMethodMap {
if err := tx.Where("app_id = ? AND request_method = ?", userApp.ID, method).Delete(&db.AppPermission{}).Error; err != nil {
for method := range existingScopeMap {
if err := tx.Where("app_id = ? AND scope = ?", userApp.ID, method).Delete(&db.AppPermission{}).Error; err != nil {
return err
}
}
Expand Down Expand Up @@ -196,11 +179,11 @@ func (api *api) GetApp(userApp *db.App) *App {
requestMethods := []string{}
for _, appPerm := range appPermissions {
expiresAt = appPerm.ExpiresAt
if appPerm.RequestMethod == nip47.PAY_INVOICE_METHOD {
if appPerm.Scope == nip47.PAY_INVOICE_METHOD {
//find the pay_invoice-specific permissions
paySpecificPermission = appPerm
}
requestMethods = append(requestMethods, appPerm.RequestMethod)
requestMethods = append(requestMethods, appPerm.Scope)
}

//renewsIn := ""
Expand Down Expand Up @@ -236,11 +219,11 @@ func (api *api) ListApps() ([]App, error) {
dbApps := []db.App{}
api.db.Find(&dbApps)

permissions := []db.AppPermission{}
api.db.Find(&permissions)
appPermissions := []db.AppPermission{}
api.db.Find(&appPermissions)

permissionsMap := make(map[uint][]db.AppPermission)
for _, perm := range permissions {
for _, perm := range appPermissions {
permissionsMap[perm.AppId] = append(permissionsMap[perm.AppId], perm)
}

Expand All @@ -255,14 +238,14 @@ func (api *api) ListApps() ([]App, error) {
NostrPubkey: userApp.NostrPubkey,
}

for _, permission := range permissionsMap[userApp.ID] {
apiApp.RequestMethods = append(apiApp.RequestMethods, permission.RequestMethod)
apiApp.ExpiresAt = permission.ExpiresAt
if permission.RequestMethod == nip47.PAY_INVOICE_METHOD {
apiApp.BudgetRenewal = permission.BudgetRenewal
apiApp.MaxAmount = uint64(permission.MaxAmount)
for _, appPermission := range permissionsMap[userApp.ID] {
apiApp.RequestMethods = append(apiApp.RequestMethods, appPermission.Scope)
apiApp.ExpiresAt = appPermission.ExpiresAt
if appPermission.Scope == permissions.PAY_INVOICE_SCOPE {
apiApp.BudgetRenewal = appPermission.BudgetRenewal
apiApp.MaxAmount = uint64(appPermission.MaxAmount)
if apiApp.MaxAmount > 0 {
apiApp.BudgetUsage = api.permissionsSvc.GetBudgetUsage(&permission)
apiApp.BudgetUsage = api.permissionsSvc.GetBudgetUsage(&appPermission)
}
}
}
Expand Down Expand Up @@ -701,18 +684,17 @@ func (api *api) GetWalletCapabilities(ctx context.Context) (*WalletCapabilitiesR
methods := strings.Split(api.svc.GetLNClient().GetSupportedNIP47Methods(), " ")
notificationTypes := strings.Split(api.svc.GetLNClient().GetSupportedNIP47NotificationTypes(), " ")

// FIXME: permissions need to be decoupled from NIP-47 methods and notifications
permissions := utils.Filter(methods, func(s string) bool {
scopes := utils.Filter(methods, func(s string) bool {
return s != nip47.PAY_KEYSEND_METHOD && s != nip47.MULTI_PAY_INVOICE_METHOD && s != nip47.MULTI_PAY_KEYSEND_METHOD
})
if len(notificationTypes) > 0 {
permissions = append(permissions, "notifications")
scopes = append(scopes, "notifications")
}

return &WalletCapabilitiesResponse{
Methods: methods,
NotificationTypes: notificationTypes,
Scopes: permissions,
Scopes: scopes,
}, nil
}

Expand Down
23 changes: 11 additions & 12 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,20 @@ type ListAppsResponse struct {
}

type UpdateAppRequest struct {
MaxAmount uint64 `json:"maxAmount"`
BudgetRenewal string `json:"budgetRenewal"`
ExpiresAt string `json:"expiresAt"`
RequestMethods string `json:"requestMethods"`
MaxAmount uint64 `json:"maxAmount"`
BudgetRenewal string `json:"budgetRenewal"`
ExpiresAt string `json:"expiresAt"`
Scopes []string `json:"scopes"`
}

type CreateAppRequest struct {
Name string `json:"name"`
Pubkey string `json:"pubkey"`
MaxAmount uint64 `json:"maxAmount"`
BudgetRenewal string `json:"budgetRenewal"`
ExpiresAt string `json:"expiresAt"`
RequestMethods []string `json:"requestMethods"`
NotificationTypes []string `json:"notificationTypes"`
ReturnTo string `json:"returnTo"`
Name string `json:"name"`
Pubkey string `json:"pubkey"`
MaxAmount uint64 `json:"maxAmount"`
BudgetRenewal string `json:"budgetRenewal"`
ExpiresAt string `json:"expiresAt"`
Scopes []string `json:"scopes"`
ReturnTo string `json:"returnTo"`
}

type StartRequest struct {
Expand Down
11 changes: 6 additions & 5 deletions db/db_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func NewDBService(db *gorm.DB) *dbService {
}
}

func (svc *dbService) CreateApp(name string, pubkey string, maxAmount uint64, budgetRenewal string, expiresAt *time.Time, requestMethods []string) (*App, string, error) {
func (svc *dbService) CreateApp(name string, pubkey string, maxAmount uint64, budgetRenewal string, expiresAt *time.Time, scopes []string) (*App, string, error) {
var pairingPublicKey string
var pairingSecretKey string
if pubkey == "" {
Expand All @@ -44,11 +44,11 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmount uint64, bu
return err
}

for _, m := range requestMethods {
for _, scope := range scopes {
appPermission := AppPermission{
App: app,
RequestMethod: m,
ExpiresAt: expiresAt,
App: app,
Scope: scope,
ExpiresAt: expiresAt,
//these fields are only relevant for pay_invoice
MaxAmount: int(maxAmount),
BudgetRenewal: budgetRenewal,
Expand All @@ -58,6 +58,7 @@ func (svc *dbService) CreateApp(name string, pubkey string, maxAmount uint64, bu
return err
}
}

// commit transaction
return nil
})
Expand Down
22 changes: 22 additions & 0 deletions db/migrations/202406301207_rename_request_methods.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package migrations

import (
_ "embed"

"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)

var _202406301207_rename_request_methods = &gormigrate.Migration{
ID: "202406301207_rename_request_methods",
Migrate: func(tx *gorm.DB) error {
if err := tx.Exec("ALTER TABLE app_permissions RENAME request_method TO scope").Error; err != nil {
return err
}

return nil
},
Rollback: func(tx *gorm.DB) error {
return nil
},
}
1 change: 1 addition & 0 deletions db/migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func Migrate(gormDB *gorm.DB) error {
_202405302121_store_decrypted_request,
_202406061259_delete_content,
_202406071726_vacuum,
_202406301207_rename_request_methods,
})

return m.Migrate()
Expand Down
4 changes: 2 additions & 2 deletions db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type AppPermission struct {
ID uint
AppId uint `validate:"required"`
App App
RequestMethod string `validate:"required"`
Scope string `validate:"required"`
MaxAmount int
BudgetRenewal string
ExpiresAt *time.Time
Expand Down Expand Up @@ -68,7 +68,7 @@ type Payment struct {
}

type DBService interface {
CreateApp(name string, pubkey string, maxAmount uint64, budgetRenewal string, expiresAt *time.Time, requestMethods []string) (*App, string, error)
CreateApp(name string, pubkey string, maxAmount uint64, budgetRenewal string, expiresAt *time.Time, scopes []string) (*App, string, error)
}

const (
Expand Down
1 change: 1 addition & 0 deletions lnclient/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type LNClient interface {
GetStorageDir() (string, error)
GetNetworkGraph(nodeIds []string) (NetworkGraphResponse, error)
UpdateLastWalletSyncRequest()
// TODO: change the below to return arrays instead of space-separated strings
GetSupportedNIP47Methods() string
GetSupportedNIP47NotificationTypes() string
}
Expand Down
2 changes: 1 addition & 1 deletion nip47/controllers/get_info_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (controller *getInfoController) HandleGetInfoEvent(ctx context.Context, nip
}

// basic permissions check
hasPermission, _, _ := controller.permissionsService.HasPermission(app, nip47Request.Method, 0)
hasPermission, _, _ := controller.permissionsService.HasPermission(app, permissions.GET_INFO_SCOPE, 0)
if hasPermission {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
Expand Down
24 changes: 12 additions & 12 deletions nip47/controllers/get_info_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func TestHandleGetInfoEvent_NoPermission(t *testing.T) {
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
RequestMethod: models.GET_BALANCE_METHOD,
ExpiresAt: nil,
AppId: app.ID,
Scope: models.GET_BALANCE_METHOD,
ExpiresAt: nil,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
Expand Down Expand Up @@ -96,9 +96,9 @@ func TestHandleGetInfoEvent_WithPermission(t *testing.T) {
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
RequestMethod: models.GET_INFO_METHOD,
ExpiresAt: nil,
AppId: app.ID,
Scope: models.GET_INFO_METHOD,
ExpiresAt: nil,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
Expand Down Expand Up @@ -148,18 +148,18 @@ func TestHandleGetInfoEvent_WithNotifications(t *testing.T) {
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
RequestMethod: models.GET_INFO_METHOD,
ExpiresAt: nil,
AppId: app.ID,
Scope: models.GET_INFO_METHOD,
ExpiresAt: nil,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

// TODO: AppPermission RequestMethod needs to change to scope
appPermission = &db.AppPermission{
AppId: app.ID,
RequestMethod: "notifications",
ExpiresAt: nil,
AppId: app.ID,
Scope: "notifications",
ExpiresAt: nil,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
Expand Down
13 changes: 12 additions & 1 deletion nip47/event_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/getAlby/nostr-wallet-connect/logger"
controllers "github.com/getAlby/nostr-wallet-connect/nip47/controllers"
"github.com/getAlby/nostr-wallet-connect/nip47/models"
"github.com/getAlby/nostr-wallet-connect/nip47/permissions"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -240,7 +241,17 @@ func (svc *nip47Service) HandleEvent(ctx context.Context, sub *nostr.Subscriptio
}

checkPermission := func(amountMsat uint64) *models.Response {
hasPermission, code, message := svc.permissionsService.HasPermission(&app, nip47Request.Method, amountMsat)
scope, err := permissions.RequestMethodToScope(nip47Request.Method)
if err != nil {
return &models.Response{
ResultType: nip47Request.Method,
Error: &models.Error{
Code: models.ERROR_INTERNAL,
Message: err.Error(),
},
}
}
hasPermission, code, message := svc.permissionsService.HasPermission(&app, scope, amountMsat)
if !hasPermission {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEvent.ID,
Expand Down
Loading

0 comments on commit 7d1fefb

Please sign in to comment.