Skip to content

Commit

Permalink
feat: cancel auth
Browse files Browse the repository at this point in the history
  • Loading branch information
igor-sirotin committed Feb 2, 2025
1 parent 91a0ad7 commit 4ff3cda
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 20 deletions.
12 changes: 6 additions & 6 deletions storybook/pages/KeychainPage.qml
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ Page {
? nativeKeychainComponent : mockedKeychainComponent
}

Binding {
target: root.Window.window.contentItem
property: "enabled"
value: !loader.item.loading
}

BiometricsPopup {
id: biometricsPopup

Expand Down Expand Up @@ -194,6 +188,12 @@ Page {
loader.item.requestGetCredential(accountInput.text)
}
}
Button {
text: "Cancel"
onClicked: {
loader.item.cancelActiveRequestBinding()
}
}
BusyIndicator {
Layout.preferredHeight: 40
running: loader.item.loading
Expand Down
11 changes: 8 additions & 3 deletions ui/StatusQ/include/StatusQ/keychain.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
#include <QObject>
#include <QFuture>

#ifdef __OBJC__
#include <LocalAuthentication/LAContext.h>
#else
class LAContext;
#endif

class Keychain : public QObject {
Q_OBJECT

Expand Down Expand Up @@ -36,9 +42,7 @@ class Keychain : public QObject {
Q_INVOKABLE void requestSaveCredential(const QString &account, const QString &password);
Q_INVOKABLE void requestDeleteCredential(const QString &account);
Q_INVOKABLE void requestGetCredential(const QString &account);

// TODO:
// cancelActiveRequest();
Q_INVOKABLE void cancelActiveRequest();

Status saveCredential(const QString &account, const QString &password);
Status deleteCredential(const QString &account);
Expand All @@ -60,4 +64,5 @@ class Keychain : public QObject {
void setLoading(bool loading);

QFuture<void> m_future;
LAContext *m_activeAuthContext;
};
5 changes: 3 additions & 2 deletions ui/StatusQ/src/keychain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
#include <QDebug>

Keychain::Keychain(QObject *parent) : QObject(parent)
{
}
{}

#ifndef Q_OS_MACOS
Keychain::~Keychain() = default;
#endif

QString Keychain::service() const
{
Expand Down
51 changes: 42 additions & 9 deletions ui/StatusQ/src/keychain.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <QDebug>
#include <QEventLoop>
#include <QFuture>
#include <QFutureWatcher>
#include <QtConcurrent/QtConcurrent>

#include <Foundation/Foundation.h>
Expand Down Expand Up @@ -39,11 +40,24 @@
}
}

Keychain::~Keychain()
{
QFutureWatcher<void> watcher;
cancelActiveRequest();
watcher.setFuture(m_future);
watcher.waitForFinished();
}

Keychain::Status authenticate(QString &reason, LAContext **context)
{
if (context == nullptr)
return Keychain::StatusGenericError;

if (*context != nullptr) {
qWarning() << "another local authentication request in progress";
return Keychain::StatusGenericError;
}

*context = [[LAContext alloc] init];
NSError *authError = nil;

Expand Down Expand Up @@ -123,10 +137,29 @@
});
}

void Keychain::cancelActiveRequest()
{
if (m_activeAuthContext != nullptr)
[m_activeAuthContext invalidate];
}

// ContextCleaner takes a pointer to LAContext* and nullifies it on destruction
class ContextCleaner
{
public:
explicit ContextCleaner(LAContext **context)
: m_context(context)
{}
~ContextCleaner() { *m_context = nullptr; };

private:
LAContext **m_context;
};

Keychain::Status Keychain::saveCredential(const QString &account, const QString &password)
{
LAContext *context;
const auto authStatus = authenticate(m_reason, &context);
ContextCleaner guard(&m_activeAuthContext);
const auto authStatus = authenticate(m_reason, &m_activeAuthContext);

if (authStatus != StatusSuccess) {
return authStatus;
Expand All @@ -153,7 +186,7 @@
(__bridge id) kSecAttrAccount: account.toNSString(),
(__bridge id) kSecValueData: [password.toNSString() dataUsingEncoding:NSUTF8StringEncoding],
// (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
(__bridge id) kSecUseAuthenticationContext: context,
(__bridge id) kSecUseAuthenticationContext: m_activeAuthContext,
};

SecItemDelete((__bridge CFDictionaryRef) query); // Ensure old item is removed
Expand All @@ -169,8 +202,8 @@

Keychain::Status Keychain::deleteCredential(const QString &account)
{
LAContext *context;
const auto authStatus = authenticate(m_reason, &context);
ContextCleaner guard(&m_activeAuthContext);
const auto authStatus = authenticate(m_reason, &m_activeAuthContext);

if (authStatus != StatusSuccess) {
return authStatus;
Expand All @@ -180,7 +213,7 @@
(__bridge id) kSecClass: (__bridge id) kSecClassGenericPassword,
(__bridge id) kSecAttrService: m_service.toNSString(),
(__bridge id) kSecAttrAccount: account.toNSString(),
(__bridge id) kSecUseAuthenticationContext: context,
(__bridge id) kSecUseAuthenticationContext: m_activeAuthContext,
};
const auto status = SecItemDelete((__bridge CFDictionaryRef) query);
if (status != errSecSuccess) {
Expand All @@ -192,8 +225,8 @@

Keychain::Status Keychain::getCredential(const QString &account, QString *out)
{
LAContext *context;
const auto authStatus = authenticate(m_reason, &context);
ContextCleaner guard(&m_activeAuthContext);
const auto authStatus = authenticate(m_reason, &m_activeAuthContext);

if (authStatus != StatusSuccess) {
return authStatus;
Expand All @@ -205,7 +238,7 @@
(__bridge id) kSecAttrAccount: account.toNSString(),
(__bridge id) kSecReturnData: @YES,
(__bridge id) kSecMatchLimit: (__bridge id) kSecMatchLimitOne,
(__bridge id) kSecUseAuthenticationContext: context,
(__bridge id) kSecUseAuthenticationContext: m_activeAuthContext,
};

CFDataRef data = NULL;
Expand Down

0 comments on commit 4ff3cda

Please sign in to comment.