diff --git a/libnymea-app-core/connection/awsclient.cpp b/libnymea-app-core/connection/awsclient.cpp index bb00d631..219bb935 100644 --- a/libnymea-app-core/connection/awsclient.cpp +++ b/libnymea-app-core/connection/awsclient.cpp @@ -30,25 +30,28 @@ AWSClient::AWSClient(QObject *parent) : QObject(parent) m_accessKeyId = settings.value("accessKeyId").toByteArray(); m_secretKey = settings.value("secretKey").toByteArray(); m_sessionToken = settings.value("sessionToken").toByteArray(); - -// if (m_accessTokenExpiry < QDateTime::currentDateTime() && !m_refreshToken.isEmpty()) { - refreshAccessToken(); -// } + m_sessionTokenExpiry = settings.value("sessionTokenExpiry").toDateTime(); } bool AWSClient::isLoggedIn() const { - return !m_username.isEmpty() && !m_accessToken.isEmpty() && !m_idToken.isEmpty(); + return !m_username.isEmpty() && !m_password.isEmpty(); } void AWSClient::login(const QString &username, const QString &password) { m_username = username; + // Ok... Please fogive me for this... AWS APIs are just unbearable... can't be bothered + // any more to walk through another chain of calls in order to have the refreshToken working. + // Will store the password in the config for now and re-login when the accessToken expires. + // See: https://forums.aws.amazon.com/thread.jspa?threadID=287978 + m_password = password; QSettings settings; settings.remove("cloud"); settings.beginGroup("cloud"); settings.setValue("username", username); + settings.setValue("password", password); QUrl url("https://cognito-idp.eu-west-1.amazonaws.com/"); @@ -108,6 +111,8 @@ void AWSClient::login(const QString &username, const QString &password) qDebug() << "AWS login successful" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented)); emit isLoggedInChanged(); + qDebug() << "Getting cognito ID"; + getId(); }); } @@ -207,56 +212,53 @@ void AWSClient::getCredentialsForIdentity(const QString &identityId) m_accessKeyId = credentialsMap.value("AccessKeyId").toByteArray(); m_secretKey = credentialsMap.value("SecretKey").toByteArray(); m_sessionToken = credentialsMap.value("SessionToken").toByteArray(); - m_expirationDate = QDateTime::fromSecsSinceEpoch(credentialsMap.value("Expiration").toLongLong()); + m_sessionTokenExpiry = QDateTime::fromSecsSinceEpoch(credentialsMap.value("Expiration").toLongLong()); QSettings settings; settings.beginGroup("cloud"); settings.setValue("accessKeyId", m_accessKeyId); settings.setValue("secretKey", m_secretKey); settings.setValue("sessionToken", m_sessionToken); + settings.setValue("sessionTokenExpiry", m_sessionTokenExpiry); qDebug() << "AWS Credentials for Identity received."; -// qDebug() << "Raw GetCredentialsForIdentity reply:" << qUtf8Printable(data); -// qDebug() << "GetCredentialsForIdentity reply: \nAccess Key ID:" << m_accessKeyId << "\nSecret Key:" << m_secretKey << "\nsessionkey:" << m_sessionToken << "\nExpiration:" << m_expirationDate; + + while (!m_callQueue.isEmpty()) { + QueuedCall qc = m_callQueue.takeFirst(); + switch (qc.args.count()) { + case 0: + QMetaObject::invokeMethod(this, qc.method.toUtf8().data()); + break; + case 1: + QMetaObject::invokeMethod(this, qc.method.toUtf8().data(), Q_ARG(QString, qc.args.first())); + break; + default: + Q_ASSERT_X(false, "AWSClient", "Call queue handling does not yet support multiple arguments"); + break; + } + } }); } -void AWSClient::connectMQTT() +bool AWSClient::tokenExpired() const { - - QString host = "a2addxakg5juii.iot.eu-west-1.amazonaws.com"; - QString uri = "/mqtt"; - - QNetworkRequest request(QUrl("wss://" + host + uri)); - request.setRawHeader("Host", host.toUtf8()); - - QByteArray dateTime = SigV4Utils::getCurrentDateTime(); -// QByteArray canonicalQueryString = SigV4Utils::getCanonicalQueryString(request, m_accessKeyId, m_secretKey, m_sessionToken, region, service, QByteArray()); - QByteArray canonicalQueryString = SigV4Utils::getCanonicalQueryString(request, m_accessKeyId, m_secretKey, QByteArray(), region, service, QByteArray()); - - QString signedRequestUrl = "wss://" + host + uri + '?' + canonicalQueryString; - - qDebug() << "Connecting MQTT to" << signedRequestUrl; - - QMQTT::Client *mqttClient = new QMQTT::Client(signedRequestUrl, QString(clientId), QWebSocketProtocol::VersionLatest, false); - mqttClient->setClientId(QString(clientId)); - mqttClient->setPort(443); - mqttClient->setVersion(QMQTT::V3_1_1); - - connect(mqttClient, &QMQTT::Client::connected, this, []{ - qDebug() << "MQTT connected"; - }); - connect(mqttClient, &QMQTT::Client::disconnected, this, []{ - qDebug() << "MQTT disconnected"; - }); - connect(mqttClient, &QMQTT::Client::error, this, [](const QMQTT::ClientError error){ - qDebug() << "MQTT error" << error; - }); - mqttClient->connectToHost(); + qDebug() << "access token expirty:" << m_accessTokenExpiry.toString() << "session token expiry" << m_sessionTokenExpiry.toString() << "current:" << QDateTime::currentDateTime().toString(); + qDebug() << "access token expired:" << (m_accessTokenExpiry.addSecs(-10) < QDateTime::currentDateTime()); + return (m_accessTokenExpiry.addSecs(-10) < QDateTime::currentDateTime()) || (m_sessionTokenExpiry.addSecs(-10) < QDateTime::currentDateTime()); } void AWSClient::postToMQTT(const QString &token) { + if (!isLoggedIn()) { + qWarning() << "Cannot post to MQTT. Not logged in to AWS"; + return; + } + if (tokenExpired()) { + qDebug() << "Cannot post to MQTT. Need to refresh the token first"; + refreshAccessToken(); + m_callQueue.append(QueuedCall("postToMQTT", token)); + return; + } QString host = "a2addxakg5juii.iot.eu-west-1.amazonaws.com"; QString topic = "850593e9-f2ab-4e89-913a-16f848d48867/eu-west-1:88c8b0f1-3f26-46cb-81f3-ccc37dcb543a/proxy"; @@ -299,6 +301,16 @@ void AWSClient::postToMQTT(const QString &token) void AWSClient::fetchDevices() { + if (!isLoggedIn()) { + qWarning() << "Not logged in at AWS. Can't fetch paired devices"; + return; + } + if (tokenExpired()) { + qDebug() << "Need to refresh our tokens"; + refreshAccessToken(); + m_callQueue.append(QueuedCall("fetchDevices")); + return; + } qDebug() << "Fetching cloud devices"; QUrl url("https://z6368zhf2m.execute-api.eu-west-1.amazonaws.com/dev/devices"); QNetworkRequest request(url); @@ -343,6 +355,16 @@ QByteArray AWSClient::accessToken() const void AWSClient::refreshAccessToken() { + if (!isLoggedIn()) { + qDebug() << "Cannot refresh tokens. Not logged in to AWS"; + return; + } + login(m_username, m_password); + return; + + + // We should use this to refresh our tokens but it's not working + // https://forums.aws.amazon.com/thread.jspa?threadID=287978 QUrl url("https://cognito-idp.eu-west-1.amazonaws.com/"); QUrlQuery query; @@ -357,13 +379,10 @@ void AWSClient::refreshAccessToken() QVariantMap params; params.insert("AuthFlow", "REFRESH_TOKEN_AUTH"); -// params.insert("AuthFlow", "USER_PASSWORD_AUTH"); params.insert("ClientId", clientId); QVariantMap authParams; authParams.insert("REFRESH_TOKEN", m_refreshToken); -// authParams.insert("USERNAME", m_username); -// authParams.insert("PASSWORD", "H22*xgemmmmm"); params.insert("AuthParameters", authParams); diff --git a/libnymea-app-core/connection/awsclient.h b/libnymea-app-core/connection/awsclient.h index 68d3f5bd..5167e987 100644 --- a/libnymea-app-core/connection/awsclient.h +++ b/libnymea-app-core/connection/awsclient.h @@ -44,10 +44,14 @@ private: void getCredentialsForIdentity(const QString &identityId); void connectMQTT(); + bool tokenExpired() const; + private: QNetworkAccessManager *m_nam = nullptr; QString m_username; + QString m_password; + QByteArray m_accessToken; QDateTime m_accessTokenExpiry; QByteArray m_idToken; @@ -56,7 +60,17 @@ private: QByteArray m_accessKeyId; QByteArray m_secretKey; QByteArray m_sessionToken; - QDateTime m_expirationDate; + QDateTime m_sessionTokenExpiry; + + class QueuedCall { + public: + QueuedCall(const QString &method): method(method) { } + QueuedCall(const QString &method, const QString &arg1): method(method) { args.append(arg1); } + QString method; + QStringList args; + }; + + QList m_callQueue; }; #endif // AWSCLIENT_H diff --git a/libnymea-app-core/connection/bluetoothtransport.cpp b/libnymea-app-core/connection/bluetoothtransport.cpp index 570b5b7a..bbb3abf6 100644 --- a/libnymea-app-core/connection/bluetoothtransport.cpp +++ b/libnymea-app-core/connection/bluetoothtransport.cpp @@ -42,11 +42,11 @@ QStringList BluetoothTransport::supportedSchemes() const return {"rfcom"}; } -void BluetoothTransport::connect(const QUrl &url) +bool BluetoothTransport::connect(const QUrl &url) { if (url.scheme() != "rfcom") { qWarning() << "BluetoothInterface: Cannot connect. Invalid scheme in url" << url.toString(); - return; + return false; } QUrlQuery query(url); @@ -56,6 +56,7 @@ void BluetoothTransport::connect(const QUrl &url) qDebug() << "Connecting to bluetooth server" << name << macAddress.toString(); m_socket->connectToService(macAddress, QBluetoothUuid(QUuid("997936b5-d2cd-4c57-b41b-c6048320cd2b"))); + return true; } void BluetoothTransport::disconnect() diff --git a/libnymea-app-core/connection/bluetoothtransport.h b/libnymea-app-core/connection/bluetoothtransport.h index be12acb2..734b360d 100644 --- a/libnymea-app-core/connection/bluetoothtransport.h +++ b/libnymea-app-core/connection/bluetoothtransport.h @@ -36,7 +36,7 @@ public: QStringList supportedSchemes() const override; - void connect(const QUrl &url) override; + bool connect(const QUrl &url) override; void disconnect() override; ConnectionState connectionState() const override; void sendData(const QByteArray &data) override; diff --git a/libnymea-app-core/connection/cloudtransport.cpp b/libnymea-app-core/connection/cloudtransport.cpp index d5c150f9..295a8422 100644 --- a/libnymea-app-core/connection/cloudtransport.cpp +++ b/libnymea-app-core/connection/cloudtransport.cpp @@ -38,15 +38,21 @@ QStringList CloudTransport::supportedSchemes() const return {"cloud"}; } -void CloudTransport::connect(const QUrl &url) +bool CloudTransport::connect(const QUrl &url) { - qDebug() << "should connect to" << url; + if (!m_awsClient->isLoggedIn()) { + qWarning() << "Not logged in to AWS, cannot connect"; + return false; + } + qDebug() << "Connecting to" << url; + QUrlQuery query(url.query()); QString m_token = query.queryItemValue("token"); m_awsClient->postToMQTT(m_token); m_remoteproxyConnection->connectServer(QHostAddress("127.0.0.1"), 1212); + return true; } void CloudTransport::disconnect() diff --git a/libnymea-app-core/connection/cloudtransport.h b/libnymea-app-core/connection/cloudtransport.h index a9fee5b3..5b19c799 100644 --- a/libnymea-app-core/connection/cloudtransport.h +++ b/libnymea-app-core/connection/cloudtransport.h @@ -18,7 +18,7 @@ public: QStringList supportedSchemes() const override; - void connect(const QUrl &url) override; + bool connect(const QUrl &url) override; void disconnect() override; ConnectionState connectionState() const override; void sendData(const QByteArray &data) override; diff --git a/libnymea-app-core/connection/nymeaconnection.cpp b/libnymea-app-core/connection/nymeaconnection.cpp index 77322f64..3421c526 100644 --- a/libnymea-app-core/connection/nymeaconnection.cpp +++ b/libnymea-app-core/connection/nymeaconnection.cpp @@ -13,22 +13,22 @@ NymeaConnection::NymeaConnection(QObject *parent) : QObject(parent) { } -void NymeaConnection::connect(const QString &url) +bool NymeaConnection::connect(const QString &url) { if (connected()) { qWarning() << "Already connected. Cannot connect multiple times"; - return; + return false; } m_currentUrl = QUrl(url); m_currentTransport = m_transports.value(m_currentUrl.scheme()); if (!m_currentTransport) { qWarning() << "Cannot connect to urls of scheme" << m_currentUrl.scheme() << "Supported schemes are" << m_transports.keys(); - return; + return false; } qDebug() << "Should connect to url" << m_currentUrl; - m_currentTransport->connect(m_currentUrl); + return m_currentTransport->connect(m_currentUrl); } void NymeaConnection::disconnect() diff --git a/libnymea-app-core/connection/nymeaconnection.h b/libnymea-app-core/connection/nymeaconnection.h index 4a18d6f5..13d9c253 100644 --- a/libnymea-app-core/connection/nymeaconnection.h +++ b/libnymea-app-core/connection/nymeaconnection.h @@ -22,7 +22,7 @@ public: void registerTransport(NymeaTransportInterface *transport); - Q_INVOKABLE void connect(const QString &url); + Q_INVOKABLE bool connect(const QString &url); Q_INVOKABLE void disconnect(); Q_INVOKABLE void acceptCertificate(const QString &url, const QByteArray &fingerprint); Q_INVOKABLE bool isTrusted(const QString &url); diff --git a/libnymea-app-core/connection/nymeatransportinterface.h b/libnymea-app-core/connection/nymeatransportinterface.h index e532a975..439ad68f 100644 --- a/libnymea-app-core/connection/nymeatransportinterface.h +++ b/libnymea-app-core/connection/nymeatransportinterface.h @@ -43,7 +43,7 @@ public: virtual QStringList supportedSchemes() const = 0; - virtual void connect(const QUrl &url) = 0; + virtual bool connect(const QUrl &url) = 0; virtual void disconnect() = 0; virtual ConnectionState connectionState() const = 0; virtual void sendData(const QByteArray &data) = 0; diff --git a/libnymea-app-core/connection/tcpsockettransport.cpp b/libnymea-app-core/connection/tcpsockettransport.cpp index 25aaab8f..3c20b03f 100644 --- a/libnymea-app-core/connection/tcpsockettransport.cpp +++ b/libnymea-app-core/connection/tcpsockettransport.cpp @@ -48,17 +48,19 @@ void TcpSocketTransport::onEncrypted() emit connected(); } -void TcpSocketTransport::connect(const QUrl &url) +bool TcpSocketTransport::connect(const QUrl &url) { m_url = url; if (url.scheme() == "nymeas") { qDebug() << "TCP socket connecting to" << url.host() << url.port(); - m_socket.connectToHostEncrypted(url.host(), url.port()); + m_socket.connectToHostEncrypted(url.host(), static_cast(url.port())); + return true; } else if (url.scheme() == "nymea") { - m_socket.connectToHost(url.host(), url.port()); - } else { - qWarning() << "TCP socket: Unsupported scheme"; + m_socket.connectToHost(url.host(), static_cast(url.port())); + return true; } + qWarning() << "TCP socket: Unsupported scheme"; + return false; } NymeaTransportInterface::ConnectionState TcpSocketTransport::connectionState() const diff --git a/libnymea-app-core/connection/tcpsockettransport.h b/libnymea-app-core/connection/tcpsockettransport.h index fabee3c0..6b1c8c02 100644 --- a/libnymea-app-core/connection/tcpsockettransport.h +++ b/libnymea-app-core/connection/tcpsockettransport.h @@ -15,7 +15,7 @@ public: QStringList supportedSchemes() const override; - void connect(const QUrl &url) override; + bool connect(const QUrl &url) override; ConnectionState connectionState() const override; void disconnect() override; void sendData(const QByteArray &data) override; diff --git a/libnymea-app-core/connection/websockettransport.cpp b/libnymea-app-core/connection/websockettransport.cpp index 73499ba4..a640a3f2 100644 --- a/libnymea-app-core/connection/websockettransport.cpp +++ b/libnymea-app-core/connection/websockettransport.cpp @@ -45,9 +45,10 @@ QStringList WebsocketTransport::supportedSchemes() const return {"ws", "wss"}; } -void WebsocketTransport::connect(const QUrl &url) +bool WebsocketTransport::connect(const QUrl &url) { m_socket->open(QUrl(url)); + return true; } NymeaTransportInterface::ConnectionState WebsocketTransport::connectionState() const diff --git a/libnymea-app-core/connection/websockettransport.h b/libnymea-app-core/connection/websockettransport.h index 461b406d..430cd23e 100644 --- a/libnymea-app-core/connection/websockettransport.h +++ b/libnymea-app-core/connection/websockettransport.h @@ -34,7 +34,7 @@ public: QStringList supportedSchemes() const override; - void connect(const QUrl &url) override; + bool connect(const QUrl &url) override; ConnectionState connectionState() const override; void disconnect() override; void sendData(const QByteArray &data) override; diff --git a/nymea-app/ui/connection/ConnectPage.qml b/nymea-app/ui/connection/ConnectPage.qml index 9bbcffbf..b4bbbc5e 100644 --- a/nymea-app/ui/connection/ConnectPage.qml +++ b/nymea-app/ui/connection/ConnectPage.qml @@ -12,14 +12,13 @@ Page { Component.onCompleted: { print("completed connectPage. last connected host:", settings.lastConnectedHost) - if (settings.lastConnectedHost.length > 0) { + if (settings.lastConnectedHost.length > 0 && Engine.connection.connect(settings.lastConnectedHost)) { var page = pageStack.push(Qt.resolvedUrl("ConnectingPage.qml")) page.cancel.connect(function() { Engine.connection.disconnect(); pageStack.pop(root, StackView.Immediate); - pageStack.push(discoveyPage) + pageStack.push(discoveryPage) }) - Engine.connection.connect(settings.lastConnectedHost) } else { pageStack.push(discoveryPage) }