release 13.0.0

This commit is contained in:
Bevan Hunt 2021-04-09 00:01:39 -07:00
parent 9120532fff
commit 14541291dd
5 changed files with 674 additions and 22 deletions

View File

@ -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

372
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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 {