From 97d4edd9b839b701026c0f3bbaccadd6905dd2c9 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Sun, 16 Nov 2025 16:43:34 -0500 Subject: [PATCH] Take delays into account when Auto-Type TOTP values * Fixes #12682 --- src/autotype/AutoType.cpp | 25 ++++++++++++++++--------- tests/TestAutoType.cpp | 25 +++++++++++++++++++++++++ tests/TestAutoType.h | 1 + 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/autotype/AutoType.cpp b/src/autotype/AutoType.cpp index c42333fba..44815fa6f 100644 --- a/src/autotype/AutoType.cpp +++ b/src/autotype/AutoType.cpp @@ -32,6 +32,7 @@ #include "core/Global.h" #include "core/Resources.h" #include "core/Tools.h" +#include "core/Totp.h" #include "gui/MainWindow.h" #include "gui/MessageBox.h" #include "gui/osutils/OSUtils.h" @@ -311,9 +312,6 @@ void AutoType::executeAutoTypeActions(const Entry* entry, // Restore executor mode m_executor->mode = mode; - int delay = qMax(100, config()->get(Config::AutoTypeStartDelay).toInt()); - Tools::wait(delay); - // Grab the current active window after everything settles if (window == 0) { window = m_plugin->activeWindow(); @@ -345,7 +343,8 @@ void AutoType::executeAutoTypeActions(const Entry* entry, break; } - Tools::wait(delay); + // Retry wait delay + Tools::wait(100); } // Last action failed to complete, cancel the rest of the sequence @@ -546,10 +545,14 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin const int maxTypeDelay = 500; const int maxWaitDelay = 10000; const int maxRepetition = 100; + int currentTypingDelay = qBound(0, config()->get(Config::AutoTypeDelay).toInt(), maxTypeDelay); + int cumulativeDelay = qBound(0, config()->get(Config::AutoTypeStartDelay).toInt(), maxWaitDelay); + // Initial actions include start delay and initial inter-key delay QList> actions; actions << QSharedPointer::create(); - actions << QSharedPointer::create(qMax(0, config()->get(Config::AutoTypeDelay).toInt()), true); + actions << QSharedPointer::create(currentTypingDelay, true); + actions << QSharedPointer::create(cumulativeDelay); // Replace escaped braces with a template for easier regex QString sequence = entrySequence; @@ -565,7 +568,7 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin // Group 1 = modifier key (opt) // Group 2 = full placeholder // Group 3 = inner placeholder (allows nested placeholders) - // Group 4 = repeat (opt) + // Group 4 = repeat / delay time (opt) // Group 5 = character QRegularExpression regex("([+%^#]*)(?:({((?>[^{}]+?|(?2))+?)(?:\\s+(\\d+))?})|(.))"); auto results = regex.globalMatch(sequence); @@ -627,19 +630,23 @@ AutoType::parseSequence(const QString& entrySequence, const Entry* entry, QStrin } actions << QSharedPointer::create(qBound(0, delay, maxTypeDelay), true); } else if (placeholder == "delay") { - // Mid typing delay (wait) + // Mid typing delay (wait), repeat represents the desired delay in milliseconds if (repeat > maxWaitDelay) { error = tr("Very long delay detected, max is %1: %2").arg(maxWaitDelay).arg(fullPlaceholder); return {}; } + cumulativeDelay += repeat; actions << QSharedPointer::create(qBound(0, repeat, maxWaitDelay)); } else if (placeholder == "clearfield") { // Platform-specific field clearing actions << QSharedPointer::create(); } else if (placeholder == "totp") { if (entry->hasValidTotp()) { - // Entry totp (requires special handling) - QString totp = entry->totp(); + // Calculate TOTP at the time of typing including delays + bool isValid = false; + auto time = + Clock::currentSecondsSinceEpoch() + (cumulativeDelay + currentTypingDelay * actions.count()) / 1000; + auto totp = Totp::generateTotp(entry->totpSettings(), &isValid, time); for (const auto& ch : totp) { actions << QSharedPointer::create(ch); } diff --git a/tests/TestAutoType.cpp b/tests/TestAutoType.cpp index 6166c1927..17974e8c2 100644 --- a/tests/TestAutoType.cpp +++ b/tests/TestAutoType.cpp @@ -27,6 +27,7 @@ #include "core/Config.h" #include "core/Group.h" #include "core/Resources.h" +#include "core/Totp.h" #include "crypto/Crypto.h" #include "gui/MessageBox.h" #include "gui/osutils/OSUtils.h" @@ -75,6 +76,9 @@ void TestAutoType::init() association.window = "custom window"; association.sequence = "{username}association{password}"; m_entry1->autoTypeAssociations()->add(association); + // Create a totp with a short time step to test delayed typing + auto totpSettings = Totp::createSettings("NNSWK4DBONZXQYZB", Totp::DEFAULT_DIGITS, 2); + m_entry1->setTotp(totpSettings); m_entry2 = new Entry(); m_entry2->setGroup(m_group); @@ -470,3 +474,24 @@ void TestAutoType::testAutoTypeEmptyWindowAssociation() assoc = m_entry6->autoTypeSequences("Some Other Window"); QVERIFY(assoc.isEmpty()); } + +void TestAutoType::testAutoTypeTotpDelay() +{ + // Get the TOTP time step in milliseconds + auto totpStep = m_entry1->totpSettings()->step * 1000; + auto sequence = QString("{TOTP} {DELAY %1}{TOTP}").arg(QString::number(totpStep * 2)); + + // Test 1: Sequence with a 3 second delay before TOTP + m_autoType->performAutoTypeWithSequence(m_entry1, sequence); + auto typedChars = m_test->actionChars(); + + // The typed TOTP should be different between the first and second one + auto totpParts = m_test->actionChars().split(' '); + QCOMPARE(totpParts.size(), 2); + QCOMPARE(totpParts[0].size(), m_entry1->totpSettings()->digits); + QCOMPARE(totpParts[1].size(), m_entry1->totpSettings()->digits); + QVERIFY2(totpParts[0] != totpParts[1], + QString("Typed TOTP (%1) should differ from current TOTP (%2) due to delay") + .arg(totpParts[0], totpParts[1]) + .toLatin1()); +} diff --git a/tests/TestAutoType.h b/tests/TestAutoType.h index de78a425c..0a8cac141 100644 --- a/tests/TestAutoType.h +++ b/tests/TestAutoType.h @@ -52,6 +52,7 @@ private slots: void testAutoTypeSyntaxChecks(); void testAutoTypeEffectiveSequences(); void testAutoTypeEmptyWindowAssociation(); + void testAutoTypeTotpDelay(); private: AutoTypePlatformInterface* m_platform;