[gnome-software/gnome-3-20] Backport GsAuth



commit dc6df789483444c081bb356e4bac8ff2161d1dab
Author: Richard Hughes <richard hughsie com>
Date:   Fri Jun 10 14:37:22 2016 +0100

    Backport GsAuth

 contrib/gnome-software.spec.in   |    1 +
 po/POTFILES.in                   |    2 +
 po/ar.po                         |   48 +++
 po/ca.po                         |   48 +++
 po/cs.po                         |   48 +++
 po/de.po                         |   48 +++
 po/en_GB.po                      |   48 +++
 po/eo.po                         |   48 +++
 po/es.po                         |   49 +++
 po/fi.po                         |   48 +++
 po/fr.po                         |   48 +++
 po/fur.po                        |   48 +++
 po/gd.po                         |   69 ++++-
 po/gl.po                         |   48 +++
 po/he.po                         |   48 +++
 po/hu.po                         |   49 +++
 po/lt.po                         |   48 +++
 po/pa.po                         |   48 +++
 po/pl.po                         |   48 +++
 po/pt.po                         |   48 +++
 po/pt_BR.po                      |   48 +++
 po/sk.po                         |   48 +++
 po/sr.po                         |   48 +++
 po/sr latin po                   |   48 +++
 po/sv.po                         |   48 +++
 po/tr.po                         |   48 +++
 po/zh_TW.po                      |   48 +++
 src/Makefile.am                  |   11 +
 src/gnome-software.gresource.xml |    1 +
 src/gs-auth-dialog.c             |  354 +++++++++++++++++++
 src/gs-auth-dialog.h             |   45 +++
 src/gs-auth-dialog.ui            |  359 ++++++++++++++++++++
 src/gs-auth.c                    |  697 ++++++++++++++++++++++++++++++++++++++
 src/gs-auth.h                    |  134 ++++++++
 src/gs-page.c                    |   97 ++++++
 src/gs-plugin-loader.c           |  178 ++++++++++
 src/gs-plugin-loader.h           |   12 +
 src/gs-plugin.c                  |   26 ++
 src/gs-plugin.h                  |   30 ++
 src/gs-self-test.c               |   40 +++
 src/gs-shell-details.c           |  114 ++++++-
 src/gs-utils.c                   |   22 ++
 src/gs-utils.h                   |    1 +
 43 files changed, 3323 insertions(+), 24 deletions(-)
---
diff --git a/contrib/gnome-software.spec.in b/contrib/gnome-software.spec.in
index 331e235..1a6db72 100644
--- a/contrib/gnome-software.spec.in
+++ b/contrib/gnome-software.spec.in
@@ -33,6 +33,7 @@ BuildRequires: libappstream-glib-devel >= 0.5.11
 BuildRequires: fwupd-devel
 BuildRequires: json-glib-devel
 BuildRequires: polkit-devel
+BuildRequires: libsecret-devel
 
 # this is not a library version
 %define gs_plugin_version               9
diff --git a/po/POTFILES.in b/po/POTFILES.in
index dcb1383..643ab04 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -13,6 +13,8 @@ src/gs-application.c
 src/gs-app-row.c
 [type: gettext/glade]src/gs-app-row.ui
 src/gs-app-tile.c
+src/gs-auth-dialog.c
+[type: gettext/glade]src/gs-auth-dialog.ui
 src/gs-category.c
 src/gs-dbus-helper.c
 src/gs-feature-tile.c
diff --git a/po/ar.po b/po/ar.po
index 29bdce2..d5ac8a8 100644
--- a/po/ar.po
+++ b/po/ar.po
@@ -267,6 +267,54 @@ msgstr "أزل"
 msgid "Updates"
 msgstr "تحديثات"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "عليك الولوج للمتابعة."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "عليك الولوج إلى %s للمتابعة."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "البريد الإلكتروني"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "لدي حساب بالفعل"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "كلمة المرور"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "أريد تسجيل حساب الآن"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "نسيتُ كلمة السر"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "لِجني تلقائيا المرة القادمة"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "أدخل رمز الاستيثاق بخطوتين"
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "الرمز"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "تابع"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:207
diff --git a/po/ca.po b/po/ca.po
index 22c4d36..227e7b7 100644
--- a/po/ca.po
+++ b/po/ca.po
@@ -394,6 +394,54 @@ msgstr "aplicació web"
 msgid "nonfree"
 msgstr "no lliure"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Per a continuar heu d'iniciar la sessió."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Per a continuar heu d'iniciar la sessió a %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adreça electrònica"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Ja tinc un compte"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Contrasenya"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Vull registrar un compte ara"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "He oblidat la contrasenya"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Inicieu la sessió automàticament la propera vegada"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Introduïu el PIN d'un ús per l'autenticació amb dos components."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continua"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/cs.po b/po/cs.po
index 485df1b..0bbdc3b 100644
--- a/po/cs.po
+++ b/po/cs.po
@@ -392,6 +392,54 @@ msgstr "webová aplikace"
 msgid "nonfree"
 msgstr "nesvobodný"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Pro pokračování se musíte přihlásit."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Pro pokračování se musíte přihlásit ke službě %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-mailová adresa"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Již mám účet"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Heslo"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Chci se hned k účtu zaregistrovat"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Zapoměl jsem své heslo"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Příště se přihlásit automaticky"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Zadejte svůj jednorázový PIN pro dvoufázové ověření."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Pokračovat"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/de.po b/po/de.po
index 007e8ba..c529837 100644
--- a/po/de.po
+++ b/po/de.po
@@ -413,6 +413,54 @@ msgstr "Web-App"
 msgid "nonfree"
 msgstr "Unfrei"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Sie müssen sich anmelden, um fortsetzen zu können."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Sie müssen sich bei %s anmelden, um fortsetzen zu können."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-Mail-Adresse"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Ich habe bereits ein Benutzerkonto"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Passwort"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Ich möchte mich für ein neues Benutzerkonto registrieren"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Ich habe mein Passwort vergessen"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Das nächste Mal automatisch anmelden"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Geben Sie Ihre einmalige PIN für die Zwei-Stufen-Legitimierung ein."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Fortfahren"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/en_GB.po b/po/en_GB.po
index f367387..12b4066 100644
--- a/po/en_GB.po
+++ b/po/en_GB.po
@@ -168,6 +168,54 @@ msgstr "Installing"
 msgid "Removing"
 msgstr "Removing"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "To continue you need to sign in."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "To continue you need to sign in to %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-mail address"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "I have an account already"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Password"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "I want to register for an account now"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "I have forgotten my password"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Sign in automatically next time"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Enter your one-time pin for two-factor authentication."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continue"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:121
diff --git a/po/eo.po b/po/eo.po
index a7dff7b..7aca50e 100644
--- a/po/eo.po
+++ b/po/eo.po
@@ -282,6 +282,54 @@ msgstr "Forigi"
 msgid "Updates"
 msgstr "Ĝisdatigoj"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Ensalutu por daŭrigi."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Ensalutu en %s por daŭrigi."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Retadreso"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Mi jam havas konton"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Pasvorto"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Mi forgesis mian pasvorton"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Daŭrigi"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:210
diff --git a/po/es.po b/po/es.po
index eb6cf5a..05833a6 100644
--- a/po/es.po
+++ b/po/es.po
@@ -3192,3 +3192,52 @@ msgstr "Localización"
 
 #~ msgid "OS Update"
 #~ msgstr "Actualización del SO"
+
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Debe iniciar sesión para continuar."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Debe iniciar sesión en %s para continuar."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Dirección de correo-e"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Ya tengo una cuenta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Contraseña"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Quiero crear una cuenta ahora"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "He olvidado mi contraseña"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Iniciar sesión automáticamente la próxima vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+"Introduzca su pin de un solo uso para la autenticación de dos factores."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
diff --git a/po/fi.po b/po/fi.po
index 79fda75..2b0594f 100644
--- a/po/fi.po
+++ b/po/fi.po
@@ -387,6 +387,54 @@ msgstr "verkkosovellus"
 msgid "nonfree"
 msgstr "ei-vapaa"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Jatkaaksesi sinun tulee olla kirjautunut sisään."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Jatkaaksesi kirjaudu sisään palveluun %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Sähköpostiosoite"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Minulla on jo tili"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Salasana"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Haluan rekisteröidä tilin nyt"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Unohdin salasanani"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Kirjaudu sisään automaattisesti ensi kerralla"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Anna kertakäyttöinen PIN-koodi kaksivaiheista tunnistaumista varten."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN-koodi"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Jatka"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/fr.po b/po/fr.po
index 2ca83f4..fb47704 100644
--- a/po/fr.po
+++ b/po/fr.po
@@ -397,6 +397,54 @@ msgstr "application Web"
 msgid "nonfree"
 msgstr "non libre"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Vous devez vous authentifier pour continuer."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Vous devez vous authentifier auprès de %s pour continuer."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adresse de courriel"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "J'ai déjà un compte"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Mot de passe"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Je souhaite enregistrer un compte maintenant"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "J'ai oublié mon mot de passe"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "M'authentifier automatiquement la prochaine fois"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Saisir votre code personnel d'authentification à usage unique."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "Code personnel"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuer"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/fur.po b/po/fur.po
index 859dc1e..a644d92 100644
--- a/po/fur.po
+++ b/po/fur.po
@@ -396,6 +396,54 @@ msgstr "Aplicazion web"
 msgid "nonfree"
 msgstr "no libare"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Par continuâ tu scugnis jentrâ."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Par continuâ tu scugnis jentrâ in %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Direzion e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "O ai za un account"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Password"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "O desideri regjistrâ un account cumò"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "O ai dismenteât la mê password"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Jentre in automatic la prossime volte"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Inserìs il to pin a ûs singul pe autenticazion a doi fatôrs."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continue"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/gd.po b/po/gd.po
index d85db66..aa5d39a 100644
--- a/po/gd.po
+++ b/po/gd.po
@@ -400,6 +400,69 @@ msgstr "aplacaid-lìn"
 msgid "nonfree"
 msgstr "chan eil e saor"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:69
+msgid "To continue you need to sign in."
+msgstr "Feumaidh tu clàradh a-steach mus lean thu air adhart."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:73
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Feumaidh tu clàradh a-steach dha %s mus lean thu air adhart."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+#| msgid "_Email address:"
+msgid "Email address"
+msgstr "Seòladh puist-d"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Tha cunntas agam mar-thà"
+
+#: ../src/gs-auth-dialog.ui.h:3
+#| msgid "_Password:"
+msgid "Password"
+msgstr "Facal-faire"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Tha mi airson cunntas a chlàradh an-dràsta"
+
+#: ../src/gs-auth-dialog.ui.h:5
+#| msgid "I've forgotten my password"
+msgid "I have forgotten my password"
+msgstr "Dhìochuimhnich mi am facal-faire agam"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Clàraich a-<steach gu fèin-obrachail an ath-thuras"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "page0"
+msgstr "duilleag0"
+
+#: ../src/gs-auth-dialog.ui.h:8
+#| msgid "Enter your one-time password for two-factor authentication."
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+"Cuir a-steach am PIN aon turais agad a chum a' chlàraidh a-steach "
+"dhà-cheumnaich"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:10
+msgid "page1"
+msgstr "duilleag1"
+
+#: ../src/gs-auth-dialog.ui.h:11
+#| msgid "_Continue"
+msgid "Continue"
+msgstr "Lean air adhart"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
@@ -2951,12 +3014,6 @@ msgstr "Ionadaileadh"
 #~ msgid "_Password:"
 #~ msgstr "_Facal-faire:"
 
-#~ msgid "Sign in automatically next time"
-#~ msgstr "Clàraich a-steach gu fèin-obrachail an ath-thuras"
-
-#~ msgid "I want to register for an account now"
-#~ msgstr "Tha mi airson cunntas a chlàradh an-dràsta"
-
 #~ msgid "I've forgotten my password"
 #~ msgstr "Dhìochuimhnich mi am facal-faire agam"
 
diff --git a/po/gl.po b/po/gl.po
index 33a3ebf..4b5dc9b 100644
--- a/po/gl.po
+++ b/po/gl.po
@@ -394,6 +394,54 @@ msgstr "aplicativo web"
 msgid "nonfree"
 msgstr "non libre"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Para continuar debe iniciar sesión."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Para continuar debe iniciar sesión en %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Enderezo de correo electrónico"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Xa teño unha conta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Contrasinal"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Quero rexistrar unha conta agora"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Esquecín o meu contrasinal"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Iniciar automaticamente a seguinte vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Escriba o seu PIN de uso único para a autenticación de dous factores."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/he.po b/po/he.po
index bb38aa9..76cc4c1 100644
--- a/po/he.po
+++ b/po/he.po
@@ -270,6 +270,54 @@ msgstr "הסרה"
 msgid "Updates"
 msgstr "עדכונים"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "על מנת להמשיך יש להתחבר."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "על מנת להמשיך יש להתחבר אל %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "כתובת דוא״ל"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "כבר יש ברשותי חשבון"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "ססמה"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "ברצוני להירשם עכשיו"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "שכחתי את הססמה שלי"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "התחברות אוטומטית להבא"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "יש להזין את הקוד הסודי החד פעמי עבור שני גורמי אימות."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "קוד סוגי"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "המשך"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:207
diff --git a/po/hu.po b/po/hu.po
index a8c5638..2592211 100644
--- a/po/hu.po
+++ b/po/hu.po
@@ -397,6 +397,55 @@ msgstr "webes app"
 msgid "nonfree"
 msgstr "nem-szabad"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "A folytatáshoz be kell jelentkeznie."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "A folytatáshoz be kell jelentkeznie ide: %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-mail cím"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Már van egy fiókom"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Jelszó"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Szeretnék egy fiókot regisztrálni most"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Elfelejtettem a jelszavamat"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Legközelebb automatikusan jelentkezzen be"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+"Adja meg az egyszer használatos PIN-kódját a kétlépcsős hitelesítéshez."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN-kód"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Folytatás"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/lt.po b/po/lt.po
index ea17387..9dff5c4 100644
--- a/po/lt.po
+++ b/po/lt.po
@@ -391,6 +391,54 @@ msgstr "internetinė programa"
 msgid "nonfree"
 msgstr "nelaisva"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Norėdami tęsti, turite prisijungti."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Norėdami tęsti, turite prisijungti prie %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "El. pašto adresas"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Jau turiu paskyrą"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Slaptažodis"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Noriu dabar registruoti paskyrą"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Pamiršau savo slaptažodį"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Automatiškai prisijungti kitą kartą"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Įveskite vienkartinį PIN dviejų žingsnių tapatybės patvirtinimui."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Tęsti"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/pa.po b/po/pa.po
index 76048a5..2b411da 100644
--- a/po/pa.po
+++ b/po/pa.po
@@ -266,6 +266,54 @@ msgstr "ਹਟਾਓ"
 msgid "Updates"
 msgstr "ਅੱਪਡੇਟ"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:69
+msgid "To continue you need to sign in."
+msgstr "ਜਾਰੀ ਰੱਖਣ ਲਈ ਤੁਹਾਨੂੰ ਸਾਇਨ ਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:73
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "ਜਾਰੀ ਰੱਖਣ ਲਈ ਤੁਹਾਨੂੰ %s ਵਿੱਚ ਸਾਇਨ ਇਨ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।"
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "ਈਮੇਲ ਪਤਾ"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "ਮੇਰੇ ਕੋਲ ਪਹਿਲਾਂ ਹੀ ਖਾਤਾ ਹੈ"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "ਪਾਸਵਰਡ"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "ਮੈਂ ਹੁਣੇ ਖਾਤੇ ਲਈ ਰਜਿਸਟਰ ਕਰਨਾ ਚਾਹੁੰਦਾ/ਚਾਹੁੰਦੀ ਹਾਂ"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "ਮੈਂ ਆਪਣਾ ਪਾਸਵਰਡ ਭੁੱਲ ਚੁੱਕਾ/ਚੁੱਕੀ ਹਾਂ"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "ਅਗਲੀ ਵਾਰ ਮੈਨੂੰ ਆਪਣੇ-ਆਪ ਸਾਇਨ ਇਨ ਕਰੋ"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "ਪਿੰਨ"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "ਜਾਰੀ ਰੱਖੋ"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:207
diff --git a/po/pl.po b/po/pl.po
index c2a9e2d..042d5c3 100644
--- a/po/pl.po
+++ b/po/pl.po
@@ -394,6 +394,54 @@ msgstr "WWW"
 msgid "nonfree"
 msgstr "własnościowe"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Należy się zalogować przed kontynuowaniem."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Należy się zalogować w serwisie %s przed kontynuowaniem."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adres e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Już mam konto"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Hasło"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Chcę zarejestrować konto"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Nie pamiętam hasła"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Zapamiętanie logowania"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Proszę wpisać jednorazowy kod PIN do uwierzytelnienia dwuetapowego."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "Kod PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Kontynuuj"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/pt.po b/po/pt.po
index 9eca86c..fd0532d 100644
--- a/po/pt.po
+++ b/po/pt.po
@@ -399,6 +399,54 @@ msgstr "aplicação web"
 msgid "nonfree"
 msgstr "não grátis"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Para continuar tem de autenticar-se"
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Para continuar tem de autenticar-se em %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Endereço de e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Eu já tenho uma conta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Palavra passe"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Pretendo criar agora uma conta"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Esqueci-me da palavra passe"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Autentique-se automaticamente da próxima vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Introduza o seu PIN único para a autenticação com dois fatores"
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/pt_BR.po b/po/pt_BR.po
index d039838..f3244d1 100644
--- a/po/pt_BR.po
+++ b/po/pt_BR.po
@@ -408,6 +408,54 @@ msgstr "aplicativo web"
 msgid "nonfree"
 msgstr "não livre"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Para continuar, você precisa se autenticar."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Para continuar, você precisa se autenticar em %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Endereço de e-mail"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Eu já tenho uma conta"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Senha"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Eu desejo registrar para ter uma conta agora"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Eu esqueci a minha senha"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Autenticar automaticamente na próxima vez"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Insira seu pin para autenticação de dois fatores."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Continuar"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/sk.po b/po/sk.po
index e9a33d6..5980222 100644
--- a/po/sk.po
+++ b/po/sk.po
@@ -412,6 +412,54 @@ msgstr "webová aplikácia"
 msgid "nonfree"
 msgstr "neslobodný"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Pre pokračovanie sa musíte prihlásiť."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Pre pokračovanie sa musíte prihlásiť do služby %s."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Emailová adresa"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Už mám účet"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Heslo"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Chcem si teraz zaregistrovať účet"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Zabudol som heslo"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Prihlásiť ma automaticky nabudúce"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Zadajte váš jednorázový pin pre dvojfaktorové overenie totožnosti."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Pokračovať"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/sr.po b/po/sr.po
index 0e10b76..0a38656 100644
--- a/po/sr.po
+++ b/po/sr.po
@@ -399,6 +399,54 @@ msgstr "програми веба"
 msgid "nonfree"
 msgstr "неслободни"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Да наставите треба да се пријавите."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Да наставите треба да се пријавите на „%s“."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Адреса е-поште"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Већ имам налог"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Лозинка"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Желим сада да се региструјем за налог"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Заборавио сам лозинку"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Сам се пријави следећи пут"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Унесите ваш једновремени пин за потврђивање идентитета са два чиниоца."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "ПИН"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Настави"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/sr latin po b/po/sr latin po
index 4163459..0791272 100644
--- a/po/sr latin po
+++ b/po/sr latin po
@@ -399,6 +399,54 @@ msgstr "programi veba"
 msgid "nonfree"
 msgstr "neslobodni"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Da nastavite treba da se prijavite."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Da nastavite treba da se prijavite na „%s“."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "Adresa e-pošte"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Već imam nalog"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Lozinka"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Želim sada da se registrujem za nalog"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Zaboravio sam lozinku"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Sam se prijavi sledeći put"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Unesite vaš jednovremeni pin za potvrđivanje identiteta sa dva činioca."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Nastavi"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/sv.po b/po/sv.po
index c8ddf99..bb5e478 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -400,6 +400,54 @@ msgstr "webbapplikation"
 msgid "nonfree"
 msgstr "icke-fri"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "Du måste logga in för att fortsätta."
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "Du måste logga in på %s för att fortsätta."
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "E-postadress"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "Jag har redan ett konto"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "Lösenord"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "Jag vill registrera ett konto nu"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "Jag har glömt mitt lösenord"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "Logga in automatiskt nästa gång"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "Ange din engångs-pin för tvåfaktorsautentisering."
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "Fortsätt"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/tr.po b/po/tr.po
index 635efae..38dfe7e 100644
--- a/po/tr.po
+++ b/po/tr.po
@@ -391,6 +391,54 @@ msgstr "web uygulaması"
 msgid "nonfree"
 msgstr "özgür olmayan"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr ""
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr ""
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr ""
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr ""
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/po/zh_TW.po b/po/zh_TW.po
index dabb2ac..f6220ad 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -379,6 +379,54 @@ msgstr "網頁應用程式"
 msgid "nonfree"
 msgstr "非自由"
 
+#. TRANSLATORS: this is when the service name is not known
+#: ../src/gs-auth-dialog.c:70
+msgid "To continue you need to sign in."
+msgstr "若要繼續,需要您登入。"
+
+#. TRANSLATORS: the %s is a service name, e.g. "Ubuntu One"
+#: ../src/gs-auth-dialog.c:74
+#, c-format
+msgid "To continue you need to sign in to %s."
+msgstr "若要繼續,需要您登入 %s。"
+
+#. vim: set noexpandtab:
+#: ../src/gs-auth-dialog.ui.h:1
+msgid "Email address"
+msgstr "電子郵件位址"
+
+#: ../src/gs-auth-dialog.ui.h:2
+msgid "I have an account already"
+msgstr "我已有帳號"
+
+#: ../src/gs-auth-dialog.ui.h:3
+msgid "Password"
+msgstr "密碼"
+
+#: ../src/gs-auth-dialog.ui.h:4
+msgid "I want to register for an account now"
+msgstr "我想要立刻註冊帳號"
+
+#: ../src/gs-auth-dialog.ui.h:5
+msgid "I have forgotten my password"
+msgstr "我竟然忘記密碼"
+
+#: ../src/gs-auth-dialog.ui.h:6
+msgid "Sign in automatically next time"
+msgstr "下次自動登入"
+
+#: ../src/gs-auth-dialog.ui.h:7
+msgid "Enter your one-time pin for two-factor authentication."
+msgstr "請輸入雙因素身份認證的一次性 PIN 碼。"
+
+#: ../src/gs-auth-dialog.ui.h:8
+msgid "PIN"
+msgstr "PIN 碼"
+
+#: ../src/gs-auth-dialog.ui.h:9
+msgid "Continue"
+msgstr "繼續"
+
 #. TRANSLATORS: this is where all applications that don't
 #. * fit in other groups are put
 #: ../src/gs-category.c:235
diff --git a/src/Makefile.am b/src/Makefile.am
index 020d22a..e8919b1 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,6 +9,7 @@ AM_CPPFLAGS =                                           \
        $(PACKAGEKIT_CFLAGS)                            \
        $(GNOME_DESKTOP_CFLAGS)                         \
        $(POLKIT_CFLAGS)                                \
+       $(LIBSECRET_CFLAGS)                             \
        -DG_LOG_DOMAIN=\"Gs\"                           \
        -DI_KNOW_THE_PACKAGEKIT_GLIB2_API_IS_SUBJECT_TO_CHANGE  \
        -DGS_MODULESETDIR=\"$(datadir)/gnome-software/modulesets.d\" \
@@ -33,6 +34,7 @@ UI_FILES =                                            \
        gs-app-folder-dialog.ui                         \
        gs-app-row.ui                                   \
        gs-app-tile.ui                                  \
+       gs-auth-dialog.ui                               \
        gs-category-tile.ui                             \
        gs-feature-tile.ui                              \
        gs-first-run-dialog.ui                          \
@@ -86,6 +88,7 @@ noinst_PROGRAMS =                                     \
 
 gnome_software_cmd_SOURCES =                           \
        gs-app.c                                        \
+       gs-auth.c                                       \
        gs-review.c                                     \
        gs-cmd.c                                        \
        gs-utils.c                                      \
@@ -99,6 +102,7 @@ gnome_software_cmd_LDADD =                           \
        $(APPSTREAM_LIBS)                               \
        $(POLKIT_LIBS)                                  \
        $(SOUP_LIBS)                                    \
+       $(LIBSECRET_LIBS)                               \
        $(GLIB_LIBS)                                    \
        $(GTK_LIBS)
 
@@ -113,6 +117,10 @@ gnome_software_SOURCES =                           \
        gs-utils.h                                      \
        gs-app.c                                        \
        gs-app.h                                        \
+       gs-auth.c                                       \
+       gs-auth.h                                       \
+       gs-auth-dialog.c                                \
+       gs-auth-dialog.h                                \
        gs-category.c                                   \
        gs-category.h                                   \
        gs-app-addon-row.c                              \
@@ -226,6 +234,7 @@ gnome_software_LDADD =                                      \
        $(GLIB_LIBS)                                    \
        $(GTK_LIBS)                                     \
        $(SOUP_LIBS)                                    \
+       $(LIBSECRET_LIBS)                               \
        $(PACKAGEKIT_LIBS)                              \
        $(GNOME_DESKTOP_LIBS)                           \
        $(POLKIT_LIBS)                                  \
@@ -283,6 +292,7 @@ check_PROGRAMS =                                            \
 
 gs_self_test_SOURCES =                                         \
        gs-app.c                                                \
+       gs-auth.c                                               \
        gs-category.c                                           \
        gs-os-release.c                                         \
        gs-plugin-loader-sync.c                                 \
@@ -296,6 +306,7 @@ gs_self_test_LDADD =                                                \
        $(APPSTREAM_LIBS)                                       \
        $(POLKIT_LIBS)                                          \
        $(SOUP_LIBS)                                            \
+       $(LIBSECRET_LIBS)                                       \
        $(GLIB_LIBS)                                            \
        $(GTK_LIBS)
 
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index 95ab5e6..73600ae 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -7,6 +7,7 @@
   <file preprocess="xml-stripblanks">gs-app-folder-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-app-row.ui</file>
   <file preprocess="xml-stripblanks">gs-app-tile.ui</file>
+  <file preprocess="xml-stripblanks">gs-auth-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-category-tile.ui</file>
   <file preprocess="xml-stripblanks">gs-feature-tile.ui</file>
   <file preprocess="xml-stripblanks">gs-first-run-dialog.ui</file>
diff --git a/src/gs-auth-dialog.c b/src/gs-auth-dialog.c
new file mode 100644
index 0000000..614638e
--- /dev/null
+++ b/src/gs-auth-dialog.c
@@ -0,0 +1,354 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "gs-auth.h"
+#include "gs-auth-dialog.h"
+#include "gs-utils.h"
+
+struct _GsAuthDialog
+{
+       GtkDialog        parent_instance;
+
+       GCancellable    *cancellable;
+       GsPluginLoader  *plugin_loader;
+       GsApp           *app;
+       GsAuth          *auth;
+       GtkWidget       *box_dialog;
+       GtkWidget       *box_error;
+       GtkWidget       *button_cancel;
+       GtkWidget       *button_continue;
+       GtkWidget       *checkbutton_remember;
+       GtkWidget       *entry_password;
+       GtkWidget       *entry_pin;
+       GtkWidget       *entry_username;
+       GtkWidget       *image_vendor;
+       GtkWidget       *label_error;
+       GtkWidget       *label_title;
+       GtkWidget       *radiobutton_already;
+       GtkWidget       *radiobutton_lost_pwd;
+       GtkWidget       *radiobutton_register;
+       GtkWidget       *stack;
+};
+
+G_DEFINE_TYPE (GsAuthDialog, gs_auth_dialog, GTK_TYPE_DIALOG)
+
+static void
+gs_auth_dialog_check_ui (GsAuthDialog *dialog)
+{
+       g_autofree gchar *title = NULL;
+       const gchar *tmp;
+       const gchar *username = gtk_entry_get_text (GTK_ENTRY (dialog->entry_username));
+       const gchar *password = gtk_entry_get_text (GTK_ENTRY (dialog->entry_password));
+
+       /* set the header */
+       tmp = gs_auth_get_provider_name (dialog->auth);
+       if (tmp == NULL) {
+               /* TRANSLATORS: this is when the service name is not known */
+               title = g_strdup (_("To continue you need to sign in."));
+               gtk_label_set_label (GTK_LABEL (dialog->label_title), title);
+       } else {
+               /* TRANSLATORS: the %s is a service name, e.g. "Ubuntu One" */
+               title = g_strdup_printf (_("To continue you need to sign in to %s."), tmp);
+               gtk_label_set_label (GTK_LABEL (dialog->label_title), title);
+       }
+
+       /* set the vendor image */
+       tmp = gs_auth_get_provider_logo (dialog->auth);
+       if (tmp == NULL) {
+               gtk_widget_hide (dialog->image_vendor);
+       } else {
+               gtk_image_set_from_file (GTK_IMAGE (dialog->image_vendor), tmp);
+               gtk_widget_show (dialog->image_vendor);
+       }
+
+       /* need username and password to continue for known account */
+       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_already))) {
+               gtk_widget_set_sensitive (dialog->button_continue,
+                                         username[0] != '\0' && password[0] != '\0');
+               gtk_widget_set_sensitive (dialog->checkbutton_remember, TRUE);
+       } else {
+               gtk_entry_set_text (GTK_ENTRY (dialog->entry_password), "");
+               gtk_widget_set_sensitive (dialog->button_continue,
+                                         username[0] != '\0');
+               gtk_widget_set_sensitive (dialog->checkbutton_remember, FALSE);
+       }
+}
+
+static void
+gs_auth_dialog_cancel_button_cb (GtkWidget *widget, GsAuthDialog *dialog)
+{
+       gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
+}
+
+static void
+gs_auth_dialog_authenticate_cb (GObject *source,
+                               GAsyncResult *res,
+                               gpointer user_data)
+{
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
+       GsAuthDialog *dialog = GS_AUTH_DIALOG (user_data);
+       g_autoptr(GError) error = NULL;
+
+       gtk_widget_set_sensitive (dialog->box_dialog, TRUE);
+       gtk_widget_set_sensitive (dialog->button_continue, TRUE);
+
+       gtk_widget_set_visible (dialog->box_error, FALSE);
+
+       /* we failed */
+       if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
+               const gchar *url;
+
+               if (g_error_matches (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_PIN_REQUIRED)) {
+                       gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "2fa");
+                       gtk_widget_grab_focus (dialog->entry_pin);
+                       return;
+               }
+
+               /* have we been given a link */
+               url = gs_utils_get_error_value (error);
+               if (url != NULL) {
+                       g_autoptr(GError) error_local = NULL;
+                       g_debug ("showing link in: %s", error->message);
+                       if (!gtk_show_uri (NULL, url, GDK_CURRENT_TIME, &error_local)) {
+                               g_warning ("failed to show URI %s: %s",
+                                          url, error_local->message);
+                       }
+                       return;
+               }
+
+               g_warning ("failed to authenticate: %s", error->message);
+               gtk_label_set_label (GTK_LABEL (dialog->label_error), error->message);
+               gtk_widget_set_visible (dialog->box_error, TRUE);
+               return;
+       }
+
+       /* we didn't get authenticated */
+       if (!gs_auth_has_flag (dialog->auth, GS_AUTH_FLAG_VALID)) {
+               return;
+       }
+
+       /* success */
+       gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+static void
+gs_auth_dialog_continue_cb (GtkWidget *widget, GsAuthDialog *dialog)
+{
+       GsPluginLoaderAction action = GS_AUTH_ACTION_LOGIN;
+
+       gtk_widget_set_sensitive (dialog->box_dialog, FALSE);
+       gtk_widget_set_sensitive (dialog->button_continue, FALSE);
+
+       /* alternate actions */
+       if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_lost_pwd)))
+               action = GS_AUTH_ACTION_LOST_PASSWORD;
+       else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->radiobutton_register)))
+               action = GS_AUTH_ACTION_REGISTER;
+       gs_plugin_loader_auth_action_async (dialog->plugin_loader,
+                                           dialog->auth,
+                                           action,
+                                           dialog->cancellable,
+                                           gs_auth_dialog_authenticate_cb,
+                                           dialog);
+}
+
+static void
+gs_auth_dialog_setup (GsAuthDialog *dialog)
+{
+
+       /* update widgets with known values */
+       if (gs_auth_get_username (dialog->auth) != NULL) {
+               gtk_entry_set_text (GTK_ENTRY (dialog->entry_username),
+                                   gs_auth_get_username (dialog->auth));
+       }
+       if (gs_auth_get_password (dialog->auth) != NULL) {
+               gtk_entry_set_text (GTK_ENTRY (dialog->entry_password),
+                                   gs_auth_get_password (dialog->auth));
+       }
+
+       /* refresh UI */
+       gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_notify_username_cb (GtkEntry *entry,
+                                  GParamSpec *pspec,
+                                  GsAuthDialog *dialog)
+{
+       gs_auth_set_username (dialog->auth, gtk_entry_get_text (entry));
+       gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_notify_password_cb (GtkEntry *entry,
+                                  GParamSpec *pspec,
+                                  GsAuthDialog *dialog)
+{
+       gs_auth_set_password (dialog->auth, gtk_entry_get_text (entry));
+       gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_notify_pin_cb (GtkEntry *entry,
+                             GParamSpec *pspec,
+                             GsAuthDialog *dialog)
+{
+       gs_auth_set_pin (dialog->auth, gtk_entry_get_text (entry));
+       gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_toggled_cb (GtkToggleButton *togglebutton, GsAuthDialog *dialog)
+{
+       gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_remember_cb (GtkToggleButton *togglebutton, GsAuthDialog *dialog)
+{
+       if (gtk_toggle_button_get_active (togglebutton))
+               gs_auth_add_flags (dialog->auth, GS_AUTH_FLAG_REMEMBER);
+       gs_auth_dialog_check_ui (dialog);
+}
+
+static void
+gs_auth_dialog_dispose (GObject *object)
+{
+       GsAuthDialog *dialog = GS_AUTH_DIALOG (object);
+
+       g_clear_object (&dialog->plugin_loader);
+       g_clear_object (&dialog->app);
+       g_clear_object (&dialog->auth);
+
+       if (dialog->cancellable != NULL) {
+               g_cancellable_cancel (dialog->cancellable);
+               g_clear_object (&dialog->cancellable);
+       }
+
+       G_OBJECT_CLASS (gs_auth_dialog_parent_class)->dispose (object);
+}
+
+static void
+gs_auth_dialog_init (GsAuthDialog *dialog)
+{
+       gtk_widget_init_template (GTK_WIDGET (dialog));
+
+       dialog->cancellable = g_cancellable_new ();
+
+       g_signal_connect (dialog->entry_username, "notify::text",
+                         G_CALLBACK (gs_auth_dialog_notify_username_cb), dialog);
+       g_signal_connect (dialog->entry_password, "notify::text",
+                         G_CALLBACK (gs_auth_dialog_notify_password_cb), dialog);
+       g_signal_connect (dialog->entry_password, "activate",
+                         G_CALLBACK (gs_auth_dialog_continue_cb), dialog);
+       g_signal_connect (dialog->entry_pin, "notify::text",
+                         G_CALLBACK (gs_auth_dialog_notify_pin_cb), dialog);
+       g_signal_connect (dialog->entry_pin, "activate",
+                         G_CALLBACK (gs_auth_dialog_continue_cb), dialog);
+       g_signal_connect (dialog->checkbutton_remember, "toggled",
+                         G_CALLBACK (gs_auth_dialog_remember_cb), dialog);
+       g_signal_connect (dialog->radiobutton_already, "toggled",
+                         G_CALLBACK (gs_auth_dialog_toggled_cb), dialog);
+       g_signal_connect (dialog->radiobutton_register, "toggled",
+                         G_CALLBACK (gs_auth_dialog_toggled_cb), dialog);
+       g_signal_connect (dialog->radiobutton_lost_pwd, "toggled",
+                         G_CALLBACK (gs_auth_dialog_toggled_cb), dialog);
+       g_signal_connect (dialog->button_cancel, "clicked",
+                         G_CALLBACK (gs_auth_dialog_cancel_button_cb), dialog);
+       g_signal_connect (dialog->button_continue, "clicked",
+                         G_CALLBACK (gs_auth_dialog_continue_cb), dialog);
+}
+
+static void
+gs_auth_dialog_class_init (GsAuthDialogClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->dispose = gs_auth_dialog_dispose;
+
+       gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-auth-dialog.ui");
+
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, box_dialog);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, box_error);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, button_cancel);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, button_continue);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, checkbutton_remember);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, entry_password);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, entry_pin);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, entry_username);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, image_vendor);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, label_error);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, label_title);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, radiobutton_already);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, radiobutton_lost_pwd);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, radiobutton_register);
+       gtk_widget_class_bind_template_child (widget_class, GsAuthDialog, stack);
+}
+
+GtkWidget *
+gs_auth_dialog_new (GsPluginLoader *plugin_loader,
+                   GsApp *app,
+                   const gchar *provider_id,
+                   GError **error)
+{
+       GsAuthDialog *dialog;
+       GsAuth *auth;
+
+       /* get the authentication provider */
+       if (provider_id == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "no auth-provider given for %s",
+                            gs_app_get_id (app));
+               return NULL;
+       }
+       auth = gs_plugin_loader_get_auth_by_id (plugin_loader, provider_id);
+       if (auth == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_NOT_SUPPORTED,
+                            "no auth-provider %s for %s",
+                            provider_id, gs_app_get_id (app));
+               return NULL;
+       }
+
+       /* create dialog */
+       dialog = g_object_new (GS_TYPE_AUTH_DIALOG,
+                              "use-header-bar", TRUE,
+                              NULL);
+       dialog->plugin_loader = g_object_ref (plugin_loader);
+       dialog->app = g_object_ref (app);
+       dialog->auth = g_object_ref (auth);
+       gs_auth_dialog_setup (dialog);
+
+       return GTK_WIDGET (dialog);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-auth-dialog.h b/src/gs-auth-dialog.h
new file mode 100644
index 0000000..5487c2b
--- /dev/null
+++ b/src/gs-auth-dialog.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GS_AUTH_DIALOG_H
+#define GS_AUTH_DIALOG_H
+
+#include <gtk/gtk.h>
+
+#include "gs-app.h"
+#include "gs-plugin-loader.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_AUTH_DIALOG (gs_auth_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsAuthDialog, gs_auth_dialog, GS, AUTH_DIALOG, GtkDialog)
+
+GtkWidget      *gs_auth_dialog_new             (GsPluginLoader *plugin_loader,
+                                                GsApp          *app,
+                                                const gchar    *provider_id,
+                                                GError         **error);
+
+G_END_DECLS
+
+#endif /* GS_AUTH_DIALOG_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-auth-dialog.ui b/src/gs-auth-dialog.ui
new file mode 100644
index 0000000..7cee9f5
--- /dev/null
+++ b/src/gs-auth-dialog.ui
@@ -0,0 +1,359 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="GsAuthDialog" parent="GtkDialog">
+    <property name="can_focus">False</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="type_hint">dialog</property>
+    <property name="deletable">False</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="box_dialog">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkGrid">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">21</property>
+            <property name="row_spacing">9</property>
+            <property name="column_spacing">21</property>
+            <child>
+              <object class="GtkStack" id="stack">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="transition_type">crossfade</property>
+                <child>
+                  <object class="GtkGrid">
+                    <property name="name">intro</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="valign">start</property>
+                    <property name="row_spacing">9</property>
+                    <property name="column_spacing">9</property>
+                    <child>
+                      <object class="GtkLabel" id="label_title">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label">To continue you need an %NAME% account.</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">end</property>
+                        <property name="label" translatable="yes">Email address</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="entry_username">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="margin_bottom">15</property>
+                        <property name="input_purpose">email</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="radiobutton_already">
+                        <property name="label" translatable="yes">I have an account already</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="halign">start</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">2</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">end</property>
+                        <property name="label" translatable="yes">Password</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="entry_password">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="input_purpose">password</property>
+                        <property name="visibility">False</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="radiobutton_register">
+                        <property name="label" translatable="yes">I want to register for an account 
now</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="halign">start</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">radiobutton_already</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">5</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkRadioButton" id="radiobutton_lost_pwd">
+                        <property name="label" translatable="yes">I have forgotten my password</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="halign">start</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                        <property name="group">radiobutton_already</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">6</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">4</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkCheckButton" id="checkbutton_remember">
+                        <property name="label" translatable="yes">Sign in automatically next time</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">False</property>
+                        <property name="active">True</property>
+                        <property name="draw_indicator">True</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">4</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name">login</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkGrid">
+                    <property name="name">2fa</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="row_spacing">9</property>
+                    <property name="column_spacing">9</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="label" translatable="yes">Enter your one-time pin for two-factor 
authentication.</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">0</property>
+                        <property name="width">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">end</property>
+                        <property name="label" translatable="yes">PIN</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="left_attach">0</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="entry_pin">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="input_purpose">password</property>
+                      </object>
+                      <packing>
+                        <property name="left_attach">1</property>
+                        <property name="top_attach">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="name">2fa</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkImage" id="image_vendor">
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <property name="valign">start</property>
+                <property name="stock">gtk-floppy</property>
+                <property name="icon_size">6</property>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+                <property name="height">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="box_error">
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">end</property>
+                    <property name="valign">start</property>
+                    <property name="icon_name">dialog-warning-symbolic</property>
+                    <property name="use_fallback">True</property>
+                    <property name="icon_size">5</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkLabel" id="label_error">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="halign">start</property>
+                    <property name="label">The supplied credentials were not correct</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="titlebar">
+      <object class="GtkHeaderBar">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="title">Authenticate</property>
+        <child>
+          <object class="GtkButton" id="button_cancel">
+            <property name="label">gtk-cancel</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <property name="use_stock">True</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkButton" id="button_continue">
+            <property name="label" translatable="yes">Continue</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="pack_type">end</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup" id="sizegroup_buttons">
+    <widgets>
+      <widget name="button_continue"/>
+      <widget name="button_cancel"/>
+    </widgets>
+  </object>
+</interface>
diff --git a/src/gs-auth.c b/src/gs-auth.c
new file mode 100644
index 0000000..e780a2a
--- /dev/null
+++ b/src/gs-auth.c
@@ -0,0 +1,697 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/**
+ * SECTION:gs-auth
+ * @title: GsAuth
+ * @include: gnome-software.h
+ * @stability: Unstable
+ * @short_description: User data used for authentication
+ *
+ * This object represents user data used for authentication.
+ * This data is shared between all plugins.
+ */
+
+#include "config.h"
+
+#include <libsecret/secret.h>
+
+#include "gs-auth.h"
+#include "gs-plugin.h"
+
+struct _GsAuth
+{
+       GObject                  parent_instance;
+
+       GsAuthFlags              flags;
+       gchar                   *provider_id;
+       gchar                   *provider_name;
+       gchar                   *provider_logo;
+       gchar                   *provider_uri;
+       gchar                   *provider_schema;
+       gchar                   *username;
+       gchar                   *password;
+       gchar                   *pin;
+       GHashTable              *metadata;      /* utf8: utf8 */
+};
+
+enum {
+       PROP_0,
+       PROP_USERNAME,
+       PROP_PASSWORD,
+       PROP_PIN,
+       PROP_FLAGS,
+       PROP_LAST
+};
+
+G_DEFINE_TYPE (GsAuth, gs_auth, G_TYPE_OBJECT)
+
+/**
+ * gs_auth_get_provider_id:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service ID.
+ *
+ * Returns: the string to use for searching, e.g. "UbuntuOne"
+ */
+const gchar *
+gs_auth_get_provider_id (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->provider_id;
+}
+
+/**
+ * gs_auth_get_provider_name:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service name.
+ *
+ * Returns: the string to show in the UI
+ */
+const gchar *
+gs_auth_get_provider_name (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->provider_name;
+}
+
+/**
+ * gs_auth_set_provider_name:
+ * @auth: a #GsAuth
+ * @provider_name: a service name, e.g. "GNOME Online Accounts"
+ *
+ * Sets the name to be used for the authentication dialog.
+ */
+void
+gs_auth_set_provider_name (GsAuth *auth, const gchar *provider_name)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->provider_name);
+       auth->provider_name = g_strdup (provider_name);
+}
+
+/**
+ * gs_auth_get_provider_logo:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service image.
+ *
+ * Returns: the filename of an image, or %NULL
+ */
+const gchar *
+gs_auth_get_provider_logo (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->provider_logo;
+}
+
+/**
+ * gs_auth_set_provider_logo:
+ * @auth: a #GsAuth
+ * @provider_logo: an image, e.g. "/usr/share/icons/gnome-online.png"
+ *
+ * Sets the image to be used for the authentication dialog.
+ */
+void
+gs_auth_set_provider_logo (GsAuth *auth, const gchar *provider_logo)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->provider_logo);
+       auth->provider_logo = g_strdup (provider_logo);
+}
+
+/**
+ * gs_auth_get_provider_uri:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication service website.
+ *
+ * Returns: the URI, or %NULL
+ */
+const gchar *
+gs_auth_get_provider_uri (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->provider_uri;
+}
+
+/**
+ * gs_auth_set_provider_uri:
+ * @auth: a #GsAuth
+ * @provider_uri: a URI, e.g. "http://www.gnome.org/sso";
+ *
+ * Sets the website to be used for the authentication dialog.
+ */
+void
+gs_auth_set_provider_uri (GsAuth *auth, const gchar *provider_uri)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->provider_uri);
+       auth->provider_uri = g_strdup (provider_uri);
+}
+
+/**
+ * gs_auth_get_provider_schema:
+ * @auth: a #GsAuth
+ *
+ * Gets the authentication schema ID.
+ *
+ * Returns: the URI, or %NULL
+ */
+const gchar *
+gs_auth_get_provider_schema (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->provider_schema;
+}
+
+/**
+ * gs_auth_set_provider_schema:
+ * @auth: a #GsAuth
+ * @provider_schema: a URI, e.g. "com.distro.provider"
+ *
+ * Sets the schema ID to be used for saving the state to disk.
+ */
+void
+gs_auth_set_provider_schema (GsAuth *auth, const gchar *provider_schema)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->provider_schema);
+       auth->provider_schema = g_strdup (provider_schema);
+}
+
+/**
+ * gs_auth_get_username:
+ * @auth: a #GsAuth
+ *
+ * Gets the auth username.
+ *
+ * Returns: the username to be used for the authentication
+ */
+const gchar *
+gs_auth_get_username (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->username;
+}
+
+/**
+ * gs_auth_set_username:
+ * @auth: a #GsAuth
+ * @username: a username, e.g. "hughsie"
+ *
+ * Sets the username to be used for the authentication.
+ */
+void
+gs_auth_set_username (GsAuth *auth, const gchar *username)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->username);
+       auth->username = g_strdup (username);
+}
+
+/**
+ * gs_auth_get_password:
+ * @auth: a #GsAuth
+ *
+ * Gets the password to be used for the authentication.
+ *
+ * Returns: the string, or %NULL
+ **/
+const gchar *
+gs_auth_get_password (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->password;
+}
+
+/**
+ * gs_auth_set_password:
+ * @auth: a #GsAuth
+ * @password: password string, e.g. "p@ssw0rd"
+ *
+ * Sets the password to be used for the authentication.
+ */
+void
+gs_auth_set_password (GsAuth *auth, const gchar *password)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->password);
+       auth->password = g_strdup (password);
+}
+
+/**
+ * gs_auth_get_flags:
+ * @auth: a #GsAuth
+ *
+ * Gets any flags set on the authentication, for example if we should remember
+ * credentials.
+ *
+ * Returns: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ */
+GsAuthFlags
+gs_auth_get_flags (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), 0);
+       return auth->flags;
+}
+
+/**
+ * gs_auth_set_flags:
+ * @auth: a #GsAuth
+ * @flags: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ *
+ * Gets any flags set on the authentication.
+ */
+void
+gs_auth_set_flags (GsAuth *auth, GsAuthFlags flags)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       auth->flags = flags;
+}
+
+/**
+ * gs_auth_add_flags:
+ * @auth: a #GsAuth
+ * @flags: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ *
+ * Adds flags to an existing authentication without replacing the other flags.
+ */
+void
+gs_auth_add_flags (GsAuth *auth, GsAuthFlags flags)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       auth->flags |= flags;
+}
+
+/**
+ * gs_auth_has_flag:
+ * @auth: a #GsAuth
+ * @flags: a #GsAuthFlags, e.g. %GS_AUTH_FLAG_REMEMBER
+ *
+ * Finds ouf if the authentication has a flag.
+ *
+ * Returns: %TRUE if set
+ */
+gboolean
+gs_auth_has_flag (GsAuth *auth, GsAuthFlags flags)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), FALSE);
+       return (auth->flags & flags) > 0;
+}
+
+/**
+ * gs_auth_get_pin:
+ * @auth: a #GsAuth
+ *
+ * Gets the PIN code.
+ *
+ * Returns: the 2 factor authentication PIN, or %NULL
+ **/
+const gchar *
+gs_auth_get_pin (GsAuth *auth)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       return auth->pin;
+}
+
+/**
+ * gs_auth_set_pin:
+ * @auth: a #GsAuth
+ * @pin: the PIN code, e.g. "12345"
+ *
+ * Sets the 2 factor authentication PIN, which can be left unset.
+ */
+void
+gs_auth_set_pin (GsAuth *auth, const gchar *pin)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_free (auth->pin);
+       auth->pin = g_strdup (pin);
+}
+
+/**
+ * gs_auth_get_metadata_item:
+ * @auth: a #GsAuth
+ * @key: a string
+ *
+ * Gets some metadata from a authentication object.
+ * It is left for the the plugin to use this method as required, but a
+ * typical use would be to retrieve some secure auth token.
+ *
+ * Returns: A string value, or %NULL for not found
+ */
+const gchar *
+gs_auth_get_metadata_item (GsAuth *auth, const gchar *key)
+{
+       g_return_val_if_fail (GS_IS_AUTH (auth), NULL);
+       g_return_val_if_fail (key != NULL, NULL);
+       return g_hash_table_lookup (auth->metadata, key);
+}
+
+/**
+ * gs_auth_add_metadata:
+ * @auth: a #GsAuth
+ * @key: a string
+ * @value: a string
+ *
+ * Adds metadata to the authentication object.
+ * It is left for the the plugin to use this method as required, but a
+ * typical use would be to store some secure auth token.
+ */
+void
+gs_auth_add_metadata (GsAuth *auth, const gchar *key, const gchar *value)
+{
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_hash_table_insert (auth->metadata, g_strdup (key), g_strdup (value));
+}
+
+static gboolean
+_g_error_is_set (GError **error)
+{
+       if (error == NULL)
+               return FALSE;
+       return *error != NULL;
+}
+
+/**
+ * gs_auth_store_load:
+ * @auth: a #GsAuth
+ * @flags: some #GsAuthStoreFlags, e.g. %GS_AUTH_STORE_FLAG_USERNAME
+ * @cancellable: a #GCancellable or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Loads authentication tokens from disk in a secure way.
+ * By default only the username and password are loaded, but they are not
+ * overwritten if already set.
+ *
+ * If additional tokens are required to be loaded you must first tell the
+ * GsAuth instance what metadata to load. This can be done using:
+ * `gs_auth_add_metadata("additional-secret-key-name",NULL)`
+ *
+ * This function is expected to be called from gs_plugin_setup().
+ *
+ * Returns: %TRUE if the tokens were loaded correctly.
+ */
+gboolean
+gs_auth_store_load (GsAuth *auth, GsAuthStoreFlags flags,
+                   GCancellable *cancellable, GError **error)
+{
+       SecretSchema schema = {
+               auth->provider_schema,
+               SECRET_SCHEMA_NONE,
+               { { "key", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+       };
+
+       /* no schema */
+       if (auth->provider_schema == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "No provider schema set for %s",
+                            auth->provider_id);
+               return FALSE;
+       }
+
+       /* username */
+       if ((flags & GS_AUTH_STORE_FLAG_USERNAME) > 0 && auth->username == NULL) {
+               auth->username = secret_password_lookup_sync (&schema,
+                                                             cancellable,
+                                                             error,
+                                                             "key", "username",
+                                                             NULL);
+               if (_g_error_is_set (error))
+                       return FALSE;
+       }
+
+       /* password */
+       if ((flags & GS_AUTH_STORE_FLAG_PASSWORD) > 0 && auth->password == NULL) {
+               auth->password = secret_password_lookup_sync (&schema,
+                                                             cancellable,
+                                                             error,
+                                                             "key", "password",
+                                                             NULL);
+               if (_g_error_is_set (error))
+                       return FALSE;
+       }
+
+       /* metadata */
+       if (flags & GS_AUTH_STORE_FLAG_METADATA) {
+               GList *l;
+               g_autoptr(GList) keys = NULL;
+               keys = g_hash_table_get_keys (auth->metadata);
+               for (l = keys; l != NULL; l = l->next) {
+                       g_autofree gchar *tmp = NULL;
+                       const gchar *key = l->data;
+                       const gchar *value = g_hash_table_lookup (auth->metadata, key);
+                       if (value != NULL)
+                               continue;
+                       tmp = secret_password_lookup_sync (&schema,
+                                                          cancellable,
+                                                          error,
+                                                          "key", key,
+                                                          NULL);
+                       if (_g_error_is_set (error))
+                               return FALSE;
+                       if (tmp != NULL)
+                               gs_auth_add_metadata (auth, key, tmp);
+               }
+       }
+
+       /* success */
+       return TRUE;
+}
+
+/**
+ * gs_auth_store_save:
+ * @auth: a #GsAuth
+ * @flags: some #GsAuthStoreFlags, e.g. %GS_AUTH_STORE_FLAG_USERNAME
+ * @cancellable: a #GCancellable or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Saves the username, password and all added metadata to disk in a secure way.
+ *
+ * This function is expected to be called from gs_plugin_setup().
+ *
+ * Returns: %TRUE if the tokens were all saved correctly.
+ */
+gboolean
+gs_auth_store_save (GsAuth *auth, GsAuthStoreFlags flags,
+                   GCancellable *cancellable, GError **error)
+{
+       SecretSchema schema = {
+               auth->provider_schema,
+               SECRET_SCHEMA_NONE,
+               { { "key", SECRET_SCHEMA_ATTRIBUTE_STRING } }
+       };
+
+       /* no schema */
+       if (auth->provider_schema == NULL) {
+               g_set_error (error,
+                            GS_PLUGIN_ERROR,
+                            GS_PLUGIN_ERROR_FAILED,
+                            "No provider schema set for %s",
+                            auth->provider_id);
+               return FALSE;
+       }
+
+       /* username */
+       if ((flags & GS_AUTH_STORE_FLAG_USERNAME) > 0 && auth->username != NULL) {
+               if (!secret_password_store_sync (&schema,
+                                                NULL, /* collection */
+                                                auth->provider_schema,
+                                                auth->username,
+                                                cancellable, error,
+                                                "key", "username", NULL))
+                       return FALSE;
+       }
+
+       /* password */
+       if ((flags & GS_AUTH_STORE_FLAG_PASSWORD) > 0 && auth->password != NULL) {
+               if (!secret_password_store_sync (&schema,
+                                                NULL, /* collection */
+                                                auth->provider_schema,
+                                                auth->password,
+                                                cancellable, error,
+                                                "key", "password", NULL))
+                       return FALSE;
+       }
+
+       /* metadata */
+       if (flags & GS_AUTH_STORE_FLAG_METADATA) {
+               GList *l;
+               g_autoptr(GList) keys = NULL;
+               keys = g_hash_table_get_keys (auth->metadata);
+               for (l = keys; l != NULL; l = l->next) {
+                       const gchar *key = l->data;
+                       const gchar *value = g_hash_table_lookup (auth->metadata, key);
+                       if (value == NULL)
+                               continue;
+                       if (!secret_password_store_sync (&schema,
+                                                        NULL, /* collection */
+                                                        auth->provider_schema,
+                                                        value,
+                                                        cancellable, error,
+                                                        "key", key, NULL))
+                               return FALSE;
+               }
+       }
+
+       /* success */
+       return TRUE;
+}
+
+static void
+gs_auth_get_property (GObject *object, guint prop_id,
+                       GValue *value, GParamSpec *pspec)
+{
+       GsAuth *auth = GS_AUTH (object);
+
+       switch (prop_id) {
+       case PROP_USERNAME:
+               g_value_set_string (value, auth->username);
+               break;
+       case PROP_PASSWORD:
+               g_value_set_string (value, auth->password);
+               break;
+       case PROP_FLAGS:
+               g_value_set_uint64 (value, auth->flags);
+               break;
+       case PROP_PIN:
+               g_value_set_string (value, auth->pin);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_auth_set_property (GObject *object, guint prop_id,
+                       const GValue *value, GParamSpec *pspec)
+{
+       GsAuth *auth = GS_AUTH (object);
+
+       switch (prop_id) {
+       case PROP_USERNAME:
+               gs_auth_set_username (auth, g_value_get_string (value));
+               break;
+       case PROP_PASSWORD:
+               gs_auth_set_password (auth, g_value_get_string (value));
+               break;
+       case PROP_FLAGS:
+               gs_auth_set_flags (auth, g_value_get_uint64 (value));
+               break;
+       case PROP_PIN:
+               gs_auth_set_pin (auth, g_value_get_string (value));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_auth_finalize (GObject *object)
+{
+       GsAuth *auth = GS_AUTH (object);
+
+       g_free (auth->provider_id);
+       g_free (auth->provider_name);
+       g_free (auth->provider_logo);
+       g_free (auth->provider_uri);
+       g_free (auth->provider_schema);
+       g_free (auth->username);
+       g_free (auth->password);
+       g_free (auth->pin);
+       g_hash_table_unref (auth->metadata);
+
+       G_OBJECT_CLASS (gs_auth_parent_class)->finalize (object);
+}
+
+static void
+gs_auth_class_init (GsAuthClass *klass)
+{
+       GParamSpec *pspec;
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = gs_auth_finalize;
+       object_class->get_property = gs_auth_get_property;
+       object_class->set_property = gs_auth_set_property;
+
+       /**
+        * GsAuth:username:
+        */
+       pspec = g_param_spec_string ("username", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_USERNAME, pspec);
+
+       /**
+        * GsAuth:password:
+        */
+       pspec = g_param_spec_string ("password", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_PASSWORD, pspec);
+
+       /**
+        * GsAuth:flags:
+        */
+       pspec = g_param_spec_uint64 ("flags", NULL, NULL,
+                                    GS_AUTH_FLAG_NONE,
+                                    GS_AUTH_FLAG_LAST,
+                                    GS_AUTH_FLAG_NONE,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_FLAGS, pspec);
+
+       /**
+        * GsAuth:pin:
+        */
+       pspec = g_param_spec_string ("pin", NULL, NULL,
+                                    NULL,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+       g_object_class_install_property (object_class, PROP_PIN, pspec);
+}
+
+static void
+gs_auth_init (GsAuth *auth)
+{
+       auth->metadata = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                               g_free, g_free);
+}
+
+/**
+ * gs_auth_new:
+ * @provider_id: a provider ID used for mapping, e.g. "GnomeSSO"
+ *
+ * Return value: a new #GsAuth object.
+ **/
+GsAuth *
+gs_auth_new (const gchar *provider_id)
+{
+       GsAuth *auth;
+       auth = g_object_new (GS_TYPE_AUTH, NULL);
+       auth->provider_id = g_strdup (provider_id);
+       return GS_AUTH (auth);
+}
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-auth.h b/src/gs-auth.h
new file mode 100644
index 0000000..9f34e92
--- /dev/null
+++ b/src/gs-auth.h
@@ -0,0 +1,134 @@
+ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2016 Richard Hughes <richard hughsie com>
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_AUTH_H
+#define __GS_AUTH_H
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_AUTH (gs_auth_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsAuth, gs_auth, GS, AUTH, GObject)
+
+/**
+ * GsAuthFlags:
+ * @GS_AUTH_FLAG_NONE:                 No special flags set
+ * @GS_AUTH_FLAG_VALID:                        Authorisation is valid
+ * @GS_AUTH_FLAG_REMEMBER:             Remember this authentication if possible
+ *
+ * The flags for the auth.
+ **/
+typedef enum {
+       GS_AUTH_FLAG_NONE       = 0,
+       GS_AUTH_FLAG_VALID      = 1 << 0,
+       GS_AUTH_FLAG_REMEMBER   = 1 << 1,
+       /*< private >*/
+       GS_AUTH_FLAG_LAST
+} GsAuthFlags;
+
+/**
+ * GsAuthAction:
+ * @GS_AUTH_ACTION_LOGIN:              Login action
+ * @GS_AUTH_ACTION_LOGOUT:             Logout action
+ * @GS_AUTH_ACTION_REGISTER:           Register action
+ * @GS_AUTH_ACTION_LOST_PASSWORD:      Lost password action
+ *
+ * The actions that can be performed on an authentication.
+ **/
+typedef enum {
+       GS_AUTH_ACTION_LOGIN,
+       GS_AUTH_ACTION_LOGOUT,
+       GS_AUTH_ACTION_REGISTER,
+       GS_AUTH_ACTION_LOST_PASSWORD,
+       /*< private >*/
+       GS_AUTH_ACTION_LAST
+} GsAuthAction;
+
+/**
+ * GsAuthStoreFlags:
+ * @GS_AUTH_STORE_FLAG_NONE:           No special flags set
+ * @GS_AUTH_STORE_FLAG_USERNAME:       Load or save the username
+ * @GS_AUTH_STORE_FLAG_PASSWORD:       Load or save the password
+ * @GS_AUTH_STORE_FLAG_METADATA:       Load or save any metadata
+ *
+ * The flags used when loading or saving the authentication to disk.
+ **/
+typedef enum {
+       GS_AUTH_STORE_FLAG_NONE = 0,
+       GS_AUTH_STORE_FLAG_USERNAME     = 1 << 0,
+       GS_AUTH_STORE_FLAG_PASSWORD     = 1 << 1,
+       GS_AUTH_STORE_FLAG_METADATA     = 1 << 2,
+       /*< private >*/
+       GS_AUTH_STORE_FLAG_LAST
+} GsAuthStoreFlags;
+
+GsAuth         *gs_auth_new                    (const gchar    *provider_id);
+const gchar    *gs_auth_get_provider_id        (GsAuth         *auth);
+const gchar    *gs_auth_get_provider_name      (GsAuth         *auth);
+void            gs_auth_set_provider_name      (GsAuth         *auth,
+                                                const gchar    *provider_name);
+const gchar    *gs_auth_get_provider_logo      (GsAuth         *auth);
+void            gs_auth_set_provider_logo      (GsAuth         *auth,
+                                                const gchar    *provider_logo);
+const gchar    *gs_auth_get_provider_uri       (GsAuth         *auth);
+void            gs_auth_set_provider_uri       (GsAuth         *auth,
+                                                const gchar    *provider_uri);
+const gchar    *gs_auth_get_provider_schema    (GsAuth         *auth);
+void            gs_auth_set_provider_schema    (GsAuth         *auth,
+                                                const gchar    *provider_schema);
+const gchar    *gs_auth_get_username           (GsAuth         *auth);
+void            gs_auth_set_username           (GsAuth         *auth,
+                                                const gchar    *username);
+const gchar    *gs_auth_get_password           (GsAuth         *auth);
+void            gs_auth_set_password           (GsAuth         *auth,
+                                                const gchar    *password);
+const gchar    *gs_auth_get_pin                (GsAuth         *auth);
+void            gs_auth_set_pin                (GsAuth         *auth,
+                                                const gchar    *pin);
+GsAuthFlags     gs_auth_get_flags              (GsAuth         *auth);
+void            gs_auth_set_flags              (GsAuth         *auth,
+                                                GsAuthFlags     flags);
+void            gs_auth_add_flags              (GsAuth         *auth,
+                                                GsAuthFlags     flags);
+gboolean        gs_auth_has_flag               (GsAuth         *auth,
+                                                GsAuthFlags     flags);
+const gchar    *gs_auth_get_metadata_item      (GsAuth         *auth,
+                                                const gchar    *key);
+void            gs_auth_add_metadata           (GsAuth         *auth,
+                                                const gchar    *key,
+                                                const gchar    *value);
+gboolean        gs_auth_store_load             (GsAuth         *auth,
+                                                GsAuthStoreFlags flags,
+                                                GCancellable   *cancellable,
+                                                GError         **error);
+gboolean        gs_auth_store_save             (GsAuth         *auth,
+                                                GsAuthStoreFlags flags,
+                                                GCancellable   *cancellable,
+                                                GError         **error);
+
+G_END_DECLS
+
+#endif /* __GS_AUTH_H */
+
+/* vim: set noexpandtab: */
diff --git a/src/gs-page.c b/src/gs-page.c
index 30104eb..852b7e3 100644
--- a/src/gs-page.c
+++ b/src/gs-page.c
@@ -28,6 +28,7 @@
 #include "gs-page.h"
 #include "gs-shell.h"
 #include "gs-utils.h"
+#include "gs-auth-dialog.h"
 
 typedef struct
 {
@@ -58,6 +59,60 @@ gs_page_helper_free (GsPageHelper *helper)
 static void
 gs_page_app_installed_cb (GObject *source,
                           GAsyncResult *res,
+                          gpointer user_data);
+
+static void
+gs_page_install_authenticate_cb (GtkDialog *dialog,
+                                GtkResponseType response_type,
+                                GsPageHelper *helper)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+       /* unmap the dialog */
+       gtk_widget_destroy (GTK_WIDGET (dialog));
+
+       if (response_type != GTK_RESPONSE_OK) {
+               gs_page_helper_free (helper);
+               return;
+       }
+       gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                          helper->app,
+                                          GS_PLUGIN_LOADER_ACTION_INSTALL,
+                                          priv->cancellable,
+                                          gs_page_app_installed_cb,
+                                          helper);
+}
+
+static void
+gs_page_app_removed_cb (GObject *source,
+                        GAsyncResult *res,
+                        gpointer user_data);
+
+static void
+gs_page_remove_authenticate_cb (GtkDialog *dialog,
+                               GtkResponseType response_type,
+                               GsPageHelper *helper)
+{
+       GsPagePrivate *priv = gs_page_get_instance_private (helper->page);
+
+       /* unmap the dialog */
+       gtk_widget_destroy (GTK_WIDGET (dialog));
+
+       if (response_type != GTK_RESPONSE_OK) {
+               gs_page_helper_free (helper);
+               return;
+       }
+       gs_plugin_loader_app_action_async (priv->plugin_loader,
+                                          helper->app,
+                                          GS_PLUGIN_LOADER_ACTION_REMOVE,
+                                          priv->cancellable,
+                                          gs_page_app_removed_cb,
+                                          helper);
+}
+
+static void
+gs_page_app_installed_cb (GObject *source,
+                          GAsyncResult *res,
                           gpointer user_data)
 {
        GError *last_error;
@@ -72,6 +127,27 @@ gs_page_app_installed_cb (GObject *source,
                                                  res,
                                                  &error);
        if (!ret) {
+               /* try to authenticate then retry */
+               if (g_error_matches (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
+                       g_autoptr(GError) error_local = NULL;
+                       GtkWidget *dialog;
+                       dialog = gs_auth_dialog_new (priv->plugin_loader,
+                                                    helper->app,
+                                                    gs_utils_get_error_value (error),
+                                                    &error_local);
+                       if (dialog == NULL) {
+                               g_warning ("%s", error_local->message);
+                               return;
+                       }
+                       gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+                       g_signal_connect (dialog, "response",
+                                         G_CALLBACK (gs_page_install_authenticate_cb),
+                                         g_steal_pointer (&helper));
+                       return;
+               }
+
                g_warning ("failed to install %s: %s",
                           gs_app_get_id (helper->app),
                           error->message);
@@ -124,6 +200,27 @@ gs_page_app_removed_cb (GObject *source,
                                                  res,
                                                  &error);
        if (!ret) {
+               /* try to authenticate then retry */
+               if (g_error_matches (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
+                       g_autoptr(GError) error_local = NULL;
+                       GtkWidget *dialog;
+                       dialog = gs_auth_dialog_new (priv->plugin_loader,
+                                                    helper->app,
+                                                    gs_utils_get_error_value (error),
+                                                    &error_local);
+                       if (dialog == NULL) {
+                               g_warning ("%s", error_local->message);
+                               return;
+                       }
+                       gs_shell_modal_dialog_present (priv->shell, GTK_DIALOG (dialog));
+                       g_signal_connect (dialog, "response",
+                                         G_CALLBACK (gs_page_remove_authenticate_cb),
+                                         g_steal_pointer (&helper));
+                       return;
+               }
+
                g_warning ("failed to remove: %s", error->message);
                gs_app_notify_failed_modal (helper->app,
                                            gs_shell_get_window (priv->shell),
diff --git a/src/gs-plugin-loader.c b/src/gs-plugin-loader.c
index fb3c50f..f0f4051 100644
--- a/src/gs-plugin-loader.c
+++ b/src/gs-plugin-loader.c
@@ -39,6 +39,7 @@ typedef struct
        GsPluginStatus           status_last;
        AsProfile               *profile;
        SoupSession             *soup_session;
+       GPtrArray               *auth_array;
 
        GMutex                   pending_apps_mutex;
        GPtrArray               *pending_apps;
@@ -74,6 +75,7 @@ typedef struct {
        GsCategory                      *category;
        GsApp                           *app;
        GsReview                        *review;
+       GsAuth                          *auth;
 } GsPluginLoaderAsyncState;
 
 static void
@@ -83,6 +85,8 @@ gs_plugin_loader_free_async_state (GsPluginLoaderAsyncState *state)
                g_object_unref (state->category);
        if (state->app != NULL)
                g_object_unref (state->app);
+       if (state->auth != NULL)
+               g_object_unref (state->auth);
        if (state->review != NULL)
                g_object_unref (state->review);
 
@@ -589,6 +593,16 @@ gs_plugin_loader_set_app_error (GsApp *app, GError *error)
        }
 }
 
+static gboolean
+gs_plugin_loader_is_auth_error (GError *err)
+{
+       if (g_error_matches (err, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_REQUIRED))
+               return TRUE;
+       if (g_error_matches (err, GS_PLUGIN_ERROR, GS_PLUGIN_ERROR_AUTH_INVALID))
+               return TRUE;
+       return FALSE;
+}
+
 /**
  * gs_plugin_loader_run_action:
  **/
@@ -630,6 +644,13 @@ gs_plugin_loader_run_action (GsPluginLoader *plugin_loader,
                ret = plugin_func (plugin, app, cancellable, &error_local);
                g_rw_lock_reader_unlock (&plugin->rwlock);
                if (!ret) {
+                       /* abort early to allow main thread to process */
+                       if (gs_plugin_loader_is_auth_error (error_local)) {
+                               g_propagate_error (error, error_local);
+                               error_local = NULL;
+                               return FALSE;
+                       }
+
                        g_warning ("failed to call %s on %s: %s",
                                   function_name, plugin->name,
                                   error_local->message);
@@ -2445,6 +2466,13 @@ gs_plugin_loader_review_action_thread_cb (GTask *task,
                                   cancellable, &error_local);
                g_rw_lock_reader_unlock (&plugin->rwlock);
                if (!ret) {
+                       /* abort early to allow main thread to process */
+                       if (gs_plugin_loader_is_auth_error (error_local)) {
+                               g_task_return_error (task, error_local);
+                               error_local = NULL;
+                               return;
+                       }
+
                        g_warning ("failed to call %s on %s: %s",
                                   state->function_name, plugin->name,
                                   error_local->message);
@@ -2756,6 +2784,134 @@ gs_plugin_loader_review_action_async (GsPluginLoader *plugin_loader,
        g_task_run_in_thread (task, gs_plugin_loader_review_action_thread_cb);
 }
 
+/******************************************************************************/
+
+/**
+ * gs_plugin_loader_auth_action_thread_cb:
+ **/
+static void
+gs_plugin_loader_auth_action_thread_cb (GTask *task,
+                                         gpointer object,
+                                         gpointer task_data,
+                                         GCancellable *cancellable)
+{
+       GError *error = NULL;
+       GsPluginLoaderAsyncState *state = (GsPluginLoaderAsyncState *) task_data;
+       GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (object);
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       GsPlugin *plugin;
+       GsPluginAuthFunc plugin_func = NULL;
+       gboolean exists;
+       gboolean ret;
+       guint i;
+
+       /* run each plugin */
+       for (i = 0; i < priv->plugins->len; i++) {
+               g_autoptr(AsProfileTask) ptask = NULL;
+               g_autoptr(GError) error_local = NULL;
+
+               plugin = g_ptr_array_index (priv->plugins, i);
+               if (!plugin->enabled)
+                       continue;
+               if (g_cancellable_set_error_if_cancelled (cancellable, &error))
+                       g_task_return_error (task, error);
+
+               exists = g_module_symbol (plugin->module,
+                                         state->function_name,
+                                         (gpointer *) &plugin_func);
+               if (!exists)
+                       continue;
+               ptask = as_profile_start (priv->profile,
+                                         "GsPlugin::%s(%s)",
+                                         plugin->name,
+                                         state->function_name);
+               g_rw_lock_reader_lock (&plugin->rwlock);
+               ret = plugin_func (plugin, state->auth, cancellable, &error_local);
+               g_rw_lock_reader_unlock (&plugin->rwlock);
+               if (!ret) {
+                       /* badly behaved plugin */
+                       if (error_local == NULL) {
+                               g_critical ("%s did not set error for %s",
+                                           plugin->name,
+                                           state->function_name);
+                               continue;
+                       }
+
+                       /* stop running other plugins on failure */
+                       g_task_return_error (task, error_local);
+                       error_local = NULL;
+                       return;
+               }
+               gs_plugin_status_update (plugin, NULL, GS_PLUGIN_STATUS_FINISHED);
+       }
+
+       g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * gs_plugin_loader_auth_action_async:
+ **/
+void
+gs_plugin_loader_auth_action_async (GsPluginLoader *plugin_loader,
+                                   GsAuth *auth,
+                                   GsAuthAction action,
+                                   GCancellable *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+       GsPluginLoaderAsyncState *state;
+       g_autoptr(GTask) task = NULL;
+
+       g_return_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader));
+       g_return_if_fail (GS_IS_AUTH (auth));
+       g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+       /* save state */
+       state = g_slice_new0 (GsPluginLoaderAsyncState);
+       state->auth = g_object_ref (auth);
+
+       switch (action) {
+       case GS_AUTH_ACTION_LOGIN:
+               state->function_name = "gs_plugin_auth_login";
+               break;
+       case GS_AUTH_ACTION_LOGOUT:
+               state->function_name = "gs_plugin_auth_logout";
+               break;
+       case GS_AUTH_ACTION_REGISTER:
+               state->function_name = "gs_plugin_auth_register";
+               break;
+       case GS_AUTH_ACTION_LOST_PASSWORD:
+               state->function_name = "gs_plugin_auth_lost_password";
+               break;
+       default:
+               g_assert_not_reached ();
+               break;
+       }
+
+       /* run in a thread */
+       task = g_task_new (plugin_loader, cancellable, callback, user_data);
+       g_task_set_task_data (task, state, (GDestroyNotify) gs_plugin_loader_free_async_state);
+       g_task_run_in_thread (task, gs_plugin_loader_auth_action_thread_cb);
+}
+
+/**
+ * gs_plugin_loader_auth_action_finish:
+ **/
+gboolean
+gs_plugin_loader_auth_action_finish (GsPluginLoader *plugin_loader,
+                                    GAsyncResult *res,
+                                    GError **error)
+{
+       g_return_val_if_fail (GS_IS_PLUGIN_LOADER (plugin_loader), FALSE);
+       g_return_val_if_fail (G_IS_TASK (res), FALSE);
+       g_return_val_if_fail (g_task_is_valid (res, plugin_loader), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+/******************************************************************************/
+
 /**
  * gs_plugin_loader_app_action_finish:
  *
@@ -2984,6 +3140,7 @@ gs_plugin_loader_open_plugin (GsPluginLoader *plugin_loader,
        plugin->updates_changed_user_data = plugin_loader;
        plugin->profile = g_object_ref (priv->profile);
        plugin->soup_session = g_object_ref (priv->soup_session);
+       plugin->auth_array = g_ptr_array_ref (priv->auth_array);
        plugin->scale = gs_plugin_loader_get_scale (plugin_loader);
        g_debug ("opened plugin %s: %s", filename, plugin->name);
 
@@ -3024,6 +3181,25 @@ gs_plugin_loader_get_scale (GsPluginLoader *plugin_loader)
 }
 
 /**
+ * gs_plugin_loader_get_auth_by_id:
+ */
+GsAuth *
+gs_plugin_loader_get_auth_by_id (GsPluginLoader *plugin_loader,
+                                const gchar *provider_id)
+{
+       GsPluginLoaderPrivate *priv = gs_plugin_loader_get_instance_private (plugin_loader);
+       guint i;
+
+       /* match on ID */
+       for (i = 0; i < priv->auth_array->len; i++) {
+               GsAuth *auth = g_ptr_array_index (priv->auth_array, i);
+               if (g_strcmp0 (gs_auth_get_provider_id (auth), provider_id) == 0)
+                       return auth;
+       }
+       return NULL;
+}
+
+/**
  * gs_plugin_loader_set_location:
  */
 void
@@ -3298,6 +3474,7 @@ gs_plugin_loader_dispose (GObject *object)
        g_clear_object (&priv->soup_session);
        g_clear_object (&priv->profile);
        g_clear_object (&priv->settings);
+       g_clear_pointer (&priv->auth_array, g_ptr_array_unref);
        g_clear_pointer (&priv->pending_apps, g_ptr_array_unref);
 
        G_OBJECT_CLASS (gs_plugin_loader_parent_class)->dispose (object);
@@ -3369,6 +3546,7 @@ gs_plugin_loader_init (GsPluginLoader *plugin_loader)
        priv->plugins = g_ptr_array_new_with_free_func ((GDestroyNotify) gs_plugin_loader_plugin_free);
        priv->status_last = GS_PLUGIN_STATUS_LAST;
        priv->pending_apps = g_ptr_array_new_with_free_func ((GFreeFunc) g_object_unref);
+       priv->auth_array = g_ptr_array_new_with_free_func ((GFreeFunc) g_object_unref);
        priv->profile = as_profile_new ();
        priv->settings = g_settings_new ("org.gnome.software");
 
diff --git a/src/gs-plugin-loader.h b/src/gs-plugin-loader.h
index af4f4ea..5f79433 100644
--- a/src/gs-plugin-loader.h
+++ b/src/gs-plugin-loader.h
@@ -25,6 +25,7 @@
 #include <glib-object.h>
 
 #include "gs-app.h"
+#include "gs-auth.h"
 #include "gs-category.h"
 #include "gs-plugin.h"
 
@@ -196,6 +197,8 @@ gboolean     gs_plugin_loader_get_enabled           (GsPluginLoader *plugin_loader,
                                                         const gchar    *plugin_name);
 void            gs_plugin_loader_set_location          (GsPluginLoader *plugin_loader,
                                                         const gchar    *location);
+GsAuth         *gs_plugin_loader_get_auth_by_id        (GsPluginLoader *plugin_loader,
+                                                        const gchar    *provider_id);
 gint            gs_plugin_loader_get_scale             (GsPluginLoader *plugin_loader);
 void            gs_plugin_loader_set_scale             (GsPluginLoader *plugin_loader,
                                                         gint            scale);
@@ -227,6 +230,15 @@ void                gs_plugin_loader_review_action_async   (GsPluginLoader 
*plugin_loader,
 gboolean        gs_plugin_loader_review_action_finish  (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
+gboolean        gs_plugin_loader_auth_action_finish    (GsPluginLoader *plugin_loader,
+                                                        GAsyncResult   *res,
+                                                        GError         **error);
+void            gs_plugin_loader_auth_action_async     (GsPluginLoader *plugin_loader,
+                                                        GsAuth         *auth,
+                                                        GsAuthAction    action,
+                                                        GCancellable   *cancellable,
+                                                        GAsyncReadyCallback callback,
+                                                        gpointer        user_data);
 gboolean        gs_plugin_loader_refresh_finish        (GsPluginLoader *plugin_loader,
                                                         GAsyncResult   *res,
                                                         GError         **error);
diff --git a/src/gs-plugin.c b/src/gs-plugin.c
index 92e82b6..c85b15d 100644
--- a/src/gs-plugin.c
+++ b/src/gs-plugin.c
@@ -79,6 +79,32 @@ gs_plugin_set_enabled (GsPlugin *plugin, gboolean enabled)
 }
 
 /**
+ * gs_plugin_add_auth:
+ **/
+void
+gs_plugin_add_auth (GsPlugin *plugin, GsAuth *auth)
+{
+       g_ptr_array_add (plugin->auth_array, g_object_ref (auth));
+}
+
+/**
+ * gs_plugin_get_auth_by_id:
+ **/
+GsAuth *
+gs_plugin_get_auth_by_id (GsPlugin *plugin, const gchar *provider_id)
+{
+       guint i;
+
+       /* match on ID */
+       for (i = 0; i < plugin->auth_array->len; i++) {
+               GsAuth *auth = g_ptr_array_index (plugin->auth_array, i);
+               if (g_strcmp0 (gs_auth_get_provider_id (auth), provider_id) == 0)
+                       return auth;
+       }
+       return NULL;
+}
+
+/**
  * gs_plugin_check_distro_id:
  **/
 gboolean
diff --git a/src/gs-plugin.h b/src/gs-plugin.h
index a95ed0b..17cea10 100644
--- a/src/gs-plugin.h
+++ b/src/gs-plugin.h
@@ -31,6 +31,7 @@
 
 #include "gs-app.h"
 #include "gs-category.h"
+#include "gs-auth.h"
 
 G_BEGIN_DECLS
 
@@ -79,6 +80,7 @@ struct GsPlugin {
        gpointer                 updates_changed_user_data;
        AsProfile               *profile;
        SoupSession             *soup_session;
+       GPtrArray               *auth_array;
        GRWLock                  rwlock;
 };
 
@@ -89,6 +91,10 @@ typedef enum {
        GS_PLUGIN_ERROR_NO_NETWORK,
        GS_PLUGIN_ERROR_NO_SECURITY,
        GS_PLUGIN_ERROR_NO_SPACE,
+       GS_PLUGIN_ERROR_AUTH_REQUIRED,
+       GS_PLUGIN_ERROR_AUTH_INVALID,
+       GS_PLUGIN_ERROR_PIN_REQUIRED,
+       /*< private >*/
        GS_PLUGIN_ERROR_LAST
 } GsPluginError;
 
@@ -177,6 +183,10 @@ typedef gboolean    (*GsPluginReviewFunc)          (GsPlugin       *plugin,
                                                         GsReview       *review,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+typedef gboolean        (*GsPluginAuthFunc)            (GsPlugin       *plugin,
+                                                        GsAuth         *auth,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 typedef gboolean        (*GsPluginRefineFunc)          (GsPlugin       *plugin,
                                                         GList          **list,
                                                         GsPluginRefineFlags flags,
@@ -207,6 +217,10 @@ void                gs_plugin_initialize                   (GsPlugin       *plugin);
 void            gs_plugin_destroy                      (GsPlugin       *plugin);
 void            gs_plugin_set_enabled                  (GsPlugin       *plugin,
                                                         gboolean        enabled);
+void            gs_plugin_add_auth                     (GsPlugin       *plugin,
+                                                        GsAuth         *auth);
+GsAuth         *gs_plugin_get_auth_by_id               (GsPlugin       *plugin,
+                                                        const gchar    *provider_id);
 GBytes         *gs_plugin_download_data                (GsPlugin       *plugin,
                                                         GsApp          *app,
                                                         const gchar    *uri,
@@ -389,6 +403,22 @@ gboolean    gs_plugin_offline_update               (GsPlugin       *plugin,
                                                         GList          *apps,
                                                         GCancellable   *cancellable,
                                                         GError         **error);
+gboolean        gs_plugin_auth_login                   (GsPlugin       *plugin,
+                                                        GsAuth         *auth,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_plugin_auth_logout                  (GsPlugin       *plugin,
+                                                        GsAuth         *auth,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_plugin_auth_lost_password           (GsPlugin       *plugin,
+                                                        GsAuth         *auth,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
+gboolean        gs_plugin_auth_register                (GsPlugin       *plugin,
+                                                        GsAuth         *auth,
+                                                        GCancellable   *cancellable,
+                                                        GError         **error);
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsAppList, gs_plugin_list_free)
 
diff --git a/src/gs-self-test.c b/src/gs-self-test.c
index 7a0cb1b..2d6d390 100644
--- a/src/gs-self-test.c
+++ b/src/gs-self-test.c
@@ -423,6 +423,45 @@ gs_plugin_loader_webapps_func (GsPluginLoader *plugin_loader)
        g_assert (gs_app_get_pixbuf (app) != NULL);
 }
 
+static void
+gs_auth_secret_func (void)
+{
+       gboolean ret;
+       g_autoptr(GError) error = NULL;
+       g_autoptr(GsAuth) auth1 = NULL;
+       g_autoptr(GsAuth) auth2 = NULL;
+
+       /* save secrets to disk */
+       auth1 = gs_auth_new ("self-test");
+       gs_auth_set_provider_schema (auth1, "org.gnome.Software.Dummy");
+       gs_auth_set_username (auth1, "hughsie");
+       gs_auth_set_password (auth1, "foobarbaz");
+       gs_auth_add_metadata (auth1, "day", "monday");
+       ret = gs_auth_store_save (auth1,
+                                 GS_AUTH_STORE_FLAG_USERNAME |
+                                 GS_AUTH_STORE_FLAG_PASSWORD |
+                                 GS_AUTH_STORE_FLAG_METADATA,
+                                 NULL, &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+
+       /* load secrets from disk */
+       auth2 = gs_auth_new ("self-test");
+       gs_auth_add_metadata (auth2, "day", NULL);
+       gs_auth_add_metadata (auth2, "notgoingtoexist", NULL);
+       gs_auth_set_provider_schema (auth2, "org.gnome.Software.Dummy");
+       ret = gs_auth_store_load (auth2,
+                                 GS_AUTH_STORE_FLAG_USERNAME |
+                                 GS_AUTH_STORE_FLAG_PASSWORD |
+                                 GS_AUTH_STORE_FLAG_METADATA,
+                                 NULL, &error);
+       g_assert_no_error (error);
+       g_assert (ret);
+       g_assert_cmpstr (gs_auth_get_username (auth2), ==, "hughsie");
+       g_assert_cmpstr (gs_auth_get_password (auth2), ==, "foobarbaz");
+       g_assert_cmpstr (gs_auth_get_metadata_item (auth2, "day"), ==, "monday");
+}
+
 int
 main (int argc, char **argv)
 {
@@ -507,6 +546,7 @@ main (int argc, char **argv)
        /* generic tests go here */
        g_test_add_func ("/gnome-software/app", gs_app_func);
        g_test_add_func ("/gnome-software/plugin", gs_plugin_func);
+       g_test_add_func ("/gnome-software/auth{secret}", gs_auth_secret_func);
 
        /* we can only load this once per process */
        plugin_loader = gs_plugin_loader_new ();
diff --git a/src/gs-shell-details.c b/src/gs-shell-details.c
index 5908762..878ac96 100644
--- a/src/gs-shell-details.c
+++ b/src/gs-shell-details.c
@@ -30,6 +30,7 @@
 
 #include "gs-shell-details.h"
 #include "gs-app-addon-row.h"
+#include "gs-auth-dialog.h"
 #include "gs-history-dialog.h"
 #include "gs-screenshot-image.h"
 #include "gs-progress-button.h"
@@ -958,24 +959,88 @@ gs_shell_details_refresh_addons (GsShellDetails *self)
 
 static void gs_shell_details_refresh_reviews (GsShellDetails *self);
 
+typedef struct {
+       GsShellDetails          *self;
+       GsReview                *review;
+       GsApp                   *app;
+       GsReviewAction           action;
+} GsShellDetailsReviewHelper;
+
+static void
+gs_shell_details_review_helper_free (GsShellDetailsReviewHelper *helper)
+{
+       g_object_unref (helper->self);
+       g_object_unref (helper->review);
+       g_object_unref (helper->app);
+       g_free (helper);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(GsShellDetailsReviewHelper, gs_shell_details_review_helper_free);
+
+static void
+gs_shell_details_app_set_review_cb (GObject *source,
+                                   GAsyncResult *res,
+                                   gpointer user_data);
+
+static void
+gs_shell_details_authenticate_cb (GtkDialog *dialog,
+                                 GtkResponseType response_type,
+                                 GsShellDetailsReviewHelper *helper)
+{
+       /* unmap the dialog */
+       gtk_widget_destroy (GTK_WIDGET (dialog));
+
+       if (response_type != GTK_RESPONSE_OK) {
+               gs_shell_details_review_helper_free (helper);
+               return;
+       }
+       gs_plugin_loader_review_action_async (helper->self->plugin_loader,
+                                             helper->app,
+                                             helper->review,
+                                             helper->action,
+                                             helper->self->cancellable,
+                                             gs_shell_details_app_set_review_cb,
+                                             helper);
+}
+
 /**
  * gs_shell_details_app_set_review_cb:
  **/
 static void
 gs_shell_details_app_set_review_cb (GObject *source,
-                               GAsyncResult *res,
-                               gpointer user_data)
+                                   GAsyncResult *res,
+                                   gpointer user_data)
 {
        GsPluginLoader *plugin_loader = GS_PLUGIN_LOADER (source);
-       GsShellDetails *self = GS_SHELL_DETAILS (user_data);
+       g_autoptr(GsShellDetailsReviewHelper) helper = (GsShellDetailsReviewHelper *) user_data;
        g_autoptr(GError) error = NULL;
 
        if (!gs_plugin_loader_app_action_finish (plugin_loader, res, &error)) {
-               g_warning ("failed to set review %s: %s",
-                          gs_app_get_id (self->app), error->message);
+               /* try to authenticate then retry */
+               if (g_error_matches (error,
+                                    GS_PLUGIN_ERROR,
+                                    GS_PLUGIN_ERROR_AUTH_REQUIRED)) {
+                       g_autoptr(GError) error_local = NULL;
+                       GtkWidget *dialog;
+                       dialog = gs_auth_dialog_new (helper->self->plugin_loader,
+                                                    helper->app,
+                                                    gs_utils_get_error_value (error),
+                                                    &error_local);
+                       if (dialog == NULL) {
+                               g_warning ("%s", error_local->message);
+                               return;
+                       }
+                       gs_shell_modal_dialog_present (helper->self->shell, GTK_DIALOG (dialog));
+                       g_signal_connect (dialog, "response",
+                                         G_CALLBACK (gs_shell_details_authenticate_cb),
+                                         g_steal_pointer (&helper));
+                       return;
+               }
+               g_warning ("failed to set review on %s: %s",
+                          gs_app_get_id (helper->app), error->message);
                return;
        }
-       gs_shell_details_refresh_reviews (self);
+       gs_shell_details_refresh_reviews (helper->self);
 }
 
 static void
@@ -983,13 +1048,18 @@ gs_shell_details_review_button_clicked_cb (GsReviewRow *row,
                                           GsReviewAction action,
                                           GsShellDetails *self)
 {
+       GsShellDetailsReviewHelper *helper = g_new0 (GsShellDetailsReviewHelper, 1);
+       helper->self = g_object_ref (self);
+       helper->app = g_object_ref (self->app);
+       helper->review = g_object_ref (gs_review_row_get_review (row));
+       helper->action = action;
        gs_plugin_loader_review_action_async (self->plugin_loader,
-                                             self->app,
-                                             gs_review_row_get_review (row),
-                                             action,
+                                             helper->app,
+                                             helper->review,
+                                             helper->action,
                                              self->cancellable,
                                              gs_shell_details_app_set_review_cb,
-                                             self);
+                                             helper);
 }
 
 static void
@@ -1428,14 +1498,14 @@ gs_shell_details_review_response_cb (GtkDialog *dialog,
        g_autofree gchar *text = NULL;
        g_autoptr(GDateTime) now = NULL;
        g_autoptr(GsReview) review = NULL;
+       GsShellDetailsReviewHelper *helper;
        GsReviewDialog *rdialog = GS_REVIEW_DIALOG (dialog);
 
-       /* unmap the dialog */
-       gtk_widget_destroy (GTK_WIDGET (dialog));
-
        /* not agreed */
-       if (response != GTK_RESPONSE_OK)
+       if (response != GTK_RESPONSE_OK) {
+               gtk_widget_destroy (GTK_WIDGET (dialog));
                return;
+       }
 
        review = gs_review_new ();
        gs_review_set_summary (review, gs_review_dialog_get_summary (rdialog));
@@ -1447,13 +1517,21 @@ gs_shell_details_review_response_cb (GtkDialog *dialog,
        gs_review_set_date (review, now);
 
        /* call into the plugins to set the new value */
+       helper = g_new0 (GsShellDetailsReviewHelper, 1);
+       helper->self = g_object_ref (self);
+       helper->app = g_object_ref (self->app);
+       helper->review = g_object_ref (review);
+       helper->action = GS_REVIEW_ACTION_SUBMIT;
        gs_plugin_loader_review_action_async (self->plugin_loader,
-                                             self->app,
-                                             review,
-                                             GS_REVIEW_ACTION_SUBMIT,
+                                             helper->app,
+                                             helper->review,
+                                             helper->action,
                                              self->cancellable,
                                              gs_shell_details_app_set_review_cb,
-                                             self);
+                                             helper);
+
+       /* unmap the dialog */
+       gtk_widget_destroy (GTK_WIDGET (dialog));
 }
 
 /**
diff --git a/src/gs-utils.c b/src/gs-utils.c
index 9fcf745..15702f6 100644
--- a/src/gs-utils.c
+++ b/src/gs-utils.c
@@ -677,6 +677,28 @@ insert_details_widget (GtkMessageDialog *dialog, const gchar *details)
 }
 
 /**
+ * gs_utils_get_error_value:
+ * @error: A GError
+ *
+ * Gets the machine-readable value stored in the error message.
+ * The machine readable string is after the first "@", e.g.
+ * message = "Requires authentication with @aaa"
+ *
+ * Returns: a string, or %NULL
+ */
+const gchar *
+gs_utils_get_error_value (const GError *error)
+{
+       gchar *str;
+       if (error == NULL)
+               return NULL;
+       str = g_strstr_len (error->message, -1, "@");
+       if (str == NULL)
+               return NULL;
+       return (const gchar *) str + 1;
+}
+
+/**
  * gs_utils_show_error_dialog:
  * @parent: transient parent, or NULL for none
  * @title: the title for the dialog
diff --git a/src/gs-utils.h b/src/gs-utils.h
index 0fd8b1e..6990ca9 100644
--- a/src/gs-utils.h
+++ b/src/gs-utils.h
@@ -70,6 +70,7 @@ gboolean       gs_utils_is_current_desktop    (const gchar    *name);
 gboolean        gs_utils_is_current_desktop    (const gchar    *name);
 void            gs_utils_widget_set_custom_css (GtkWidget      *widget,
                                                 const gchar    *css);
+const gchar    *gs_utils_get_error_value       (const GError   *error);
 void            gs_utils_show_error_dialog     (GtkWindow      *parent,
                                                 const gchar    *title,
                                                 const gchar    *msg,


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]