alby-hub/nip47/event_handler_test.go
Roland 7ec4746bc5
feat: do not require unlock password to recover encrypted scb (#768)
* feat: do not require unlock password to recover encrypted scb

* fix: use seed as key to encrypt static channel backup

* chore: move encrypted channel backup derivation to alby oauth service

* chore: add extra test

* chore: reduce duplicated code

* fix: change key derivation paths

* chore: simplify backup page copy for connected alby accounts

* chore: improve copy

* fix: initialize keys before starting ln backend

* chore: improve test assertions
2024-11-01 13:26:10 +07:00

374 lines
10 KiB
Go

package nip47
import (
"context"
"encoding/json"
"slices"
"testing"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/nip47/models"
"github.com/getAlby/hub/nip47/permissions"
"github.com/getAlby/hub/tests"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip04"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TODO: test HandleEvent
// TODO: test a request cannot be processed twice
// TODO: test if an app doesn't exist it returns the right error code
func TestCreateResponse(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
assert.NoError(t, err)
reqEvent := &nostr.Event{
Kind: models.REQUEST_KIND,
PubKey: reqPubkey,
Content: "1",
}
reqEvent.ID = "12345"
ss, err := nip04.ComputeSharedSecret(reqPubkey, svc.Keys.GetNostrSecretKey())
assert.NoError(t, err)
type dummyResponse struct {
Foo int
}
nip47Response := &models.Response{
ResultType: "dummy_method",
Result: dummyResponse{
Foo: 1000,
},
}
nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
res, err := nip47svc.CreateResponse(reqEvent, nip47Response, nostr.Tags{}, ss)
assert.NoError(t, err)
assert.Equal(t, reqPubkey, res.Tags.GetFirst([]string{"p"}).Value())
assert.Equal(t, reqEvent.ID, res.Tags.GetFirst([]string{"e"}).Value())
assert.Equal(t, svc.Keys.GetNostrPublicKey(), res.PubKey)
decrypted, err := nip04.Decrypt(res.Content, ss)
assert.NoError(t, err)
unmarshalledResponse := models.Response{
Result: &dummyResponse{},
}
err = json.Unmarshal([]byte(decrypted), &unmarshalledResponse)
assert.NoError(t, err)
assert.Nil(t, nip47Response.Error)
assert.Equal(t, nip47Response.ResultType, unmarshalledResponse.ResultType)
assert.Equal(t, nip47Response.Result, *unmarshalledResponse.Result.(*dummyResponse))
}
func TestHandleResponse_WithPermission(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
assert.NoError(t, err)
app, ss, err := tests.CreateAppWithPrivateKey(svc, reqPrivateKey)
assert.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.GET_BALANCE_SCOPE,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
content := map[string]interface{}{
"method": models.GET_INFO_METHOD,
}
payloadBytes, err := json.Marshal(content)
assert.NoError(t, err)
msg, err := nip04.Encrypt(string(payloadBytes), ss)
assert.NoError(t, err)
reqEvent := &nostr.Event{
Kind: models.REQUEST_KIND,
PubKey: reqPubkey,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{},
Content: msg,
}
err = reqEvent.Sign(reqPrivateKey)
assert.NoError(t, err)
relay := tests.NewMockRelay()
nip47svc.HandleEvent(context.TODO(), relay, reqEvent, svc.LNClient)
assert.NotNil(t, relay.PublishedEvent)
assert.NotEmpty(t, relay.PublishedEvent.Content)
ss, err = nip04.ComputeSharedSecret(svc.Keys.GetNostrPublicKey(), reqPrivateKey)
assert.NoError(t, err)
decrypted, err := nip04.Decrypt(relay.PublishedEvent.Content, ss)
assert.NoError(t, err)
type getInfoResult struct {
Methods []string `json:"methods"`
}
type getInfoResponseWrapper struct {
models.Response
Result getInfoResult `json:"result"`
}
unmarshalledResponse := getInfoResponseWrapper{}
err = json.Unmarshal([]byte(decrypted), &unmarshalledResponse)
assert.NoError(t, err)
assert.Nil(t, unmarshalledResponse.Error)
assert.Equal(t, models.GET_INFO_METHOD, unmarshalledResponse.ResultType)
expectedMethods := slices.Concat([]string{constants.GET_BALANCE_SCOPE}, permissions.GetAlwaysGrantedMethods())
assert.Equal(t, expectedMethods, unmarshalledResponse.Result.Methods)
}
func TestHandleResponse_DuplicateRequest(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
assert.NoError(t, err)
app, ss, err := tests.CreateAppWithPrivateKey(svc, reqPrivateKey)
assert.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.GET_BALANCE_SCOPE,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
content := map[string]interface{}{
"method": models.GET_INFO_METHOD,
}
payloadBytes, err := json.Marshal(content)
assert.NoError(t, err)
msg, err := nip04.Encrypt(string(payloadBytes), ss)
assert.NoError(t, err)
reqEvent := &nostr.Event{
Kind: models.REQUEST_KIND,
PubKey: reqPubkey,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{},
Content: msg,
}
err = reqEvent.Sign(reqPrivateKey)
assert.NoError(t, err)
relay := tests.NewMockRelay()
nip47svc.HandleEvent(context.TODO(), relay, reqEvent, svc.LNClient)
assert.NotNil(t, relay.PublishedEvent)
assert.NotEmpty(t, relay.PublishedEvent.Content)
relay.PublishedEvent = nil
nip47svc.HandleEvent(context.TODO(), relay, reqEvent, svc.LNClient)
// second time it should not publish
assert.Nil(t, relay.PublishedEvent)
}
func TestHandleResponse_NoPermission(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
assert.NoError(t, err)
_, ss, err := tests.CreateAppWithPrivateKey(svc, reqPrivateKey)
assert.NoError(t, err)
content := map[string]interface{}{
"method": models.GET_BALANCE_METHOD,
}
payloadBytes, err := json.Marshal(content)
assert.NoError(t, err)
msg, err := nip04.Encrypt(string(payloadBytes), ss)
assert.NoError(t, err)
reqEvent := &nostr.Event{
Kind: models.REQUEST_KIND,
PubKey: reqPubkey,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{},
Content: msg,
}
err = reqEvent.Sign(reqPrivateKey)
assert.NoError(t, err)
relay := tests.NewMockRelay()
nip47svc.HandleEvent(context.TODO(), relay, reqEvent, svc.LNClient)
assert.NotNil(t, relay.PublishedEvent)
assert.NotEmpty(t, relay.PublishedEvent.Content)
ss, err = nip04.ComputeSharedSecret(svc.Keys.GetNostrPublicKey(), reqPrivateKey)
assert.NoError(t, err)
decrypted, err := nip04.Decrypt(relay.PublishedEvent.Content, ss)
assert.NoError(t, err)
unmarshalledResponse := models.Response{}
err = json.Unmarshal([]byte(decrypted), &unmarshalledResponse)
assert.NoError(t, err)
assert.Nil(t, unmarshalledResponse.Result)
assert.Equal(t, models.GET_BALANCE_METHOD, unmarshalledResponse.ResultType)
assert.Equal(t, "RESTRICTED", unmarshalledResponse.Error.Code)
assert.Equal(t, "This app does not have the get_balance scope", unmarshalledResponse.Error.Message)
}
func TestHandleResponse_NoApp(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
assert.NoError(t, err)
app, ss, err := tests.CreateAppWithPrivateKey(svc, reqPrivateKey)
assert.NoError(t, err)
// delete the app
err = svc.DB.Delete(app).Error
assert.NoError(t, err)
content := map[string]interface{}{
"method": models.GET_BALANCE_METHOD,
}
payloadBytes, err := json.Marshal(content)
assert.NoError(t, err)
msg, err := nip04.Encrypt(string(payloadBytes), ss)
assert.NoError(t, err)
reqEvent := &nostr.Event{
Kind: models.REQUEST_KIND,
PubKey: reqPubkey,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{},
Content: msg,
}
err = reqEvent.Sign(reqPrivateKey)
assert.NoError(t, err)
relay := tests.NewMockRelay()
nip47svc.HandleEvent(context.TODO(), relay, reqEvent, svc.LNClient)
assert.NotNil(t, relay.PublishedEvent)
assert.NotEmpty(t, relay.PublishedEvent.Content)
ss, err = nip04.ComputeSharedSecret(svc.Keys.GetNostrPublicKey(), reqPrivateKey)
assert.NoError(t, err)
decrypted, err := nip04.Decrypt(relay.PublishedEvent.Content, ss)
assert.NoError(t, err)
unmarshalledResponse := models.Response{}
err = json.Unmarshal([]byte(decrypted), &unmarshalledResponse)
assert.NoError(t, err)
assert.Nil(t, unmarshalledResponse.Result)
assert.Equal(t, "", unmarshalledResponse.ResultType)
assert.Equal(t, "UNAUTHORIZED", unmarshalledResponse.Error.Code)
assert.Equal(t, "The public key does not have a wallet connected.", unmarshalledResponse.Error.Message)
}
func TestHandleResponse_IncorrectPubkey(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher)
reqPrivateKey := nostr.GeneratePrivateKey()
reqPubkey, err := nostr.GetPublicKey(reqPrivateKey)
assert.NoError(t, err)
reqPrivateKey2 := nostr.GeneratePrivateKey()
app, _, err := tests.CreateAppWithPrivateKey(svc, reqPrivateKey)
assert.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.GET_BALANCE_SCOPE,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)
_, ss, err := tests.CreateAppWithPrivateKey(svc, reqPrivateKey2)
assert.NoError(t, err)
content := map[string]interface{}{
"method": models.GET_BALANCE_METHOD,
}
payloadBytes, err := json.Marshal(content)
assert.NoError(t, err)
msg, err := nip04.Encrypt(string(payloadBytes), ss)
assert.NoError(t, err)
reqEvent := &nostr.Event{
Kind: models.REQUEST_KIND,
CreatedAt: nostr.Now(),
Tags: nostr.Tags{},
Content: msg,
}
err = reqEvent.Sign(reqPrivateKey2)
assert.NoError(t, err)
// set a different pubkey (this will not pass validation)
reqEvent.PubKey = reqPubkey
relay := tests.NewMockRelay()
nip47svc.HandleEvent(context.TODO(), relay, reqEvent, svc.LNClient)
assert.Nil(t, relay.PublishedEvent)
}