mirror of https://github.com/apibillme/broker
Compare commits
13 Commits
155b14d023
...
34848f7860
Author | SHA1 | Date |
---|---|---|
Bevan Hunt | 34848f7860 | |
Bevan Hunt | a2b872651e | |
Bevan Hunt | 14541291dd | |
Bevan Hunt | 9120532fff | |
Bevan Hunt | 067a83a2ad | |
Bevan Hunt | ff3ee38608 | |
Bevan Hunt | e488cc75e0 | |
Bevan Hunt | 559cc792e8 | |
Bevan Hunt | 597b05b6f7 | |
Bevan Hunt | 8eaac0fe9a | |
Bevan Hunt | 5a167fba5a | |
Bevan Hunt | d866272f02 | |
Bevan Hunt | f8ed649000 |
83
CHANGELOG.md
83
CHANGELOG.md
|
@ -4,25 +4,96 @@ 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
|
||||
- User password reset
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [12.0.2] - 2021-04-08
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [12.0.1] - 2021-04-08
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [12.0.0] - 2021-04-08
|
||||
|
||||
### Added
|
||||
- Added JWT custom scopes on user
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [11.2.0] - 2021-04-06
|
||||
|
||||
### Added
|
||||
- Added zxcvbn password strength checker
|
||||
|
||||
### Fixed
|
||||
- Fixed JWT issued not being in JSON format
|
||||
|
||||
## [11.1.0] - 2021-04-06
|
||||
|
||||
### Added
|
||||
- Added use your own SSL cert
|
||||
- Added health check endpoints
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [11.0.0] - 2021-04-05
|
||||
|
||||
### Added
|
||||
- Added email field on user
|
||||
- Added data field on user
|
||||
- Added user email address validation
|
||||
- Added update user endpoint
|
||||
|
||||
## [10.0.0] - 2021-04-05
|
||||
|
||||
### Fixed
|
||||
- Fixed infinite SSE event sending
|
||||
|
||||
### Changed
|
||||
- Changed RocksDB keys for user and event to be tenanted
|
||||
- Changed default RocksDB path from tmp to db
|
||||
- Changed create user endpoint URL
|
||||
|
||||
### Added
|
||||
- Added list users endpoint
|
||||
- Added revoke user endpoint
|
||||
- Added get user endpoint
|
||||
- Added unrevoke user endpoint
|
||||
|
||||
### Updated
|
||||
- Updated README
|
||||
|
||||
## [9.1.0] - 2021-04-02
|
||||
|
||||
### Added
|
||||
- verify optional endpoint
|
||||
- Added verify endpoint
|
||||
|
||||
## [9.0.2] - 2021-03-25
|
||||
|
||||
### Fixed
|
||||
- keys on event
|
||||
- Fixed keys on event
|
||||
|
||||
## [9.0.0] - 2021-03-25
|
||||
|
||||
### Removed
|
||||
- removed tenant_name from event - uses users event name
|
||||
- Removed tenant_name from event - uses users event name
|
||||
|
||||
## [8.1.x] - 2021-03-25
|
||||
|
||||
### Added
|
||||
- Adds http basic auth
|
||||
- Added http basic auth
|
||||
|
||||
## [8.0.x] - 2021-03-23
|
||||
|
||||
|
@ -33,10 +104,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
## [7.0.x] - 2021-03-22
|
||||
|
||||
### Added
|
||||
- Adds tide-acme
|
||||
- Added tide-acme
|
||||
|
||||
## Changed
|
||||
- Changes command args
|
||||
- Changed command args
|
||||
|
||||
## [6.1.x] - 2021-03-21
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -90,6 +111,12 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||
|
||||
[[package]]
|
||||
name = "ascii_utils"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
|
||||
|
||||
[[package]]
|
||||
name = "async-attributes"
|
||||
version = "1.1.2"
|
||||
|
@ -338,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"
|
||||
|
@ -390,6 +437,21 @@ dependencies = [
|
|||
"which",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
|
@ -459,11 +521,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "broker"
|
||||
version = "9.1.0"
|
||||
version = "13.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"base64 0.13.0",
|
||||
"chbs",
|
||||
"driftwood",
|
||||
"futures",
|
||||
"go-flag",
|
||||
|
@ -471,6 +534,7 @@ dependencies = [
|
|||
"json",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
"mailchecker",
|
||||
"nippy",
|
||||
"rmp-serde",
|
||||
"rocksdb",
|
||||
|
@ -481,7 +545,9 @@ dependencies = [
|
|||
"tide",
|
||||
"tide-acme",
|
||||
"tide-rustls",
|
||||
"totp-rs",
|
||||
"uuid",
|
||||
"zxcvbn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -500,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"
|
||||
|
@ -542,17 +614,36 @@ 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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.44",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -585,12 +676,18 @@ dependencies = [
|
|||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"strsim 0.8.0",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"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"
|
||||
|
@ -661,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"
|
||||
|
@ -727,6 +867,41 @@ version = "0.1.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.9.3",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "4.0.2"
|
||||
|
@ -743,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"
|
||||
|
@ -768,6 +953,31 @@ dependencies = [
|
|||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"derive_builder_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
|
@ -795,6 +1005,12 @@ dependencies = [
|
|||
"tide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
|
@ -814,6 +1030,47 @@ 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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36996e5f56f32ca51a937f325094fa450b32df871af1a89be331b7145b931bfc"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast_chemail"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
|
||||
dependencies = [
|
||||
"ascii_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.4.0"
|
||||
|
@ -839,6 +1096,12 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
|
@ -1004,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"
|
||||
|
@ -1118,9 +1397,15 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
"quick-error 1.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.2"
|
||||
|
@ -1132,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"
|
||||
|
@ -1147,6 +1451,15 @@ dependencies = [
|
|||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
|
@ -1162,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"
|
||||
|
@ -1263,6 +1585,15 @@ dependencies = [
|
|||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mailchecker"
|
||||
version = "4.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1be9884d8adeab79160bc07b42bce71324406c4792fcf9e785476dbc796d3d55"
|
||||
dependencies = [
|
||||
"fast_chemail",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.8"
|
||||
|
@ -1275,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"
|
||||
|
@ -1354,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"
|
||||
|
@ -1373,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"
|
||||
|
@ -1461,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"
|
||||
|
@ -1512,12 +1911,28 @@ 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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.9"
|
||||
|
@ -1614,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"
|
||||
|
@ -1707,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"
|
||||
|
@ -1780,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"
|
||||
|
@ -1860,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"
|
||||
|
@ -2031,6 +2502,12 @@ version = "0.8.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.0"
|
||||
|
@ -2054,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"
|
||||
|
@ -2147,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"
|
||||
|
@ -2211,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"
|
||||
|
@ -2429,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"
|
||||
|
@ -2510,3 +3032,18 @@ checksum = "0de7bff972b4f2a06c85f6d8454b09df153af7e3a4ec2aac81db1b105b684ddb"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zxcvbn"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5f9db3a05b2ee81dcda4602487314ab654eca316f517be2e2e64175658f0dd0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"derive_builder",
|
||||
"fancy-regex",
|
||||
"itertools",
|
||||
"lazy_static",
|
||||
"quick-error 2.0.0",
|
||||
"regex",
|
||||
]
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "broker"
|
||||
version = "9.1.0"
|
||||
version = "13.0.0"
|
||||
authors = ["Bevan Hunt <bevan@bevanhunt.com>"]
|
||||
edition = "2018"
|
||||
license = "MIT"
|
||||
|
@ -12,14 +12,14 @@ 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"
|
||||
json = "0.12"
|
||||
rocksdb = "0.15"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
jsonwebtoken = "7.0.1"
|
||||
jsonwebtoken = "7"
|
||||
go-flag = "0.1"
|
||||
lazy_static = "1.4"
|
||||
nippy = "2"
|
||||
|
@ -32,3 +32,7 @@ tide-rustls = "0.3"
|
|||
futures = "0.3"
|
||||
tide-acme = "0.1.0"
|
||||
base64 = "0.13"
|
||||
mailchecker = "4"
|
||||
zxcvbn = "2"
|
||||
totp-rs = { version = "0.6", features = ["qr"] }
|
||||
chbs = "0.1"
|
||||
|
|
292
README.md
292
README.md
|
@ -15,19 +15,25 @@ Broker follows an insert-only/publish/subscribe paradigm rather than a REST CRUD
|
|||
### Features
|
||||
|
||||
* Very performant with almost no CPU and memory usage
|
||||
* Under 500 lines of code
|
||||
* Under 1000 lines of code
|
||||
* Secure Real-time Event Stream via SSE - requires the use of [broker-client](https://www.npmjs.com/package/broker-client)
|
||||
* Supports CORS
|
||||
* JSON API
|
||||
* Add users with admin token permission
|
||||
* Multi-tenant
|
||||
* Supports SSL - full end-to-end encryption
|
||||
* Provides user authentication with JWTs or HTTP Basic
|
||||
* Secure passwords with Argon2 encoding
|
||||
* Uses Global NTP servers and doesn't rely on your local server time
|
||||
* Insert event via JSON POST request
|
||||
* Sync latest events on SSE client connection
|
||||
* Auto-provision and renews SSL cert via LetsEncrypt
|
||||
* 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 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
|
||||
|
||||
|
@ -44,9 +50,11 @@ The side-effect of this system is that the latest event is the schema. This is p
|
|||
* [React Debounce Input](https://www.npmjs.com/package/react-debounce-input) - React input for Real-time Submission (Edit in Place forms)
|
||||
|
||||
### Broker FAQ
|
||||
* Why compete against Parse Server and Firebase?
|
||||
* Why compete against Parse Server, Auth0, and Firebase?
|
||||
|
||||
[Firebase](https://firebase.google.com/) is not open-source, is not free, and has complicated pricing. [Parse Server](https://github.com/parse-community/parse-server) doesn't have real-time features and is about 30,000 LOC of JS.
|
||||
* [Firebase](https://firebase.google.com/) is not open-source, is not free, and has complicated pricing.
|
||||
* [Parse Server](https://github.com/parse-community/parse-server) doesn't have real-time features and is about 30,000 LOC of JS.
|
||||
* [Auth0](https://auth0.com) is not open-source, is not free, and is expensive.
|
||||
|
||||
* Will broker work with mobile apps?
|
||||
|
||||
|
@ -57,15 +65,26 @@ Yes with React Native. There may be native 3rd party libraries for SSE that work
|
|||
#### Step 1 - create a user
|
||||
|
||||
```html
|
||||
POST /users
|
||||
POST /create_user
|
||||
```
|
||||
- public endpoint
|
||||
- POST JSON to create a user
|
||||
```json
|
||||
{"username":{...}, "password":{...}, "admin_token":{...}, "tenant_name":{...}}
|
||||
{
|
||||
"username": "bob",
|
||||
"password": "password1",
|
||||
"admin_token": "letmein",
|
||||
"tenant_name": "tenant_1",
|
||||
"email": "bob@hotmail.com",
|
||||
"two_factor": true,
|
||||
"scopes": ["news:get", "news:post"],
|
||||
"data": {
|
||||
"name": "Robert Wieland",
|
||||
"image": "https://img.com/bucket/123/123.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
- where {...} is for username is a string, password is a string, admin_token is a string, and tenant_name is a string
|
||||
- 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`
|
||||
- `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`, `two_factor`, and `data` are optional fields
|
||||
|
||||
will return `200` or `500` or `400`
|
||||
|
||||
|
@ -75,17 +94,23 @@ will return `200` or `500` or `400`
|
|||
POST /login
|
||||
```
|
||||
- public endpoint
|
||||
- POST JSON to login
|
||||
```json
|
||||
{"username":{...}, "password":{...}}
|
||||
{
|
||||
"username": "bob",
|
||||
"password": "password1",
|
||||
"totp": "123456",
|
||||
}
|
||||
```
|
||||
- where {...} is for username is a string and password is a string
|
||||
- `totp` is required if two factor is enabled for the user - if not the field can be omitted
|
||||
|
||||
will return
|
||||
```json
|
||||
{"jwt":{...}}
|
||||
{
|
||||
"jwt": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTc2NzQ5MTUsImlhdCI6MTYxNzU4ODUxNSwiaXNzIjoiRGlzcGF0Y2hlciIsInN1YiI6ImZvbyJ9.OwiaZJcFUC_B0CA0ffRZVTWKRf5_vQ7vt5USNJEeKRE"
|
||||
}
|
||||
```
|
||||
- where {...} is a JWT (string)
|
||||
- note: `iat` is the issue time, `exp` is the expiry time, `sub` is the username, `iss` is `Broker`, while `aud` is the user scopes joined with a comma like in this example `news:get,news:post`
|
||||
- note: if you need to debug your JWT then visit [jwt.io](https://jwt.io)
|
||||
|
||||
#### Step 3 - connect to SSE
|
||||
|
||||
|
@ -102,11 +127,15 @@ GET /sse
|
|||
POST /insert
|
||||
```
|
||||
- authenticated endpoint (Authorization: Bearer {jwt}) or (Authorization: Basic {username:password})
|
||||
- POST JSON to insert an event
|
||||
```json
|
||||
{"event":{...}, "data":{...}}
|
||||
{
|
||||
"event": "test",
|
||||
"data": {
|
||||
"name": "robert",
|
||||
"image": "https://img.com/bucket/123/123.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
- where {...} is for the event a string and data is any JSON you want
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
|
@ -120,21 +149,221 @@ GET /verify
|
|||
|
||||
will return: `200` or `500` or `401`
|
||||
|
||||
#### Optional - revoke user
|
||||
|
||||
```html
|
||||
POST /revoke_user
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"admin_token": "letmein",
|
||||
"username": "bob"
|
||||
}
|
||||
```
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
- note: revoked users cannot login
|
||||
|
||||
#### Optional - unrevoke user
|
||||
|
||||
```html
|
||||
POST /unrevoke_user
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"admin_token": "letmein",
|
||||
"username": "bob"
|
||||
}
|
||||
```
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
#### Optional - list users
|
||||
|
||||
```html
|
||||
POST /list_users
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"admin_token": "letmein",
|
||||
}
|
||||
```
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
200 - will return an array of objects
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "69123c04-fa42-4193-a6c5-ab2fc27658b1",
|
||||
"password": "***",
|
||||
"totp": "***",
|
||||
"revoked": false,
|
||||
"tenant_name": "tenant_1",
|
||||
"username": "bob",
|
||||
"email": "bob@hotmail.com",
|
||||
"scopes": ["news:get", "news:post"],
|
||||
"data": {
|
||||
"name": "Robert Wieland",
|
||||
"image": "https://img.com/bucket/123/123.jpg"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
- note: `email`, `scopes`, `two_factor`, and `data` can be `null`
|
||||
|
||||
#### Optional - get user
|
||||
|
||||
```html
|
||||
POST /get_user
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"admin_token": "letmein",
|
||||
"username": "bob"
|
||||
}
|
||||
```
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
200 - will return an array of objects
|
||||
```json
|
||||
{
|
||||
"id": "69123c04-fa42-4193-a6c5-ab2fc27658b1",
|
||||
"password": "***",
|
||||
"totp": "***",
|
||||
"revoked": false,
|
||||
"tenant_name": "tenant_1",
|
||||
"username": "bob",
|
||||
"email": "bob@hotmail.com",
|
||||
"scopes": ["news:get", "news:post"],
|
||||
"data": {
|
||||
"name": "Robert Wieland",
|
||||
"image": "https://img.com/bucket/123/123.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
- note: `email`, `scopes`, `two_factor`, and `data` can be `null`
|
||||
|
||||
#### Optional - update user
|
||||
|
||||
```html
|
||||
POST /update_user
|
||||
```
|
||||
- public endpoint
|
||||
```json
|
||||
{
|
||||
"admin_token": "letmein",
|
||||
"username": "bob",
|
||||
"tenant_name": "tenant_2",
|
||||
"password": "new_password",
|
||||
"email": "bober@hotmail.com",
|
||||
"scopes": ["news:get", "news:post"],
|
||||
"data": {
|
||||
"name": "Robert Falcon",
|
||||
"image": "https://img.com/bucket/123/1234.jpg"
|
||||
}
|
||||
}
|
||||
```
|
||||
- note: `tenant_name`, `password`, `email`, `scopes`, `data` are optional fields
|
||||
|
||||
will return: `200` or `500` or `400` or `401`
|
||||
|
||||
#### Optional - Health Check
|
||||
|
||||
```html
|
||||
GET or HEAD /
|
||||
```
|
||||
- public endpoint
|
||||
|
||||
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 ```
|
||||
|
||||
- the origin can be passed in as a flag - default `*`
|
||||
- the port can be passed in as a flag - default `8080` - can only be set for unsecure connections
|
||||
- the jwt_expiry (for jwts) can be passed in as a flag - default `86400`
|
||||
- the jwt_secret (for jwts) should be passed in as a flag - default `secret`
|
||||
- the secure flag (https) and can be true or false - default `false`
|
||||
- the certs flag is the storage path of LetsEncrypt certs - default `certs`
|
||||
- the db flag is the path where the embedded database will be saved - default `tmp`
|
||||
- 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`
|
||||
- production example: `./broker --secure="true" --admin_token"23ce4234@123$" --jwt_secret="xTJEX234$##$" --domain="api.broker.com"`
|
||||
- the `origin` can be passed in as a flag - default `*`
|
||||
- the `port` can be passed in as a flag - default `8080` - can only be set for unsecure connections
|
||||
- the `jwt_expiry` for jwts can be passed in as a flag - default `86400`
|
||||
- the `jwt_secret` for jwts should be passed in as a flag - default `secret`
|
||||
- the `secure` flag for https and can be true or false - default `false`
|
||||
- the `auto_cert` flag for an autorenewing LetsEncrypt SSL cert can be true or false - requires a resolvable domain - default `true`
|
||||
- the `key_path` flag when `auto_cert` is `false` to set the SSL key path for your own cert - default `certs/private_key.pem`
|
||||
- the `cert_path` flag when `auto_cert` is `false` to set the SSL cert path for your own cert - default `certs/chain.pem`
|
||||
- the `certs` flag is the storage path of LetsEncrypt certs - default `certs`
|
||||
- the `db` flag is the path where the embedded database will be saved - default `db`
|
||||
- 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
|
||||
|
||||
|
@ -147,6 +376,7 @@ There is an example `systemctl` service for Ubuntu called `broker.service` in th
|
|||
|
||||
### Inspiration
|
||||
|
||||
* [Auth0](https://auth0.com)
|
||||
* [React Hooks](https://reactjs.org/docs/hooks-intro.html)
|
||||
* [Meteor](https://meteor.com)
|
||||
* [MongoDB](https://www.mongodb.com/)
|
||||
|
|
594
src/main.rs
594
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;
|
||||
|
@ -14,6 +14,10 @@ use async_std::stream;
|
|||
use std::time::Duration;
|
||||
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> = {
|
||||
|
@ -41,28 +45,86 @@ pub struct EnvVarConfig {
|
|||
pub certs: String,
|
||||
pub domain: String,
|
||||
pub admin_token: String,
|
||||
pub auto_cert: bool,
|
||||
pub key_path: String,
|
||||
pub cert_path: String,
|
||||
pub password_checker: bool,
|
||||
pub totp_duration: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct User {
|
||||
id: uuid::Uuid,
|
||||
username: String,
|
||||
password: String,
|
||||
tenant_name: String,
|
||||
pub id: uuid::Uuid,
|
||||
pub revoked: bool,
|
||||
pub email: Option<String>,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
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)]
|
||||
pub struct UserForm {
|
||||
username: String,
|
||||
password: String,
|
||||
tenant_name: String,
|
||||
admin_token: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub tenant_name: String,
|
||||
pub admin_token: String,
|
||||
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)]
|
||||
pub struct UpdateUserForm {
|
||||
pub username: String,
|
||||
pub tenant_name: Option<String>,
|
||||
pub admin_token: String,
|
||||
pub email: Option<String>,
|
||||
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 {
|
||||
pub admin_token: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct LoginForm {
|
||||
username: String,
|
||||
password: String,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub totp: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -71,6 +133,7 @@ pub struct Claims {
|
|||
pub iat: i64,
|
||||
pub iss: String,
|
||||
pub sub: String,
|
||||
pub aud: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
|
@ -85,8 +148,8 @@ pub struct Event {
|
|||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct EventForm {
|
||||
event: String,
|
||||
data: serde_json::Value
|
||||
pub event: String,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
fn replace(key: String, value: Vec<u8>) -> Result<()> {
|
||||
|
@ -94,14 +157,105 @@ fn replace(key: String, value: Vec<u8>) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn soft_delete_user(username: String) -> Result<()> {
|
||||
match get_user_by_username(username)? {
|
||||
Some(mut user) => {
|
||||
user.revoked = true;
|
||||
puts_user(user)?;
|
||||
Ok(())
|
||||
},
|
||||
None => { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_user(username: String) -> Result<()> {
|
||||
match get_user_by_username(username)? {
|
||||
Some(mut user) => {
|
||||
user.revoked = false;
|
||||
puts_user(user)?;
|
||||
Ok(())
|
||||
},
|
||||
None => { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn modify_user(update_user_form: UpdateUserForm) -> Result<Option<String>> {
|
||||
match get_user_by_username(update_user_form.clone().username)? {
|
||||
Some(mut user) => {
|
||||
|
||||
match update_user_form.tenant_name {
|
||||
Some(tn) => {
|
||||
user.tenant_name = tn;
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
match update_user_form.email {
|
||||
Some(email) => {
|
||||
if is_valid(&email) {
|
||||
user.email = Some(email);
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
let configure = env_var_config();
|
||||
|
||||
if configure.password_checker {
|
||||
let estimate = zxcvbn(&password, &[&user.username]).unwrap();
|
||||
if estimate.score() < 3 {
|
||||
let err: String;
|
||||
match estimate.feedback() {
|
||||
Some(feedback) => {
|
||||
match feedback.warning() {
|
||||
Some(warning) => {
|
||||
err = format!("password is too weak because {}", warning);
|
||||
},
|
||||
None => {
|
||||
err = format!("password is too weak");
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
err = format!("password is too weak");
|
||||
}
|
||||
}
|
||||
let j = json!({"error": err}).to_string();
|
||||
return Ok(Some(j));
|
||||
}
|
||||
}
|
||||
|
||||
let config = Argon2Config::default();
|
||||
let uuid_string = Uuid::new_v4().to_string();
|
||||
let salt = uuid_string.as_bytes();
|
||||
let password = password.as_bytes();
|
||||
let hashed = argon2::hash_encoded(password, salt, &config).unwrap();
|
||||
user.password = hashed;
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
puts_user(user)?;
|
||||
Ok(None)
|
||||
},
|
||||
None => { Ok(None) }
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_by_username(user_username: String) -> Result<Option<User>> {
|
||||
let users = get_users()?;
|
||||
Ok(users.into_iter().filter(|user| user.username == user_username).last())
|
||||
}
|
||||
|
||||
fn get_users() -> Result<Vec<User>> {
|
||||
let prefix = "users".as_bytes();
|
||||
let i = DB.prefix_iterator(prefix);
|
||||
let prefix = "users".to_string();
|
||||
let i = DB.prefix_iterator(prefix.as_bytes());
|
||||
let res : Vec<User> = i.map(|(_, v)| {
|
||||
let data: User = rmp_serde::from_read_ref(&v).unwrap();
|
||||
data
|
||||
|
@ -110,7 +264,7 @@ fn get_users() -> Result<Vec<User>> {
|
|||
}
|
||||
|
||||
fn puts_user(user: User) -> Result<()> {
|
||||
let key = format!("users_{}", user.id);
|
||||
let key = format!("users_{}", user.username);
|
||||
let value = rmp_serde::to_vec_named(&user)?;
|
||||
replace(key, value)?;
|
||||
Ok(())
|
||||
|
@ -126,9 +280,20 @@ fn is_user_unique(user_username: String) -> Result<bool> {
|
|||
Ok(true)
|
||||
}
|
||||
|
||||
fn get_events() -> Result<Vec<Event>> {
|
||||
let prefix = "events".as_bytes();
|
||||
let i = DB.prefix_iterator(prefix);
|
||||
fn get_events(tenant_name: Option<String>) -> Result<Vec<Event>> {
|
||||
|
||||
let prefix: String;
|
||||
|
||||
match tenant_name {
|
||||
Some(tn) => {
|
||||
prefix = format!("events_{}", tn);
|
||||
},
|
||||
None => {
|
||||
prefix = format!("events");
|
||||
}
|
||||
}
|
||||
|
||||
let i = DB.prefix_iterator(prefix.as_bytes());
|
||||
let res : Vec<Event> = i.map(|(_, v)| {
|
||||
let data: Event = rmp_serde::from_read_ref(&v).unwrap();
|
||||
data
|
||||
|
@ -137,7 +302,7 @@ fn get_events() -> Result<Vec<Event>> {
|
|||
}
|
||||
|
||||
fn puts_event(event: Event) -> Result<()> {
|
||||
let key = format!("events_{}", event.id);
|
||||
let key = format!("events_{}_{}", event.tenant_name, event.event);
|
||||
let value = rmp_serde::to_vec_named(&event)?;
|
||||
replace(key, value)?;
|
||||
Ok(())
|
||||
|
@ -148,19 +313,69 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
|
|||
let configure = env_var_config();
|
||||
|
||||
if configure.admin_token == user_form.clone().admin_token {
|
||||
|
||||
match user_form.clone().email {
|
||||
Some(email) => {
|
||||
if !is_valid(&email) {
|
||||
let j = json!({"error": "email is invalid"}).to_string();
|
||||
return Ok(Some(j));
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
if !is_user_unique(user_form.clone().username)? {
|
||||
let j = json!({"error": "username already taken"}).to_string();
|
||||
return Ok(Some(j));
|
||||
} else {
|
||||
// set as future value
|
||||
if configure.password_checker {
|
||||
let estimate = zxcvbn(&user_form.password, &[&user_form.username]).unwrap();
|
||||
if estimate.score() < 3 {
|
||||
let err: String;
|
||||
match estimate.feedback() {
|
||||
Some(feedback) => {
|
||||
match feedback.warning() {
|
||||
Some(warning) => {
|
||||
err = format!("password is too weak because {}", warning);
|
||||
},
|
||||
None => {
|
||||
err = format!("password is too weak");
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
err = format!("password is too weak");
|
||||
}
|
||||
}
|
||||
let j = json!({"error": err}).to_string();
|
||||
return Ok(Some(j));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
let salt = uuid_string.as_bytes();
|
||||
let password = user_form.password.as_bytes();
|
||||
let hashed = argon2::hash_encoded(password, salt, &config).unwrap();
|
||||
let new_user = User{id: uuid, username: user_form.clone().username, password: hashed, tenant_name: user_form.clone().tenant_name };
|
||||
|
||||
let new_user = User{
|
||||
id: uuid,
|
||||
username: user_form.clone().username,
|
||||
password: hashed,
|
||||
tenant_name: user_form.clone().tenant_name,
|
||||
revoked: false,
|
||||
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();
|
||||
return Ok(None);
|
||||
}
|
||||
|
@ -172,18 +387,82 @@ fn user_create(user_form: UserForm) -> Result<Option<String>> {
|
|||
|
||||
async fn create_jwt(login: LoginForm) -> Result<Option<String>> {
|
||||
|
||||
let user_value = get_user_by_username(login.username)?;
|
||||
match user_value {
|
||||
match get_user_by_username(login.username)? {
|
||||
Some(user) => {
|
||||
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 = "Dispatcher".to_string();
|
||||
let my_claims = Claims{sub: user.clone().username, exp, iat, iss};
|
||||
let token = encode(&Header::default(), &my_claims, &EncodingKey::from_secret(app.jwt_secret.as_ref()))?;
|
||||
Ok(Some(token))
|
||||
if !user.revoked {
|
||||
let verified = argon2::verify_encoded(&user.password, login.password.as_bytes())?;
|
||||
if verified {
|
||||
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 => {
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -197,12 +476,17 @@ fn env_var_config() -> EnvVarConfig {
|
|||
let mut port : u16 = 8080;
|
||||
let mut jwt_expiry : i64 = 86400;
|
||||
let mut secure = false;
|
||||
let mut auto_cert = true;
|
||||
let mut origin = "*".to_string();
|
||||
let mut jwt_secret = "secret".to_string();
|
||||
let mut db: String = "tmp".to_string();
|
||||
let mut db: String = "db".to_string();
|
||||
let mut certs = "certs".to_string();
|
||||
let mut domain = "localhost".to_string();
|
||||
let mut admin_token = "letmein".to_string();
|
||||
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);
|
||||
|
@ -213,9 +497,14 @@ fn env_var_config() -> EnvVarConfig {
|
|||
flags.add_flag("domain", &mut domain);
|
||||
flags.add_flag("certs", &mut certs);
|
||||
flags.add_flag("admin_token", &mut admin_token);
|
||||
flags.add_flag("auto_cert", &mut auto_cert);
|
||||
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}
|
||||
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>>> {
|
||||
|
@ -247,8 +536,15 @@ async fn jwt_verify(token: String) -> Result<Option<TokenData<Claims>>> {
|
|||
let app = env_var_config();
|
||||
let iat = nippy::get_unix_ntp_time().await?;
|
||||
let exp = iat + app.jwt_expiry;
|
||||
let iss = "Dispatcher".to_string();
|
||||
let my_claims = Claims{sub: user.clone().username, exp, iat, iss};
|
||||
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 my_token = TokenData{
|
||||
header: Header::default(),
|
||||
claims: my_claims,
|
||||
|
@ -291,7 +587,6 @@ async fn create_user(mut req: Request<()>) -> tide::Result {
|
|||
let user_form : UserForm = serde_json::from_str(&r)?;
|
||||
match user_create(user_form)? {
|
||||
Some(err) => {
|
||||
let err = format!("error: {}", err);
|
||||
Ok(tide::Response::builder(400).body(err).header("content-type", "application/json").build())
|
||||
},
|
||||
None => {
|
||||
|
@ -305,7 +600,7 @@ async fn login_user(mut req: Request<()>) -> tide::Result {
|
|||
let login_form : LoginForm = serde_json::from_str(&r)?;
|
||||
match create_jwt(login_form).await? {
|
||||
Some(jwt) => {
|
||||
let msg = format!("jwt: {}", jwt);
|
||||
let msg = json!({"jwt": jwt}).to_string();
|
||||
Ok(tide::Response::builder(200).body(msg).header("content-type", "application/json").build())
|
||||
},
|
||||
None => {
|
||||
|
@ -363,6 +658,193 @@ async fn verify_user(req: Request<()>) -> tide::Result {
|
|||
}
|
||||
}
|
||||
|
||||
async fn get_user(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let admin_token_form : AdminTokenForm = serde_json::from_str(&r)?;
|
||||
let configure = env_var_config();
|
||||
if configure.admin_token == admin_token_form.admin_token {
|
||||
let users = get_users()?;
|
||||
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())
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_users(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let admin_token_form : AdminTokenForm = serde_json::from_str(&r)?;
|
||||
let configure = env_var_config();
|
||||
if configure.admin_token == admin_token_form.admin_token {
|
||||
let users = get_users()?;
|
||||
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())
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
async fn revoke_user(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let revoke_user_form : RevokeUserForm = serde_json::from_str(&r)?;
|
||||
let configure = env_var_config();
|
||||
if configure.admin_token == revoke_user_form.admin_token {
|
||||
soft_delete_user(revoke_user_form.username)?;
|
||||
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
async fn unrevoke_user(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let revoke_user_form : RevokeUserForm = serde_json::from_str(&r)?;
|
||||
let configure = env_var_config();
|
||||
if configure.admin_token == revoke_user_form.admin_token {
|
||||
activate_user(revoke_user_form.username)?;
|
||||
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_user(mut req: Request<()>) -> tide::Result {
|
||||
let r = req.body_string().await?;
|
||||
let update_user_form : UpdateUserForm = serde_json::from_str(&r)?;
|
||||
let configure = env_var_config();
|
||||
if configure.admin_token == update_user_form.admin_token {
|
||||
match modify_user(update_user_form)? {
|
||||
Some(err) => {
|
||||
Ok(tide::Response::builder(400).body(err).header("content-type", "application/json").build())
|
||||
},
|
||||
None => {
|
||||
Ok(tide::Response::builder(200).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Ok(tide::Response::builder(401).header("content-type", "application/json").build())
|
||||
}
|
||||
}
|
||||
|
||||
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<()> {
|
||||
|
||||
|
@ -377,10 +859,20 @@ async fn main() -> tide::Result<()> {
|
|||
let mut app = tide::new();
|
||||
app.with(driftwood::DevLogger);
|
||||
app.with(cors);
|
||||
app.at("/").get(health);
|
||||
app.at("/").head(health);
|
||||
app.at("/insert").post(insert_event);
|
||||
app.at("/users").post(create_user);
|
||||
app.at("/create_user").post(create_user);
|
||||
app.at("/login").post(login_user);
|
||||
app.at("/verify").get(verify_user);
|
||||
app.at("/list_users").post(list_users);
|
||||
app.at("/revoke_user").post(revoke_user);
|
||||
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 {
|
||||
|
||||
|
@ -400,7 +892,7 @@ async fn main() -> tide::Result<()> {
|
|||
|
||||
let mut interval = stream::interval(Duration::from_millis(100));
|
||||
while let Some(_) = interval.next().await {
|
||||
let events = get_events()?;
|
||||
let events = get_events(None)?;
|
||||
|
||||
for evt in events {
|
||||
if evt.tenant_name == user.tenant_name {
|
||||
|
@ -412,13 +904,16 @@ async fn main() -> tide::Result<()> {
|
|||
let value_maybe = cache.get_key_value(&evt.event);
|
||||
match value_maybe {
|
||||
Some((_, v)) => {
|
||||
if &evt != v {
|
||||
let current_data = evt.data.to_string();
|
||||
let stored_data = v.data.to_string();
|
||||
|
||||
if current_data != stored_data {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
sender.send(&evt.event, evt.data.to_string(), Some(&id.to_string())).await?;
|
||||
cache.insert(evt.event.clone(), evt.clone());
|
||||
}
|
||||
},
|
||||
None => { println!("helo"); return Ok(()); }
|
||||
None => { return Ok(()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -438,7 +933,7 @@ async fn main() -> tide::Result<()> {
|
|||
|
||||
let ip = format!("0.0.0.0:{}", configure.port);
|
||||
|
||||
if configure.secure {
|
||||
if configure.secure && configure.auto_cert {
|
||||
app.listen(
|
||||
tide_rustls::TlsListener::build().addrs("0.0.0.0:443").acme(
|
||||
AcmeConfig::new()
|
||||
|
@ -448,10 +943,17 @@ async fn main() -> tide::Result<()> {
|
|||
),
|
||||
)
|
||||
.await?;
|
||||
} else if configure.secure && !configure.auto_cert {
|
||||
app.listen(
|
||||
tide_rustls::TlsListener::build()
|
||||
.addrs("0.0.0.0:443")
|
||||
.cert(configure.cert_path)
|
||||
.key(configure.key_path)
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
app.listen(ip).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue