refactor: move setupAccount to storage

This commit is contained in:
Fernandez Ludovic 2026-01-24 23:20:25 +01:00
commit e4341c77f6
8 changed files with 139 additions and 98 deletions

View file

@ -64,7 +64,7 @@ func renew(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("get the key type: %w", err)
}
account, err := setupAccount(ctx, keyType, accountsStorage)
account, err := accountsStorage.Get(ctx, keyType)
if err != nil {
return fmt.Errorf("set up account: %w", err)
}

View file

@ -30,7 +30,7 @@ func revoke(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("get the key type: %w", err)
}
account, err := setupAccount(ctx, keyType, accountsStorage)
account, err := accountsStorage.Get(ctx, keyType)
if err != nil {
return fmt.Errorf("set up account: %w", err)
}

View file

@ -49,7 +49,7 @@ func run(ctx context.Context, cmd *cli.Command) error {
return fmt.Errorf("get the key type: %w", err)
}
account, err := setupAccount(ctx, keyType, accountsStorage)
account, err := accountsStorage.Get(ctx, keyType)
if err != nil {
return fmt.Errorf("set up account: %w", err)
}

View file

@ -10,7 +10,8 @@ import (
type Account struct {
Email string `json:"email"`
Registration *registration.Resource `json:"registration"`
key crypto.PrivateKey
key crypto.PrivateKey
}
func NewAccount(email string, key crypto.PrivateKey) *Account {

View file

@ -108,31 +108,10 @@ func NewAccountsStorage(config AccountsStorageConfig) (*AccountsStorage, error)
}, nil
}
func (s *AccountsStorage) ExistsAccountFilePath() bool {
if _, err := os.Stat(s.accountFilePath); os.IsNotExist(err) {
return false
} else if err != nil {
log.Fatal("Could not read the account file.",
slog.String("filepath", s.accountFilePath),
log.ErrorAttr(err),
)
}
return true
}
func (s *AccountsStorage) GetRootPath() string {
return s.rootPath
}
func (s *AccountsStorage) GetUserID() string {
return s.userID
}
func (s *AccountsStorage) GetEmail() string {
return s.email
}
func (s *AccountsStorage) Save(account *Account) error {
jsonBytes, err := json.MarshalIndent(account, "", "\t")
if err != nil {
@ -142,17 +121,30 @@ func (s *AccountsStorage) Save(account *Account) error {
return os.WriteFile(s.accountFilePath, jsonBytes, filePerm)
}
func (s *AccountsStorage) LoadAccount(ctx context.Context, privateKey crypto.PrivateKey) (*Account, error) {
func (s *AccountsStorage) Get(ctx context.Context, keyType certcrypto.KeyType) (*Account, error) {
privateKey, err := s.getPrivateKey(keyType)
if err != nil {
return nil, fmt.Errorf("get private key: %w", err)
}
if s.existsAccountFilePath() {
return s.load(ctx, privateKey)
}
return NewAccount(s.email, privateKey), nil
}
func (s *AccountsStorage) load(ctx context.Context, privateKey crypto.PrivateKey) (*Account, error) {
fileBytes, err := os.ReadFile(s.accountFilePath)
if err != nil {
return nil, fmt.Errorf("could not read the account file (userID: %s): %w", s.GetUserID(), err)
return nil, fmt.Errorf("could not read the account file (userID: %s): %w", s.userID, err)
}
var account Account
err = json.Unmarshal(fileBytes, &account)
if err != nil {
return nil, fmt.Errorf("could not parse the account file (userID: %s): %w", s.GetUserID(), err)
return nil, fmt.Errorf("could not parse the account file (userID: %s): %w", s.userID, err)
}
account.key = privateKey
@ -160,38 +152,38 @@ func (s *AccountsStorage) LoadAccount(ctx context.Context, privateKey crypto.Pri
if account.Registration == nil || account.Registration.Body.Status == "" {
reg, err := s.tryRecoverRegistration(ctx, privateKey)
if err != nil {
return nil, fmt.Errorf("could not load the account file, registration is nil (userID: %s): %w", s.GetUserID(), err)
return nil, fmt.Errorf("could not load the account file, registration is nil (userID: %s): %w", s.userID, err)
}
account.Registration = reg
err = s.Save(&account)
if err != nil {
return nil, fmt.Errorf("could not save the account file, registration is nil (userID: %s): %w", s.GetUserID(), err)
return nil, fmt.Errorf("could not save the account file, registration is nil (userID: %s): %w", s.userID, err)
}
}
return &account, nil
}
func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) (crypto.PrivateKey, error) {
accKeyPath := filepath.Join(s.keysPath, s.GetUserID()+".key")
func (s *AccountsStorage) getPrivateKey(keyType certcrypto.KeyType) (crypto.PrivateKey, error) {
accKeyPath := filepath.Join(s.keysPath, s.userID+".key")
if _, err := os.Stat(accKeyPath); os.IsNotExist(err) {
// TODO(ldez): debug level?
log.Info("No key found for the account. Generating a new private key.",
slog.String("userID", s.GetUserID()),
slog.String("userID", s.userID),
slog.Any("keyType", keyType),
)
err := CreateNonExistingFolder(s.keysPath)
if err != nil {
return nil, fmt.Errorf("could not check/create the directory %q for the account (userID: %s): %w", s.keysPath, s.GetUserID(), err)
return nil, fmt.Errorf("could not check/create the directory %q for the account (userID: %s): %w", s.keysPath, s.userID, err)
}
privateKey, err := generatePrivateKey(accKeyPath, keyType)
if err != nil {
return nil, fmt.Errorf("could not generate the private account key (userID: %s): %w", s.GetUserID(), err)
return nil, fmt.Errorf("could not generate the private account key (userID: %s): %w", s.userID, err)
}
// TODO(ldez): debug level?
@ -208,6 +200,19 @@ func (s *AccountsStorage) GetPrivateKey(keyType certcrypto.KeyType) (crypto.Priv
return privateKey, nil
}
func (s *AccountsStorage) existsAccountFilePath() bool {
if _, err := os.Stat(s.accountFilePath); os.IsNotExist(err) {
return false
} else if err != nil {
log.Fatal("Could not read the account file.",
slog.String("filepath", s.accountFilePath),
log.ErrorAttr(err),
)
}
return true
}
func (s *AccountsStorage) tryRecoverRegistration(ctx context.Context, privateKey crypto.PrivateKey) (*registration.Resource, error) {
// couldn't load account but got a key. Try to look the account up.
config := lego.NewConfig(&Account{key: privateKey})

View file

@ -15,7 +15,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestAccountsStorage_GetUserID(t *testing.T) {
func TestNewAccountsStorage_userID(t *testing.T) {
testCases := []struct {
desc string
email string
@ -42,51 +42,8 @@ func TestAccountsStorage_GetUserID(t *testing.T) {
})
require.NoError(t, err)
assert.Equal(t, test.email, storage.GetEmail())
assert.Equal(t, test.expected, storage.GetUserID())
})
}
}
func TestAccountsStorage_ExistsAccountFilePath(t *testing.T) {
testCases := []struct {
desc string
setup func(t *testing.T, storage *AccountsStorage)
assert assert.BoolAssertionFunc
}{
{
desc: "an account file exists",
setup: func(t *testing.T, storage *AccountsStorage) {
t.Helper()
err := os.MkdirAll(filepath.Dir(storage.accountFilePath), 0o755)
require.NoError(t, err)
err = os.WriteFile(storage.accountFilePath, []byte("test"), 0o644)
require.NoError(t, err)
},
assert: assert.True,
},
{
desc: "no account file",
assert: assert.False,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
storage, err := NewAccountsStorage(AccountsStorageConfig{
BasePath: t.TempDir(),
})
require.NoError(t, err)
if test.setup != nil {
test.setup(t, storage)
}
test.assert(t, storage.ExistsAccountFilePath())
assert.Equal(t, test.email, storage.userID)
assert.Equal(t, test.expected, storage.userID)
})
}
}
@ -155,7 +112,46 @@ func TestAccountsStorage_Save(t *testing.T) {
assert.JSONEq(t, string(expected), string(file))
}
func TestAccountsStorage_LoadAccount(t *testing.T) {
func TestAccountsStorage_Get_newAccount(t *testing.T) {
storage, err := NewAccountsStorage(AccountsStorageConfig{
Email: "test@example.com",
BasePath: t.TempDir(),
})
require.NoError(t, err)
account, err := storage.Get(t.Context(), certcrypto.RSA4096)
require.NoError(t, err)
assert.Equal(t, "test@example.com", account.GetEmail())
assert.Nil(t, account.GetRegistration())
assert.NotNil(t, account.GetPrivateKey())
}
func TestAccountsStorage_Get_existingAccount(t *testing.T) {
storage, err := NewAccountsStorage(AccountsStorageConfig{
Email: "test@example.com",
BasePath: "testdata",
})
require.NoError(t, err)
account, err := storage.Get(t.Context(), certcrypto.RSA4096)
require.NoError(t, err)
assert.Equal(t, "test@example.com", account.GetEmail())
expectedRegistration := &registration.Resource{
Body: acme.Account{
Status: "valid",
},
URI: "https://example.org/acme/acct/123456",
}
assert.Equal(t, expectedRegistration, account.GetRegistration())
assert.NotNil(t, account.GetPrivateKey())
}
func TestAccountsStorage_load(t *testing.T) {
storage, err := NewAccountsStorage(AccountsStorageConfig{
Email: "test@example.com",
BasePath: t.TempDir(),
@ -164,7 +160,7 @@ func TestAccountsStorage_LoadAccount(t *testing.T) {
storage.accountFilePath = filepath.Join("testdata", accountFileName)
account, err := storage.LoadAccount(t.Context(), "")
account, err := storage.load(t.Context(), "")
require.NoError(t, err)
expected := &Account{
@ -186,7 +182,7 @@ func TestAccountsStorage_LoadAccount(t *testing.T) {
assert.Equal(t, expected, account)
}
func TestAccountsStorage_GetPrivateKey(t *testing.T) {
func TestAccountsStorage_getPrivateKey(t *testing.T) {
testCases := []struct {
desc string
basePath string
@ -214,7 +210,7 @@ func TestAccountsStorage_GetPrivateKey(t *testing.T) {
expectedPath := filepath.Join(test.basePath, baseAccountsRootFolderName, "test@example.com", baseKeysFolderName, "test@example.com.key")
privateKey, err := storage.GetPrivateKey(certcrypto.RSA4096)
privateKey, err := storage.getPrivateKey(certcrypto.RSA4096)
require.NoError(t, err)
assert.FileExists(t, expectedPath)
@ -223,3 +219,46 @@ func TestAccountsStorage_GetPrivateKey(t *testing.T) {
})
}
}
func TestAccountsStorage_existsAccountFilePath(t *testing.T) {
testCases := []struct {
desc string
setup func(t *testing.T, storage *AccountsStorage)
assert assert.BoolAssertionFunc
}{
{
desc: "an account file exists",
setup: func(t *testing.T, storage *AccountsStorage) {
t.Helper()
err := os.MkdirAll(filepath.Dir(storage.accountFilePath), 0o755)
require.NoError(t, err)
err = os.WriteFile(storage.accountFilePath, []byte("test"), 0o644)
require.NoError(t, err)
},
assert: assert.True,
},
{
desc: "no account file",
assert: assert.False,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
storage, err := NewAccountsStorage(AccountsStorageConfig{
BasePath: t.TempDir(),
})
require.NoError(t, err)
if test.setup != nil {
test.setup(t, storage)
}
test.assert(t, storage.existsAccountFilePath())
})
}
}

View file

@ -0,0 +1,9 @@
{
"email": "test@example.com",
"registration": {
"body": {
"status": "valid"
},
"uri": "https://example.org/acme/acct/123456"
}
}

View file

@ -35,19 +35,6 @@ func setupClient(cmd *cli.Command, account *storage.Account, keyType certcrypto.
return client, nil
}
func setupAccount(ctx context.Context, keyType certcrypto.KeyType, accountsStorage *storage.AccountsStorage) (*storage.Account, error) {
privateKey, err := accountsStorage.GetPrivateKey(keyType)
if err != nil {
return nil, fmt.Errorf("get private key: %w", err)
}
if accountsStorage.ExistsAccountFilePath() {
return accountsStorage.LoadAccount(ctx, privateKey)
}
return storage.NewAccount(accountsStorage.GetEmail(), privateKey), nil
}
func newClient(cmd *cli.Command, acc registration.User, keyType certcrypto.KeyType) (*lego.Client, error) {
client, err := lego.NewClient(newClientConfig(cmd, acc, keyType))
if err != nil {