Take delays into account when Auto-Type TOTP values

* Fixes #12682
This commit is contained in:
Jonathan White
2025-11-16 16:43:34 -05:00
parent 72308a1706
commit 97d4edd9b8
3 changed files with 42 additions and 9 deletions

View File

@@ -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<QSharedPointer<AutoTypeAction>> actions;
actions << QSharedPointer<AutoTypeBegin>::create();
actions << QSharedPointer<AutoTypeDelay>::create(qMax(0, config()->get(Config::AutoTypeDelay).toInt()), true);
actions << QSharedPointer<AutoTypeDelay>::create(currentTypingDelay, true);
actions << QSharedPointer<AutoTypeDelay>::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<AutoTypeDelay>::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<AutoTypeDelay>::create(qBound(0, repeat, maxWaitDelay));
} else if (placeholder == "clearfield") {
// Platform-specific field clearing
actions << QSharedPointer<AutoTypeClearField>::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<AutoTypeKey>::create(ch);
}

View File

@@ -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());
}

View File

@@ -52,6 +52,7 @@ private slots:
void testAutoTypeSyntaxChecks();
void testAutoTypeEffectiveSequences();
void testAutoTypeEmptyWindowAssociation();
void testAutoTypeTotpDelay();
private:
AutoTypePlatformInterface* m_platform;