mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
chore: use encrypted jwt secret (#1988)
* chore: use encrypted jwt secret * chore: improve jwt secret unlock test * chore: replace unencrypted jwt secret on unlock * chore: add TODO to remove encrypted JWT secret check after 2027
This commit is contained in:
parent
7824576214
commit
1f1aacfa8b
10 changed files with 248 additions and 60 deletions
|
|
@ -19,7 +19,6 @@ FRONTEND_URL=http://localhost:5173
|
|||
#AUTO_UNLOCK_PASSWORD=123
|
||||
#WORK_DIR=.data
|
||||
#DATABASE_URI=nwc.db
|
||||
#JWT_SECRET=secretsecret
|
||||
#RELAY=wss://relay.getalby.com/v1
|
||||
#RELAY=ws://localhost:7447/v1
|
||||
#PORT=8080
|
||||
|
|
|
|||
|
|
@ -158,7 +158,6 @@ For more information on the Go pprof library, see the [official documentation](h
|
|||
The following configuration options can be set as environment variables or in a .env file
|
||||
|
||||
- `RELAY`: default: "wss://relay.getalby.com/v1" (can support multiple separated by commas)
|
||||
- `JWT_SECRET`: A randomly generated secret string, applied if no JWT secret is already set. (only needed in http mode). If not provided, one will be automatically generated. On password change, a new JWT secret will be generated.
|
||||
- `DATABASE_URI`: A sqlite filename or postgres URL. Default is SQLite DB `nwc.db` without a path, which will be put in the user home directory: $XDG_DATA_HOME/albyhub/nwc.db
|
||||
- `PORT`: The port on which the app should listen on (default: 8080)
|
||||
- `WORK_DIR`: Directory to store NWC data files. Default: $XDG_DATA_HOME/albyhub
|
||||
|
|
|
|||
|
|
@ -1336,7 +1336,7 @@ var startMutex sync.Mutex
|
|||
|
||||
func (api *api) Start(startRequest *StartRequest) {
|
||||
api.startupError = nil
|
||||
err := api.StartInternal(startRequest)
|
||||
err := api.startInternal(startRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to start node")
|
||||
api.startupError = err
|
||||
|
|
@ -1344,7 +1344,7 @@ func (api *api) Start(startRequest *StartRequest) {
|
|||
}
|
||||
}
|
||||
|
||||
func (api *api) StartInternal(startRequest *StartRequest) (err error) {
|
||||
func (api *api) startInternal(startRequest *StartRequest) (err error) {
|
||||
if !startMutex.TryLock() {
|
||||
// do not allow to start twice in case this is somehow called twice
|
||||
return errors.New("app is busy")
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type config struct {
|
|||
db *gorm.DB
|
||||
cache map[string]map[string]string // key -> encryptionKeyHash -> value
|
||||
cacheMutex sync.Mutex
|
||||
jwtSecret string
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -111,26 +112,6 @@ func (cfg *config) init(env *AppConfig) error {
|
|||
}
|
||||
}
|
||||
|
||||
// set the JWT secret from the env, or generate a new one
|
||||
existingSecret, _ := cfg.Get("JWTSecret", "")
|
||||
if existingSecret == "" {
|
||||
jwtSecret := cfg.Env.JWTSecret
|
||||
if jwtSecret == "" {
|
||||
hexSecret, err := randomHex(32)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to generate JWT secret")
|
||||
return err
|
||||
}
|
||||
jwtSecret = hexSecret
|
||||
logger.Logger.Info("Generated new JWT secret")
|
||||
}
|
||||
|
||||
err := cfg.SetIgnore("JWTSecret", jwtSecret, "")
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to save JWT secret")
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -148,13 +129,47 @@ func (cfg *config) SetupCompleted() bool {
|
|||
return nodeLastStartTime != "" || hasLdkDir
|
||||
}
|
||||
|
||||
func (cfg *config) GetJWTSecret() string {
|
||||
secret, err := cfg.Get("JWTSecret", "")
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to retrieve JWTSecret from database")
|
||||
return ""
|
||||
func (cfg *config) GetJWTSecret() (string, error) {
|
||||
if cfg.jwtSecret == "" {
|
||||
return "", errors.New("config not unlocked")
|
||||
}
|
||||
return secret
|
||||
|
||||
return cfg.jwtSecret, nil
|
||||
}
|
||||
|
||||
func (cfg *config) Unlock(encryptionKey string) error {
|
||||
if !cfg.CheckUnlockPassword(encryptionKey) {
|
||||
return errors.New("incorrect password")
|
||||
}
|
||||
|
||||
// TODO: remove encryptedJwtSecret check after 2027-01-01
|
||||
// - all hubs should have updated to use an encrypted JWT secret by then
|
||||
encryptedJwtSecret, err := cfg.Get("JWTSecret", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jwtSecret, err := cfg.Get("JWTSecret", encryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// generate a new one if none exists yet OR if the user has an unencrypted secret
|
||||
if jwtSecret == "" || jwtSecret == encryptedJwtSecret {
|
||||
hexSecret, err := randomHex(32)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to generate JWT secret")
|
||||
return err
|
||||
}
|
||||
jwtSecret = hexSecret
|
||||
logger.Logger.Info("Generated new JWT secret")
|
||||
|
||||
err = cfg.SetUpdate("JWTSecret", jwtSecret, encryptionKey)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to save JWT secret")
|
||||
return err
|
||||
}
|
||||
}
|
||||
cfg.jwtSecret = jwtSecret
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *config) GetRelayUrls() []string {
|
||||
|
|
@ -321,23 +336,14 @@ func (cfg *config) ChangeUnlockPassword(currentUnlockPassword string, newUnlockP
|
|||
logger.Logger.WithField("key", userConfig.Key).Info("re-encrypted key")
|
||||
}
|
||||
|
||||
newSecret, err := randomHex(32)
|
||||
// delete the JWT secret so it will be re-generated on next unlock (to log all sessions out on password change)
|
||||
err = tx.Where(&db.UserConfig{Key: "JWTSecret"}).Delete(&db.UserConfig{}).Error
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to generate new JWT secret during password change transaction")
|
||||
return fmt.Errorf("failed to generate new JWT secret: %w", err)
|
||||
logger.Logger.WithError(err).Error("failed to remove JWT secret during password change transaction")
|
||||
return fmt.Errorf("failed to delete new JWT secret: %w", err)
|
||||
}
|
||||
|
||||
updateClauses := clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "key"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{"value"}),
|
||||
}
|
||||
err = cfg.set("JWTSecret", newSecret, updateClauses, "", tx)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to save new JWT secret during password change transaction")
|
||||
return fmt.Errorf("failed to save new JWT secret: %w", err)
|
||||
}
|
||||
logger.Logger.Info("Successfully regenerated JWT secret as part of password change transaction")
|
||||
|
||||
logger.Logger.Info("Successfully removed JWT secret as part of password change transaction")
|
||||
return nil
|
||||
})
|
||||
|
||||
|
|
@ -345,6 +351,9 @@ func (cfg *config) ChangeUnlockPassword(currentUnlockPassword string, newUnlockP
|
|||
logger.Logger.WithError(err).Error("failed to execute password change transaction")
|
||||
return err
|
||||
}
|
||||
|
||||
// JWT secret will be set on config unlock (required after password change)
|
||||
cfg.jwtSecret = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ type AppConfig struct {
|
|||
Workdir string `envconfig:"WORK_DIR"`
|
||||
Port string `envconfig:"PORT" default:"8080"`
|
||||
DatabaseUri string `envconfig:"DATABASE_URI" default:"nwc.db"`
|
||||
JWTSecret string `envconfig:"JWT_SECRET"`
|
||||
LogLevel string `envconfig:"LOG_LEVEL" default:"4"`
|
||||
LogToFile bool `envconfig:"LOG_TO_FILE" default:"true"`
|
||||
Network string `envconfig:"NETWORK"`
|
||||
|
|
@ -73,10 +72,11 @@ func (c *AppConfig) GetBaseFrontendUrl() string {
|
|||
}
|
||||
|
||||
type Config interface {
|
||||
Unlock(encryptionKey string) error
|
||||
Get(key string, encryptionKey string) (string, error)
|
||||
SetIgnore(key string, value string, encryptionKey string) error
|
||||
SetUpdate(key string, value string, encryptionKey string) error
|
||||
GetJWTSecret() string
|
||||
GetJWTSecret() (string, error)
|
||||
GetRelayUrls() []string
|
||||
GetNetwork() string
|
||||
GetMempoolUrl() string
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/getAlby/hub/config"
|
||||
"github.com/getAlby/hub/tests"
|
||||
)
|
||||
|
||||
|
|
@ -209,3 +210,99 @@ func TestSetUpdate_EncryptionKeyToNoEncryptionKey(t *testing.T) {
|
|||
updatedValue, err := svc.Cfg.Get("key", "")
|
||||
assert.Equal(t, "value2", updatedValue)
|
||||
}
|
||||
|
||||
func TestJWTSecret_GeneratedOnUnlock(t *testing.T) {
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
cfg, err := config.NewConfig(&config.AppConfig{}, svc.DB)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.ChangeUnlockPassword("", "123")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.Unlock("123")
|
||||
require.NoError(t, err)
|
||||
|
||||
jwtSecret, err := cfg.GetJWTSecret()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, jwtSecret)
|
||||
|
||||
encryptedSecret, err := cfg.Get("JWTSecret", "")
|
||||
require.NoError(t, err)
|
||||
decryptedSecret, err := cfg.Get("JWTSecret", "123")
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, encryptedSecret, decryptedSecret)
|
||||
|
||||
// unlock again without doing anything, ensure the same JWT secret is returned
|
||||
err = cfg.Unlock("123")
|
||||
require.NoError(t, err)
|
||||
jwtSecret2, err := cfg.GetJWTSecret()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, jwtSecret, jwtSecret2)
|
||||
}
|
||||
|
||||
func TestJWTSecret_ChangePassword(t *testing.T) {
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
cfg, err := config.NewConfig(&config.AppConfig{}, svc.DB)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.ChangeUnlockPassword("", "123")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.Unlock("123")
|
||||
require.NoError(t, err)
|
||||
|
||||
jwtSecret, err := cfg.GetJWTSecret()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, jwtSecret)
|
||||
|
||||
err = cfg.ChangeUnlockPassword("", "1234")
|
||||
require.NoError(t, err)
|
||||
|
||||
newJwtSecret, err := cfg.GetJWTSecret()
|
||||
require.ErrorContains(t, err, "unlock")
|
||||
|
||||
err = cfg.Unlock("1234")
|
||||
require.NoError(t, err)
|
||||
|
||||
// a new JWT secret must be generated after password change
|
||||
newJwtSecret, err = cfg.GetJWTSecret()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, newJwtSecret)
|
||||
assert.NotEqual(t, newJwtSecret, jwtSecret)
|
||||
}
|
||||
|
||||
func TestJWTSecret_ReplaceUnencryptedSecretOnUnlock(t *testing.T) {
|
||||
svc, err := tests.CreateTestService(t)
|
||||
require.NoError(t, err)
|
||||
defer svc.Remove()
|
||||
|
||||
cfg, err := config.NewConfig(&config.AppConfig{}, svc.DB)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.ChangeUnlockPassword("", "123")
|
||||
require.NoError(t, err)
|
||||
|
||||
// simulate a hub that had an unencrypted JWT secret
|
||||
oldJwtSecret := "dummy secret"
|
||||
err = svc.Cfg.SetUpdate("JWTSecret", oldJwtSecret, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cfg.Unlock("123")
|
||||
require.NoError(t, err)
|
||||
|
||||
jwtSecret, err := cfg.GetJWTSecret()
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, jwtSecret)
|
||||
assert.NotEqual(t, jwtSecret, oldJwtSecret)
|
||||
|
||||
// ensure it is saved to DB
|
||||
jwtSecretFromCfg, err := cfg.Get("JWTSecret", "123")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, jwtSecret, jwtSecretFromCfg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,7 +113,11 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
|
|||
},
|
||||
// use a custom key func as the JWT secret will change if the user changes their unlock password
|
||||
KeyFunc: func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(httpSvc.cfg.GetJWTSecret()), nil
|
||||
secret, err := httpSvc.cfg.GetJWTSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(secret), nil
|
||||
},
|
||||
}
|
||||
// Read-only API group - accessible to both full and readonly tokens
|
||||
|
|
@ -209,7 +213,12 @@ func (httpSvc *HttpService) infoHandler(c echo.Context) error {
|
|||
if parts[0] == "Bearer" {
|
||||
tokenString := parts[1]
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(httpSvc.cfg.GetJWTSecret()), nil
|
||||
secret, err := httpSvc.cfg.GetJWTSecret()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to parse token")
|
||||
|
|
@ -285,6 +294,17 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
// NOTE: the config is also unlocked as part of the start
|
||||
// goroutine below. But since we execute start asynchronously it's hard to
|
||||
// know when the config has been unlocked before being able to create the JWT token
|
||||
err := httpSvc.cfg.Unlock(startRequest.UnlockPassword)
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to unlock config: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
token, err := httpSvc.createJWT(nil, "full")
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -438,7 +458,12 @@ func (httpSvc *HttpService) createJWT(tokenExpiryDays *uint64, permission string
|
|||
return "", errors.New("failed to create token")
|
||||
}
|
||||
|
||||
signed, err := token.SignedString([]byte(httpSvc.cfg.GetJWTSecret()))
|
||||
secret, err := httpSvc.cfg.GetJWTSecret()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
signed, err := token.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func TestGetApps_ReadonlyPermission(t *testing.T) {
|
|||
mockConfig := mocks.NewMockConfig(t)
|
||||
mockConfig.On("GetEnv").Return(&config.AppConfig{})
|
||||
mockConfig.On("CheckUnlockPassword", "123").Return(true)
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret")
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
|
||||
|
||||
mockSvc.On("GetDB").Return(gormDb)
|
||||
mockSvc.On("GetConfig").Return(mockConfig)
|
||||
|
|
@ -185,7 +185,7 @@ func TestGetApps_FullPermission(t *testing.T) {
|
|||
mockConfig := mocks.NewMockConfig(t)
|
||||
mockConfig.On("GetEnv").Return(&config.AppConfig{})
|
||||
mockConfig.On("CheckUnlockPassword", "123").Return(true)
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret")
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
|
||||
|
||||
mockSvc.On("GetDB").Return(gormDb)
|
||||
mockSvc.On("GetConfig").Return(mockConfig)
|
||||
|
|
@ -270,7 +270,7 @@ func TestCreateApp_FullPermission(t *testing.T) {
|
|||
mockConfig := mocks.NewMockConfig(t)
|
||||
mockConfig.On("GetEnv").Return(&config.AppConfig{})
|
||||
mockConfig.On("CheckUnlockPassword", "123").Return(true)
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret")
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
|
||||
mockConfig.On("GetRelayUrls").Return([]string{})
|
||||
|
||||
mockKeys := mocks.NewMockKeys(t)
|
||||
|
|
@ -334,7 +334,7 @@ func TestCreateApp_ReadonlyPermission(t *testing.T) {
|
|||
mockConfig := mocks.NewMockConfig(t)
|
||||
mockConfig.On("GetEnv").Return(&config.AppConfig{})
|
||||
mockConfig.On("CheckUnlockPassword", "123").Return(true)
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret")
|
||||
mockConfig.On("GetJWTSecret").Return("dummy secret", nil)
|
||||
|
||||
mockKeys := mocks.NewMockKeys(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -276,15 +276,20 @@ func (svc *service) StartApp(encryptionKey string) error {
|
|||
return errors.New("invalid password")
|
||||
}
|
||||
|
||||
ctx, cancelFn := context.WithCancel(svc.ctx)
|
||||
err = svc.cfg.Unlock(encryptionKey)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to unlock config")
|
||||
return err
|
||||
}
|
||||
|
||||
err = svc.keys.Init(svc.cfg, encryptionKey)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to init nostr keys")
|
||||
cancelFn()
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancelFn := context.WithCancel(svc.ctx)
|
||||
|
||||
svc.startupState = "Launching Node"
|
||||
err = svc.launchLNBackend(ctx, encryptionKey)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -317,7 +317,7 @@ func (_c *MockConfig_GetEnv_Call) RunAndReturn(run func() *config.AppConfig) *Mo
|
|||
}
|
||||
|
||||
// GetJWTSecret provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) GetJWTSecret() string {
|
||||
func (_mock *MockConfig) GetJWTSecret() (string, error) {
|
||||
ret := _mock.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
|
|
@ -325,12 +325,21 @@ func (_mock *MockConfig) GetJWTSecret() string {
|
|||
}
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func() (string, error)); ok {
|
||||
return returnFunc()
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = returnFunc()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
return r0
|
||||
if returnFunc, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = returnFunc()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockConfig_GetJWTSecret_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetJWTSecret'
|
||||
|
|
@ -350,12 +359,12 @@ func (_c *MockConfig_GetJWTSecret_Call) Run(run func()) *MockConfig_GetJWTSecret
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetJWTSecret_Call) Return(s string) *MockConfig_GetJWTSecret_Call {
|
||||
_c.Call.Return(s)
|
||||
func (_c *MockConfig_GetJWTSecret_Call) Return(s string, err error) *MockConfig_GetJWTSecret_Call {
|
||||
_c.Call.Return(s, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_GetJWTSecret_Call) RunAndReturn(run func() string) *MockConfig_GetJWTSecret_Call {
|
||||
func (_c *MockConfig_GetJWTSecret_Call) RunAndReturn(run func() (string, error)) *MockConfig_GetJWTSecret_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
|
@ -811,3 +820,48 @@ func (_c *MockConfig_SetupCompleted_Call) RunAndReturn(run func() bool) *MockCon
|
|||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Unlock provides a mock function for the type MockConfig
|
||||
func (_mock *MockConfig) Unlock(encryptionKey string) error {
|
||||
ret := _mock.Called(encryptionKey)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Unlock")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if returnFunc, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = returnFunc(encryptionKey)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockConfig_Unlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Unlock'
|
||||
type MockConfig_Unlock_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Unlock is a helper method to define mock.On call
|
||||
// - encryptionKey
|
||||
func (_e *MockConfig_Expecter) Unlock(encryptionKey interface{}) *MockConfig_Unlock_Call {
|
||||
return &MockConfig_Unlock_Call{Call: _e.mock.On("Unlock", encryptionKey)}
|
||||
}
|
||||
|
||||
func (_c *MockConfig_Unlock_Call) Run(run func(encryptionKey string)) *MockConfig_Unlock_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_Unlock_Call) Return(err error) *MockConfig_Unlock_Call {
|
||||
_c.Call.Return(err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConfig_Unlock_Call) RunAndReturn(run func(encryptionKey string) error) *MockConfig_Unlock_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue