feat: read only JWT for http api use (#1717)

* feat: read only JWT for http api use

* fix: pass permission to start and unlock endpoints

* chore: add http service JWT tests

* chore: update mocks

* fix: close db in tests
This commit is contained in:
Roland 2025-09-19 13:07:10 +07:00 committed by GitHub
parent a294e1b169
commit 595361df27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2384 additions and 85 deletions

View file

@ -12,3 +12,13 @@ packages:
github.com/getAlby/hub/service:
interfaces:
Service: {}
github.com/getAlby/hub/service/keys:
interfaces:
Keys: {}
github.com/getAlby/hub/alby:
interfaces:
AlbyService: {}
AlbyOAuthService: {}
github.com/getAlby/hub/events:
interfaces:
EventPublisher: {}

View file

@ -215,6 +215,7 @@ type StartRequest struct {
type UnlockRequest struct {
UnlockPassword string `json:"unlockPassword"`
TokenExpiryDays *uint64 `json:"tokenExpiryDays"`
Permission string `json:"permission,omitempty"` // "full" or "readonly"
}
type BackupReminderRequest struct {

View file

@ -53,6 +53,7 @@ export default function Start() {
},
body: JSON.stringify({
unlockPassword,
permission: "full",
}),
});
if (authTokenResponse) {

View file

@ -41,6 +41,7 @@ export default function Unlock() {
},
body: JSON.stringify({
unlockPassword,
permission: "full",
}),
}
);

View file

@ -8,6 +8,7 @@ import { ExternalLinkButton } from "src/components/ui/custom/external-link-butto
import { LoadingButton } from "src/components/ui/custom/loading-button";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { RadioGroup, RadioGroupItem } from "src/components/ui/radio-group";
import { Separator } from "src/components/ui/separator";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { copyToClipboard } from "src/lib/clipboard";
@ -17,8 +18,12 @@ import { request } from "src/utils/request";
export default function DeveloperSettings() {
const { data: albyMe } = useAlbyMe();
const [token, setToken] = React.useState<string>();
const [tokenPermission, setTokenPermission] = React.useState<string>();
const [expiryDays, setExpiryDays] = React.useState<string>("365");
const [unlockPassword, setUnlockPassword] = React.useState<string>("");
const [permission, setPermission] = React.useState<"full" | "readonly">(
"full"
);
const [showCreateTokenForm, setShowCreateTokenForm] =
React.useState<boolean>();
const [loading, setLoading] = React.useState<boolean>();
@ -27,7 +32,7 @@ export default function DeveloperSettings() {
e.preventDefault();
try {
setLoading(true);
if (!expiryDays || !unlockPassword) {
if (!expiryDays || !unlockPassword || !permission) {
throw new Error("Form not filled");
}
const authTokenResponse = await request<AuthTokenResponse>(
@ -40,11 +45,13 @@ export default function DeveloperSettings() {
body: JSON.stringify({
unlockPassword,
tokenExpiryDays: +expiryDays,
permission,
}),
}
);
if (authTokenResponse) {
setToken(authTokenResponse.token);
setTokenPermission(permission);
}
} catch (error) {
console.error(error);
@ -113,14 +120,55 @@ export default function DeveloperSettings() {
className="w-full md:w-96 flex flex-col gap-4"
>
<>
<div className="grid gap-3">
<Label>Token Type</Label>
<RadioGroup
value={permission}
onValueChange={(v) => {
if (v != "readonly" && v !== "full") {
throw new Error("Unknown permission type");
}
setPermission(v);
}}
className="mt-4 gap-4"
>
<div className="flex items-start space-x-2">
<RadioGroupItem value="full" id="full" />
<Label
htmlFor="full"
className="flex-1 flex flex-col justify-center items-start cursor-pointer"
>
<div className="font-medium shrink-0">Full Access</div>
<div className="text-sm text-muted-foreground">
Complete control over your hub - can read data and
perform all operations (send payments, manage apps,
etc.)
</div>
</Label>
</div>
<div className="flex items-start space-x-2">
<RadioGroupItem value="readonly" id="readonly" />
<Label
htmlFor="readonly"
className="flex-1 flex flex-col justify-center items-start cursor-pointer"
>
<div className="font-medium">Read-Only Access</div>
<div className="text-sm text-muted-foreground">
View-only access - can read balances, transactions, and
other data but cannot perform operations
</div>
</Label>
</div>
</RadioGroup>
</div>
<div className="grid gap-2">
<Label htmlFor="password">Token Expiry (Days)</Label>
<Label htmlFor="token-expiry">Token Expiry (Days)</Label>
<Input
type="number"
name="token-expiry"
id="token-expiry"
onChange={(e) => setExpiryDays(e.target.value)}
value={expiryDays}
autoFocus
/>
</div>
<div className="grid gap-2">
@ -129,6 +177,7 @@ export default function DeveloperSettings() {
id="password"
onChange={setUnlockPassword}
value={unlockPassword}
autoFocus
/>
</div>
<div className="mt-4">
@ -184,10 +233,20 @@ export default function DeveloperSettings() {
</div>
<p className="text-xs">
This token grants full access to your hub. Please keep it secure.
{tokenPermission === "readonly" ? (
<>
This is a read-only token that can view data but cannot
perform operations like sending payments. Please keep it
secure.
</>
) : (
<>
This token grants full access to your hub. Please keep it
secure.
</>
)}{" "}
If you suspect that the token has been compromised, immediately
change your JWT_SECRET environment variable or contact
support@getalby.com.
change your unlock password.
</p>
</>
)}

View file

@ -28,16 +28,16 @@ func NewAlbyHttpService(svc service.Service, albySvc alby.AlbyService, albyOAuth
}
}
func (albyHttpSvc *AlbyHttpService) RegisterSharedRoutes(restrictedApiGroup *echo.Group, e *echo.Echo) {
func (albyHttpSvc *AlbyHttpService) RegisterSharedRoutes(readOnlyApiGroup *echo.Group, fullAccessApiGroup *echo.Group, e *echo.Echo) {
e.GET("/api/alby/callback", albyHttpSvc.albyCallbackHandler)
e.GET("/api/alby/info", albyHttpSvc.albyInfoHandler)
e.GET("/api/alby/rates", albyHttpSvc.albyBitcoinRateHandler)
restrictedApiGroup.GET("/alby/me", albyHttpSvc.albyMeHandler)
restrictedApiGroup.GET("/alby/balance", albyHttpSvc.albyBalanceHandler)
restrictedApiGroup.POST("/alby/pay", albyHttpSvc.albyPayHandler)
restrictedApiGroup.POST("/alby/link-account", albyHttpSvc.albyLinkAccountHandler)
restrictedApiGroup.POST("/alby/auto-channel", albyHttpSvc.autoChannelHandler)
restrictedApiGroup.POST("/alby/unlink-account", albyHttpSvc.unlinkHandler)
readOnlyApiGroup.GET("/alby/me", albyHttpSvc.albyMeHandler)
readOnlyApiGroup.GET("/alby/balance", albyHttpSvc.albyBalanceHandler)
fullAccessApiGroup.POST("/alby/pay", albyHttpSvc.albyPayHandler)
fullAccessApiGroup.POST("/alby/link-account", albyHttpSvc.albyLinkAccountHandler)
fullAccessApiGroup.POST("/alby/auto-channel", albyHttpSvc.autoChannelHandler)
fullAccessApiGroup.POST("/alby/unlink-account", albyHttpSvc.unlinkHandler)
}
func (albyHttpSvc *AlbyHttpService) autoChannelHandler(c echo.Context) error {

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"time"
@ -35,6 +36,7 @@ type jwtCustomClaims struct {
// we can add extra claims here
// Name string `json:"name"`
// Admin bool `json:"admin"`
Permission string `json:"permission,omitempty"` // "full" or "readonly"
jwt.RegisteredClaims
}
@ -49,7 +51,7 @@ type HttpService struct {
func NewHttpService(svc service.Service, eventPublisher events.EventPublisher) *HttpService {
return &HttpService{
api: api.NewAPI(svc, svc.GetDB(), svc.GetConfig(), svc.GetKeys(), svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), svc.GetEventPublisher()),
api: api.NewAPI(svc, svc.GetDB(), svc.GetConfig(), svc.GetKeys(), svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), eventPublisher),
albyHttpSvc: NewAlbyHttpService(svc, svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), svc.GetConfig().GetEnv()),
cfg: svc.GetConfig(),
eventPublisher: eventPublisher,
@ -116,75 +118,82 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
return []byte(httpSvc.cfg.GetJWTSecret()), nil
},
}
restrictedApiGroup := e.Group("/api")
restrictedApiGroup.Use(echojwt.WithConfig(jwtConfig))
// Read-only API group - accessible to both full and readonly tokens
readOnlyApiGroup := e.Group("/api")
readOnlyApiGroup.Use(echojwt.WithConfig(jwtConfig))
restrictedApiGroup.PATCH("/unlock-password", httpSvc.changeUnlockPasswordHandler)
restrictedApiGroup.PATCH("/auto-unlock", httpSvc.autoUnlockHandler)
restrictedApiGroup.PATCH("/settings", httpSvc.updateSettingsHandler)
restrictedApiGroup.GET("/apps", httpSvc.appsListHandler)
restrictedApiGroup.GET("/apps/:pubkey", httpSvc.appsShowByPubkeyHandler)
restrictedApiGroup.GET("/v2/apps/:id", httpSvc.appsShowHandler)
restrictedApiGroup.PATCH("/apps/:pubkey", httpSvc.appsUpdateHandler)
restrictedApiGroup.DELETE("/apps/:pubkey", httpSvc.appsDeleteHandler)
restrictedApiGroup.POST("/transfers", httpSvc.transfersHandler)
restrictedApiGroup.POST("/apps", httpSvc.appsCreateHandler)
restrictedApiGroup.POST("/lightning-addresses", httpSvc.lightningAddressesCreateHandler)
restrictedApiGroup.DELETE("/lightning-addresses/:appId", httpSvc.lightningAddressesDeleteHandler)
restrictedApiGroup.POST("/mnemonic", httpSvc.mnemonicHandler)
restrictedApiGroup.PATCH("/backup-reminder", httpSvc.backupReminderHandler)
restrictedApiGroup.GET("/channels", httpSvc.channelsListHandler)
restrictedApiGroup.POST("/channels", httpSvc.openChannelHandler)
restrictedApiGroup.POST("/channels/rebalance", httpSvc.rebalanceChannelHandler)
restrictedApiGroup.GET("/channels/suggestions", httpSvc.channelPeerSuggestionsHandler)
restrictedApiGroup.GET("/channel-offer", httpSvc.channelOfferHandler)
restrictedApiGroup.POST("/lsp-orders", httpSvc.newInstantChannelInvoiceHandler)
restrictedApiGroup.GET("/node/connection-info", httpSvc.nodeConnectionInfoHandler)
restrictedApiGroup.GET("/node/status", httpSvc.nodeStatusHandler)
restrictedApiGroup.GET("/node/network-graph", httpSvc.nodeNetworkGraphHandler)
restrictedApiGroup.POST("/node/migrate-storage", httpSvc.migrateNodeStorageHandler)
restrictedApiGroup.GET("/node/transactions", httpSvc.listOnchainTransactionsHandler)
restrictedApiGroup.GET("/peers", httpSvc.listPeers)
restrictedApiGroup.POST("/peers", httpSvc.connectPeerHandler)
restrictedApiGroup.DELETE("/peers/:peerId", httpSvc.disconnectPeerHandler)
restrictedApiGroup.DELETE("/peers/:peerId/channels/:channelId", httpSvc.closeChannelHandler)
restrictedApiGroup.PATCH("/peers/:peerId/channels/:channelId", httpSvc.updateChannelHandler)
restrictedApiGroup.GET("/wallet/address", httpSvc.onchainAddressHandler)
restrictedApiGroup.POST("/wallet/new-address", httpSvc.newOnchainAddressHandler)
restrictedApiGroup.POST("/wallet/redeem-onchain-funds", httpSvc.redeemOnchainFundsHandler)
restrictedApiGroup.POST("/wallet/sign-message", httpSvc.signMessageHandler)
restrictedApiGroup.POST("/wallet/sync", httpSvc.walletSyncHandler)
restrictedApiGroup.GET("/wallet/capabilities", httpSvc.capabilitiesHandler)
restrictedApiGroup.POST("/payments/:invoice", httpSvc.sendPaymentHandler)
restrictedApiGroup.POST("/invoices", httpSvc.makeInvoiceHandler)
restrictedApiGroup.POST("/offers", httpSvc.makeOfferHandler)
restrictedApiGroup.GET("/transactions", httpSvc.listTransactionsHandler)
restrictedApiGroup.GET("/transactions/:paymentHash", httpSvc.lookupTransactionHandler)
restrictedApiGroup.GET("/balances", httpSvc.balancesHandler)
restrictedApiGroup.POST("/reset-router", httpSvc.resetRouterHandler)
restrictedApiGroup.POST("/stop", httpSvc.stopHandler)
restrictedApiGroup.GET("/mempool", httpSvc.mempoolApiHandler)
restrictedApiGroup.POST("/send-payment-probes", httpSvc.sendPaymentProbesHandler)
restrictedApiGroup.POST("/send-spontaneous-payment-probes", httpSvc.sendSpontaneousPaymentProbesHandler)
restrictedApiGroup.GET("/log/:type", httpSvc.getLogOutputHandler)
restrictedApiGroup.GET("/health", httpSvc.healthHandler)
restrictedApiGroup.GET("/commands", httpSvc.getCustomNodeCommandsHandler)
restrictedApiGroup.POST("/command", httpSvc.execCustomNodeCommandHandler)
restrictedApiGroup.GET("/swaps", httpSvc.listSwapsHandler)
restrictedApiGroup.GET("/swaps/:swapId", httpSvc.lookupSwapHandler)
restrictedApiGroup.GET("/swaps/out/info", httpSvc.getSwapOutInfoHandler)
restrictedApiGroup.GET("/swaps/in/info", httpSvc.getSwapInInfoHandler)
restrictedApiGroup.POST("/swaps/out", httpSvc.initiateSwapOutHandler)
restrictedApiGroup.POST("/swaps/in", httpSvc.initiateSwapInHandler)
restrictedApiGroup.POST("/swaps/refund", httpSvc.refundSwapHandler)
restrictedApiGroup.GET("/swaps/mnemonic", httpSvc.swapMnemonicHandler)
restrictedApiGroup.GET("/autoswap", httpSvc.getAutoSwapConfigHandler)
restrictedApiGroup.POST("/autoswap", httpSvc.enableAutoSwapOutHandler)
restrictedApiGroup.DELETE("/autoswap", httpSvc.disableAutoSwapOutHandler)
restrictedApiGroup.POST("/node/alias", httpSvc.setNodeAliasHandler)
restrictedApiGroup.GET("/forwards", httpSvc.forwardsHandler)
readOnlyApiGroup.GET("/apps", httpSvc.appsListHandler)
readOnlyApiGroup.GET("/apps/:pubkey", httpSvc.appsShowByPubkeyHandler)
readOnlyApiGroup.GET("/v2/apps/:id", httpSvc.appsShowHandler)
readOnlyApiGroup.GET("/channels", httpSvc.channelsListHandler)
readOnlyApiGroup.GET("/channels/suggestions", httpSvc.channelPeerSuggestionsHandler)
readOnlyApiGroup.GET("/channel-offer", httpSvc.channelOfferHandler)
readOnlyApiGroup.GET("/node/connection-info", httpSvc.nodeConnectionInfoHandler)
readOnlyApiGroup.GET("/node/status", httpSvc.nodeStatusHandler)
readOnlyApiGroup.GET("/node/network-graph", httpSvc.nodeNetworkGraphHandler)
readOnlyApiGroup.GET("/node/transactions", httpSvc.listOnchainTransactionsHandler)
readOnlyApiGroup.GET("/peers", httpSvc.listPeers)
readOnlyApiGroup.GET("/wallet/address", httpSvc.onchainAddressHandler)
readOnlyApiGroup.GET("/wallet/capabilities", httpSvc.capabilitiesHandler)
readOnlyApiGroup.GET("/transactions", httpSvc.listTransactionsHandler)
readOnlyApiGroup.GET("/transactions/:paymentHash", httpSvc.lookupTransactionHandler)
readOnlyApiGroup.GET("/balances", httpSvc.balancesHandler)
readOnlyApiGroup.GET("/mempool", httpSvc.mempoolApiHandler)
readOnlyApiGroup.GET("/log/:type", httpSvc.getLogOutputHandler)
readOnlyApiGroup.GET("/health", httpSvc.healthHandler)
readOnlyApiGroup.GET("/commands", httpSvc.getCustomNodeCommandsHandler)
readOnlyApiGroup.GET("/swaps", httpSvc.listSwapsHandler)
readOnlyApiGroup.GET("/swaps/:swapId", httpSvc.lookupSwapHandler)
readOnlyApiGroup.GET("/swaps/out/info", httpSvc.getSwapOutInfoHandler)
readOnlyApiGroup.GET("/swaps/in/info", httpSvc.getSwapInInfoHandler)
readOnlyApiGroup.GET("/swaps/mnemonic", httpSvc.swapMnemonicHandler)
readOnlyApiGroup.GET("/autoswap", httpSvc.getAutoSwapConfigHandler)
readOnlyApiGroup.GET("/forwards", httpSvc.forwardsHandler)
httpSvc.albyHttpSvc.RegisterSharedRoutes(restrictedApiGroup, e)
// Full access API group - requires a token with full permissions
fullAccessApiGroup := e.Group("/api")
fullAccessApiGroup.Use(echojwt.WithConfig(jwtConfig))
fullAccessApiGroup.Use(httpSvc.requireFullAccess)
fullAccessApiGroup.PATCH("/unlock-password", httpSvc.changeUnlockPasswordHandler)
fullAccessApiGroup.PATCH("/auto-unlock", httpSvc.autoUnlockHandler)
fullAccessApiGroup.PATCH("/settings", httpSvc.updateSettingsHandler)
fullAccessApiGroup.PATCH("/apps/:pubkey", httpSvc.appsUpdateHandler)
fullAccessApiGroup.DELETE("/apps/:pubkey", httpSvc.appsDeleteHandler)
fullAccessApiGroup.POST("/transfers", httpSvc.transfersHandler)
fullAccessApiGroup.POST("/apps", httpSvc.appsCreateHandler)
fullAccessApiGroup.POST("/lightning-addresses", httpSvc.lightningAddressesCreateHandler)
fullAccessApiGroup.DELETE("/lightning-addresses/:appId", httpSvc.lightningAddressesDeleteHandler)
fullAccessApiGroup.POST("/mnemonic", httpSvc.mnemonicHandler)
fullAccessApiGroup.PATCH("/backup-reminder", httpSvc.backupReminderHandler)
fullAccessApiGroup.POST("/channels", httpSvc.openChannelHandler)
fullAccessApiGroup.POST("/channels/rebalance", httpSvc.rebalanceChannelHandler)
fullAccessApiGroup.POST("/lsp-orders", httpSvc.newInstantChannelInvoiceHandler)
fullAccessApiGroup.POST("/node/migrate-storage", httpSvc.migrateNodeStorageHandler)
fullAccessApiGroup.POST("/peers", httpSvc.connectPeerHandler)
fullAccessApiGroup.DELETE("/peers/:peerId", httpSvc.disconnectPeerHandler)
fullAccessApiGroup.DELETE("/peers/:peerId/channels/:channelId", httpSvc.closeChannelHandler)
fullAccessApiGroup.PATCH("/peers/:peerId/channels/:channelId", httpSvc.updateChannelHandler)
fullAccessApiGroup.POST("/wallet/new-address", httpSvc.newOnchainAddressHandler)
fullAccessApiGroup.POST("/wallet/redeem-onchain-funds", httpSvc.redeemOnchainFundsHandler)
fullAccessApiGroup.POST("/wallet/sign-message", httpSvc.signMessageHandler)
fullAccessApiGroup.POST("/wallet/sync", httpSvc.walletSyncHandler)
fullAccessApiGroup.POST("/payments/:invoice", httpSvc.sendPaymentHandler)
fullAccessApiGroup.POST("/invoices", httpSvc.makeInvoiceHandler)
fullAccessApiGroup.POST("/offers", httpSvc.makeOfferHandler)
fullAccessApiGroup.POST("/reset-router", httpSvc.resetRouterHandler)
fullAccessApiGroup.POST("/stop", httpSvc.stopHandler)
fullAccessApiGroup.POST("/send-payment-probes", httpSvc.sendPaymentProbesHandler)
fullAccessApiGroup.POST("/send-spontaneous-payment-probes", httpSvc.sendSpontaneousPaymentProbesHandler)
fullAccessApiGroup.POST("/command", httpSvc.execCustomNodeCommandHandler)
fullAccessApiGroup.POST("/swaps/out", httpSvc.initiateSwapOutHandler)
fullAccessApiGroup.POST("/swaps/in", httpSvc.initiateSwapInHandler)
fullAccessApiGroup.POST("/swaps/refund", httpSvc.refundSwapHandler)
fullAccessApiGroup.POST("/autoswap", httpSvc.enableAutoSwapOutHandler)
fullAccessApiGroup.DELETE("/autoswap", httpSvc.disableAutoSwapOutHandler)
fullAccessApiGroup.POST("/node/alias", httpSvc.setNodeAliasHandler)
httpSvc.albyHttpSvc.RegisterSharedRoutes(readOnlyApiGroup, fullAccessApiGroup, e)
}
func (httpSvc *HttpService) infoHandler(c echo.Context) error {
@ -277,7 +286,7 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
})
}
token, err := httpSvc.createJWT(nil)
token, err := httpSvc.createJWT(nil, "full")
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
@ -306,7 +315,19 @@ func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
})
}
token, err := httpSvc.createJWT(unlockRequest.TokenExpiryDays)
if unlockRequest.Permission == "" {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "Permission field is required",
})
}
if !slices.Contains([]string{"full", "readonly"}, unlockRequest.Permission) {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "Permission field is unknown",
})
}
token, err := httpSvc.createJWT(unlockRequest.TokenExpiryDays, unlockRequest.Permission)
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
@ -323,6 +344,22 @@ func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
})
}
func (httpSvc *HttpService) requireFullAccess(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := c.Get("user").(*jwt.Token)
claims := token.Claims.(*jwtCustomClaims)
// Allow if no permission specified (backward compatibility) or if full access
if claims.Permission == "" || claims.Permission == "full" {
return next(c)
}
return c.JSON(http.StatusForbidden, ErrorResponse{
Message: "This operation requires full access permissions",
})
}
}
func (httpSvc *HttpService) changeUnlockPasswordHandler(c echo.Context) error {
var changeUnlockPasswordRequest api.ChangeUnlockPasswordRequest
if err := c.Bind(&changeUnlockPasswordRequest); err != nil {
@ -383,7 +420,11 @@ func (httpSvc *HttpService) autoUnlockHandler(c echo.Context) error {
return c.NoContent(http.StatusNoContent)
}
func (httpSvc *HttpService) createJWT(tokenExpiryDays *uint64) (string, error) {
func (httpSvc *HttpService) createJWT(tokenExpiryDays *uint64, permission string) (string, error) {
if !slices.Contains([]string{"full", "readonly"}, permission) {
return "", errors.New("invalid token permission")
}
expiryDays := uint64(30)
if tokenExpiryDays != nil {
expiryDays = *tokenExpiryDays
@ -391,7 +432,8 @@ func (httpSvc *HttpService) createJWT(tokenExpiryDays *uint64) (string, error) {
// Set custom claims
claims := &jwtCustomClaims{
jwt.RegisteredClaims{
Permission: permission,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * time.Duration(expiryDays))),
},
}

383
http/http_service_test.go Normal file
View file

@ -0,0 +1,383 @@
package http
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/getAlby/hub/api"
"github.com/getAlby/hub/config"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/tests/db"
"github.com/getAlby/hub/tests/mocks"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUnlock_IncorrectPassword(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := events.NewEventPublisher()
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockConfig.On("CheckUnlockPassword", "123").Return(false)
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusUnauthorized, rec.Code)
mockConfig.AssertNotCalled(t, "GetJWTSecret")
}
func TestUnlock_UnknownPermission(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := events.NewEventPublisher()
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockConfig.On("CheckUnlockPassword", "123").Return(true)
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "unknown"}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusBadRequest, rec.Code)
mockConfig.AssertNotCalled(t, "GetJWTSecret")
}
func TestGetApps_NoToken(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := mocks.NewMockEventPublisher(t)
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
req := httptest.NewRequest(http.MethodGet, "/api/apps", nil)
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusBadRequest, rec.Code)
}
func TestGetApps_ReadonlyPermission(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := events.NewEventPublisher()
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockConfig.On("CheckUnlockPassword", "123").Return(true)
mockConfig.On("GetJWTSecret").Return("dummy secret")
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "readonly"}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
body, err := io.ReadAll(rec.Body)
require.NoError(t, err)
type authTokenResponse struct {
Token string `json:"token"`
}
var unlockAuthTokenResponse authTokenResponse
err = json.Unmarshal(body, &unlockAuthTokenResponse)
require.NoError(t, err)
assert.NotEmpty(t, unlockAuthTokenResponse.Token)
req2 := httptest.NewRequest(http.MethodGet, "/api/apps", nil)
req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
rec2 := httptest.NewRecorder()
e.ServeHTTP(rec2, req2)
assert.Equal(t, http.StatusOK, rec2.Code)
}
func TestGetApps_FullPermission(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := events.NewEventPublisher()
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockConfig.On("CheckUnlockPassword", "123").Return(true)
mockConfig.On("GetJWTSecret").Return("dummy secret")
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
body, err := io.ReadAll(rec.Body)
require.NoError(t, err)
type authTokenResponse struct {
Token string `json:"token"`
}
var unlockAuthTokenResponse authTokenResponse
err = json.Unmarshal(body, &unlockAuthTokenResponse)
require.NoError(t, err)
assert.NotEmpty(t, unlockAuthTokenResponse.Token)
req2 := httptest.NewRequest(http.MethodGet, "/api/apps", nil)
req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
rec2 := httptest.NewRecorder()
e.ServeHTTP(rec2, req2)
assert.Equal(t, http.StatusOK, rec2.Code)
}
func TestCreateApp_NoToken(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := mocks.NewMockEventPublisher(t)
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mocks.NewMockKeys(t))
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mocks.NewMockAlbyOAuthService(t))
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.CreateAppRequest{Name: "Test app", Scopes: []string{constants.PAY_INVOICE_SCOPE}}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/apps", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusBadRequest, rec.Code)
}
func TestCreateApp_FullPermission(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := events.NewEventPublisher()
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockConfig.On("CheckUnlockPassword", "123").Return(true)
mockConfig.On("GetJWTSecret").Return("dummy secret")
mockConfig.On("GetRelayUrl").Return("")
mockKeys := mocks.NewMockKeys(t)
mockKeys.On("GetAppWalletKey", uint(1)).Return("", nil)
mockAlbyOAuthService := mocks.NewMockAlbyOAuthService(t)
mockAlbyOAuthService.On("GetLightningAddress").Return("", nil)
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mockKeys)
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mockAlbyOAuthService)
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "full"}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
body, err := io.ReadAll(rec.Body)
require.NoError(t, err)
type authTokenResponse struct {
Token string `json:"token"`
}
var unlockAuthTokenResponse authTokenResponse
err = json.Unmarshal(body, &unlockAuthTokenResponse)
require.NoError(t, err)
assert.NotEmpty(t, unlockAuthTokenResponse.Token)
requestBody2 := api.CreateAppRequest{Name: "Test app", Scopes: []string{constants.PAY_INVOICE_SCOPE}}
jsonBody2, _ := json.Marshal(requestBody2)
req2 := httptest.NewRequest(http.MethodPost, "/api/apps", bytes.NewBuffer(jsonBody2))
req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
req2.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec2 := httptest.NewRecorder()
e.ServeHTTP(rec2, req2)
assert.Equal(t, http.StatusOK, rec2.Code)
}
func TestCreateApp_ReadonlyPermission(t *testing.T) {
e := echo.New()
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
mockSvc := mocks.NewMockService(t)
gormDb, err := db.NewDB(t)
require.NoError(t, err)
defer db.CloseDB(gormDb)
mockEventPublisher := events.NewEventPublisher()
mockConfig := mocks.NewMockConfig(t)
mockConfig.On("GetEnv").Return(&config.AppConfig{})
mockConfig.On("CheckUnlockPassword", "123").Return(true)
mockConfig.On("GetJWTSecret").Return("dummy secret")
mockKeys := mocks.NewMockKeys(t)
mockAlbyOAuthService := mocks.NewMockAlbyOAuthService(t)
mockSvc.On("GetDB").Return(gormDb)
mockSvc.On("GetConfig").Return(mockConfig)
mockSvc.On("GetKeys").Return(mockKeys)
mockSvc.On("GetAlbySvc").Return(mocks.NewMockAlbyService(t))
mockSvc.On("GetAlbyOAuthSvc").Return(mockAlbyOAuthService)
httpSvc := NewHttpService(mockSvc, mockEventPublisher)
httpSvc.RegisterSharedRoutes(e)
requestBody := api.UnlockRequest{UnlockPassword: "123", Permission: "readonly"}
jsonBody, _ := json.Marshal(requestBody)
req := httptest.NewRequest(http.MethodPost, "/api/unlock", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec := httptest.NewRecorder()
e.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
body, err := io.ReadAll(rec.Body)
require.NoError(t, err)
type authTokenResponse struct {
Token string `json:"token"`
}
var unlockAuthTokenResponse authTokenResponse
err = json.Unmarshal(body, &unlockAuthTokenResponse)
require.NoError(t, err)
assert.NotEmpty(t, unlockAuthTokenResponse.Token)
requestBody2 := api.CreateAppRequest{Name: "Test app", Scopes: []string{constants.PAY_INVOICE_SCOPE}}
jsonBody2, _ := json.Marshal(requestBody2)
req2 := httptest.NewRequest(http.MethodPost, "/api/apps", bytes.NewBuffer(jsonBody2))
req2.Header.Set("Authorization", "Bearer "+unlockAuthTokenResponse.Token)
req2.Header.Set("Content-Type", "application/json") // Set Content-Type header
rec2 := httptest.NewRecorder()
e.ServeHTTP(rec2, req2)
assert.Equal(t, http.StatusForbidden, rec2.Code)
}

File diff suppressed because it is too large Load diff

207
tests/mocks/AlbyService.go Normal file
View file

@ -0,0 +1,207 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
"context"
"github.com/getAlby/hub/alby"
mock "github.com/stretchr/testify/mock"
)
// NewMockAlbyService creates a new instance of MockAlbyService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockAlbyService(t interface {
mock.TestingT
Cleanup(func())
}) *MockAlbyService {
mock := &MockAlbyService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// MockAlbyService is an autogenerated mock type for the AlbyService type
type MockAlbyService struct {
mock.Mock
}
type MockAlbyService_Expecter struct {
mock *mock.Mock
}
func (_m *MockAlbyService) EXPECT() *MockAlbyService_Expecter {
return &MockAlbyService_Expecter{mock: &_m.Mock}
}
// GetBitcoinRate provides a mock function for the type MockAlbyService
func (_mock *MockAlbyService) GetBitcoinRate(ctx context.Context) (*alby.BitcoinRate, error) {
ret := _mock.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetBitcoinRate")
}
var r0 *alby.BitcoinRate
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context) (*alby.BitcoinRate, error)); ok {
return returnFunc(ctx)
}
if returnFunc, ok := ret.Get(0).(func(context.Context) *alby.BitcoinRate); ok {
r0 = returnFunc(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*alby.BitcoinRate)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = returnFunc(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockAlbyService_GetBitcoinRate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBitcoinRate'
type MockAlbyService_GetBitcoinRate_Call struct {
*mock.Call
}
// GetBitcoinRate is a helper method to define mock.On call
// - ctx
func (_e *MockAlbyService_Expecter) GetBitcoinRate(ctx interface{}) *MockAlbyService_GetBitcoinRate_Call {
return &MockAlbyService_GetBitcoinRate_Call{Call: _e.mock.On("GetBitcoinRate", ctx)}
}
func (_c *MockAlbyService_GetBitcoinRate_Call) Run(run func(ctx context.Context)) *MockAlbyService_GetBitcoinRate_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockAlbyService_GetBitcoinRate_Call) Return(bitcoinRate *alby.BitcoinRate, err error) *MockAlbyService_GetBitcoinRate_Call {
_c.Call.Return(bitcoinRate, err)
return _c
}
func (_c *MockAlbyService_GetBitcoinRate_Call) RunAndReturn(run func(ctx context.Context) (*alby.BitcoinRate, error)) *MockAlbyService_GetBitcoinRate_Call {
_c.Call.Return(run)
return _c
}
// GetChannelPeerSuggestions provides a mock function for the type MockAlbyService
func (_mock *MockAlbyService) GetChannelPeerSuggestions(ctx context.Context) ([]alby.ChannelPeerSuggestion, error) {
ret := _mock.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetChannelPeerSuggestions")
}
var r0 []alby.ChannelPeerSuggestion
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context) ([]alby.ChannelPeerSuggestion, error)); ok {
return returnFunc(ctx)
}
if returnFunc, ok := ret.Get(0).(func(context.Context) []alby.ChannelPeerSuggestion); ok {
r0 = returnFunc(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]alby.ChannelPeerSuggestion)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = returnFunc(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockAlbyService_GetChannelPeerSuggestions_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetChannelPeerSuggestions'
type MockAlbyService_GetChannelPeerSuggestions_Call struct {
*mock.Call
}
// GetChannelPeerSuggestions is a helper method to define mock.On call
// - ctx
func (_e *MockAlbyService_Expecter) GetChannelPeerSuggestions(ctx interface{}) *MockAlbyService_GetChannelPeerSuggestions_Call {
return &MockAlbyService_GetChannelPeerSuggestions_Call{Call: _e.mock.On("GetChannelPeerSuggestions", ctx)}
}
func (_c *MockAlbyService_GetChannelPeerSuggestions_Call) Run(run func(ctx context.Context)) *MockAlbyService_GetChannelPeerSuggestions_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockAlbyService_GetChannelPeerSuggestions_Call) Return(channelPeerSuggestions []alby.ChannelPeerSuggestion, err error) *MockAlbyService_GetChannelPeerSuggestions_Call {
_c.Call.Return(channelPeerSuggestions, err)
return _c
}
func (_c *MockAlbyService_GetChannelPeerSuggestions_Call) RunAndReturn(run func(ctx context.Context) ([]alby.ChannelPeerSuggestion, error)) *MockAlbyService_GetChannelPeerSuggestions_Call {
_c.Call.Return(run)
return _c
}
// GetInfo provides a mock function for the type MockAlbyService
func (_mock *MockAlbyService) GetInfo(ctx context.Context) (*alby.AlbyInfo, error) {
ret := _mock.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetInfo")
}
var r0 *alby.AlbyInfo
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context) (*alby.AlbyInfo, error)); ok {
return returnFunc(ctx)
}
if returnFunc, ok := ret.Get(0).(func(context.Context) *alby.AlbyInfo); ok {
r0 = returnFunc(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*alby.AlbyInfo)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = returnFunc(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockAlbyService_GetInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetInfo'
type MockAlbyService_GetInfo_Call struct {
*mock.Call
}
// GetInfo is a helper method to define mock.On call
// - ctx
func (_e *MockAlbyService_Expecter) GetInfo(ctx interface{}) *MockAlbyService_GetInfo_Call {
return &MockAlbyService_GetInfo_Call{Call: _e.mock.On("GetInfo", ctx)}
}
func (_c *MockAlbyService_GetInfo_Call) Run(run func(ctx context.Context)) *MockAlbyService_GetInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *MockAlbyService_GetInfo_Call) Return(albyInfo *alby.AlbyInfo, err error) *MockAlbyService_GetInfo_Call {
_c.Call.Return(albyInfo, err)
return _c
}
func (_c *MockAlbyService_GetInfo_Call) RunAndReturn(run func(ctx context.Context) (*alby.AlbyInfo, error)) *MockAlbyService_GetInfo_Call {
_c.Call.Return(run)
return _c
}

View file

@ -0,0 +1,208 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
"github.com/getAlby/hub/events"
mock "github.com/stretchr/testify/mock"
)
// NewMockEventPublisher creates a new instance of MockEventPublisher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockEventPublisher(t interface {
mock.TestingT
Cleanup(func())
}) *MockEventPublisher {
mock := &MockEventPublisher{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// MockEventPublisher is an autogenerated mock type for the EventPublisher type
type MockEventPublisher struct {
mock.Mock
}
type MockEventPublisher_Expecter struct {
mock *mock.Mock
}
func (_m *MockEventPublisher) EXPECT() *MockEventPublisher_Expecter {
return &MockEventPublisher_Expecter{mock: &_m.Mock}
}
// Publish provides a mock function for the type MockEventPublisher
func (_mock *MockEventPublisher) Publish(event *events.Event) {
_mock.Called(event)
return
}
// MockEventPublisher_Publish_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Publish'
type MockEventPublisher_Publish_Call struct {
*mock.Call
}
// Publish is a helper method to define mock.On call
// - event
func (_e *MockEventPublisher_Expecter) Publish(event interface{}) *MockEventPublisher_Publish_Call {
return &MockEventPublisher_Publish_Call{Call: _e.mock.On("Publish", event)}
}
func (_c *MockEventPublisher_Publish_Call) Run(run func(event *events.Event)) *MockEventPublisher_Publish_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*events.Event))
})
return _c
}
func (_c *MockEventPublisher_Publish_Call) Return() *MockEventPublisher_Publish_Call {
_c.Call.Return()
return _c
}
func (_c *MockEventPublisher_Publish_Call) RunAndReturn(run func(event *events.Event)) *MockEventPublisher_Publish_Call {
_c.Run(run)
return _c
}
// PublishSync provides a mock function for the type MockEventPublisher
func (_mock *MockEventPublisher) PublishSync(event *events.Event) {
_mock.Called(event)
return
}
// MockEventPublisher_PublishSync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PublishSync'
type MockEventPublisher_PublishSync_Call struct {
*mock.Call
}
// PublishSync is a helper method to define mock.On call
// - event
func (_e *MockEventPublisher_Expecter) PublishSync(event interface{}) *MockEventPublisher_PublishSync_Call {
return &MockEventPublisher_PublishSync_Call{Call: _e.mock.On("PublishSync", event)}
}
func (_c *MockEventPublisher_PublishSync_Call) Run(run func(event *events.Event)) *MockEventPublisher_PublishSync_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(*events.Event))
})
return _c
}
func (_c *MockEventPublisher_PublishSync_Call) Return() *MockEventPublisher_PublishSync_Call {
_c.Call.Return()
return _c
}
func (_c *MockEventPublisher_PublishSync_Call) RunAndReturn(run func(event *events.Event)) *MockEventPublisher_PublishSync_Call {
_c.Run(run)
return _c
}
// RegisterSubscriber provides a mock function for the type MockEventPublisher
func (_mock *MockEventPublisher) RegisterSubscriber(eventListener events.EventSubscriber) {
_mock.Called(eventListener)
return
}
// MockEventPublisher_RegisterSubscriber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterSubscriber'
type MockEventPublisher_RegisterSubscriber_Call struct {
*mock.Call
}
// RegisterSubscriber is a helper method to define mock.On call
// - eventListener
func (_e *MockEventPublisher_Expecter) RegisterSubscriber(eventListener interface{}) *MockEventPublisher_RegisterSubscriber_Call {
return &MockEventPublisher_RegisterSubscriber_Call{Call: _e.mock.On("RegisterSubscriber", eventListener)}
}
func (_c *MockEventPublisher_RegisterSubscriber_Call) Run(run func(eventListener events.EventSubscriber)) *MockEventPublisher_RegisterSubscriber_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(events.EventSubscriber))
})
return _c
}
func (_c *MockEventPublisher_RegisterSubscriber_Call) Return() *MockEventPublisher_RegisterSubscriber_Call {
_c.Call.Return()
return _c
}
func (_c *MockEventPublisher_RegisterSubscriber_Call) RunAndReturn(run func(eventListener events.EventSubscriber)) *MockEventPublisher_RegisterSubscriber_Call {
_c.Run(run)
return _c
}
// RemoveSubscriber provides a mock function for the type MockEventPublisher
func (_mock *MockEventPublisher) RemoveSubscriber(eventListener events.EventSubscriber) {
_mock.Called(eventListener)
return
}
// MockEventPublisher_RemoveSubscriber_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveSubscriber'
type MockEventPublisher_RemoveSubscriber_Call struct {
*mock.Call
}
// RemoveSubscriber is a helper method to define mock.On call
// - eventListener
func (_e *MockEventPublisher_Expecter) RemoveSubscriber(eventListener interface{}) *MockEventPublisher_RemoveSubscriber_Call {
return &MockEventPublisher_RemoveSubscriber_Call{Call: _e.mock.On("RemoveSubscriber", eventListener)}
}
func (_c *MockEventPublisher_RemoveSubscriber_Call) Run(run func(eventListener events.EventSubscriber)) *MockEventPublisher_RemoveSubscriber_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(events.EventSubscriber))
})
return _c
}
func (_c *MockEventPublisher_RemoveSubscriber_Call) Return() *MockEventPublisher_RemoveSubscriber_Call {
_c.Call.Return()
return _c
}
func (_c *MockEventPublisher_RemoveSubscriber_Call) RunAndReturn(run func(eventListener events.EventSubscriber)) *MockEventPublisher_RemoveSubscriber_Call {
_c.Run(run)
return _c
}
// SetGlobalProperty provides a mock function for the type MockEventPublisher
func (_mock *MockEventPublisher) SetGlobalProperty(key string, value interface{}) {
_mock.Called(key, value)
return
}
// MockEventPublisher_SetGlobalProperty_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetGlobalProperty'
type MockEventPublisher_SetGlobalProperty_Call struct {
*mock.Call
}
// SetGlobalProperty is a helper method to define mock.On call
// - key
// - value
func (_e *MockEventPublisher_Expecter) SetGlobalProperty(key interface{}, value interface{}) *MockEventPublisher_SetGlobalProperty_Call {
return &MockEventPublisher_SetGlobalProperty_Call{Call: _e.mock.On("SetGlobalProperty", key, value)}
}
func (_c *MockEventPublisher_SetGlobalProperty_Call) Run(run func(key string, value interface{})) *MockEventPublisher_SetGlobalProperty_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string), args[1].(interface{}))
})
return _c
}
func (_c *MockEventPublisher_SetGlobalProperty_Call) Return() *MockEventPublisher_SetGlobalProperty_Call {
_c.Call.Return()
return _c
}
func (_c *MockEventPublisher_SetGlobalProperty_Call) RunAndReturn(run func(key string, value interface{})) *MockEventPublisher_SetGlobalProperty_Call {
_c.Run(run)
return _c
}

383
tests/mocks/Keys.go Normal file
View file

@ -0,0 +1,383 @@
// Code generated by mockery; DO NOT EDIT.
// github.com/vektra/mockery
// template: testify
package mocks
import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/getAlby/hub/config"
mock "github.com/stretchr/testify/mock"
"github.com/tyler-smith/go-bip32"
)
// NewMockKeys creates a new instance of MockKeys. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockKeys(t interface {
mock.TestingT
Cleanup(func())
}) *MockKeys {
mock := &MockKeys{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
// MockKeys is an autogenerated mock type for the Keys type
type MockKeys struct {
mock.Mock
}
type MockKeys_Expecter struct {
mock *mock.Mock
}
func (_m *MockKeys) EXPECT() *MockKeys_Expecter {
return &MockKeys_Expecter{mock: &_m.Mock}
}
// DeriveKey provides a mock function for the type MockKeys
func (_mock *MockKeys) DeriveKey(path []uint32) (*bip32.Key, error) {
ret := _mock.Called(path)
if len(ret) == 0 {
panic("no return value specified for DeriveKey")
}
var r0 *bip32.Key
var r1 error
if returnFunc, ok := ret.Get(0).(func([]uint32) (*bip32.Key, error)); ok {
return returnFunc(path)
}
if returnFunc, ok := ret.Get(0).(func([]uint32) *bip32.Key); ok {
r0 = returnFunc(path)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bip32.Key)
}
}
if returnFunc, ok := ret.Get(1).(func([]uint32) error); ok {
r1 = returnFunc(path)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockKeys_DeriveKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeriveKey'
type MockKeys_DeriveKey_Call struct {
*mock.Call
}
// DeriveKey is a helper method to define mock.On call
// - path
func (_e *MockKeys_Expecter) DeriveKey(path interface{}) *MockKeys_DeriveKey_Call {
return &MockKeys_DeriveKey_Call{Call: _e.mock.On("DeriveKey", path)}
}
func (_c *MockKeys_DeriveKey_Call) Run(run func(path []uint32)) *MockKeys_DeriveKey_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].([]uint32))
})
return _c
}
func (_c *MockKeys_DeriveKey_Call) Return(key *bip32.Key, err error) *MockKeys_DeriveKey_Call {
_c.Call.Return(key, err)
return _c
}
func (_c *MockKeys_DeriveKey_Call) RunAndReturn(run func(path []uint32) (*bip32.Key, error)) *MockKeys_DeriveKey_Call {
_c.Call.Return(run)
return _c
}
// GetAppWalletKey provides a mock function for the type MockKeys
func (_mock *MockKeys) GetAppWalletKey(childIndex uint) (string, error) {
ret := _mock.Called(childIndex)
if len(ret) == 0 {
panic("no return value specified for GetAppWalletKey")
}
var r0 string
var r1 error
if returnFunc, ok := ret.Get(0).(func(uint) (string, error)); ok {
return returnFunc(childIndex)
}
if returnFunc, ok := ret.Get(0).(func(uint) string); ok {
r0 = returnFunc(childIndex)
} else {
r0 = ret.Get(0).(string)
}
if returnFunc, ok := ret.Get(1).(func(uint) error); ok {
r1 = returnFunc(childIndex)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockKeys_GetAppWalletKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAppWalletKey'
type MockKeys_GetAppWalletKey_Call struct {
*mock.Call
}
// GetAppWalletKey is a helper method to define mock.On call
// - childIndex
func (_e *MockKeys_Expecter) GetAppWalletKey(childIndex interface{}) *MockKeys_GetAppWalletKey_Call {
return &MockKeys_GetAppWalletKey_Call{Call: _e.mock.On("GetAppWalletKey", childIndex)}
}
func (_c *MockKeys_GetAppWalletKey_Call) Run(run func(childIndex uint)) *MockKeys_GetAppWalletKey_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(uint))
})
return _c
}
func (_c *MockKeys_GetAppWalletKey_Call) Return(s string, err error) *MockKeys_GetAppWalletKey_Call {
_c.Call.Return(s, err)
return _c
}
func (_c *MockKeys_GetAppWalletKey_Call) RunAndReturn(run func(childIndex uint) (string, error)) *MockKeys_GetAppWalletKey_Call {
_c.Call.Return(run)
return _c
}
// GetNostrPublicKey provides a mock function for the type MockKeys
func (_mock *MockKeys) GetNostrPublicKey() string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for GetNostrPublicKey")
}
var r0 string
if returnFunc, ok := ret.Get(0).(func() string); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockKeys_GetNostrPublicKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNostrPublicKey'
type MockKeys_GetNostrPublicKey_Call struct {
*mock.Call
}
// GetNostrPublicKey is a helper method to define mock.On call
func (_e *MockKeys_Expecter) GetNostrPublicKey() *MockKeys_GetNostrPublicKey_Call {
return &MockKeys_GetNostrPublicKey_Call{Call: _e.mock.On("GetNostrPublicKey")}
}
func (_c *MockKeys_GetNostrPublicKey_Call) Run(run func()) *MockKeys_GetNostrPublicKey_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockKeys_GetNostrPublicKey_Call) Return(s string) *MockKeys_GetNostrPublicKey_Call {
_c.Call.Return(s)
return _c
}
func (_c *MockKeys_GetNostrPublicKey_Call) RunAndReturn(run func() string) *MockKeys_GetNostrPublicKey_Call {
_c.Call.Return(run)
return _c
}
// GetNostrSecretKey provides a mock function for the type MockKeys
func (_mock *MockKeys) GetNostrSecretKey() string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for GetNostrSecretKey")
}
var r0 string
if returnFunc, ok := ret.Get(0).(func() string); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockKeys_GetNostrSecretKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNostrSecretKey'
type MockKeys_GetNostrSecretKey_Call struct {
*mock.Call
}
// GetNostrSecretKey is a helper method to define mock.On call
func (_e *MockKeys_Expecter) GetNostrSecretKey() *MockKeys_GetNostrSecretKey_Call {
return &MockKeys_GetNostrSecretKey_Call{Call: _e.mock.On("GetNostrSecretKey")}
}
func (_c *MockKeys_GetNostrSecretKey_Call) Run(run func()) *MockKeys_GetNostrSecretKey_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockKeys_GetNostrSecretKey_Call) Return(s string) *MockKeys_GetNostrSecretKey_Call {
_c.Call.Return(s)
return _c
}
func (_c *MockKeys_GetNostrSecretKey_Call) RunAndReturn(run func() string) *MockKeys_GetNostrSecretKey_Call {
_c.Call.Return(run)
return _c
}
// GetSwapKey provides a mock function for the type MockKeys
func (_mock *MockKeys) GetSwapKey(childIndex uint) (*btcec.PrivateKey, error) {
ret := _mock.Called(childIndex)
if len(ret) == 0 {
panic("no return value specified for GetSwapKey")
}
var r0 *btcec.PrivateKey
var r1 error
if returnFunc, ok := ret.Get(0).(func(uint) (*btcec.PrivateKey, error)); ok {
return returnFunc(childIndex)
}
if returnFunc, ok := ret.Get(0).(func(uint) *btcec.PrivateKey); ok {
r0 = returnFunc(childIndex)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*btcec.PrivateKey)
}
}
if returnFunc, ok := ret.Get(1).(func(uint) error); ok {
r1 = returnFunc(childIndex)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockKeys_GetSwapKey_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSwapKey'
type MockKeys_GetSwapKey_Call struct {
*mock.Call
}
// GetSwapKey is a helper method to define mock.On call
// - childIndex
func (_e *MockKeys_Expecter) GetSwapKey(childIndex interface{}) *MockKeys_GetSwapKey_Call {
return &MockKeys_GetSwapKey_Call{Call: _e.mock.On("GetSwapKey", childIndex)}
}
func (_c *MockKeys_GetSwapKey_Call) Run(run func(childIndex uint)) *MockKeys_GetSwapKey_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(uint))
})
return _c
}
func (_c *MockKeys_GetSwapKey_Call) Return(v *btcec.PrivateKey, err error) *MockKeys_GetSwapKey_Call {
_c.Call.Return(v, err)
return _c
}
func (_c *MockKeys_GetSwapKey_Call) RunAndReturn(run func(childIndex uint) (*btcec.PrivateKey, error)) *MockKeys_GetSwapKey_Call {
_c.Call.Return(run)
return _c
}
// GetSwapMnemonic provides a mock function for the type MockKeys
func (_mock *MockKeys) GetSwapMnemonic() string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for GetSwapMnemonic")
}
var r0 string
if returnFunc, ok := ret.Get(0).(func() string); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(string)
}
return r0
}
// MockKeys_GetSwapMnemonic_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSwapMnemonic'
type MockKeys_GetSwapMnemonic_Call struct {
*mock.Call
}
// GetSwapMnemonic is a helper method to define mock.On call
func (_e *MockKeys_Expecter) GetSwapMnemonic() *MockKeys_GetSwapMnemonic_Call {
return &MockKeys_GetSwapMnemonic_Call{Call: _e.mock.On("GetSwapMnemonic")}
}
func (_c *MockKeys_GetSwapMnemonic_Call) Run(run func()) *MockKeys_GetSwapMnemonic_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockKeys_GetSwapMnemonic_Call) Return(s string) *MockKeys_GetSwapMnemonic_Call {
_c.Call.Return(s)
return _c
}
func (_c *MockKeys_GetSwapMnemonic_Call) RunAndReturn(run func() string) *MockKeys_GetSwapMnemonic_Call {
_c.Call.Return(run)
return _c
}
// Init provides a mock function for the type MockKeys
func (_mock *MockKeys) Init(cfg config.Config, encryptionKey string) error {
ret := _mock.Called(cfg, encryptionKey)
if len(ret) == 0 {
panic("no return value specified for Init")
}
var r0 error
if returnFunc, ok := ret.Get(0).(func(config.Config, string) error); ok {
r0 = returnFunc(cfg, encryptionKey)
} else {
r0 = ret.Error(0)
}
return r0
}
// MockKeys_Init_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Init'
type MockKeys_Init_Call struct {
*mock.Call
}
// Init is a helper method to define mock.On call
// - cfg
// - encryptionKey
func (_e *MockKeys_Expecter) Init(cfg interface{}, encryptionKey interface{}) *MockKeys_Init_Call {
return &MockKeys_Init_Call{Call: _e.mock.On("Init", cfg, encryptionKey)}
}
func (_c *MockKeys_Init_Call) Run(run func(cfg config.Config, encryptionKey string)) *MockKeys_Init_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(config.Config), args[1].(string))
})
return _c
}
func (_c *MockKeys_Init_Call) Return(err error) *MockKeys_Init_Call {
_c.Call.Return(err)
return _c
}
func (_c *MockKeys_Init_Call) RunAndReturn(run func(cfg config.Config, encryptionKey string) error) *MockKeys_Init_Call {
_c.Call.Return(run)
return _c
}