inwx: wait before generating new TOTP TANs (#2084)

Co-authored-by: Fernandez Ludovic <ldez@users.noreply.github.com>
This commit is contained in:
Günther Noack 2024-01-18 21:51:57 +01:00 committed by GitHub
parent c17f659c5d
commit 9d4c60e67a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 70 additions and 2 deletions

View file

@ -53,6 +53,7 @@ func NewDefaultConfig() *Config {
type DNSProvider struct { type DNSProvider struct {
config *Config config *Config
client *goinwx.Client client *goinwx.Client
previousUnlock time.Time
} }
// NewDNSProvider returns a DNSProvider instance configured for Dyn DNS. // NewDNSProvider returns a DNSProvider instance configured for Dyn DNS.
@ -202,10 +203,33 @@ func (d *DNSProvider) twoFactorAuth(info *goinwx.LoginResponse) error {
return errors.New("two-factor authentication but no shared secret is given") return errors.New("two-factor authentication but no shared secret is given")
} }
// INWX forbids re-authentication with a previously used TAN.
// To avoid using the same TAN twice, we wait until the next TOTP period.
sleep := d.computeSleep(time.Now())
if sleep != 0 {
log.Infof("inwx: waiting %s for next TOTP token", sleep)
time.Sleep(sleep)
}
tan, err := totp.GenerateCode(d.config.SharedSecret, time.Now()) tan, err := totp.GenerateCode(d.config.SharedSecret, time.Now())
if err != nil { if err != nil {
return err return err
} }
d.previousUnlock = time.Now()
return d.client.Account.Unlock(tan) return d.client.Account.Unlock(tan)
} }
func (d *DNSProvider) computeSleep(now time.Time) time.Duration {
if d.previousUnlock.IsZero() {
return 0 * time.Second
}
endPeriod := d.previousUnlock.Add(30 * time.Second)
if endPeriod.After(now) {
return endPeriod.Sub(now)
}
return 0 * time.Second
}

View file

@ -2,8 +2,10 @@ package inwx
import ( import (
"testing" "testing"
"time"
"github.com/go-acme/lego/v4/platform/tester" "github.com/go-acme/lego/v4/platform/tester"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -141,3 +143,45 @@ func TestLivePresentAndCleanup(t *testing.T) {
err = provider.CleanUp(envTest.GetDomain(), "", "123d==") err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
require.NoError(t, err) require.NoError(t, err)
} }
func Test_computeSleep(t *testing.T) {
testCases := []struct {
desc string
previous string
expected time.Duration
}{
{
desc: "after 30s",
previous: "2024-01-01T06:29:20Z",
expected: 0 * time.Second,
},
{
desc: "0s",
previous: "2024-01-01T06:29:30Z",
expected: 0 * time.Second,
},
{
desc: "before 30s",
previous: "2024-01-01T06:29:50Z", // 10 s
expected: 20 * time.Second,
},
}
now, err := time.Parse(time.RFC3339, "2024-01-01T06:30:00Z")
require.NoError(t, err)
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
previous, err := time.Parse(time.RFC3339, test.previous)
require.NoError(t, err)
d := &DNSProvider{previousUnlock: previous}
sleep := d.computeSleep(now)
assert.Equal(t, test.expected, sleep)
})
}
}