Repository: infinyon/fluvio-client-python (language: python)

Issue:
Support fluvio-cloud login with oauth flow
```
$ fluvio cloud login --use-oauth2
A web browser has been opened at https://infinyon-cloud.us.auth0.com/activate?user_code=LBHV-NWHQ.
Please proceed with authentication.
```

Lets have something similar for the python client.

Depends on #199.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 0de6bdea..6bc5e850 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6,13 +6,30 @@ version = 3
 name = "_fluvio_python"
 version = "0.14.0"
 dependencies = [
+ "async-h1",
+ "async-std",
  "cpython",
+ "dirs",
  "env_logger",
  "flapigen",
  "flate2",
  "fluvio",
  "fluvio-future",
+ "fluvio-types",
+ "hex",
+ "http-types",
  "log",
+ "md-5",
+ "rpassword",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "thiserror",
+ "tokio",
+ "toml",
+ "tracing",
+ "url",
+ "webbrowser",
 ]
 
 [[package]]
@@ -21,6 +38,60 @@ version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "aead"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "aes"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
+dependencies = [
+ "aes-soft",
+ "aesni",
+ "cipher",
+]
+
+[[package]]
+name = "aes-gcm"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da"
+dependencies = [
+ "aead",
+ "aes",
+ "cipher",
+ "ctr",
+ "ghash",
+ "subtle",
+]
+
+[[package]]
+name = "aes-soft"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
+dependencies = [
+ "cipher",
+ "opaque-debug",
+]
+
+[[package]]
+name = "aesni"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
+dependencies = [
+ "cipher",
+ "opaque-debug",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.20"
@@ -39,6 +110,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "anyhow"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
+
 [[package]]
 name = "async-channel"
 version = "1.8.0"
@@ -50,6 +127,16 @@ dependencies = [
  "futures-core",
 ]
 
+[[package]]
+name = "async-dup"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7427a12b8dc09291528cfb1da2447059adb4a257388c2acd6497a79d55cf6f7c"
+dependencies = [
+ "futures-io",
+ "simple-mutex",
+]
+
 [[package]]
 name = "async-executor"
 version = "1.5.0"
@@ -79,6 +166,22 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "async-h1"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8101020758a4fc3a7c326cb42aa99e9fa77cbfb76987c128ad956406fe1f70a7"
+dependencies = [
+ "async-channel",
+ "async-dup",
+ "async-std",
+ "futures-core",
+ "http-types",
+ "httparse",
+ "log",
+ "pin-project",
+]
+
 [[package]]
 name = "async-io"
 version = "1.12.0"
@@ -96,7 +199,7 @@ dependencies = [
  "slab",
  "socket2",
  "waker-fn",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -118,6 +221,18 @@ dependencies = [
  "event-listener",
 ]
 
+[[package]]
+name = "async-native-tls"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d57d4cec3c647232e1094dc013546c0b33ce785d8aeb251e1f20dfaf8a9a13fe"
+dependencies = [
+ "futures-util",
+ "native-tls",
+ "thiserror",
+ "url",
+]
+
 [[package]]
 name = "async-net"
 version = "1.7.0"
@@ -145,7 +260,7 @@ dependencies = [
  "futures-lite",
  "libc",
  "signal-hook",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -210,7 +325,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c"
 dependencies = [
  "futures",
  "pharos",
- "rustc_version",
+ "rustc_version 0.4.0",
 ]
 
 [[package]]
@@ -225,6 +340,12 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "base-x"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
+
 [[package]]
 name = "base64"
 version = "0.13.1"
@@ -237,6 +358,24 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
+[[package]]
+name = "block-buffer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "blocking"
 version = "1.3.0"
@@ -278,7 +417,7 @@ version = "8.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996"
 dependencies = [
- "semver",
+ "semver 1.0.16",
  "serde",
  "toml",
  "url",
@@ -290,6 +429,12 @@ version = "1.0.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
 
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -306,11 +451,20 @@ dependencies = [
  "js-sys",
  "num-integer",
  "num-traits",
- "time",
+ "time 0.1.45",
  "wasm-bindgen",
  "winapi",
 ]
 
+[[package]]
+name = "cipher"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "codespan-reporting"
 version = "0.11.1"
@@ -321,6 +475,16 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "combine"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
 [[package]]
 name = "concurrent-queue"
 version = "2.0.0"
@@ -330,6 +494,12 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "const_fn"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
+
 [[package]]
 name = "content_inspector"
 version = "0.2.4"
@@ -339,12 +509,54 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "cookie"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
+dependencies = [
+ "aes-gcm",
+ "base64",
+ "hkdf",
+ "hmac",
+ "percent-encoding",
+ "rand 0.8.5",
+ "sha2",
+ "time 0.2.27",
+ "version_check",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
 [[package]]
 name = "core-foundation-sys"
 version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
 
+[[package]]
+name = "cpufeatures"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpuid-bool"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
+
 [[package]]
 name = "cpython"
 version = "0.7.1"
@@ -363,7 +575,7 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3dfea2db42e9927a3845fb268a10a72faed6d416065f77873f05e411457c363e"
 dependencies = [
- "rustc_version",
+ "rustc_version 0.4.0",
 ]
 
 [[package]]
@@ -384,6 +596,26 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-mac"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
 [[package]]
 name = "ctor"
 version = "0.1.26"
@@ -394,6 +626,15 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "ctr"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f"
+dependencies = [
+ "cipher",
+]
+
 [[package]]
 name = "cxx"
 version = "1.0.85"
@@ -438,14 +679,38 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "darling"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
+dependencies = [
+ "darling_core 0.13.4",
+ "darling_macro 0.13.4",
+]
+
 [[package]]
 name = "darling"
 version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
 dependencies = [
- "darling_core",
- "darling_macro",
+ "darling_core 0.14.2",
+ "darling_macro 0.14.2",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
 ]
 
 [[package]]
@@ -462,13 +727,24 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "darling_macro"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
+dependencies = [
+ "darling_core 0.13.4",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "darling_macro"
 version = "0.14.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
 dependencies = [
- "darling_core",
+ "darling_core 0.14.2",
  "quote",
  "syn",
 ]
@@ -488,7 +764,7 @@ version = "0.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f"
 dependencies = [
- "darling",
+ "darling 0.14.2",
  "proc-macro2",
  "quote",
  "syn",
@@ -504,6 +780,25 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
+dependencies = [
+ "block-buffer 0.10.3",
+ "crypto-common",
+]
+
 [[package]]
 name = "dirs"
 version = "4.0.0"
@@ -524,6 +819,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "discard"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
+
 [[package]]
 name = "educe"
 version = "0.4.20"
@@ -552,7 +853,7 @@ dependencies = [
  "num-traits",
  "proc-macro2",
  "quote",
- "rustc_version",
+ "rustc_version 0.4.0",
  "syn",
 ]
 
@@ -680,7 +981,7 @@ dependencies = [
  "futures-util",
  "once_cell",
  "pin-project-lite",
- "semver",
+ "semver 1.0.16",
  "serde",
  "serde_json",
  "siphasher",
@@ -720,7 +1021,7 @@ dependencies = [
  "fluvio-types",
  "flv-util",
  "lenient_semver",
- "semver",
+ "semver 1.0.16",
  "serde",
  "thiserror",
  "toml",
@@ -734,6 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1c2f719aca3883ed31553c51bcf3cab241de66ed9821da4f86837f7d983569da"
 dependencies = [
  "async-io",
+ "async-native-tls",
  "async-net",
  "async-std",
  "async-trait",
@@ -742,11 +1044,13 @@ dependencies = [
  "futures-lite",
  "futures-util",
  "log",
+ "native-tls",
  "openssl",
  "openssl-sys",
  "pin-project",
  "thiserror",
  "tracing",
+ "tracing-subscriber",
  "ws_stream_wasm",
 ]
 
@@ -766,7 +1070,7 @@ dependencies = [
  "fluvio-types",
  "flv-util",
  "once_cell",
- "semver",
+ "semver 1.0.16",
  "thiserror",
  "tokio-util",
  "tracing",
@@ -843,7 +1147,7 @@ dependencies = [
  "futures-util",
  "once_cell",
  "pin-project",
- "semver",
+ "semver 1.0.16",
  "thiserror",
  "tokio",
  "tokio-util",
@@ -1051,6 +1355,27 @@ dependencies = [
  "slab",
 ]
 
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
 [[package]]
 name = "getrandom"
 version = "0.2.8"
@@ -1062,6 +1387,16 @@ dependencies = [
  "wasi 0.11.0+wasi-snapshot-preview1",
 ]
 
+[[package]]
+name = "ghash"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375"
+dependencies = [
+ "opaque-debug",
+ "polyval",
+]
+
 [[package]]
 name = "gloo-timers"
 version = "0.2.5"
@@ -1095,6 +1430,60 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hkdf"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f"
+dependencies = [
+ "digest 0.9.0",
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
+dependencies = [
+ "crypto-mac",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "http-types"
+version = "2.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad"
+dependencies = [
+ "anyhow",
+ "async-channel",
+ "async-std",
+ "base64",
+ "cookie",
+ "futures-lite",
+ "infer",
+ "pin-project-lite",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "serde_qs",
+ "serde_urlencoded",
+ "url",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
 [[package]]
 name = "humantime"
 version = "2.1.0"
@@ -1157,6 +1546,12 @@ dependencies = [
  "hashbrown",
 ]
 
+[[package]]
+name = "infer"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac"
+
 [[package]]
 name = "instant"
 version = "0.1.12"
@@ -1176,7 +1571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
 dependencies = [
  "libc",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1188,7 +1583,7 @@ dependencies = [
  "hermit-abi",
  "io-lifetimes",
  "rustix",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1197,6 +1592,26 @@ version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
 
+[[package]]
+name = "jni"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
+dependencies = [
+ "cesu8",
+ "combine",
+ "jni-sys",
+ "log",
+ "thiserror",
+ "walkdir",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
+
 [[package]]
 name = "js-sys"
 version = "0.3.60"
@@ -1228,7 +1643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "de8de3f4f3754c280ce1c8c42ed8dd26a9c8385c2e5ad4ec5a77e774cea9c1ec"
 dependencies = [
  "lenient_semver_parser",
- "semver",
+ "semver 1.0.16",
 ]
 
 [[package]]
@@ -1238,7 +1653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f650c1d024ddc26b4bb79c3076b30030f2cf2b18292af698c81f7337a64d7d6"
 dependencies = [
  "lenient_semver_version_builder",
- "semver",
+ "semver 1.0.16",
 ]
 
 [[package]]
@@ -1247,7 +1662,7 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9049f8ff49f75b946f95557148e70230499c8a642bf2d6528246afc7d0282d17"
 dependencies = [
- "semver",
+ "semver 1.0.16",
 ]
 
 [[package]]
@@ -1300,6 +1715,24 @@ dependencies = [
  "twox-hash",
 ]
 
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "md-5"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca"
+dependencies = [
+ "digest 0.10.6",
+]
+
 [[package]]
 name = "memchr"
 version = "2.5.0"
@@ -1316,7 +1749,91 @@ dependencies = [
 ]
 
 [[package]]
-name = "num-bigint"
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ndk"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
+dependencies = [
+ "bitflags",
+ "jni-sys",
+ "ndk-sys",
+ "num_enum",
+ "thiserror",
+]
+
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
+[[package]]
+name = "ndk-glue"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-context",
+ "ndk-macro",
+ "ndk-sys",
+]
+
+[[package]]
+name = "ndk-macro"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c"
+dependencies = [
+ "darling 0.13.4",
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "ndk-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
+dependencies = [
+ "jni-sys",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
+name = "num-bigint"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
@@ -1345,12 +1862,39 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "num_enum"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.16.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
 
+[[package]]
+name = "opaque-debug"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
+
 [[package]]
 name = "openssl"
 version = "0.10.45"
@@ -1377,6 +1921,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
 [[package]]
 name = "openssl-src"
 version = "111.24.0+1.1.1s"
@@ -1400,6 +1950,12 @@ dependencies = [
  "vcpkg",
 ]
 
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
 [[package]]
 name = "parking"
 version = "2.0.0"
@@ -1460,7 +2016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414"
 dependencies = [
  "futures",
- "rustc_version",
+ "rustc_version 0.4.0",
 ]
 
 [[package]]
@@ -1512,9 +2068,43 @@ dependencies = [
  "libc",
  "log",
  "wepoll-ffi",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
+[[package]]
+name = "polyval"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd"
+dependencies = [
+ "cpuid-bool",
+ "opaque-debug",
+ "universal-hash",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
+dependencies = [
+ "once_cell",
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.20+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.49"
@@ -1543,6 +2133,77 @@ dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.8",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core 0.5.1",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
@@ -1558,7 +2219,7 @@ version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
 dependencies = [
- "getrandom",
+ "getrandom 0.2.8",
  "redox_syscall",
  "thiserror",
 ]
@@ -1574,25 +2235,62 @@ dependencies = [
  "regex-syntax",
 ]
 
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax",
+]
+
 [[package]]
 name = "regex-syntax"
 version = "0.6.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
 
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rpassword"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "rustc-hash"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
 
+[[package]]
+name = "rustc_version"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
+dependencies = [
+ "semver 0.9.0",
+]
+
 [[package]]
 name = "rustc_version"
 version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
 dependencies = [
- "semver",
+ "semver 1.0.16",
 ]
 
 [[package]]
@@ -1606,7 +2304,7 @@ dependencies = [
  "io-lifetimes",
  "libc",
  "linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1621,6 +2319,25 @@ version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
 
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schannel"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
+dependencies = [
+ "lazy_static",
+ "windows-sys 0.36.1",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -1633,6 +2350,38 @@ version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
 
+[[package]]
+name = "security-framework"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
+dependencies = [
+ "semver-parser",
+]
+
 [[package]]
 name = "semver"
 version = "1.0.16"
@@ -1642,6 +2391,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
 [[package]]
 name = "send_wrapper"
 version = "0.5.0"
@@ -1679,6 +2434,66 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_qs"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6"
+dependencies = [
+ "percent-encoding",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
+dependencies = [
+ "sha1_smol",
+]
+
+[[package]]
+name = "sha1_smol"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
+
+[[package]]
+name = "sha2"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
+dependencies = [
+ "block-buffer 0.9.0",
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.9.0",
+ "opaque-debug",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
 [[package]]
 name = "signal-hook"
 version = "0.3.14"
@@ -1698,6 +2513,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "simple-mutex"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38aabbeafa6f6dead8cebf246fe9fae1f9215c8d29b3a69f93bd62a9e4a3dcd6"
+dependencies = [
+ "event-listener",
+]
+
 [[package]]
 name = "siphasher"
 version = "0.3.10"
@@ -1744,12 +2568,70 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "standback"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
+dependencies = [
+ "version_check",
+]
+
 [[package]]
 name = "static_assertions"
 version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "stdweb"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
+dependencies = [
+ "discard",
+ "rustc_version 0.2.3",
+ "stdweb-derive",
+ "stdweb-internal-macros",
+ "stdweb-internal-runtime",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "stdweb-derive"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-macros"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
+dependencies = [
+ "base-x",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha1",
+ "syn",
+]
+
+[[package]]
+name = "stdweb-internal-runtime"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
+
 [[package]]
 name = "strsim"
 version = "0.10.0"
@@ -1778,6 +2660,12 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
 [[package]]
 name = "syn"
 version = "1.0.107"
@@ -1789,6 +2677,20 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.1.3"
@@ -1818,6 +2720,15 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
 [[package]]
 name = "time"
 version = "0.1.45"
@@ -1829,6 +2740,44 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "time"
+version = "0.2.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
+dependencies = [
+ "const_fn",
+ "libc",
+ "standback",
+ "stdweb",
+ "time-macros",
+ "version_check",
+ "winapi",
+]
+
+[[package]]
+name = "time-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
+dependencies = [
+ "proc-macro-hack",
+ "time-macros-impl",
+]
+
+[[package]]
+name = "time-macros-impl"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
+dependencies = [
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "standback",
+ "syn",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.6.0"
@@ -1853,7 +2802,7 @@ dependencies = [
  "autocfg",
  "pin-project-lite",
  "tokio-macros",
- "windows-sys",
+ "windows-sys 0.42.0",
 ]
 
 [[package]]
@@ -1921,6 +2870,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
 dependencies = [
  "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
 ]
 
 [[package]]
@@ -1933,6 +2912,12 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
 [[package]]
 name = "unicode-bidi"
 version = "0.3.8"
@@ -1960,6 +2945,16 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
+[[package]]
+name = "universal-hash"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
 [[package]]
 name = "url"
 version = "2.3.1"
@@ -1969,8 +2964,15 @@ dependencies = [
  "form_urlencoded",
  "idna",
  "percent-encoding",
+ "serde",
 ]
 
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
 [[package]]
 name = "value-bag"
 version = "1.0.0-alpha.9"
@@ -1999,6 +3001,23 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
 
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
 [[package]]
 name = "wasi"
 version = "0.10.0+wasi-snapshot-preview1"
@@ -2087,6 +3106,20 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "webbrowser"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a3cffdb686fbb24d9fb8f03a213803277ed2300f11026a3afe1f108dc021b"
+dependencies = [
+ "jni",
+ "ndk-glue",
+ "url",
+ "web-sys",
+ "widestring",
+ "winapi",
+]
+
 [[package]]
 name = "wepoll-ffi"
 version = "0.1.2"
@@ -2107,6 +3140,12 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "widestring"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -2138,6 +3177,19 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.42.0"
@@ -2145,12 +3197,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
 dependencies = [
  "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
+ "windows_aarch64_msvc 0.42.0",
+ "windows_i686_gnu 0.42.0",
+ "windows_i686_msvc 0.42.0",
+ "windows_x86_64_gnu 0.42.0",
  "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_x86_64_msvc 0.42.0",
 ]
 
 [[package]]
@@ -2159,24 +3211,48 @@ version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
 
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
 [[package]]
 name = "windows_aarch64_msvc"
 version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
 
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
 [[package]]
 name = "windows_i686_gnu"
 version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
 
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
 [[package]]
 name = "windows_i686_msvc"
 version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
 
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
 [[package]]
 name = "windows_x86_64_gnu"
 version = "0.42.0"
@@ -2189,6 +3265,12 @@ version = "0.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
 
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
 [[package]]
 name = "windows_x86_64_msvc"
 version = "0.42.0"
@@ -2205,7 +3287,7 @@ dependencies = [
  "futures",
  "js-sys",
  "pharos",
- "rustc_version",
+ "rustc_version 0.4.0",
  "send_wrapper",
  "thiserror",
  "wasm-bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index 6b4df193..dede3e29 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,4 +17,23 @@ log = "^0.4.17"
 cpython = { version = "0.7", features = ["extension-module"] }
 flate2 = "1.0.24"
 fluvio = { version = "0.15" }
-fluvio-future = { version = "0.4.2", features = ["task", "io"] }
+fluvio-future = { version = "0.4.2", features = ["task", "io", "native2_tls", "subscriber"] }
+
+thiserror = "1.0.21"
+fluvio-types = "0.4.0"
+serde = "1.0.117"
+tracing = "0.1.37"
+
+url = "2.1.1"
+dirs = "4.0.0"
+http-types = "2.6.0"
+tokio = { version = "1.3.0", default-features = false, features = ["macros"] }
+async-h1 = "2.3.3"
+async-std = "1.6.5"
+serde_urlencoded = "0.7.1"
+md-5 = "0.10.0"
+hex = "0.4.2"
+webbrowser = "0.7.1"
+toml = "0.5.7"
+serde_json = "1.0.59"
+rpassword = "5.0.0"
diff --git a/Makefile b/Makefile
index 48810026..79818e66 100644
--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,9 @@ integration-tests: build-dev
 macos-ci-tests: build-dev
 	cd macos-ci-tests && $(PYTHON) -m unittest
 
+manual-tests: build-dev
+	cd manual-tests/ && $(PYTHON) -m unittest
+
 ci-build: # This is for testing builds
 	CIBW_BUILD="cp311-manylinux_x86_64 cp311-manylinux_aarch64 cp311-macosx_x86_64 cp311-macosx_universal2 cp311-macosx_arm64"  CIBW_SKIP="cp27-*" CIBW_BEFORE_ALL_LINUX="{package}/tools/cibw_before_all_linux.sh"  $(PYTHON) -m cibuildwheel --platform linux --output-dir wheelhouse
 
diff --git a/fluvio/cloud.py b/fluvio/cloud.py
new file mode 100644
index 00000000..c9a49c58
--- /dev/null
+++ b/fluvio/cloud.py
@@ -0,0 +1,13 @@
+from ._fluvio_python import Cloud
+
+DEFAULT_REMOTE = "https://infinyon.cloud"
+
+
+def login(
+    useOauth2=True,
+    remote=DEFAULT_REMOTE,
+    profile=None,
+    email=None,
+    password=None,
+):
+    Cloud.login(useOauth2, remote, profile, email, password)
diff --git a/setup.py b/setup.py
index 14e7c404..43d6df31 100644
--- a/setup.py
+++ b/setup.py
@@ -3,12 +3,12 @@
 
 setup(
     name='fluvio',
-    version="0.14.1-beta.0",
+    version="0.14.1-beta.1",
     long_description=open('README.md').read(),
     long_description_content_type='text/markdown',
     author = "Fluvio Contributors",
     description='Python client library for Fluvio',
-    python_requires='>=3.7',
+    python_requires='>=3.8',
     url='https://www.fluvio.io/',
     keywords=['fluvio', 'streaming', 'stream'],
     license='APACHE',
@@ -30,7 +30,6 @@
         # that you indicate you support Python 3. These classifiers are *not*
         # checked by 'pip install'. See instead 'python_requires' below.
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
diff --git a/src/cloud/error.rs b/src/cloud/error.rs
new file mode 100644
index 00000000..246279cd
--- /dev/null
+++ b/src/cloud/error.rs
@@ -0,0 +1,69 @@
+use fluvio::FluvioError;
+use http_types::url::ParseError;
+use http_types::StatusCode;
+use std::io::Error as IoError;
+use std::path::PathBuf;
+use thiserror::Error;
+use toml::de::Error as TomlError;
+
+#[derive(Error, Debug)]
+pub enum CloudLoginError {
+    #[error("Unable to access the default Fluvio directory")]
+    FluvioDirError,
+    /// Failed to parse request URL
+    #[error("Failed to parse URL: {url_string}")]
+    UrlError {
+        source: ParseError,
+        url_string: String,
+    },
+    #[error("Failed to make HTTP request to Infinyon cloud")]
+    HttpError(#[source] HttpError),
+    #[error("Failed to get token from Auth0")]
+    FailedToGetAuth0Token,
+    #[error("Failed to authenticate with Auth0: {0}")]
+    Auth0LoginError(String),
+    #[error("Account not found with email provided by third party service, please create account through WEB UI.")]
+    Auth0AccountNotFound,
+    #[error("Timeout while waiting for user authentication through third party service.")]
+    Auth0TimeoutError,
+    #[error("Unable to url encode the string")]
+    UrlEncode(#[from] serde_urlencoded::ser::Error),
+    #[error("Failed to save cloud credentials")]
+    UnableToSaveCredentials(#[source] IoError),
+    /// Failed to do some IO.
+    #[error(transparent)]
+    IoError(#[from] IoError),
+    #[error("Failed to create logins dir {path}")]
+    UnableToCreateLoginsDir { source: IoError, path: PathBuf },
+    #[error("Cluster for \"{0}\" does not exist")]
+    ClusterDoesNotExist(String),
+    #[error("Profile not available yet, please try again later.")]
+    ProfileNotAvailable,
+    #[error("Failed to parse login token from file")]
+    UnableToParseCredentials(#[from] TomlError),
+    #[error("Failed to load cloud credentials")]
+    UnableToLoadCredentials(#[source] IoError),
+    #[error("Failed to download cloud profile: Status code {0}: {1}")]
+    ProfileDownloadError(StatusCode, &'static str),
+    /// Failed to open Infinyon Cloud login file
+    #[error("Not logged in")]
+    NotLoggedIn,
+    #[error("Fluvio client error")]
+    FluvioError(#[from] FluvioError),
+    #[error("Failed to authenticate with username: {0}")]
+    AuthenticationError(String),
+    #[error("Account not active. Please validate email address.")]
+    AccountNotActive,
+}
+
+#[derive(Error, Debug)]
+#[error("An HTTP error occurred: {inner}")]
+pub struct HttpError {
+    inner: http_types::Error,
+}
+
+impl From<http_types::Error> for CloudLoginError {
+    fn from(inner: http_types::Error) -> Self {
+        Self::HttpError(HttpError { inner })
+    }
+}
diff --git a/src/cloud/http.rs b/src/cloud/http.rs
new file mode 100644
index 00000000..3ab2e3e9
--- /dev/null
+++ b/src/cloud/http.rs
@@ -0,0 +1,42 @@
+use async_h1::client;
+use http_types::{headers::USER_AGENT, Error, Request, Response, StatusCode};
+use tracing::debug;
+
+pub const FLUVIO_CLI_USER_AGENT: &str = "Fluvio Python Client";
+
+pub async fn execute(req: Request) -> Result<Response, Error> {
+    debug!(?req, "executing request");
+    let mut req = req;
+    let host = req
+        .url()
+        .host_str()
+        .ok_or_else(|| Error::from_str(StatusCode::BadRequest, "missing hostname"))?
+        .to_string();
+
+    let is_https = req.url().scheme() == "https";
+
+    let addr = req
+        .url()
+        .socket_addrs(|| if is_https { Some(443) } else { Some(80) })?
+        .into_iter()
+        .next()
+        .ok_or_else(|| Error::from_str(StatusCode::BadRequest, "missing valid address"))?;
+
+    let tcp_stream = fluvio_future::net::TcpStream::connect(addr).await?;
+
+    req.append_header(
+        USER_AGENT,
+        format!("{}/{}", FLUVIO_CLI_USER_AGENT, env!("CARGO_PKG_VERSION")),
+    );
+
+    let result = if is_https {
+        let tls_connector = fluvio_future::native_tls::TlsConnector::default();
+        let tls_stream = tls_connector.connect(host, tcp_stream).await?;
+        client::connect(tls_stream, req).await
+    } else {
+        client::connect(tcp_stream, req).await
+    };
+
+    debug!(?result, "http result");
+    result
+}
diff --git a/src/cloud/mod.rs b/src/cloud/mod.rs
new file mode 100644
index 00000000..415882af
--- /dev/null
+++ b/src/cloud/mod.rs
@@ -0,0 +1,430 @@
+mod http;
+use http::execute;
+mod error;
+use async_std::prelude::StreamExt;
+pub use error::CloudLoginError;
+use fluvio::FluvioConfig;
+use fluvio_types::defaults::CLI_CONFIG_PATH;
+use http_types::{Mime, Request, Response, StatusCode, Url};
+use serde::{Deserialize, Serialize};
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use tracing::{debug, trace};
+
+const DEFAULT_LOGINS_DIR: &str = "logins";
+const CURRENT_LOGIN_FILE_NAME: &str = "current";
+
+#[derive(Debug)]
+pub struct CloudClient {
+    /// Configured path for storing credentials in filesystem
+    credentials_path: PathBuf,
+}
+impl CloudClient {
+    pub fn new<P: Into<PathBuf>>(path: P) -> Self {
+        Self {
+            credentials_path: path.into(),
+        }
+    }
+    fn default_file_path() -> Result<PathBuf, CloudLoginError> {
+        let mut login_path = dirs::home_dir().ok_or(CloudLoginError::FluvioDirError)?;
+        login_path.push(CLI_CONFIG_PATH);
+        login_path.push(DEFAULT_LOGINS_DIR);
+        Ok(login_path)
+    }
+
+    pub fn with_default_path() -> Result<Self, CloudLoginError> {
+        Ok(Self::new(Self::default_file_path()?))
+    }
+
+    pub async fn authenticate_with_auth0(
+        &mut self,
+        remote: &str,
+    ) -> Result<String, CloudLoginError> {
+        let mut response = get_auth0_config(remote).await?;
+
+        if response.status() != StatusCode::Ok {
+            return Err(CloudLoginError::Auth0LoginError(
+                "Auth0 not configured in Fluvio Cloud".into(),
+            ));
+        }
+        let auth0_config = response.body_json::<Auth0Config>().await?;
+
+        let mut response = get_device_code(&auth0_config).await?;
+        if response.status() != StatusCode::Ok {
+            return Err(CloudLoginError::Auth0LoginError(
+                "Failed to get Device Code from Auth0".into(),
+            ));
+        }
+
+        let device_code = response.body_json::<DeviceCodeResponse>().await?;
+
+        if webbrowser::open(&device_code.verification_uri_complete).is_ok() {
+            println!(
+                "A web browser has been opened at {}.\nPlease proceed with authentication.",
+                device_code.verification_uri_complete
+            );
+        } else {
+            debug!("Failed to open browser to authenticate with Auth0");
+            println!(
+                "Please visit the following URL: {} and use the following code: {}.\n Then, proceed with authentication.",
+                device_code.verification_uri, device_code.user_code
+            );
+        }
+
+        let mut response = tokio::select!(
+             response = get_auth0_token(
+                &auth0_config,
+                device_code.interval,
+                device_code.device_code,
+            ) => {response?},
+            _ = fluvio_future::timer::sleep(std::time::Duration::from_secs(device_code.expires_in)) => {
+                return Err(CloudLoginError::Auth0TimeoutError)
+            }
+        );
+        let token = response.body_json::<TokenResponse>().await?;
+
+        let mut response = authorize_auth0_user(
+            remote,
+            format!("{} {}", token.token_type, token.access_token),
+        )
+        .await?;
+
+        match response.status() {
+            StatusCode::Ok => {
+                let creds = response.body_json::<CredentialsAuth0Response>().await?;
+                let email = creds.email;
+                println!("Successfully authenticated with Auth0");
+                self.save_credentials(remote.to_owned(), email.clone(), creds.inner)
+                    .await?;
+                Ok(email)
+            }
+            StatusCode::Forbidden => Err(CloudLoginError::Auth0AccountNotFound),
+            _ => {
+                debug!(?response, "Failed to login");
+                Err(CloudLoginError::Auth0LoginError(
+                    "Something went wrong while authenticating with Auth0".into(),
+                ))
+            }
+        }
+    }
+    pub async fn authenticate(
+        &mut self,
+        email: &str,
+        password: &str,
+        remote: &str,
+    ) -> Result<(), CloudLoginError> {
+        let mut response = login_user(remote, email, password).await?;
+        match response.status() {
+            StatusCode::Ok => {
+                debug!("Successfully authenticated with token");
+                let creds = response.body_json::<CredentialsResponse>().await?;
+                if let Some(false) = creds.active {
+                    return Err(CloudLoginError::AccountNotActive);
+                }
+                self.save_credentials(remote.to_owned(), email.to_owned(), creds)
+                    .await?;
+                Ok(())
+            }
+            _ => {
+                debug!(?response, "Failed to login");
+                Err(CloudLoginError::AuthenticationError(email.to_owned()))
+            }
+        }
+    }
+
+    async fn save_credentials(
+        &mut self,
+        remote: String,
+        email: String,
+        creds: CredentialsResponse,
+    ) -> Result<(), CloudLoginError> {
+        let creds = creds.into_saved(remote, email);
+        // Save credentials to disk
+        creds.try_save(&self.credentials_path)?;
+        Ok(())
+    }
+
+    pub async fn download_profile(&mut self) -> Result<FluvioConfig, CloudLoginError> {
+        // Load the current credentials from disk
+        let creds = Credentials::try_load(&self.credentials_path, None)?;
+
+        let mut response = download_profile(&creds).await?;
+        trace!(?response, "Response");
+        debug!(status = response.status() as u16);
+
+        match response.status() {
+            StatusCode::Ok => {
+                debug!("Profile downloaded");
+                let config: FluvioConfig = response.body_json().await?;
+                Ok(config)
+            }
+            StatusCode::NoContent => Err(CloudLoginError::ProfileNotAvailable),
+            StatusCode::NotFound => Err(CloudLoginError::ClusterDoesNotExist(creds.email)),
+            status => Err(CloudLoginError::ProfileDownloadError(
+                status,
+                status.canonical_reason(),
+            )),
+        }
+    }
+}
+async fn get_auth0_config(remote: &str) -> Result<Response, CloudLoginError> {
+    let url_string = format!("{}/api/v1/oauth2/auth0/config", remote);
+    let url = Url::parse(&url_string)
+        .map_err(|source| CloudLoginError::UrlError { source, url_string })?;
+    let request = Request::get(url);
+    let response = execute(request).await?;
+    Ok(response)
+}
+async fn get_device_code(config: &Auth0Config) -> Result<Response, CloudLoginError> {
+    let url_string = format!("https://{}/oauth/device/code", config.domain);
+    let url = Url::parse(&url_string)
+        .map_err(|source| CloudLoginError::UrlError { source, url_string })?;
+    let mut request = Request::post(url);
+    let mime = Mime::from_str("application/x-www-form-urlencoded")?;
+    request.set_content_type(mime);
+
+    let dv = DeviceCodeRequestBody {
+        client_id: config.client_id.to_owned(),
+        scope: "openid profile email".into(),
+        audience: format!("https://{}/api/v2/", config.domain),
+    };
+
+    request.set_body(serde_urlencoded::to_string(dv)?);
+
+    let response = execute(request).await?;
+    Ok(response)
+}
+
+async fn get_auth0_token(
+    config: &Auth0Config,
+    interval: u64,
+    device_code: String,
+) -> Result<Response, CloudLoginError> {
+    let token_request_payload = TokenRequestBody {
+        client_id: config.client_id.to_owned(),
+        device_code,
+        grant_type: "urn:ietf:params:oauth:grant-type:device_code".into(),
+    };
+
+    let mut interval = async_std::stream::interval(std::time::Duration::from_secs(interval));
+    while interval.next().await.is_some() {
+        let response = try_get_auth0_token(config, &token_request_payload).await?;
+        if response.status() == StatusCode::Ok {
+            return Ok(response);
+        }
+    }
+    Err(CloudLoginError::FailedToGetAuth0Token)
+}
+async fn try_get_auth0_token(
+    config: &Auth0Config,
+    payload: &TokenRequestBody,
+) -> Result<Response, CloudLoginError> {
+    let url_string = format!("https://{}/oauth/token", config.domain);
+    let url = Url::parse(&url_string)
+        .map_err(|source| CloudLoginError::UrlError { source, url_string })?;
+    let mut request = Request::post(url);
+    let mime = Mime::from_str("application/x-www-form-urlencoded")?;
+    request.set_content_type(mime);
+    request.set_body(serde_urlencoded::to_string(payload)?);
+
+    let response = execute(request).await?;
+    Ok(response)
+}
+
+async fn authorize_auth0_user(
+    host: &str,
+    access_token: String,
+) -> Result<Response, CloudLoginError> {
+    let url_string = format!("{}/api/v1/oauth2/auth0/authorize?dont_create=1", host);
+    let url = Url::parse(&url_string)
+        .map_err(|source| CloudLoginError::UrlError { source, url_string })?;
+    let mut request = Request::get(url);
+
+    request.append_header("Authorization", &*access_token);
+
+    let response = execute(request).await?;
+    Ok(response)
+}
+async fn download_profile(creds: &Credentials) -> Result<Response, CloudLoginError> {
+    let url_string = format!("{}/api/v1/downloadProfile", creds.remote);
+    let url = Url::parse(&url_string)
+        .map_err(|source| CloudLoginError::UrlError { source, url_string })?;
+    let mut request = Request::get(url);
+    request.append_header("Authorization", &*creds.token);
+
+    let response = execute(request).await?;
+    Ok(response)
+}
+
+#[derive(Deserialize, Debug)]
+struct Auth0Config {
+    domain: String,
+    client_id: String,
+}
+
+#[derive(Serialize)]
+struct DeviceCodeRequestBody {
+    client_id: String,
+    scope: String,
+    audience: String,
+}
+
+#[derive(Deserialize)]
+struct DeviceCodeResponse {
+    device_code: String,
+    user_code: String,
+    verification_uri: String,
+    verification_uri_complete: String,
+    expires_in: u64,
+    interval: u64,
+}
+
+#[derive(Serialize)]
+struct TokenRequestBody {
+    client_id: String,
+    device_code: String,
+    grant_type: String,
+}
+
+#[derive(Deserialize)]
+struct TokenResponse {
+    access_token: String,
+    token_type: String,
+}
+
+#[derive(Debug, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct CredentialsResponse {
+    id: String,
+    token: String,
+    #[serde(skip_serializing, default)]
+    active: Option<bool>,
+}
+
+impl CredentialsResponse {
+    fn into_saved(self, remote: String, email: String) -> Credentials {
+        Credentials {
+            remote,
+            email,
+            id: self.id,
+            token: self.token,
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct CredentialsAuth0Response {
+    #[serde(flatten)]
+    inner: CredentialsResponse,
+    email: String,
+}
+
+#[derive(Debug, PartialEq, Deserialize, Serialize)]
+struct Credentials {
+    remote: String,
+    email: String,
+    id: String,
+    token: String,
+}
+impl Credentials {
+    /// Try to load credentials from disk
+    fn try_load<P: AsRef<Path>>(
+        base_path: P,
+        key: Option<CredentialKey>,
+    ) -> Result<Self, CloudLoginError> {
+        let key_md5 = if let Some(key) = key {
+            key.md5()
+        } else {
+            let current_login_path = base_path.as_ref().join(CURRENT_LOGIN_FILE_NAME);
+            fs::read_to_string(current_login_path).map_err(|_| CloudLoginError::NotLoggedIn)?
+        };
+
+        let cred_path = base_path.as_ref().join(key_md5);
+
+        let file_str =
+            fs::read_to_string(cred_path).map_err(CloudLoginError::UnableToLoadCredentials)?;
+        let creds: Credentials =
+            toml::from_str(&file_str).map_err(CloudLoginError::UnableToParseCredentials)?;
+        Ok(creds)
+    }
+    /// Try to save credentials to disk
+    fn try_save<P: AsRef<Path>>(&self, base_path: P) -> Result<(), CloudLoginError> {
+        use std::io::Write;
+        let base_path = base_path.as_ref();
+
+        // Make sure the logins directory exists
+        fs::create_dir_all(base_path).map_err(|source| {
+            CloudLoginError::UnableToCreateLoginsDir {
+                source,
+                path: base_path.to_owned(),
+            }
+        })?;
+
+        let key = CredentialKey {
+            remote: self.remote.clone(),
+            email: self.email.clone(),
+        };
+        let key_md5 = key.md5();
+        let cred_path = base_path.join(&key_md5);
+        let mut cred_file = fs::File::create(cred_path)?;
+
+        // Serializing self can never fail because Credentials: Serialize
+        cred_file
+            .write_all(toml::to_string(self).unwrap().as_bytes())
+            .map_err(CloudLoginError::UnableToSaveCredentials)?;
+
+        let current_login_path = base_path.join("current");
+        let mut current_login_file = fs::File::create(current_login_path)?;
+
+        current_login_file
+            .write_all(key_md5.as_bytes())
+            .map_err(CloudLoginError::UnableToSaveCredentials)?;
+
+        Ok(())
+    }
+}
+struct CredentialKey {
+    remote: String,
+    email: String,
+}
+
+impl CredentialKey {
+    fn md5(&self) -> String {
+        use md5::Digest;
+        let mut hasher = md5::Md5::new();
+        hasher.update(&self.remote);
+        hasher.update(&self.email);
+        let output = hasher.finalize();
+        hex::encode(output)
+    }
+}
+
+#[derive(Debug, Serialize)]
+struct LoginRequest {
+    email: String,
+    password: String,
+}
+
+async fn login_user(
+    remote: &str,
+    email: &str,
+    password: &str,
+) -> Result<Response, CloudLoginError> {
+    let url_string = format!("{}/api/v1/loginUser", remote);
+    let url = Url::parse(&url_string)
+        .map_err(|source| CloudLoginError::UrlError { source, url_string })?;
+    let mut request = Request::post(url);
+    let login = LoginRequest {
+        email: email.to_owned(),
+        password: password.to_owned(),
+    };
+
+    // Always safe to serialize when Self: Serialize
+    let body = serde_json::to_string(&login).unwrap();
+    request.set_body(body);
+
+    let response = execute(request).await?;
+    Ok(response)
+}
diff --git a/src/glue.rs.in b/src/glue.rs.in
index 413bdefa..23a24504 100644
--- a/src/glue.rs.in
+++ b/src/glue.rs.in
@@ -76,3 +76,14 @@ foreign_class!(class Offset {
     fn Offset::end() -> Offset;
     fn Offset::from_end(_: u32) -> Offset;
 });
+foreign_class!(class Cloud {
+    self_type Fluvio;
+    private constructor = empty;
+    fn _Cloud::login(
+        _: bool,
+        _: String,
+        _: Option<String>,
+        _: Option<String>,
+        _: Option<String>
+    ) -> Result<(), CloudLoginError>;
+});
diff --git a/src/lib.rs b/src/lib.rs
index 36c9d187..de13fbab 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,6 +12,8 @@ use fluvio_future::{
 use std::io::{Error, Read};
 use std::pin::Pin;
 use std::string::FromUtf8Error;
+mod cloud;
+use cloud::{CloudClient, CloudLoginError};
 
 mod _Fluvio {
     use super::*;
@@ -132,4 +134,94 @@ mod _Record {
     }
 }
 
+mod _Cloud {
+    use super::*;
+    use fluvio::config::{ConfigFile, FluvioConfig, Profile};
+    use tracing::info;
+    use url::Host;
+    const DEFAULT_PROFILE_NAME: &'static str = "cloud";
+
+    pub fn login(
+        use_oauth2: bool,
+        remote: String,
+        profile: Option<String>,
+        email: Option<String>,
+        password: Option<String>,
+    ) -> Result<(), CloudLoginError> {
+        run_block_on(async {
+            let mut client = CloudClient::with_default_path()?;
+            if use_oauth2 {
+                client.authenticate_with_auth0(remote.as_str()).await?;
+            } else {
+                use std::io;
+                use std::io::Write;
+                let email = match email {
+                    Some(email) => email.clone(),
+                    None => {
+                        print!("Infinyon Cloud email: ");
+                        io::stdout().flush()?;
+                        let mut email = String::new();
+                        io::stdin().read_line(&mut email)?;
+                        email
+                    }
+                };
+                let email = email.trim();
+                let password = match password {
+                    Some(pw) => pw.clone(),
+                    None => rpassword::read_password_from_tty(Some("Password: "))?,
+                };
+                client.authenticate(email, &password, &remote).await?;
+            }
+
+            let cluster = match client.download_profile().await {
+                Ok(cluster) => cluster,
+                Err(CloudLoginError::ClusterDoesNotExist(_))
+                | Err(CloudLoginError::ProfileNotAvailable) => {
+                    println!("Warning: You don't have any clusters, please create cluster if you want to perform fluvio functions");
+                    return Ok(());
+                }
+                Err(err) => {
+                    return Err(err);
+                }
+            };
+            println!("Fluvio cluster found, switching to profile");
+
+            save_cluster(cluster, remote, profile)?;
+            Ok(())
+        })
+    }
+
+    fn save_cluster(
+        cluster: FluvioConfig,
+        remote: String,
+        profile: Option<String>,
+    ) -> Result<(), CloudLoginError> {
+        let mut config_file = ConfigFile::load_default_or_new()?;
+        let config = config_file.mut_config();
+        let profile_name = if let Some(profile) = profile {
+            profile
+        } else {
+            profile_from_remote(remote).unwrap_or_else(|| DEFAULT_PROFILE_NAME.to_string())
+        };
+
+        let profile = Profile::new(profile_name.clone());
+        config.add_cluster(cluster, profile_name.clone());
+        config.add_profile(profile, profile_name.clone());
+        config.set_current_profile(&profile_name);
+        config_file.save()?;
+        info!(%profile_name, "Successfully saved profile");
+        Ok(())
+    }
+
+    fn profile_from_remote(remote: String) -> Option<String> {
+        let url = url::Url::parse(remote.as_str()).ok()?;
+        let host = url.host()?;
+        match host {
+            Host::Ipv4(ip4) => Some(format!("{}", ip4)),
+            Host::Ipv6(ip6) => Some(format!("{}", ip6)),
+            Host::Domain(domain) => Some(domain.to_owned().replace('.', "-")),
+        }
+    }
+}
+
 include!(concat!(env!("OUT_DIR"), "/glue.rs"));


Test patch:
diff --git a/manual-tests/test_cloud_login.py b/manual-tests/test_cloud_login.py
new file mode 100644
index 00000000..aaf57e18
--- /dev/null
+++ b/manual-tests/test_cloud_login.py
@@ -0,0 +1,11 @@
+import unittest
+from fluvio.cloud import login
+
+
+class TestFluvioCloudLogin(unittest.TestCase):
+    def test_login(self):
+        login()
+        # Comment/uncomment these as needed.
+        #login(profile='test-profile')
+        #login(useOauth2=False, email='youremail@gmail.com', password='PUT_YOURPASSWORD_HERE')
+        #login(useOauth2=False)