mirror of https://github.com/apibillme/broker
release 13.0.0
This commit is contained in:
parent
9120532fff
commit
14541291dd
|
@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [13.0.0] - 2021-04-08
|
||||
|
||||
### Added
|
||||
- Two Factor Auth
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [12.0.2] - 2021-04-08
|
||||
|
||||
### Updated
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.3.2"
|
||||
|
@ -344,12 +365,32 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"miniz_oxide 0.4.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base-x"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
||||
|
||||
[[package]]
|
||||
name = "base32"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.12.3"
|
||||
|
@ -480,11 +521,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "broker"
|
||||
version = "12.0.2"
|
||||
version = "13.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"base64 0.13.0",
|
||||
"chbs",
|
||||
"driftwood",
|
||||
"futures",
|
||||
"go-flag",
|
||||
|
@ -503,6 +545,7 @@ dependencies = [
|
|||
"tide",
|
||||
"tide-acme",
|
||||
"tide-rustls",
|
||||
"totp-rs",
|
||||
"uuid",
|
||||
"zxcvbn",
|
||||
]
|
||||
|
@ -523,6 +566,12 @@ dependencies = [
|
|||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
|
@ -565,6 +614,23 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chbs"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3553a1f278b0090017b37d0f2e4878ee1de65439626bd51030ef2bd97ebdf7f7"
|
||||
dependencies = [
|
||||
"derive_builder",
|
||||
"failure",
|
||||
"rand 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "checked_int_cast"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.19"
|
||||
|
@ -616,6 +682,12 @@ dependencies = [
|
|||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
|
@ -686,6 +758,49 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.1"
|
||||
|
@ -803,6 +918,16 @@ version = "2.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-oid-macro"
|
||||
version = "0.4.0"
|
||||
|
@ -905,6 +1030,28 @@ version = "2.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
|
||||
|
||||
[[package]]
|
||||
name = "failure"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"failure_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "failure_derive"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.4.1"
|
||||
|
@ -1120,6 +1267,22 @@ dependencies = [
|
|||
"polyval",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.0"
|
||||
|
@ -1254,6 +1417,25 @@ dependencies = [
|
|||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
"scoped_threadpool",
|
||||
"tiff",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "infer"
|
||||
version = "0.2.3"
|
||||
|
@ -1293,6 +1475,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.50"
|
||||
|
@ -1415,6 +1606,34 @@ version = "2.3.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nb-connect"
|
||||
version = "1.1.0"
|
||||
|
@ -1494,6 +1713,28 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.14"
|
||||
|
@ -1513,6 +1754,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.1.1"
|
||||
|
@ -1601,6 +1848,18 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.0.3"
|
||||
|
@ -1652,6 +1911,16 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qrcode"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f"
|
||||
dependencies = [
|
||||
"checked_int_cast",
|
||||
"image",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
|
@ -1760,6 +2029,31 @@ dependencies = [
|
|||
"rand_core 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rcgen"
|
||||
version = "0.8.9"
|
||||
|
@ -1853,6 +2147,12 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
|
@ -1926,6 +2226,18 @@ version = "1.0.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "scoped_threadpool"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.0"
|
||||
|
@ -2006,6 +2318,19 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if 1.0.0",
|
||||
"cpuid-bool 0.1.2",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.6.0"
|
||||
|
@ -2206,6 +2531,18 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
|
@ -2299,6 +2636,17 @@ dependencies = [
|
|||
"tide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
|
||||
dependencies = [
|
||||
"jpeg-decoder",
|
||||
"miniz_oxide 0.4.4",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.44"
|
||||
|
@ -2363,6 +2711,22 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "totp-rs"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71aa19b1e6a6bdf8c212498da1a8e54c50ec7c22da9862dea576910616d1137e"
|
||||
dependencies = [
|
||||
"base32",
|
||||
"base64 0.13.0",
|
||||
"byteorder",
|
||||
"hmac 0.8.1",
|
||||
"image",
|
||||
"qrcode",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.13.0"
|
||||
|
@ -2581,6 +2945,12 @@ dependencies = [
|
|||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c"
|
||||
|
||||
[[package]]
|
||||
name = "wepoll-sys"
|
||||
version = "3.0.1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "broker"
|
||||
version = "12.0.2"
|
||||
version = "13.0.0"
|
||||
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
@ -12,7 +12,7 @@ readme = "README.md"
|
|||
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
tide = "0.16.0"
|
||||
tide = "0.16"
|
||||
async-std = { version = "1.9", features = ["attributes"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_derive = "1"
|
||||
|
@ -34,3 +34,5 @@ tide-acme = "0.1.0"
|
|||
base64 = "0.13"
|
||||
mailchecker = "4"
|
||||
zxcvbn = "2"
|
||||
totp-rs = { version = "0.6", features = ["qr"] }
|
||||
chbs = "0.1"
|
||||
|
|
81
README.md
81
README.md
|
@ -26,12 +26,14 @@ Broker follows an insert-only/publish/subscribe paradigm rather than a REST CRUD
|
|||
* Issues JWTs for authentication (username) and authorization (scopes) for external services
|
||||
* Verify endpoint for external services using Broker user system like [portal](https://crates.io/crates/portal)
|
||||
* Secure password storage with Argon2 encoding
|
||||
* Uses Global NTP servers and doesn't rely on your local server time for JWT expiry timing
|
||||
* Uses Global NTP servers and doesn't rely on your local server time for JWT expiry timing and Two Factor timing
|
||||
* Sync latest events on SSE client connection
|
||||
* Auto-provision and renews SSL cert via LetsEncrypt or use your own SSL cert
|
||||
* User Management API endpoints (create, revoke, unrevoke, list, get, update)
|
||||
* User Email Address Validation (regex and blacklist check against throwaway emails) using [mailchecker](https://crates.io/crates/mailchecker)
|
||||
* Password Strength Checker using [zxcvbn](https://crates.io/crates/zxcvbn)
|
||||
* Two Factor Authenication with QR code generation for Google Authenticator, Authy, etc.
|
||||
* Secure user password resets with a TOTP with a configurable time duration
|
||||
|
||||
### How it works
|
||||
|
||||
|
@ -73,6 +75,7 @@ POST /create_user
|
|||
"admin_token": "letmein",
|
||||
"tenant_name": "tenant_1",
|
||||
"email": "bob@hotmail.com",
|
||||
"two_factor": true,
|
||||
"scopes": ["news:get", "news:post"],
|
||||
"data": {
|
||||
"name": "Robert Wieland",
|
||||
|
@ -81,7 +84,7 @@ POST /create_user
|
|||
}
|
||||
```
|
||||
- `admin_token` is required and can be set in the command args - it is for not allowing everyone to add a user - the default is `letmein`
|
||||
- `email`, `scopes`, and `data` are optional fields
|
||||
- `email`, `scopes`, `two_factor`, and `data` are optional fields
|
||||
|
||||
will return `200` or `500` or `400`
|
||||
|
||||
|
@ -94,9 +97,11 @@ POST /login
|
|||
```json
|
||||
{
|
||||
"username": "bob",
|
||||
"password": "password1"
|
||||
"password": "password1",
|
||||
"totp": "123456",
|
||||
}
|
||||
```
|
||||
- `totp` is an optional field for two factor authentication
|
||||
|
||||
will return
|
||||
```json
|
||||
|
@ -195,6 +200,7 @@ will return: `200` or `500` or `400` or `401`
|
|||
{
|
||||
"id": "69123c04-fa42-4193-a6c5-ab2fc27658b1",
|
||||
"password": "***",
|
||||
"totp": "***",
|
||||
"revoked": false,
|
||||
"tenant_name": "tenant_1",
|
||||
"username": "bob",
|
||||
|
@ -207,7 +213,7 @@ will return: `200` or `500` or `400` or `401`
|
|||
}
|
||||
]
|
||||
```
|
||||
- note: `email`, `scopes`, and `data` can be `null`
|
||||
- note: `email`, `scopes`, `two_factor`, and `data` can be `null`
|
||||
|
||||
#### Optional - get user
|
||||
|
||||
|
@ -229,6 +235,7 @@ will return: `200` or `500` or `400` or `401`
|
|||
{
|
||||
"id": "69123c04-fa42-4193-a6c5-ab2fc27658b1",
|
||||
"password": "***",
|
||||
"totp": "***",
|
||||
"revoked": false,
|
||||
"tenant_name": "tenant_1",
|
||||
"username": "bob",
|
||||
|
@ -240,7 +247,7 @@ will return: `200` or `500` or `400` or `401`
|
|||
}
|
||||
}
|
||||
```
|
||||
- note: `email`, `scopes`, and `data` can be `null`
|
||||
- note: `email`, `scopes`, `two_factor`, and `data` can be `null`
|
||||
|
||||
#### Optional - update user
|
||||
|
||||
|
@ -275,6 +282,69 @@ GET or HEAD /
|
|||
|
||||
will return: `200`
|
||||
|
||||
#### Optional - generate two factor QR Code
|
||||
|
||||
```html
|
||||
POST /create_qr
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"issuer": "Broker",
|
||||
"admin_token": "letmein",
|
||||
"username": "bob"
|
||||
}
|
||||
```
|
||||
- note: put the name of your application in the issuer field
|
||||
- note: the ID of the QR will be the user's username and your issuer field
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
200 - will return the qr code in PNG format in base64
|
||||
```json
|
||||
{
|
||||
"qr": "dGhpc2lzYXN0cmluZw=="
|
||||
}
|
||||
```
|
||||
|
||||
#### Optional - create totp
|
||||
|
||||
```html
|
||||
POST /create_totp
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"admin_token": "letmein",
|
||||
"username": "bob",
|
||||
}
|
||||
```
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
200 - will return the totp
|
||||
```json
|
||||
{
|
||||
"totp": "622346"
|
||||
}
|
||||
```
|
||||
- note: these TOTPs can only be used with the password reset endpoint
|
||||
|
||||
#### Optional - user password reset
|
||||
|
||||
```html
|
||||
POST /password_reset
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"totp": "622346",
|
||||
"username": "bob",
|
||||
"password": "password1"
|
||||
}
|
||||
```
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
### Install
|
||||
|
||||
``` cargo install broker ```
|
||||
|
@ -292,6 +362,7 @@ will return: `200`
|
|||
- the `domain` flag is the domain name (e.g. api.broker.com) of the domain you want to register with LetsEncrypt - must be fully resolvable
|
||||
- the `admin_token` flag is the password for the admin to add users - default `letmein`
|
||||
- the `password_checker` flag enables zxcvbn password checking - default `false`
|
||||
- the `totp_duration` flag is the duration of the TOTP for user generated password reset - default 300 seconds (5 min)
|
||||
- production example: `./broker --secure="true" --admin_token"23ce4234@123$" --jwt_secret="xTJEX234$##$" --domain="api.broker.com" --password_checker="true"`
|
||||
|
||||
### Service
|
||||
|
|
229
src/main.rs
229
src/main.rs
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, iter::Iterator};
|
||||
use std::{collections::HashMap, convert::TryInto, iter::Iterator};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
@ -16,6 +16,8 @@ use futures::StreamExt;
|
|||
use tide_acme::{AcmeConfig, TideRustlsExt};
|
||||
use mailchecker::is_valid;
|
||||
use zxcvbn::zxcvbn;
|
||||
use chbs::{config::BasicConfig, prelude::*};
|
||||
use totp_rs::{Algorithm, TOTP};
|
||||
|
||||
lazy_static! {
|
||||
static ref DB : Arc<rocksdb::DB> = {
|
||||
|
@ -47,6 +49,7 @@ pub struct EnvVarConfig {
|
|||
pub key_path: String,
|
||||
pub cert_path: String,
|
||||
pub password_checker: bool,
|
||||
pub totp_duration: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -59,6 +62,8 @@ pub struct User {
|
|||
pub tenant_name: String,
|
||||
pub data: Option<serde_json::Value>,
|
||||
pub scopes: Option<Vec<String>>,
|
||||
pub totp: String,
|
||||
pub two_factor: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -70,6 +75,7 @@ pub struct UserForm {
|
|||
pub email: Option<String>,
|
||||
pub data: Option<serde_json::Value>,
|
||||
pub scopes: Option<Vec<String>>,
|
||||
pub two_factor: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
@ -81,12 +87,32 @@ pub struct UpdateUserForm {
|
|||
pub password: Option<String>,
|
||||
pub data: Option<serde_json::Value>,
|
||||
pub scopes: Option<Vec<String>>,
|
||||
pub two_factor: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AdminTokenForm {
|
||||
pub admin_token: String,
|
||||
}
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CreateQRForm {
|
||||
pub issuer: String,
|
||||
pub admin_token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct CreateTOTPForm {
|
||||
pub admin_token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PasswordResetForm {
|
||||
pub totp: String,
|
||||
pub password: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct RevokeUserForm {
|
||||
|
@ -98,6 +124,7 @@ pub struct RevokeUserForm {
|
|||
pub struct LoginForm {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub totp: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -174,6 +201,7 @@ fn modify_user(update_user_form: UpdateUserForm) -> Result<Option<String>> {
|
|||
|
||||
user.data = update_user_form.data;
|
||||
user.scopes = update_user_form.scopes;
|
||||
user.two_factor = update_user_form.two_factor;
|
||||
|
||||
match update_user_form.password {
|
||||
Some(password) => {
|
||||
|
@ -323,6 +351,12 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut config = BasicConfig::default();
|
||||
config.words = 12;
|
||||
config.separator = "-".into();
|
||||
let scheme = config.to_scheme();
|
||||
let totp = scheme.generate();
|
||||
|
||||
let uuid = Uuid::new_v4();
|
||||
let config = Argon2Config::default();
|
||||
let uuid_string = Uuid::new_v4().to_string();
|
||||
|
@ -338,6 +372,8 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
|
|||
email: user_form.clone().email,
|
||||
data: user_form.clone().data,
|
||||
scopes: user_form.clone().scopes,
|
||||
totp,
|
||||
two_factor: user_form.clone().two_factor,
|
||||
};
|
||||
|
||||
puts_user(new_user).unwrap();
|
||||
|
@ -356,20 +392,74 @@ async fn create_jwt(login: LoginForm) -> Result<Option<String>> {
|
|||
if !user.revoked {
|
||||
let verified = argon2::verify_encoded(&user.password, login.password.as_bytes())?;
|
||||
if verified {
|
||||
let app = env_var_config();
|
||||
let iat = nippy::get_unix_ntp_time().await?;
|
||||
let exp = iat + app.jwt_expiry;
|
||||
let iss = "Broker".to_string();
|
||||
let aud: String;
|
||||
match user.scopes.clone() {
|
||||
Some(scopes) => {
|
||||
aud = scopes.join(",");
|
||||
match user.two_factor {
|
||||
Some(two_factor) => {
|
||||
if two_factor {
|
||||
match login.totp {
|
||||
Some(token) => {
|
||||
let totp = TOTP::new(
|
||||
Algorithm::SHA512,
|
||||
6,
|
||||
1,
|
||||
30,
|
||||
user.clone().totp,
|
||||
);
|
||||
let time = nippy::get_unix_ntp_time().await?;
|
||||
if totp.check(&token, time.try_into()?) {
|
||||
let app = env_var_config();
|
||||
let iat = nippy::get_unix_ntp_time().await?;
|
||||
let exp = iat + app.jwt_expiry;
|
||||
let iss = "Broker".to_string();
|
||||
let aud: String;
|
||||
match user.scopes.clone() {
|
||||
Some(scopes) => {
|
||||
aud = scopes.join(",");
|
||||
},
|
||||
None => { aud = "".to_string() }
|
||||
}
|
||||
let my_claims = Claims{sub: user.clone().username, exp, iat, iss, aud};
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(app.jwt_secret.as_ref()))?;
|
||||
Ok(Some(token))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
None => { Ok(None) }
|
||||
}
|
||||
} else {
|
||||
let app = env_var_config();
|
||||
let iat = nippy::get_unix_ntp_time().await?;
|
||||
let exp = iat + app.jwt_expiry;
|
||||
let iss = "Broker".to_string();
|
||||
let aud: String;
|
||||
match user.scopes.clone() {
|
||||
Some(scopes) => {
|
||||
aud = scopes.join(",");
|
||||
},
|
||||
None => { aud = "".to_string() }
|
||||
}
|
||||
let my_claims = Claims{sub: user.clone().username, exp, iat, iss, aud};
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(app.jwt_secret.as_ref()))?;
|
||||
Ok(Some(token))
|
||||
}
|
||||
},
|
||||
None => { aud = "".to_string() }
|
||||
None => {
|
||||
let app = env_var_config();
|
||||
let iat = nippy::get_unix_ntp_time().await?;
|
||||
let exp = iat + app.jwt_expiry;
|
||||
let iss = "Broker".to_string();
|
||||
let aud: String;
|
||||
match user.scopes.clone() {
|
||||
Some(scopes) => {
|
||||
aud = scopes.join(",");
|
||||
},
|
||||
None => { aud = "".to_string() }
|
||||
}
|
||||
let my_claims = Claims{sub: user.clone().username, exp, iat, iss, aud};
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(app.jwt_secret.as_ref()))?;
|
||||
Ok(Some(token))
|
||||
}
|
||||
}
|
||||
let my_claims = Claims{sub: user.clone().username, exp, iat, iss, aud};
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(app.jwt_secret.as_ref()))?;
|
||||
Ok(Some(token))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -396,6 +486,7 @@ fn env_var_config() -> EnvVarConfig {
|
|||
let mut key_path = "certs/private_key.pem".to_string();
|
||||
let mut cert_path = "certs/chain.pem".to_string();
|
||||
let mut password_checker = false;
|
||||
let mut totp_duration: u64 = 300;
|
||||
let _ : Vec<String> = go_flag::parse(|flags| {
|
||||
flags.add_flag("port", &mut port);
|
||||
flags.add_flag("origin", &mut origin);
|
||||
|
@ -410,9 +501,10 @@ fn env_var_config() -> EnvVarConfig {
|
|||
flags.add_flag("key_path", &mut key_path);
|
||||
flags.add_flag("cert_path", &mut cert_path);
|
||||
flags.add_flag("password_checker", &mut password_checker);
|
||||
flags.add_flag("totp_duration", &mut totp_duration);
|
||||
});
|
||||
|
||||
EnvVarConfig{port, origin, jwt_expiry, jwt_secret, secure, domain, certs, db, admin_token, auto_cert, key_path, cert_path, password_checker}
|
||||
EnvVarConfig{port, origin, jwt_expiry, jwt_secret, secure, domain, certs, db, admin_token, auto_cert, key_path, cert_path, password_checker, totp_duration}
|
||||
}
|
||||
|
||||
async fn jwt_verify(token: String) -> Result<Option<TokenData<Claims>>> {
|
||||
|
@ -575,6 +667,7 @@ async fn get_user(mut req: Request<()>) -> tide::Result {
|
|||
let users: Vec<_> = users.iter().map(|user| {
|
||||
let mut u = user.to_owned();
|
||||
u.password = "***".to_string();
|
||||
u.totp = "***".to_string();
|
||||
u
|
||||
}).collect();
|
||||
Ok(tide::Response::builder(200).body(json!(users)).header("content-type", "application/json").build())
|
||||
|
@ -592,6 +685,7 @@ async fn list_users(mut req: Request<()>) -> tide::Result {
|
|||
let users: Vec<_> = users.iter().map(|user| {
|
||||
let mut u = user.to_owned();
|
||||
u.password = "***".to_string();
|
||||
u.totp = "***".to_string();
|
||||
u
|
||||
}).collect();
|
||||
Ok(tide::Response::builder(200).body(json!(users)).header("content-type", "application/json").build())
|
||||
|
@ -647,6 +741,110 @@ async fn health(_: Request<()>) -> tide::Result {
|
|||
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
|
||||
}
|
||||
|
||||
async fn create_qr(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let create_qr_form : CreateQRForm = serde_json::from_str(&r)?;
|
||||
|
||||
let configure = env_var_config();
|
||||
|
||||
if create_qr_form.admin_token == configure.admin_token {
|
||||
match get_user_by_username(create_qr_form.username)? {
|
||||
Some(user) => {
|
||||
let totp = TOTP::new(
|
||||
Algorithm::SHA512,
|
||||
6,
|
||||
1,
|
||||
30,
|
||||
user.totp,
|
||||
);
|
||||
let code = totp.get_qr(&user.username, &create_qr_form.issuer).unwrap();
|
||||
let j = json!({"qr": code});
|
||||
|
||||
Ok(tide::Response::builder(200).body(j).header("content-type", "application/json").build())
|
||||
},
|
||||
None => {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_totp(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let create_totp_form : CreateTOTPForm = serde_json::from_str(&r)?;
|
||||
|
||||
let configure = env_var_config();
|
||||
|
||||
if create_totp_form.admin_token == configure.admin_token {
|
||||
match get_user_by_username(create_totp_form.username)? {
|
||||
Some(user) => {
|
||||
let totp = TOTP::new(
|
||||
Algorithm::SHA512,
|
||||
6,
|
||||
1,
|
||||
configure.totp_duration,
|
||||
user.totp,
|
||||
);
|
||||
|
||||
let time = nippy::get_unix_ntp_time().await?;
|
||||
let token = totp.generate(time.try_into()?);
|
||||
let j = json!({"totp": token});
|
||||
|
||||
Ok(tide::Response::builder(200).body(j).header("content-type", "application/json").build())
|
||||
},
|
||||
None => {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
async fn password_reset(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let password_reset_form : PasswordResetForm = serde_json::from_str(&r)?;
|
||||
|
||||
let configure = env_var_config();
|
||||
|
||||
match get_user_by_username(password_reset_form.username)? {
|
||||
Some(user) => {
|
||||
let totp = TOTP::new(
|
||||
Algorithm::SHA512,
|
||||
6,
|
||||
1,
|
||||
configure.totp_duration,
|
||||
user.totp,
|
||||
);
|
||||
|
||||
let time = nippy::get_unix_ntp_time().await?;
|
||||
let check = totp.check(&password_reset_form.totp, time.try_into()?);
|
||||
|
||||
if check {
|
||||
let update_user_form = UpdateUserForm{
|
||||
username: user.username,
|
||||
password: Some(password_reset_form.password),
|
||||
tenant_name: Some(user.tenant_name),
|
||||
admin_token: configure.admin_token,
|
||||
email: user.email,
|
||||
data: user.data,
|
||||
scopes: user.scopes,
|
||||
two_factor: user.two_factor,
|
||||
};
|
||||
modify_user(update_user_form)?;
|
||||
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
},
|
||||
None => {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
|
||||
|
@ -672,6 +870,9 @@ async fn main() -> tide::Result<()> {
|
|||
app.at("/get_user").post(get_user);
|
||||
app.at("/unrevoke_user").post(unrevoke_user);
|
||||
app.at("/update_user").post(update_user);
|
||||
app.at("/create_qr").post(create_qr);
|
||||
app.at("/create_totp").post(create_totp);
|
||||
app.at("/password_reset").post(password_reset);
|
||||
|
||||
app.at("/sse").get(tide::sse::endpoint(|req: Request<()>, sender| async move {
|
||||
|
||||
|
|
Loading…
Reference in New Issue