summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2022-03-06 19:51:17 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2022-03-06 19:51:17 +0100
commit9482ac4e7acd23b1873450be977c50526677b1a3 (patch)
treef0c6a134491b33b3f7e2a7d6e095f381c8edbf17
parentMobile message input (#962) (diff)
downloadnheko-9482ac4e7acd23b1873450be977c50526677b1a3.tar.xz
Allow explicit selection of SSO method
fixes #975
-rw-r--r--CMakeLists.txt4
-rw-r--r--io.github.NhekoReborn.Nheko.yaml6
-rw-r--r--resources/qml/components/FlatButton.qml37
-rw-r--r--resources/qml/pages/LoginPage.qml41
-rw-r--r--src/LoginPage.cpp67
-rw-r--r--src/LoginPage.h27
6 files changed, 129 insertions, 53 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9393432a..d350e71a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -405,7 +405,7 @@ if(USE_BUNDLED_MTXCLIENT)
 	FetchContent_Declare(
 		MatrixClient
 		GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
-		GIT_TAG        25857f17272809ce2359f214d76fa11d46b1fa7e
+		GIT_TAG        e1b75074b501d2d3e0100d1170b3edef8a00799c
 		)
 	set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
 	set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
@@ -692,7 +692,7 @@ if(USE_BUNDLED_COEURL)
 	FetchContent_Declare(
 		coeurl
 		GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git
-		GIT_TAG        v0.1.1
+		GIT_TAG        v0.1.2
 		)
 	FetchContent_MakeAvailable(coeurl)
 	target_link_libraries(nheko PUBLIC coeurl::coeurl)
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index 562be71f..31248a28 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -177,8 +177,8 @@ modules:
       - -Ddefault_library=static
     name: coeurl
     sources:
-      - commit: fa108b25a92b0e037723debc4388a300e737dc2d
-        tag: v0.1.1
+      - commit: 1c530c153687c9072619f00ad77fff9960bdb048
+        tag: v0.1.2
         type: git
         url: https://nheko.im/nheko-reborn/coeurl.git
   - config-opts:
@@ -189,7 +189,7 @@ modules:
     buildsystem: cmake-ninja
     name: mtxclient
     sources:
-      - commit: 25857f17272809ce2359f214d76fa11d46b1fa7e
+      - commit: e1b75074b501d2d3e0100d1170b3edef8a00799c
         #tag: v0.6.2
         type: git
         url: https://github.com/Nheko-Reborn/mtxclient.git
diff --git a/resources/qml/components/FlatButton.qml b/resources/qml/components/FlatButton.qml
index 72184d28..2c9ea061 100644
--- a/resources/qml/components/FlatButton.qml
+++ b/resources/qml/components/FlatButton.qml
@@ -6,6 +6,7 @@
 import QtGraphicalEffects 1.12
 import QtQuick 2.9
 import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.2
 import im.nheko 1.0
 
 // FIXME(Nico): Don't use hardcoded colors.
@@ -16,6 +17,8 @@ Button {
     implicitWidth: Math.ceil(control.contentItem.implicitWidth + control.contentItem.implicitHeight)
     hoverEnabled: true
 
+    property string iconImage: ""
+
     DropShadow {
         anchors.fill: control.background
         horizontalOffset: 3
@@ -27,16 +30,30 @@ Button {
         source: control.background
     }
 
-    contentItem: Text {
-        text: control.text
-        //font: control.font
-        font.capitalization: Font.AllUppercase
-        font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
-        //font.capitalization: Font.AllUppercase
-        color: Nheko.colors.light
-        horizontalAlignment: Text.AlignHCenter
-        verticalAlignment: Text.AlignVCenter
-        elide: Text.ElideRight
+    contentItem: RowLayout {
+        spacing: 0
+        anchors.centerIn: parent
+        Image {
+            Layout.leftMargin: Nheko.paddingMedium
+            Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
+            Layout.preferredHeight: fontMetrics.font.pixelSize * 1.5
+            Layout.preferredWidth:  fontMetrics.font.pixelSize * 1.5
+            visible: !!iconImage
+            source: iconImage
+        }
+
+        Text {
+            Layout.alignment: Qt.AlignHCenter
+            text: control.text
+            //font: control.font
+            font.capitalization: Font.AllUppercase
+            font.pointSize: Math.ceil(fontMetrics.font.pointSize * 1.5)
+            //font.capitalization: Font.AllUppercase
+            color: Nheko.colors.light
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            elide: Text.ElideRight
+        }
     }
 
     background: Rectangle {
diff --git a/resources/qml/pages/LoginPage.qml b/resources/qml/pages/LoginPage.qml
index 4d3a52b3..87234a22 100644
--- a/resources/qml/pages/LoginPage.qml
+++ b/resources/qml/pages/LoginPage.qml
@@ -61,7 +61,7 @@ Item {
                     onEditingFinished: login.mxid = text
 
                     ToolTip.text: qsTr("Your login name. A mxid should start with @ followed by the user id. After the user id you need to include your server name after a :.\nYou can also put your homeserver address there, if your server doesn't support .well-known lookup.\nExample: @user:server.my\nIf Nheko fails to discover your homeserver, it will show you a field to enter the server manually.")
-                    Keys.forwardTo: [pwBtn, ssoBtn]
+                    Keys.forwardTo: [pwBtn, ssoRepeater]
                 }
 
 
@@ -89,7 +89,7 @@ Item {
                 echoMode: TextInput.Password
                 ToolTip.text: qsTr("Your password.")
                 visible: login.passwordSupported
-                Keys.forwardTo: [pwBtn, ssoBtn]
+                Keys.forwardTo: [pwBtn, ssoRepeater]
             }
 
             MatrixTextField {
@@ -98,7 +98,7 @@ Item {
                 label: qsTr("Device name")
                 placeholderText: login.initialDeviceName()
                 ToolTip.text: qsTr("A name for this device, which will be shown to others, when verifying your devices. If none is provided a default is used.")
-                Keys.forwardTo: [pwBtn, ssoBtn]
+                Keys.forwardTo: [pwBtn, ssoRepeater]
             }
 
             MatrixTextField {
@@ -112,7 +112,7 @@ Item {
                 text: login.homeserver
                 onEditingFinished: login.homeserver = text
                 ToolTip.text: qsTr("The address that can be used to contact you homeservers client API.\nExample: https://server.my:8787")
-                Keys.forwardTo: [pwBtn, ssoBtn]
+                Keys.forwardTo: [pwBtn, ssoRepeater]
             }
 
             Item {
@@ -150,21 +150,28 @@ Item {
                 Keys.onReturnPressed: pwBtn.pwLogin()
                 Keys.enabled: pwBtn.enabled && login.passwordSupported
             }
-            FlatButton {
-                id: ssoBtn
-                visible: login.ssoSupported
-                enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
-                Layout.alignment: Qt.AlignHCenter
-                text: qsTr("SSO LOGIN")
-                function ssoLogin() {
-                    login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, passwordLabel.text, deviceNameLabel.text)
+
+            Repeater {
+                id: ssoRepeater
+
+                model: login.identityProviders
+
+                delegate: FlatButton {
+                    id: ssoBtn
+                    visible: login.ssoSupported
+                    enabled: login.homeserverValid && matrixIdLabel.text == login.mxid && login.homeserver == hsLabel.text
+                    Layout.alignment: Qt.AlignHCenter
+                    text: modelData.name
+                    iconImage: modelData.avatarUrl.replace("mxc://", "image://MxcImage/")
+                    function ssoLogin() {
+                        login.onLoginButtonClicked(Login.SSO, matrixIdLabel.text, modelData.id, deviceNameLabel.text)
+                    }
+                    onClicked: ssoBtn.ssoLogin()
+                    Keys.onEnterPressed: ssoBtn.ssoLogin()
+                    Keys.onReturnPressed: ssoBtn.ssoLogin()
+                    Keys.enabled: ssoBtn.enabled && !login.passwordSupported
                 }
-                onClicked: ssoBtn.ssoLogin()
-                Keys.onEnterPressed: ssoBtn.ssoLogin()
-                Keys.onReturnPressed: ssoBtn.ssoLogin()
-                Keys.enabled: ssoBtn.enabled && !login.passwordSupported
             }
-
         }
     }
 
diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index 6bed446e..cdc2262f 100644
--- a/src/LoginPage.cpp
+++ b/src/LoginPage.cpp
@@ -19,6 +19,7 @@
 #include "UserSettingsPage.h"
 
 Q_DECLARE_METATYPE(LoginPage::LoginMethod)
+Q_DECLARE_METATYPE(SSOProvider)
 
 using namespace mtx::identifiers;
 
@@ -28,6 +29,7 @@ LoginPage::LoginPage(QObject *parent)
 {
     [[maybe_unused]] static auto ignored =
       qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
+    [[maybe_unused]] static auto ignored2 = qRegisterMetaType<SSOProvider>();
 
     connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
     connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
@@ -166,22 +168,47 @@ LoginPage::checkHomeserverVersion()
             return;
         }
 
-        http::client()->get_login(
-          [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) {
-              if (err || flows.flows.empty())
-                  emit versionOkCb(true, false);
-
-              bool ssoSupported      = false;
-              bool passwordSupported = false;
-              for (const auto &flow : flows.flows) {
-                  if (flow.type == mtx::user_interactive::auth_types::sso) {
-                      ssoSupported = true;
-                  } else if (flow.type == mtx::user_interactive::auth_types::password) {
-                      passwordSupported = true;
-                  }
-              }
-              emit versionOkCb(passwordSupported, ssoSupported);
-          });
+        http::client()->get_login([this](mtx::responses::LoginFlows flows,
+                                         mtx::http::RequestErr err) {
+            if (err || flows.flows.empty())
+                emit versionOkCb(true, false, {});
+
+            QVariantList idps;
+            bool ssoSupported      = false;
+            bool passwordSupported = false;
+            for (const auto &flow : flows.flows) {
+                if (flow.type == mtx::user_interactive::auth_types::sso) {
+                    ssoSupported = true;
+
+                    for (const auto &idp : flow.identity_providers) {
+                        SSOProvider prov;
+                        if (idp.brand == "apple")
+                            prov.name_ = tr("Sign in with Apple");
+                        else if (idp.brand == "facebook")
+                            prov.name_ = tr("Continue with Facebook");
+                        else if (idp.brand == "google")
+                            prov.name_ = tr("Sign in with Google");
+                        else if (idp.brand == "twitter")
+                            prov.name_ = tr("Sign in with Twitter");
+                        else
+                            prov.name_ = tr("Login using %1").arg(QString::fromStdString(idp.name));
+
+                        prov.avatarUrl_ = QString::fromStdString(idp.icon);
+                        prov.id_        = QString::fromStdString(idp.id);
+                        idps.push_back(QVariant::fromValue(prov));
+                    }
+
+                    if (flow.identity_providers.empty()) {
+                        SSOProvider prov;
+                        prov.name_ = tr("SSO LOGIN");
+                        idps.push_back(QVariant::fromValue(prov));
+                    }
+                } else if (flow.type == mtx::user_interactive::auth_types::password) {
+                    passwordSupported = true;
+                }
+            }
+            emit versionOkCb(passwordSupported, ssoSupported, idps);
+        });
     });
 }
 
@@ -198,10 +225,11 @@ LoginPage::versionError(const QString &error)
 }
 
 void
-LoginPage::versionOk(bool passwordSupported, bool ssoSupported)
+LoginPage::versionOk(bool passwordSupported, bool ssoSupported, QVariantList idps)
 {
     passwordSupported_ = passwordSupported;
     ssoSupported_      = ssoSupported;
+    identityProviders_ = idps;
 
     lookingUpHs_     = false;
     homeserverValid_ = true;
@@ -287,8 +315,9 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod,
             sso->deleteLater();
         });
 
-        QDesktopServices::openUrl(
-          QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
+        // password doubles as the idp id for SSO login
+        QDesktopServices::openUrl(QString::fromStdString(
+          http::client()->login_sso_redirect(sso->url(), password.toStdString())));
     }
 
     loggingIn_ = true;
diff --git a/src/LoginPage.h b/src/LoginPage.h
index 9a1b9653..47896fda 100644
--- a/src/LoginPage.h
+++ b/src/LoginPage.h
@@ -7,6 +7,7 @@
 #pragma once
 
 #include <QObject>
+#include <QVariantList>
 
 namespace mtx {
 namespace responses {
@@ -14,6 +15,23 @@ struct Login;
 }
 }
 
+struct SSOProvider
+{
+    Q_GADGET
+    Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
+    Q_PROPERTY(QString name READ name CONSTANT)
+    Q_PROPERTY(QString id READ id CONSTANT)
+
+public:
+    [[nodiscard]] QString avatarUrl() const { return avatarUrl_; }
+    [[nodiscard]] QString name() const { return name_.toHtmlEscaped(); }
+    [[nodiscard]] QString id() const { return id_; }
+
+    QString avatarUrl_;
+    QString name_;
+    QString id_;
+};
+
 class LoginPage : public QObject
 {
     Q_OBJECT
@@ -30,6 +48,8 @@ class LoginPage : public QObject
     Q_PROPERTY(bool ssoSupported READ ssoSupported NOTIFY versionLookedUp)
     Q_PROPERTY(bool homeserverNeeded READ homeserverNeeded NOTIFY versionLookedUp)
 
+    Q_PROPERTY(QVariantList identityProviders READ identityProviders NOTIFY versionLookedUp)
+
 public:
     enum class LoginMethod
     {
@@ -51,6 +71,7 @@ public:
     bool ssoSupported() const { return ssoSupported_; }
     bool homeserverNeeded() const { return homeserverNeeded_; }
     bool homeserverValid() const { return homeserverValid_; }
+    QVariantList identityProviders() const { return identityProviders_; }
 
     QString homeserver() { return homeserver_; }
     QString mxid() { return mxid_; }
@@ -89,7 +110,7 @@ signals:
 
     //! Used to trigger the corresponding slot outside of the main thread.
     void versionErrorCb(const QString &err);
-    void versionOkCb(bool passwordSupported, bool ssoSupported);
+    void versionOkCb(bool passwordSupported, bool ssoSupported, QVariantList identityProviders);
 
     void loginOk(const mtx::responses::Login &res);
 
@@ -116,7 +137,7 @@ public slots:
     // Callback for errors produced during server probing
     void versionError(const QString &error_message);
     // Callback for successful server probing
-    void versionOk(bool passwordSupported, bool ssoSupported);
+    void versionOk(bool passwordSupported, bool ssoSupported, QVariantList identityProviders);
 
 private:
     void checkHomeserverVersion();
@@ -137,6 +158,8 @@ private:
     QString mxidError_;
     QString error_;
 
+    QVariantList identityProviders_;
+
     bool passwordSupported_ = true;
     bool ssoSupported_      = false;