[fractal/fractal-next] login: Add SSO support



commit d8e58a76aa402b5ec0f9ee8debd3a14bbe58e6c4
Author: JCWasmx86 <JCWasmx86 t-online de>
Date:   Thu Feb 3 17:20:49 2022 +0100

    login: Add SSO support

 Cargo.lock                                         | 105 ++++++++++
 Cargo.toml                                         |   1 +
 .../icons/scalable/actions/idp-apple-dark.svg      |   1 +
 .../resources/icons/scalable/actions/idp-apple.svg |   1 +
 .../icons/scalable/actions/idp-facebook.svg        |   1 +
 .../icons/scalable/actions/idp-github-dark.svg     |   1 +
 .../icons/scalable/actions/idp-github.svg          |   1 +
 .../icons/scalable/actions/idp-gitlab.svg          |   1 +
 .../icons/scalable/actions/idp-google-dark.svg     |   1 +
 .../icons/scalable/actions/idp-google.svg          |   1 +
 .../icons/scalable/actions/idp-twitter.svg         |   1 +
 data/resources/resources.gresource.xml             |  10 +
 data/resources/style.css                           |   6 +
 data/resources/ui/idp-button.ui                    |  10 +
 data/resources/ui/login.ui                         |  48 ++++-
 src/idp_button.rs                                  | 231 +++++++++++++++++++++
 src/login.rs                                       | 110 +++++++++-
 src/main.rs                                        |   1 +
 src/session/mod.rs                                 |  56 +++++
 19 files changed, 584 insertions(+), 3 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index e7ec3ea7b..db3baa238 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1711,6 +1711,31 @@ dependencies = [
  "ahash",
 ]
 
+[[package]]
+name = "headers"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d"
+dependencies = [
+ "base64",
+ "bitflags",
+ "bytes",
+ "headers-core",
+ "http",
+ "httpdate",
+ "mime",
+ "sha-1",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+dependencies = [
+ "http",
+]
+
 [[package]]
 name = "heck"
 version = "0.3.3"
@@ -2239,14 +2264,17 @@ dependencies = [
  "matrix-sdk-common",
  "matrix-sdk-sled",
  "mime",
+ "rand 0.8.5",
  "reqwest",
  "ruma",
  "serde",
  "serde_json",
  "thiserror",
  "tokio",
+ "tokio-stream",
  "tracing",
  "url",
+ "warp",
  "zeroize",
 ]
 
@@ -2872,6 +2900,26 @@ dependencies = [
  "siphasher",
 ]
 
+[[package]]
+name = "pin-project"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb"
+dependencies = [
+ "proc-macro2 1.0.36",
+ "quote 1.0.15",
+ "syn 1.0.86",
+]
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.8"
@@ -3558,6 +3606,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
 [[package]]
 name = "scoped_threadpool"
 version = "0.1.9"
@@ -3671,6 +3725,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "sha-1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest 0.10.3",
+]
+
 [[package]]
 name = "sha1"
 version = "0.6.1"
@@ -4115,6 +4180,17 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tokio-stream"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
 [[package]]
 name = "tokio-util"
 version = "0.6.9"
@@ -4151,6 +4227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f"
 dependencies = [
  "cfg-if 1.0.0",
+ "log",
  "pin-project-lite",
  "tracing-attributes",
  "tracing-core",
@@ -4370,6 +4447,34 @@ dependencies = [
  "try-lock",
 ]
 
+[[package]]
+name = "warp"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "3cef4e1e9114a4b7f1ac799f16ce71c14de5778500c5450ec6b7b920c55b587e"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "headers",
+ "http",
+ "hyper",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project",
+ "scoped-tls",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-stream",
+ "tokio-util",
+ "tower-service",
+ "tracing",
+]
+
 [[package]]
 name = "wasi"
 version = "0.9.0+wasi-snapshot-preview1"
diff --git a/Cargo.toml b/Cargo.toml
index 0d38a5bf1..8c0c40284 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -66,5 +66,6 @@ features = [
     "encryption",
     "sled_cryptostore",
     "sled_state_store",
+    "sso_login",
     "markdown",
 ]
diff --git a/data/resources/icons/scalable/actions/idp-apple-dark.svg 
b/data/resources/icons/scalable/actions/idp-apple-dark.svg
new file mode 100644
index 000000000..a1c990815
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-apple-dark.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg"; viewBox="3.78 1.25 17.75 21.82"><path 
fill-rule="evenodd" clip-rule="evenodd" d="M16.98 1.28a4.872 4.872 0 0 1-1.114 3.49 4.099 4.099 0 0 1-3.237 
1.53 4.636 4.636 0 0 1 1.144-3.36 4.957 4.957 0 0 1 3.207-1.66Zm3.974 7.428a4.949 4.949 0 0 0-2.357 4.152 
4.782 4.782 0 0 0 2.92 4.4 10.963 10.963 0 0 1-1.519 3.092c-.894 1.338-1.832 2.645-3.32 
2.669-.708.016-1.186-.187-1.684-.4-.52-.22-1.06-.451-1.907-.451-.899 
0-1.464.238-2.01.467-.47.198-.927.39-1.57.417-1.417.053-2.5-1.428-3.427-2.753-1.853-2.707-3.296-7.628-1.362-10.976a5.315
 5.315 0 0 1 4.473-2.728c.804-.017 1.576.293 2.252.565.517.208.979.393 1.357.393.332 0 .78-.178 
1.304-.386.824-.326 1.832-.727 2.859-.619 1.596.05 3.075.85 3.99 2.158Z" fill="#fff"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-apple.svg 
b/data/resources/icons/scalable/actions/idp-apple.svg
new file mode 100644
index 000000000..39d8356ca
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-apple.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg"; viewBox="3.78 1.25 17.75 21.82"><path 
fill-rule="evenodd" clip-rule="evenodd" d="M16.98 1.28a4.872 4.872 0 0 1-1.114 3.49 4.099 4.099 0 0 1-3.237 
1.53 4.636 4.636 0 0 1 1.144-3.36 4.957 4.957 0 0 1 3.207-1.66Zm3.974 7.428a4.949 4.949 0 0 0-2.357 4.152 
4.782 4.782 0 0 0 2.92 4.4 10.963 10.963 0 0 1-1.519 3.092c-.894 1.338-1.832 2.645-3.32 
2.669-.708.016-1.186-.187-1.684-.4-.52-.22-1.06-.451-1.907-.451-.899 
0-1.464.238-2.01.467-.47.198-.927.39-1.57.417-1.417.053-2.5-1.428-3.427-2.753-1.853-2.707-3.296-7.628-1.362-10.976a5.315
 5.315 0 0 1 4.473-2.728c.804-.017 1.576.293 2.252.565.517.208.979.393 1.357.393.332 0 .78-.178 
1.304-.386.824-.326 1.832-.727 2.859-.619 1.596.05 3.075.85 3.99 2.158Z" fill="#17191C"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-facebook.svg 
b/data/resources/icons/scalable/actions/idp-facebook.svg
new file mode 100644
index 000000000..9aabcdc5b
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-facebook.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; xmlns:xlink="http://www.w3.org/1999/xlink"; width="22" 
height="22"><defs><clipPath id="c"><path d="M0 0h23v22H0z"/></clipPath><clipPath id="d"><path d="M0 
0h22v22H0z"/></clipPath><g id="e" clip-path="url(#c)"><path 
style="stroke:none;fill-rule:evenodd;fill:#1877f2;fill-opacity:1" d="M22 11c0-6.074-4.926-11-11-11C4.922 
0-.004 4.926-.004 11c0 5.488 4.024 10.043 9.285 10.867V14.18H6.484V11h2.793V8.578c0-2.758 1.645-4.281 
4.157-4.281 1.207 0 2.46.215 2.46.215v2.707h-1.382c-1.367 0-1.793.847-1.793 1.718V11h3.05l-.488 
3.18H12.72v7.687C17.977 21.043 22 16.492 22 11Zm0 0"/></g><g id="g" clip-path="url(#d)"><use xlink:href="#e" 
mask="url(#f)"/></g><filter id="a" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" 
height="100%"><feColorMatrix in="SourceGraphic" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 
0"/></filter><image id="b" width="23" height="22" 
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAWCAYAAAArdgcFAAAABmJLR0QA/wD
 
/AP+gvaeTAAAALUlEQVQ4je3MQQ0AIBADsB7Bv0WkgIp9yCqggytjVigGzZs3b968+cf5xknlDwBCAg1iefLzAAAAAElFTkSuQmCC"/><mask 
id="f"><g filter="url(#a)"><use xlink:href="#b"/></g></mask></defs><use xlink:href="#g"/><path 
style="stroke:none;fill-rule:evenodd;fill:#fff;fill-opacity:1" d="M15.281 14.18 15.77 
11h-3.051V8.937c0-.87.426-1.718 1.793-1.718h1.386V4.512s-1.257-.215-2.46-.215c-2.516 0-4.157 1.523-4.157 
4.281V11H6.488v3.18h2.793v7.687c1.137.18 2.297.18 3.438 0V14.18Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-github-dark.svg 
b/data/resources/icons/scalable/actions/idp-github-dark.svg
new file mode 100644
index 000000000..fee0a9fc3
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-github-dark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="22" height="22"><path 
style="stroke:none;fill-rule:nonzero;fill:#fff;fill-opacity:1" d="M20.504 5.613a11.103 11.103 0 0 
0-4-4.101C14.82.504 12.984 0 10.988 0 8.996 0 7.156.504 5.473 1.512a11.103 11.103 0 0 0-4 4.101C.493 7.34 0 
9.227 0 11.273c0 2.454.7 4.665 2.098 6.625 1.394 1.961 3.199 3.317 5.414 4.07.258.052.449.016.574-.1a.576.576 
0 0 0 .184-.438l-.008-.797c-.004-.496-.008-.934-.008-1.305l-.328.059a3.849 3.849 0 0 1-.793.05 5.667 5.667 0 
0 1-.996-.101 2.214 2.214 0 0 1-.957-.441 1.87 1.87 0 0 
1-.633-.899l-.14-.34c-.122-.27-.27-.52-.45-.75-.207-.273-.414-.457-.625-.554l-.098-.075a1.001 1.001 0 0 
1-.316-.382c-.027-.07-.004-.125.07-.168.078-.047.215-.067.418-.067l.285.043c.192.04.426.156.707.352.286.199.512.453.696.765.218.403.484.707.793.918.312.207.625.313.937.313.317
 0 .586-.024.817-.07.226-.051.441-.126.644-.223.086-.656.32-1.16.7-1.512a9.84 9.84 0 0 1-1.465-.262 5.758 
5.758 0 0 1-1.344-.574 3.818 3.818 0 0 1-1.153-.984c-.308-.39-
 .558-.906-.753-1.54-.196-.636-.293-1.37-.293-2.202 0-1.184.378-2.192 
1.132-3.024-.355-.89-.32-1.887.098-2.996.277-.086.688-.02 1.23.2.543.222.942.41 
1.196.566.254.156.457.289.61.398a9.776 9.776 0 0 1 2.745-.383c.946 0 1.86.125 
2.75.383l.543-.355c.371-.235.809-.45 1.317-.645.504-.195.89-.25 1.156-.164.43 1.11.469 2.106.117 2.996.75.832 
1.129 1.84 1.129 3.024 0 .832-.098 1.57-.293 2.207-.195.644-.45 1.156-.758 
1.543-.324.398-.715.73-1.16.976a5.84 5.84 0 0 1-1.344.574 9.41 9.41 0 0 1-1.468.262c.496.442.746 1.137.746 
2.086v3.094c0 .176.058.324.18.441.117.117.304.153.562.102 2.215-.754 4.02-2.11 5.418-4.07 1.394-1.965 
2.094-4.172 2.094-6.63 0-2.042-.493-3.93-1.473-5.656Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-github.svg 
b/data/resources/icons/scalable/actions/idp-github.svg
new file mode 100644
index 000000000..cb628a472
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-github.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="22" height="22"><path 
style="stroke:none;fill-rule:nonzero;fill:#000;fill-opacity:1" d="M20.504 5.613a11.103 11.103 0 0 
0-4-4.101C14.82.504 12.984 0 10.988 0 8.996 0 7.156.504 5.473 1.512a11.103 11.103 0 0 0-4 4.101C.493 7.34 0 
9.227 0 11.273c0 2.454.7 4.665 2.098 6.625 1.394 1.961 3.199 3.317 5.414 4.07.258.052.449.016.574-.1a.576.576 
0 0 0 .184-.438l-.008-.797c-.004-.496-.008-.934-.008-1.305l-.328.059a3.849 3.849 0 0 1-.793.05 5.667 5.667 0 
0 1-.996-.101 2.214 2.214 0 0 1-.957-.441 1.87 1.87 0 0 
1-.633-.899l-.14-.34c-.122-.27-.27-.52-.45-.75-.207-.273-.414-.457-.625-.554l-.098-.075a1.001 1.001 0 0 
1-.316-.382c-.027-.07-.004-.125.07-.168.078-.047.215-.067.418-.067l.285.043c.192.04.426.156.707.352.286.199.512.453.696.765.218.403.484.707.793.918.312.207.625.313.937.313.317
 0 .586-.024.817-.07.226-.051.441-.126.644-.223.086-.656.32-1.16.7-1.512a9.84 9.84 0 0 1-1.465-.262 5.758 
5.758 0 0 1-1.344-.574 3.818 3.818 0 0 1-1.153-.984c-.308-.39-
 .558-.906-.753-1.54-.196-.636-.293-1.37-.293-2.202 0-1.184.378-2.192 
1.132-3.024-.355-.89-.32-1.887.098-2.996.277-.086.688-.02 1.23.2.543.222.942.41 
1.196.566.254.156.457.289.61.398a9.776 9.776 0 0 1 2.745-.383c.946 0 1.86.125 
2.75.383l.543-.355c.371-.235.809-.45 1.317-.645.504-.195.89-.25 1.156-.164.43 1.11.469 2.106.117 2.996.75.832 
1.129 1.84 1.129 3.024 0 .832-.098 1.57-.293 2.207-.195.644-.45 1.156-.758 
1.543-.324.398-.715.73-1.16.976a5.84 5.84 0 0 1-1.344.574 9.41 9.41 0 0 1-1.468.262c.496.442.746 1.137.746 
2.086v3.094c0 .176.058.324.18.441.117.117.304.153.562.102 2.215-.754 4.02-2.11 5.418-4.07 1.394-1.965 
2.094-4.172 2.094-6.63 0-2.042-.493-3.93-1.473-5.656Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-gitlab.svg 
b/data/resources/icons/scalable/actions/idp-gitlab.svg
new file mode 100644
index 000000000..47087debf
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-gitlab.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="22" height="22"><path 
style="stroke:none;fill-rule:nonzero;fill:#e24329;fill-opacity:1" d="m11.008 22 4.047-13.535h-8.09Zm0 
0"/><path style="stroke:none;fill-rule:nonzero;fill:#fca326;fill-opacity:1" d="m1.297 8.465-1.23 
4.113a.944.944 0 0 0 .304 1.016L11.008 22Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#e24329;fill-opacity:1" d="M1.297 
8.465h5.668L4.523.313c-.125-.418-.668-.418-.796 0Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#fca326;fill-opacity:1" d="m20.719 8.465 1.226 4.113a.95.95 0 0 
1-.3 1.016L11.004 22Zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#e24329;fill-opacity:1" 
d="M20.723 8.465h-5.668L17.488.313c.125-.418.668-.418.797 0Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#fc6d26;fill-opacity:1" d="M11.008 22 15.05 8.465h5.668Zm-.004 
0L1.297 8.465h5.668L11.008 22Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-google-dark.svg 
b/data/resources/icons/scalable/actions/idp-google-dark.svg
new file mode 100644
index 000000000..11614169e
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-google-dark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="22" height="22"><path 
style="stroke:none;fill-rule:nonzero;fill:#4285f4;fill-opacity:1" d="M21.98 
11.234c0-.902-.074-1.562-.238-2.246H11.215v4.078h6.18c-.125 1.012-.797 2.54-2.293 3.563l-.02.137 3.328 
2.527.23.023c2.118-1.918 3.34-4.738 3.34-8.082Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#34a853;fill-opacity:1" d="M11.215 21.98c3.027 0 5.57-.976 
7.426-2.664L15.1 16.63c-.945.648-2.218 1.101-3.886 1.101a6.736 6.736 0 0 1-6.38-4.566l-.132.012-3.46 
2.625-.048.12c1.844 3.59 5.633 6.06 10.02 6.06Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#fbbc05;fill-opacity:1" d="M4.836 13.164a6.652 6.652 0 0 
1-.375-2.176c0-.754.137-1.488.363-2.172l-.008-.144-3.503-2.668-.114.05A10.846 10.846 0 0 0 0 10.989c0 
1.774.438 3.446 1.2 4.934Zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#eb4335;fill-opacity:1" 
d="M11.215 4.25c2.105 0 3.527.89 4.336 1.637l3.164-3.032C16.77 1.085 14.242 0 11.215 0 6.828 0 3.039 2.469 
1.195 6.059L4.
 82 8.816a6.773 6.773 0 0 1 6.395-4.566Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-google.svg 
b/data/resources/icons/scalable/actions/idp-google.svg
new file mode 100644
index 000000000..11614169e
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-google.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"; width="22" height="22"><path 
style="stroke:none;fill-rule:nonzero;fill:#4285f4;fill-opacity:1" d="M21.98 
11.234c0-.902-.074-1.562-.238-2.246H11.215v4.078h6.18c-.125 1.012-.797 2.54-2.293 3.563l-.02.137 3.328 
2.527.23.023c2.118-1.918 3.34-4.738 3.34-8.082Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#34a853;fill-opacity:1" d="M11.215 21.98c3.027 0 5.57-.976 
7.426-2.664L15.1 16.63c-.945.648-2.218 1.101-3.886 1.101a6.736 6.736 0 0 1-6.38-4.566l-.132.012-3.46 
2.625-.048.12c1.844 3.59 5.633 6.06 10.02 6.06Zm0 0"/><path 
style="stroke:none;fill-rule:nonzero;fill:#fbbc05;fill-opacity:1" d="M4.836 13.164a6.652 6.652 0 0 
1-.375-2.176c0-.754.137-1.488.363-2.172l-.008-.144-3.503-2.668-.114.05A10.846 10.846 0 0 0 0 10.989c0 
1.774.438 3.446 1.2 4.934Zm0 0"/><path style="stroke:none;fill-rule:nonzero;fill:#eb4335;fill-opacity:1" 
d="M11.215 4.25c2.105 0 3.527.89 4.336 1.637l3.164-3.032C16.77 1.085 14.242 0 11.215 0 6.828 0 3.039 2.469 
1.195 6.059L4.
 82 8.816a6.773 6.773 0 0 1 6.395-4.566Zm0 0"/></svg>
\ No newline at end of file
diff --git a/data/resources/icons/scalable/actions/idp-twitter.svg 
b/data/resources/icons/scalable/actions/idp-twitter.svg
new file mode 100644
index 000000000..d128bd5fd
--- /dev/null
+++ b/data/resources/icons/scalable/actions/idp-twitter.svg
@@ -0,0 +1 @@
+<svg xml:space="preserve" width="246.15" height="200.013" xmlns="http://www.w3.org/2000/svg";><path 
d="M220.95 49.793c.15 2.17.15 4.34.15 6.53 0 66.73-50.8 143.69-143.69 
143.69v-.04c-27.44.04-54.31-7.82-77.41-22.64 3.99.48 8 .72 12.02.73 22.74.02 44.83-7.61 
62.72-21.66-21.61-.41-40.56-14.5-47.18-35.07a50.338 50.338 0 0 0 
22.8-.87c-23.56-4.76-40.51-25.46-40.51-49.5v-.64a50.18 50.18 0 0 0 22.92 
6.32c-22.19-14.83-29.03-44.35-15.63-67.43a143.333 143.333 0 0 0 104.08 52.76 50.532 50.532 0 0 1 
14.61-48.25c20.34-19.12 52.33-18.14 71.45 2.19 11.31-2.23 22.15-6.38 32.07-12.26a50.69 50.69 0 0 1-22.2 
27.93c10.01-1.18 19.79-3.86 29-7.95a102.594 102.594 0 0 1-25.2 26.16z" style="fill:#1d9bf0"/></svg>
\ No newline at end of file
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 8d1d2cd8d..2b4f94aaf 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -5,6 +5,15 @@
     <file preprocess="xml-stripblanks">assets/other-device.svg</file>
     <file preprocess="xml-stripblanks">assets/setup-complete.svg</file>
     <file preprocess="xml-stripblanks">assets/welcome.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-apple-dark.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-apple.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-facebook.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-github-dark.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-github.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-gitlab.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-google-dark.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-google.svg</file>
+    <file preprocess="xml-stripblanks">icons/scalable/actions/idp-twitter.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/status/devices-symbolic.svg</file>
     <file preprocess="xml-stripblanks">icons/scalable/status/empty-page.svg</file>
@@ -64,6 +73,7 @@
     <file compressed="true" preprocess="xml-stripblanks" 
alias="event-source-dialog.ui">ui/event-source-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="greeter.ui">ui/greeter.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="identity-verification-widget.ui">ui/identity-verification-widget.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" alias="idp-button.ui">ui/idp-button.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="in-app-notification.ui">ui/in-app-notification.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="login-advanced-dialog.ui">ui/login-advanced-dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 526933700..ccf0aa4bf 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -184,6 +184,12 @@ login entry {
   padding: 18px 24px;
 }
 
+.sso-button {
+  padding: 4px;
+  -gtk-icon-size: 26px;
+  min-height: 34px;
+}
+
 /* Session */
 
 .session-loading-spinner {
diff --git a/data/resources/ui/idp-button.ui b/data/resources/ui/idp-button.ui
new file mode 100644
index 000000000..45f237bcb
--- /dev/null
+++ b/data/resources/ui/idp-button.ui
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="IdpButton" parent="GtkButton">
+    <style>
+      <class name="card"/>
+      <class name="sso-button"/>
+    </style>
+  </template>
+</interface>
+
diff --git a/data/resources/ui/login.ui b/data/resources/ui/login.ui
index 790c4bf4e..2c354704b 100644
--- a/data/resources/ui/login.ui
+++ b/data/resources/ui/login.ui
@@ -115,7 +115,7 @@
                     <child>
                       <object class="GtkBox">
                         <property name="orientation">vertical</property>
-                        <property name="spacing">30</property>    
+                        <property name="spacing">30</property>
                         <child>
                           <object class="GtkBox">
                             <property name="orientation">vertical</property>
@@ -182,6 +182,26 @@
                                 <property name="use_underline">True</property>
                                 <property name="label" translatable="yes">_Forgot Password?</property>
                                 <property name="uri">https://app.element.io/#/forgot_password</property>
+                                <property name="halign">center</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkBox" id="sso_box">
+                                <property name="visible">false</property>
+                                <property name="orientation">horizontal</property>
+                                <property name="spacing">12</property>
+                                <property name="homogeneous">true</property>
+                                <property name="hexpand">true</property>
+                                <property name="vexpand">true</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkButton" id="more_sso_option">
+                                <style>
+                                  <class name="pill"/>
+                                </style>
+                                <property name="halign">center</property>
+                                <property name="label">More SSO Providers</property>
                               </object>
                             </child>
                           </object>
@@ -192,9 +212,35 @@
                 </property>
               </object>
             </child>
+            <child>
+              <object class="GtkStackPage">
+                <property name="name">sso_message_page</property>
+                <property name="child">
+                  <object class="AdwClamp">
+                    <property name="maximum-size">360</property>
+                    <property name="tightening-threshold">360</property>
+                    <property name="valign">center</property>
+                    <child>
+                      <object class="GtkLabel">
+                        <property name="valign">center</property>
+                        <property name="wrap">True</property>
+                        <property name="wrap-mode">word-char</property>
+                        <property name="justify">center</property>
+                        <property name="label" translatable="yes">Please follow the steps in the 
browser.</property>
+                        <style>
+                          <class name="title-1"/>
+                        </style>
+                      </object>
+                    </child>
+                  </object>
+                </property>
+              </object>
+            </child>
           </object>
         </child>
       </object>
     </child>
   </template>
 </interface>
+
+
diff --git a/src/idp_button.rs b/src/idp_button.rs
new file mode 100644
index 000000000..ac459b808
--- /dev/null
+++ b/src/idp_button.rs
@@ -0,0 +1,231 @@
+use std::convert::{TryFrom, TryInto};
+
+use gtk::{self, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
+use matrix_sdk::ruma::api::client::session::get_login_types::v3::{
+    IdentityProvider, IdentityProviderBrand,
+};
+use url::Url;
+
+#[derive(Hash, Debug, Eq, PartialEq, Clone, Copy, glib::Enum)]
+#[repr(i32)]
+#[enum_type(name = "IdpBrand")]
+pub enum IdpBrand {
+    Apple = 0,
+    Facebook = 1,
+    GitHub = 2,
+    GitLab = 3,
+    Google = 4,
+    Twitter = 5,
+}
+impl Default for IdpBrand {
+    fn default() -> Self {
+        IdpBrand::Apple
+    }
+}
+
+impl TryFrom<&IdentityProviderBrand> for IdpBrand {
+    type Error = ();
+
+    fn try_from(item: &IdentityProviderBrand) -> Result<Self, Self::Error> {
+        match item {
+            IdentityProviderBrand::Apple => Ok(IdpBrand::Apple),
+            IdentityProviderBrand::Facebook => Ok(IdpBrand::Facebook),
+            IdentityProviderBrand::GitHub => Ok(IdpBrand::GitHub),
+            IdentityProviderBrand::GitLab => Ok(IdpBrand::GitLab),
+            IdentityProviderBrand::Google => Ok(IdpBrand::Google),
+            IdentityProviderBrand::Twitter => Ok(IdpBrand::Twitter),
+            _ => Err(()),
+        }
+    }
+}
+impl From<IdpBrand> for &str {
+    fn from(val: IdpBrand) -> Self {
+        let dark = adw::StyleManager::default().is_dark();
+        match val {
+            IdpBrand::Apple => {
+                if dark {
+                    "idp-apple-dark"
+                } else {
+                    "idp-apple"
+                }
+            }
+            IdpBrand::Facebook => "idp-facebook",
+            IdpBrand::GitHub => {
+                if dark {
+                    "idp-github-dark"
+                } else {
+                    "idp-github"
+                }
+            }
+            IdpBrand::GitLab => "idp-gitlab",
+            IdpBrand::Google => "idp-google",
+            IdpBrand::Twitter => "idp-twitter",
+        }
+    }
+}
+
+mod imp {
+    use std::cell::{Cell, RefCell};
+
+    use glib::subclass::InitializingObject;
+    use once_cell::sync::Lazy;
+
+    use super::*;
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/idp-button.ui")]
+    pub struct IdpButton {
+        pub brand: Cell<IdpBrand>,
+        pub id: RefCell<Option<String>>,
+        pub homeserver: RefCell<Option<String>>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for IdpButton {
+        const NAME: &'static str = "IdpButton";
+        type Type = super::IdpButton;
+        type ParentType = gtk::Button;
+
+        fn class_init(klass: &mut Self::Class) {
+            Self::bind_template(klass);
+            klass.set_accessible_role(gtk::AccessibleRole::Button);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for IdpButton {
+        fn properties() -> &'static [glib::ParamSpec] {
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![
+                    glib::ParamSpecEnum::new(
+                        "brand",
+                        "Brand",
+                        "The brand of this button",
+                        IdpBrand::static_type(),
+                        IdpBrand::default() as i32,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                    glib::ParamSpecString::new(
+                        "id",
+                        "Id",
+                        "The id of the selected identity-provider",
+                        None,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                    glib::ParamSpecString::new(
+                        "homeserver",
+                        "homeserver",
+                        "The homeserver url",
+                        None,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
+                ]
+            });
+
+            PROPERTIES.as_ref()
+        }
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "id" => obj.id().unwrap().to_value(),
+                "brand" => obj.brand().to_value(),
+                "homeserver" => obj.homeserver().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "brand" => {
+                    obj.set_brand(value.get().unwrap());
+                }
+                "id" => {
+                    obj.set_id(value.get().unwrap());
+                }
+                "homeserver" => {
+                    obj.set_homeserver(value.get().unwrap());
+                }
+                _ => unimplemented!(),
+            };
+        }
+
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+            adw::StyleManager::default()
+                .connect_dark_notify(clone!(@weak obj => move |_| obj.update_icon()));
+            obj.update_icon();
+        }
+    }
+
+    impl WidgetImpl for IdpButton {}
+
+    impl ButtonImpl for IdpButton {}
+}
+glib::wrapper! {
+    pub struct IdpButton(ObjectSubclass<imp::IdpButton>)
+        @extends gtk::Widget, gtk::Button, @implements gtk::Accessible;
+}
+impl IdpButton {
+    pub fn update_icon(&self) {
+        self.set_icon_name(brand_to_icon(self.brand()));
+    }
+
+    pub fn set_id(&self, id: String) {
+        self.imp().id.replace(Some(id));
+    }
+    pub fn set_homeserver(&self, url: String) {
+        self.imp().homeserver.replace(Some(url));
+    }
+    pub fn set_brand(&self, brand: IdpBrand) {
+        self.imp().brand.replace(brand);
+    }
+    pub fn id(&self) -> Option<String> {
+        self.imp().id.borrow().clone()
+    }
+    pub fn homeserver(&self) -> Option<String> {
+        self.imp().homeserver.borrow().clone()
+    }
+    pub fn brand(&self) -> IdpBrand {
+        self.imp().brand.get()
+    }
+    pub fn new_from_identity_provider(homeserver: Url, idp: &IdentityProvider) -> Option<Self> {
+        let gidp: IdpBrand = idp.brand.as_ref()?.try_into().ok()?;
+        let ret: IdpButton = glib::Object::new(&[
+            ("brand", &gidp),
+            ("id", &idp.id),
+            ("homeserver", &homeserver.as_str()),
+        ])
+        .expect("Failed to create IdpButton");
+        Some(ret)
+    }
+}
+fn brand_to_icon(brand: IdpBrand) -> &'static str {
+    let dark = adw::StyleManager::default().is_dark();
+    match brand {
+        IdpBrand::Apple => {
+            if dark {
+                "idp-apple-dark"
+            } else {
+                "idp-apple"
+            }
+        }
+        IdpBrand::Facebook => "idp-facebook",
+        IdpBrand::GitHub => {
+            if dark {
+                "idp-github-dark"
+            } else {
+                "idp-github"
+            }
+        }
+        IdpBrand::GitLab => "idp-gitlab",
+        IdpBrand::Google => "idp-google",
+        IdpBrand::Twitter => "idp-twitter",
+    }
+}
diff --git a/src/login.rs b/src/login.rs
index fd6a2d5ae..8ad9aadf4 100644
--- a/src/login.rs
+++ b/src/login.rs
@@ -5,7 +5,14 @@ use log::{debug, warn};
 use matrix_sdk::{
     config::RequestConfig,
     ruma::{
-        api::client::discover::get_supported_versions, identifiers::Error as IdentifierError,
+        api::client::{
+            discover::get_supported_versions,
+            session::get_login_types::v3::{
+                LoginType::{Password, Sso},
+                SsoLoginType,
+            },
+        },
+        identifiers::Error as IdentifierError,
         ServerName,
     },
     Client, Result as MatrixResult,
@@ -15,6 +22,7 @@ use url::{ParseError, Url};
 
 use crate::{
     components::{SpinnerButton, Toast},
+    idp_button::IdpButton,
     login_advanced_dialog::LoginAdvancedDialog,
     spawn, spawn_tokio,
     user_facing_error::UserFacingError,
@@ -52,11 +60,16 @@ mod imp {
         pub username_entry: TemplateChild<gtk::Entry>,
         #[template_child]
         pub password_entry: TemplateChild<gtk::PasswordEntry>,
+        #[template_child]
+        pub sso_box: TemplateChild<gtk::Box>,
+        #[template_child]
+        pub more_sso_option: TemplateChild<gtk::Button>,
         pub prepared_source_id: RefCell<Option<SignalHandlerId>>,
         pub logged_out_source_id: RefCell<Option<SignalHandlerId>>,
         pub ready_source_id: RefCell<Option<SignalHandlerId>>,
         /// Whether auto-discovery is enabled.
         pub autodiscovery: Cell<bool>,
+        pub supports_password: Cell<bool>,
         /// The homeserver to log into.
         pub homeserver: RefCell<Option<Url>>,
     }
@@ -159,6 +172,8 @@ mod imp {
                 .connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
             self.password_entry
                 .connect_changed(clone!(@weak obj => move |_| obj.update_next_action()));
+            self.more_sso_option
+                .connect_clicked(clone!(@weak obj => move |_| obj.login_with_sso(None)));
         }
     }
 
@@ -182,6 +197,37 @@ impl Login {
         self.imp().homeserver.borrow().clone()
     }
 
+    fn reload_sso_panel(&self, login_types: &SsoLoginType) {
+        let priv_ = &mut imp::Login::from_instance(self);
+        let mut child = priv_.sso_box.first_child();
+        while child.is_some() {
+            priv_.sso_box.remove(&child.unwrap());
+            child = priv_.sso_box.first_child();
+        }
+        let mut has_unknown_methods = false;
+        let mut has_known_methods = false;
+        let homeserver: Url = self.homeserver().unwrap();
+        for provider in login_types.identity_providers.iter() {
+            let opt_brand = provider.brand.as_ref();
+            if opt_brand.is_none() {
+                has_unknown_methods = true;
+                continue;
+            }
+            let btn = IdpButton::new_from_identity_provider(homeserver.clone(), provider);
+            if let Some(real) = btn {
+                self.imp().sso_box.append(&real);
+                real.connect_clicked(
+                    clone!(@weak self as obj => move |btn| obj.login_with_sso(btn.id())),
+                );
+                has_known_methods = true;
+            } else {
+                has_unknown_methods = true;
+            }
+        }
+        priv_.sso_box.set_visible(has_known_methods);
+        priv_.more_sso_option.set_visible(has_unknown_methods);
+    }
+
     pub fn homeserver_pretty(&self) -> Option<String> {
         let homeserver = self.homeserver();
         homeserver
@@ -253,6 +299,13 @@ impl Login {
     fn backward(&self) {
         match self.visible_child().as_ref() {
             "password" => self.set_visible_child("homeserver"),
+            "sso_message_page" => {
+                self.set_visible_child(if self.imp().supports_password.get() {
+                    "password"
+                } else {
+                    "homeserver"
+                });
+            }
             _ => {
                 self.activate_action("app.show-greeter", None).unwrap();
             }
@@ -307,7 +360,7 @@ impl Login {
                     Ok(client) => {
                         let homeserver = client.homeserver().await;
                         obj.set_homeserver(Some(homeserver));
-                        obj.show_password_page();
+                        obj.check_login_types(client).await;
                     }
                     Err(error) => {
                         warn!("Failed to discover homeserver: {}", error);
@@ -319,6 +372,42 @@ impl Login {
         );
     }
 
+    fn switch_off_sso(&self) {
+        let priv_ = self.imp();
+        priv_.sso_box.set_visible(false);
+        priv_.more_sso_option.set_visible(false);
+    }
+
+    async fn check_login_types(&self, client: Client) {
+        let login_types = spawn_tokio!(async move { client.get_login_types().await })
+            .await
+            .unwrap()
+            .unwrap();
+        let sso = login_types
+            .flows
+            .iter()
+            .find(|flow| matches!(flow, Sso(_sso_providers)));
+        let password = login_types
+            .flows
+            .iter()
+            .find(|flow| matches!(flow, Password(_)));
+        let has_sso = sso.is_some();
+        let has_password = password.is_some();
+        self.imp().supports_password.replace(has_password);
+        if has_sso && has_password {
+            if let Sso(login_type) = sso.unwrap() {
+                self.reload_sso_panel(login_type);
+            }
+        } else if !has_sso {
+            self.switch_off_sso();
+        }
+        if has_password {
+            self.show_password_page();
+        } else {
+            self.login_with_sso(None);
+        }
+    }
+
     fn check_homeserver(&self) {
         let homeserver = build_homeserver_url(self.imp().homeserver_entry.text().as_str()).unwrap();
         let homeserver_clone = homeserver.clone();
@@ -397,6 +486,23 @@ impl Login {
         priv_.current_session.replace(Some(session));
     }
 
+    fn login_with_sso(&self, idp_id: Option<String>) {
+        let priv_ = imp::Login::from_instance(self);
+        let homeserver = self.homeserver().unwrap();
+        self.set_visible_child("sso_message_page");
+
+        let session = Session::new();
+        self.set_handler_for_prepared_session(&session);
+        spawn!(
+            glib::PRIORITY_DEFAULT_IDLE,
+            clone!(@weak session, @weak self as s => async move {
+                session.login_with_sso(homeserver, idp_id).await;
+                s.set_visible_child("homeserver");
+            })
+        );
+        priv_.current_session.replace(Some(session));
+    }
+
     pub fn clean(&self) {
         let priv_ = self.imp();
         priv_.homeserver_entry.set_text("");
diff --git a/src/main.rs b/src/main.rs
index 72577f0f6..3ba18e61a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,7 @@ mod components;
 mod contrib;
 mod error_page;
 mod greeter;
+mod idp_button;
 mod login;
 mod login_advanced_dialog;
 mod secret;
diff --git a/src/session/mod.rs b/src/session/mod.rs
index c5ab3922f..db515e7e3 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -361,6 +361,62 @@ impl Session {
         self.handle_login_result(handle.await.unwrap(), true).await;
     }
 
+    pub async fn login_with_sso(&self, homeserver: Url, idp_id: Option<String>) {
+        self.imp().logout_on_dispose.set(true);
+        let mut path = glib::user_data_dir();
+        path.push(glib::uuid_string_random().as_str());
+        let passphrase: String = {
+            let mut rng = thread_rng();
+            (&mut rng)
+                .sample_iter(Alphanumeric)
+                .take(30)
+                .map(char::from)
+                .collect()
+        };
+        let handle = spawn_tokio!(async move {
+            let client = create_client(&homeserver, &path, &passphrase, true).await?;
+
+            let response = client
+                .login_with_sso(
+                    |sso_url| async move {
+                        let ctx = glib::MainContext::default();
+                        ctx.spawn(async move {
+                            gtk::show_uri(gtk::Window::NONE, &sso_url, gdk::CURRENT_TIME);
+                        });
+                        Ok(())
+                    },
+                    None,
+                    None,
+                    None,
+                    Some("Fractal Next"),
+                    idp_id.as_deref(),
+                )
+                .await;
+            match response {
+                Ok(response) => Ok((
+                    client,
+                    StoredSession {
+                        homeserver,
+                        path,
+                        user_id: response.user_id,
+                        device_id: response.device_id,
+                        secret: Secret {
+                            passphrase,
+                            access_token: response.access_token,
+                        },
+                    },
+                )),
+                Err(error) => {
+                    // Remove the store created by Client::new()
+                    fs::remove_dir_all(path).unwrap();
+                    Err(error.into())
+                }
+            }
+        });
+
+        self.handle_login_result(handle.await.unwrap(), true).await;
+    }
+
     pub async fn login_with_previous_session(&self, session: StoredSession) {
         let handle = spawn_tokio!(async move {
             let client = create_client(


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