mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
* fix: remove legacy acceptance of empty unlock password check CheckUnlockPassword previously treated a missing or empty UnlockPasswordCheck value as a match — a legacy compatibility path from before the canary was always written. It now requires the stored value to be present and to equal the expected string. StartApp checks for the canary up front and, if it is missing, stops with a message asking the user to restore from a backup rather than continuing. A new IsUnlockPasswordCheckSet helper reports whether the value is present. keys.Init now returns the error from reading NostrSecretKey instead of ignoring it, so a read failure aborts instead of generating and saving a new key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: add operation context to unlock password check errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
364 lines
10 KiB
Go
364 lines
10 KiB
Go
package test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/getAlby/hub/config"
|
|
"github.com/getAlby/hub/tests"
|
|
)
|
|
|
|
func TestCheckUnlockPasswordCache_InvalidSecond(t *testing.T) {
|
|
unlockPassword := "123"
|
|
svc, err := tests.CreateTestServiceWithMnemonic(t, "", unlockPassword)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
err = svc.Cfg.SaveUnlockPasswordCheck(unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("UnlockPasswordCheck", unlockPassword)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "THIS STRING SHOULD MATCH IF PASSWORD IS CORRECT", value)
|
|
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword(unlockPassword))
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(unlockPassword+"1"))
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(""))
|
|
}
|
|
func TestCheckUnlockPasswordCache_InvalidFirst(t *testing.T) {
|
|
unlockPassword := "123"
|
|
svc, err := tests.CreateTestServiceWithMnemonic(t, "", unlockPassword)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
err = svc.Cfg.SaveUnlockPasswordCheck(unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("UnlockPasswordCheck", unlockPassword)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "THIS STRING SHOULD MATCH IF PASSWORD IS CORRECT", value)
|
|
|
|
value, err = svc.Cfg.Get("UnlockPasswordCheck", unlockPassword+"1")
|
|
require.Error(t, err)
|
|
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(""))
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(unlockPassword+"1"))
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword(unlockPassword))
|
|
}
|
|
|
|
func TestCheckUnlockPassword_ChangePassword(t *testing.T) {
|
|
unlockPassword := "123"
|
|
svc, err := tests.CreateTestServiceWithMnemonic(t, "", unlockPassword)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
err = svc.Cfg.SaveUnlockPasswordCheck(unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("UnlockPasswordCheck", unlockPassword)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "THIS STRING SHOULD MATCH IF PASSWORD IS CORRECT", value)
|
|
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword(unlockPassword))
|
|
|
|
newUnlockPassword := "1234"
|
|
|
|
err = svc.Cfg.ChangeUnlockPassword(unlockPassword, newUnlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(unlockPassword))
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword(newUnlockPassword))
|
|
// test caching
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(unlockPassword))
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword(newUnlockPassword))
|
|
}
|
|
|
|
func TestCheckUnlockPassword_MissingCanaryFailsClosed(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
// A fresh hub has not saved the unlock-password canary yet.
|
|
set, err := svc.Cfg.IsUnlockPasswordCheckSet()
|
|
require.NoError(t, err)
|
|
assert.False(t, set)
|
|
|
|
// Without the canary, no password may validate - including an empty one.
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(""))
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword("any-password"))
|
|
|
|
// After the canary is saved, only the correct password validates.
|
|
err = svc.Cfg.SaveUnlockPasswordCheck("correct")
|
|
require.NoError(t, err)
|
|
|
|
set, err = svc.Cfg.IsUnlockPasswordCheckSet()
|
|
require.NoError(t, err)
|
|
assert.True(t, set)
|
|
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword("correct"))
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword("wrong"))
|
|
assert.False(t, svc.Cfg.CheckUnlockPassword(""))
|
|
}
|
|
|
|
func TestCheckUnlockPassword_NoPasswordHub(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
// A hub configured without an unlock password stores the canary unencrypted;
|
|
// the empty password must still validate after the fail-closed change.
|
|
err = svc.Cfg.SaveUnlockPasswordCheck("")
|
|
require.NoError(t, err)
|
|
|
|
set, err := svc.Cfg.IsUnlockPasswordCheckSet()
|
|
require.NoError(t, err)
|
|
assert.True(t, set)
|
|
|
|
assert.True(t, svc.Cfg.CheckUnlockPassword(""))
|
|
}
|
|
|
|
func TestSetIgnore_NoEncryptionKey(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
err = svc.Cfg.SetIgnore("key", "value", "")
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("key", "")
|
|
assert.Equal(t, "value", value)
|
|
|
|
err = svc.Cfg.SetIgnore("key", "value2", "")
|
|
require.NoError(t, err)
|
|
|
|
// value should not be updated
|
|
updatedValue, err := svc.Cfg.Get("key", "")
|
|
assert.Equal(t, "value", updatedValue)
|
|
}
|
|
|
|
func TestSetIgnore_EncryptionKey(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
unlockPassword := "123"
|
|
|
|
err = svc.Cfg.SetIgnore("key", "value", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("key", unlockPassword)
|
|
assert.Equal(t, "value", value)
|
|
|
|
invalidValue, err := svc.Cfg.Get("key", unlockPassword+"1")
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "", invalidValue)
|
|
|
|
err = svc.Cfg.SetIgnore("key", "value2", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
// value should not be updated
|
|
updatedValue, err := svc.Cfg.Get("key", unlockPassword)
|
|
assert.Equal(t, "value", updatedValue)
|
|
}
|
|
|
|
func TestSetUpdate_NoEncryptionKey(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value", "")
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("key", "")
|
|
assert.Equal(t, "value", value)
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value2", "")
|
|
require.NoError(t, err)
|
|
|
|
// value should be updated
|
|
updatedValue, err := svc.Cfg.Get("key", "")
|
|
assert.Equal(t, "value2", updatedValue)
|
|
}
|
|
|
|
func TestSetUpdate_EncryptionKey(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
unlockPassword := "123"
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("key", unlockPassword)
|
|
assert.Equal(t, "value", value)
|
|
|
|
invalidValue, err := svc.Cfg.Get("key", unlockPassword+"1")
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "", invalidValue)
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value2", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
// value should be updated
|
|
updatedValue, err := svc.Cfg.Get("key", unlockPassword)
|
|
assert.Equal(t, "value2", updatedValue)
|
|
}
|
|
|
|
func TestSetUpdate_NoEncryptionKeyToEncryptionKey(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value", "")
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("key", "")
|
|
assert.Equal(t, "value", value)
|
|
|
|
unlockPassword := "123"
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value2", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
// value should be updated
|
|
updatedValue, err := svc.Cfg.Get("key", unlockPassword)
|
|
assert.Equal(t, "value2", updatedValue)
|
|
}
|
|
|
|
func TestSetUpdate_EncryptionKeyToNoEncryptionKey(t *testing.T) {
|
|
svc, err := tests.CreateTestService(t)
|
|
require.NoError(t, err)
|
|
defer svc.Remove()
|
|
|
|
unlockPassword := "123"
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value", unlockPassword)
|
|
require.NoError(t, err)
|
|
|
|
value, err := svc.Cfg.Get("key", unlockPassword)
|
|
assert.Equal(t, "value", value)
|
|
|
|
err = svc.Cfg.SetUpdate("key", "value2", "")
|
|
require.NoError(t, err)
|
|
|
|
// value should be updated
|
|
updatedValue, err := svc.Cfg.Get("key", "")
|
|
assert.Equal(t, "value2", updatedValue)
|
|
}
|
|
|
|
func TestJWTSecret_GeneratedOnLoad(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.SaveUnlockPasswordCheck("123")
|
|
require.NoError(t, err)
|
|
|
|
err = cfg.LoadJWTSecret("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)
|
|
|
|
// load again without doing anything, ensure the same JWT secret is returned
|
|
err = cfg.LoadJWTSecret("123")
|
|
require.NoError(t, err)
|
|
jwtSecret2, err := cfg.GetJWTSecret()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, jwtSecret, jwtSecret2)
|
|
}
|
|
|
|
func TestJWTSecret_WrongPassword(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.SaveUnlockPasswordCheck("123")
|
|
require.NoError(t, err)
|
|
|
|
err = cfg.LoadJWTSecret("123")
|
|
require.NoError(t, err)
|
|
|
|
err = cfg.LoadJWTSecret("wrong")
|
|
require.ErrorContains(t, err, "incorrect password")
|
|
}
|
|
|
|
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.SaveUnlockPasswordCheck("123")
|
|
require.NoError(t, err)
|
|
|
|
err = cfg.LoadJWTSecret("123")
|
|
require.NoError(t, err)
|
|
|
|
jwtSecret, err := cfg.GetJWTSecret()
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, jwtSecret)
|
|
|
|
err = cfg.ChangeUnlockPassword("123", "1234")
|
|
require.NoError(t, err)
|
|
|
|
newJwtSecret, err := cfg.GetJWTSecret()
|
|
require.ErrorContains(t, err, "unlock")
|
|
|
|
err = cfg.LoadJWTSecret("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_ReplaceUnencryptedSecretOnLoad(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.SaveUnlockPasswordCheck("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.LoadJWTSecret("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)
|
|
}
|