Sign in
This commit is contained in:
parent
0923427551
commit
70cb64a2e0
217
Cargo.lock
generated
217
Cargo.lock
generated
@ -498,7 +498,7 @@ dependencies = [
|
|||||||
"attohttpc",
|
"attohttpc",
|
||||||
"dirs",
|
"dirs",
|
||||||
"log",
|
"log",
|
||||||
"quick-xml",
|
"quick-xml 0.26.0",
|
||||||
"rust-ini",
|
"rust-ini",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -664,6 +664,17 @@ version = "3.14.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byte-unit"
|
||||||
|
version = "5.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e"
|
||||||
|
dependencies = [
|
||||||
|
"rust_decimal",
|
||||||
|
"serde",
|
||||||
|
"utf8-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytecheck"
|
name = "bytecheck"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
@ -829,6 +840,12 @@ version = "0.8.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cow-utils"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "417bef24afe1460300965a25ff4a24b8b45ad011948302ec221e8a0a81eb2c79"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@ -960,6 +977,12 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deadpool"
|
name = "deadpool"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -1094,6 +1117,18 @@ version = "1.0.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
|
checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "educe"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8"
|
||||||
|
dependencies = [
|
||||||
|
"enum-ordinalize",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -1129,6 +1164,26 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-ordinalize"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5"
|
||||||
|
dependencies = [
|
||||||
|
"enum-ordinalize-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-ordinalize-derive"
|
||||||
|
version = "4.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -1740,6 +1795,15 @@ dependencies = [
|
|||||||
"waker-fn",
|
"waker-fn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@ -1796,6 +1860,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"validators",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1949,6 +2014,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@ -1988,6 +2059,15 @@ version = "0.4.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lru-cache"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2327,6 +2407,12 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oncemutex"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.63"
|
version = "0.10.63"
|
||||||
@ -2513,6 +2599,27 @@ version = "2.3.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phonenumber"
|
||||||
|
version = "0.3.3+8.13.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "635f3e6288e4f01c049d89332a031bd74f25d64b6fb94703ca966e819488cd06"
|
||||||
|
dependencies = [
|
||||||
|
"bincode",
|
||||||
|
"either",
|
||||||
|
"fnv",
|
||||||
|
"itertools 0.11.0",
|
||||||
|
"lazy_static",
|
||||||
|
"nom",
|
||||||
|
"quick-xml 0.28.2",
|
||||||
|
"regex",
|
||||||
|
"regex-cache",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"strum 0.24.1",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@ -2691,6 +2798,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quick-xml"
|
||||||
|
version = "0.28.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.35"
|
version = "1.0.35"
|
||||||
@ -2847,6 +2963,18 @@ dependencies = [
|
|||||||
"regex-syntax 0.8.2",
|
"regex-syntax 0.8.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-cache"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f7b62d69743b8b94f353b6b7c3deb4c5582828328bcb8d5fedf214373808793"
|
||||||
|
dependencies = [
|
||||||
|
"lru-cache",
|
||||||
|
"oncemutex",
|
||||||
|
"regex",
|
||||||
|
"regex-syntax 0.6.29",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.29"
|
version = "0.6.29"
|
||||||
@ -3044,7 +3172,7 @@ dependencies = [
|
|||||||
"md5",
|
"md5",
|
||||||
"minidom",
|
"minidom",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"quick-xml",
|
"quick-xml 0.26.0",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
@ -3155,6 +3283,12 @@ dependencies = [
|
|||||||
"untrusted 0.9.0",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rxml"
|
name = "rxml"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@ -3251,7 +3385,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"strum",
|
"strum 0.25.0",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -3340,6 +3474,9 @@ name = "semver"
|
|||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
@ -3628,7 +3765,7 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c"
|
checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
"itertools 0.12.0",
|
||||||
"nom",
|
"nom",
|
||||||
"unicode_categories",
|
"unicode_categories",
|
||||||
]
|
]
|
||||||
@ -3855,6 +3992,16 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "str-utils"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60bcb3d541a8fd455189b9e022f27d255d103dafd5087a93cff4c0a156a8b597"
|
||||||
|
dependencies = [
|
||||||
|
"cow-utils",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stringprep"
|
name = "stringprep"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@ -3866,12 +4013,34 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||||
|
dependencies = [
|
||||||
|
"strum_macros",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strum"
|
name = "strum"
|
||||||
version = "0.25.0"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.24.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -4368,6 +4537,12 @@ version = "2.1.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8-width"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
@ -4378,6 +4553,40 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "validators"
|
||||||
|
version = "0.25.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57e4dd623e1c294e7d7850097c41863cda2703166c5f58c225c5ca969299fb7a"
|
||||||
|
dependencies = [
|
||||||
|
"byte-unit",
|
||||||
|
"data-encoding",
|
||||||
|
"idna",
|
||||||
|
"phonenumber",
|
||||||
|
"regex",
|
||||||
|
"semver",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"str-utils",
|
||||||
|
"url",
|
||||||
|
"validators-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "validators-derive"
|
||||||
|
version = "0.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72377736834d42b3e4029d46058f7ee2dbd44340bed0e82aaffd5c395bb0e6b3"
|
||||||
|
dependencies = [
|
||||||
|
"educe",
|
||||||
|
"enum-ordinalize",
|
||||||
|
"phonenumber",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -41,3 +41,4 @@ reqwest = { version = "0.11.23", default-features = false, features = ["rustls",
|
|||||||
http-api-isahc-client = { version = "0.2.2", features = ["with-sleep-via-tokio"] }
|
http-api-isahc-client = { version = "0.2.2", features = ["with-sleep-via-tokio"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
chrono = { version = "0.4.32", default-features = false, features = ["clock", "serde"] }
|
chrono = { version = "0.4.32", default-features = false, features = ["clock", "serde"] }
|
||||||
|
validators = { version = "0.25.3", default-features = false, features = ["email", "derive", "all-validators"] }
|
||||||
|
3
crates/jet-api/src/extractors/mod.rs
Normal file
3
crates/jet-api/src/extractors/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod require_instance;
|
||||||
|
|
||||||
|
pub use require_instance::*;
|
69
crates/jet-api/src/extractors/require_instance.rs
Normal file
69
crates/jet-api/src/extractors/require_instance.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
use actix_web::{web::Data, FromRequest};
|
||||||
|
use derive_more::*;
|
||||||
|
use futures_util::{future::LocalBoxFuture, FutureExt};
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
#[derive(Debug, Display)]
|
||||||
|
#[display(fmt = "{{\"error\":\"Instance is not configured\"}}")]
|
||||||
|
pub struct NoInstance;
|
||||||
|
|
||||||
|
impl actix_web::error::ResponseError for NoInstance {
|
||||||
|
fn status_code(&self) -> reqwest::StatusCode {
|
||||||
|
reqwest::StatusCode::BAD_REQUEST
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deref)]
|
||||||
|
pub struct RequireInstance(pub entities::instances::Model);
|
||||||
|
|
||||||
|
impl FromRequest for RequireInstance {
|
||||||
|
type Error = NoInstance;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(
|
||||||
|
req: &actix_web::HttpRequest,
|
||||||
|
_payload: &mut actix_web::dev::Payload,
|
||||||
|
) -> Self::Future {
|
||||||
|
let db = req.app_data::<Data<DatabaseConnection>>().cloned();
|
||||||
|
async move {
|
||||||
|
let Some(db) = db else {
|
||||||
|
return Err(NoInstance);
|
||||||
|
};
|
||||||
|
use sea_orm::EntityTrait;
|
||||||
|
let Ok(Some(instance)) = entities::prelude::Instances::find().one(&**db).await else {
|
||||||
|
return Err(NoInstance);
|
||||||
|
};
|
||||||
|
Ok(Self(instance))
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deref)]
|
||||||
|
pub struct RequireInstanceConfigured(pub entities::instances::Model);
|
||||||
|
|
||||||
|
impl FromRequest for RequireInstanceConfigured {
|
||||||
|
type Error = NoInstance;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(
|
||||||
|
req: &actix_web::HttpRequest,
|
||||||
|
_payload: &mut actix_web::dev::Payload,
|
||||||
|
) -> Self::Future {
|
||||||
|
let db = req.app_data::<Data<DatabaseConnection>>().cloned();
|
||||||
|
async move {
|
||||||
|
let Some(db) = db else {
|
||||||
|
return Err(NoInstance);
|
||||||
|
};
|
||||||
|
use sea_orm::EntityTrait;
|
||||||
|
let Ok(Some(instance)) = entities::prelude::Instances::find().one(&**db).await else {
|
||||||
|
return Err(NoInstance);
|
||||||
|
};
|
||||||
|
if !instance.is_setup_done {
|
||||||
|
return Err(NoInstance);
|
||||||
|
}
|
||||||
|
return Ok(Self(instance));
|
||||||
|
}
|
||||||
|
.boxed_local()
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,33 @@
|
|||||||
|
use crate::models::Error;
|
||||||
|
use crate::session::AppClaims;
|
||||||
|
use actix_jwt_session::{
|
||||||
|
Duration, Hashing, JwtTtl, RefreshTtl, SessionStorage, JWT_HEADER_NAME, REFRESH_HEADER_NAME,
|
||||||
|
};
|
||||||
use actix_web::web::{Data, ServiceConfig};
|
use actix_web::web::{Data, ServiceConfig};
|
||||||
use actix_web::{delete, get, HttpResponse};
|
use actix_web::{delete, get, HttpRequest, HttpResponse};
|
||||||
|
use entities::prelude::Users;
|
||||||
|
use entities::users::Model as User;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use sea_orm::prelude::*;
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
|
use sea_orm::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
mod email_check;
|
mod email_check;
|
||||||
|
mod sign_in;
|
||||||
mod social_auth;
|
mod social_auth;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub struct AuthResponseBody {
|
||||||
|
access_token: String,
|
||||||
|
refresh_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||||
config.service(email_check::email_check).service(oauth);
|
config
|
||||||
|
.service(email_check::email_check)
|
||||||
|
.service(oauth)
|
||||||
|
.service(sign_in::sign_in);
|
||||||
social_auth::configure(http_client, config);
|
social_auth::configure(http_client, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +73,8 @@ pub enum AuthError {
|
|||||||
#[display(fmt = "{}", _0)]
|
#[display(fmt = "{}", _0)]
|
||||||
#[from]
|
#[from]
|
||||||
Oauth(OAuthError),
|
Oauth(OAuthError),
|
||||||
|
#[display(fmt = "Encrypt password failed")]
|
||||||
|
EncryptPass,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/social-auth")]
|
#[get("/social-auth")]
|
||||||
@ -60,12 +82,100 @@ async fn oauth(_db: Data<DatabaseConnection>) -> HttpResponse {
|
|||||||
HttpResponse::NotImplemented().finish()
|
HttpResponse::NotImplemented().finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/sign-in")]
|
async fn create_user(
|
||||||
async fn sign_in(_db: Data<DatabaseConnection>) -> HttpResponse {
|
req: &HttpRequest,
|
||||||
HttpResponse::NotImplemented().finish()
|
email: &str,
|
||||||
|
password: &str,
|
||||||
|
db: &mut DatabaseTransaction,
|
||||||
|
) -> Result<entities::users::Model, Error> {
|
||||||
|
use entities::users::ActiveModel;
|
||||||
|
|
||||||
|
let (ip, user_agent, _current_site) = crate::utils::extract_req_info(&req)?;
|
||||||
|
let password = Hashing::encrypt(password).map_err(|e| {
|
||||||
|
tracing::error!("Failed to encrypt password: {e}");
|
||||||
|
AuthError::EncryptPass
|
||||||
|
})?;
|
||||||
|
Users::insert(ActiveModel {
|
||||||
|
password: Set(password),
|
||||||
|
email: Set(Some(email.to_string())),
|
||||||
|
display_name: Set(email.to_string()),
|
||||||
|
username: Set(Uuid::new_v4().to_string()),
|
||||||
|
first_name: Set("".to_string()),
|
||||||
|
last_name: Set("".to_string()),
|
||||||
|
last_location: Set("".to_string()),
|
||||||
|
created_location: Set("".to_string()),
|
||||||
|
is_password_autoset: Set(false),
|
||||||
|
token: Set(Uuid::new_v4().to_string()),
|
||||||
|
billing_address_country: Set("".to_string()),
|
||||||
|
user_timezone: Set("UTC".to_string()),
|
||||||
|
last_login_ip: Set(ip.to_string()),
|
||||||
|
last_logout_ip: Set(ip.to_string()),
|
||||||
|
last_login_uagent: Set(user_agent.clone()),
|
||||||
|
is_active: Set(true),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.exec_with_returning(db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to create account for {email:?}: {e}");
|
||||||
|
Error::DatabaseError
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn has_workspace_invites(email: &str, db: &mut DatabaseTransaction) -> Result<bool, Error> {
|
||||||
|
entities::prelude::WorkspaceMemberInvites::find()
|
||||||
|
.filter(entities::workspace_member_invites::Column::Email.eq(email))
|
||||||
|
.count(db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to count workspace member invites for {email:?}: {e}");
|
||||||
|
Error::DatabaseError
|
||||||
|
})
|
||||||
|
.map(|n| n > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/sign-out")]
|
#[delete("/sign-out")]
|
||||||
async fn sign_out(_db: Data<DatabaseConnection>) -> HttpResponse {
|
async fn sign_out(_db: Data<DatabaseConnection>) -> HttpResponse {
|
||||||
HttpResponse::NotImplemented().finish()
|
HttpResponse::NotImplemented().finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn auth_http_response(
|
||||||
|
user: User,
|
||||||
|
session: Data<SessionStorage>,
|
||||||
|
status: StatusCode,
|
||||||
|
) -> Result<HttpResponse, Error> {
|
||||||
|
let claims = AppClaims::from_user(user, JwtTtl::new(Duration::days(99999)));
|
||||||
|
let pair = session
|
||||||
|
.store(
|
||||||
|
claims,
|
||||||
|
JwtTtl::new(Duration::days(99999)),
|
||||||
|
RefreshTtl::new(Duration::days(99999)),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("Failed to store session: {e}");
|
||||||
|
Error::DatabaseError
|
||||||
|
})?;
|
||||||
|
let access_token = match pair.jwt.encode() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to store session: {e}");
|
||||||
|
return Ok(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let refresh_token = match pair.refresh.encode() {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to store session: {e}");
|
||||||
|
return Ok(HttpResponse::InternalServerError().finish());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::build(status)
|
||||||
|
.append_header((JWT_HEADER_NAME, access_token.clone()))
|
||||||
|
.append_header((REFRESH_HEADER_NAME, refresh_token.clone()))
|
||||||
|
.json(AuthResponseBody {
|
||||||
|
access_token,
|
||||||
|
refresh_token,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ use super::{AuthError, PublishError};
|
|||||||
use actix_web::http::header::USER_AGENT;
|
use actix_web::http::header::USER_AGENT;
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use actix_web::{post, HttpRequest, HttpResponse};
|
use actix_web::{post, HttpRequest, HttpResponse};
|
||||||
use entities::prelude::{Instances, Users, WorkspaceMemberInvites};
|
use entities::prelude::{Users, WorkspaceMemberInvites};
|
||||||
use entities::users::Model as User;
|
use entities::users::Model as User;
|
||||||
use jet_contract::redis::AsyncCommands;
|
use jet_contract::redis::AsyncCommands;
|
||||||
use jet_contract::{MagicLinkKey, MagicLinkToken};
|
use jet_contract::{MagicLinkKey, MagicLinkToken};
|
||||||
@ -13,6 +13,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_email::Email;
|
use serde_email::Email;
|
||||||
|
|
||||||
use crate::config::ApplicationConfig;
|
use crate::config::ApplicationConfig;
|
||||||
|
use crate::extractors::RequireInstanceConfigured;
|
||||||
use crate::models::*;
|
use crate::models::*;
|
||||||
use crate::{EventBusClient, RedisClient};
|
use crate::{EventBusClient, RedisClient};
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ struct EmailCheckPayload {
|
|||||||
|
|
||||||
#[post("/email-check")]
|
#[post("/email-check")]
|
||||||
pub async fn email_check(
|
pub async fn email_check(
|
||||||
|
_: RequireInstanceConfigured,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
payload: Json<EmailCheckPayload>,
|
payload: Json<EmailCheckPayload>,
|
||||||
db: Data<DatabaseConnection>,
|
db: Data<DatabaseConnection>,
|
||||||
@ -30,13 +32,6 @@ pub async fn email_check(
|
|||||||
event_bus: Data<EventBusClient>,
|
event_bus: Data<EventBusClient>,
|
||||||
redis: Data<RedisClient>,
|
redis: Data<RedisClient>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let instance = Instances::find()
|
|
||||||
.one(&**db)
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::DatabaseError)?;
|
|
||||||
let _instance = instance
|
|
||||||
.filter(|i| !i.is_setup_done)
|
|
||||||
.ok_or(Error::NotConfigured)?;
|
|
||||||
if !serde_email::is_valid_email(&payload.email) {
|
if !serde_email::is_valid_email(&payload.email) {
|
||||||
return Ok(HttpResponse::BadRequest().json(JsonError::new("Email is not valid")));
|
return Ok(HttpResponse::BadRequest().json(JsonError::new("Email is not valid")));
|
||||||
}
|
}
|
||||||
@ -157,15 +152,7 @@ async fn register(
|
|||||||
return Err(Error::Auth(AuthError::RegisterOff));
|
return Err(Error::Auth(AuthError::RegisterOff));
|
||||||
}
|
}
|
||||||
let payload = payload.into_inner();
|
let payload = payload.into_inner();
|
||||||
let ip = req.peer_addr().ok_or(AuthError::NoPeerAddr)?.ip();
|
let (ip, user_agent, current_site) = crate::utils::extract_req_info(&req)?;
|
||||||
let user_agent = req
|
|
||||||
.headers()
|
|
||||||
.get(USER_AGENT)
|
|
||||||
.ok_or(AuthError::NoUserAgent)?
|
|
||||||
.to_str()
|
|
||||||
.map_err(|_| AuthError::InvalidUserAgent)?
|
|
||||||
.to_string();
|
|
||||||
let current_site = req.uri().host().ok_or(Error::NoHost)?.to_owned();
|
|
||||||
|
|
||||||
let user = entities::users::ActiveModel {
|
let user = entities::users::ActiveModel {
|
||||||
password: Set(Uuid::new_v4().to_string()),
|
password: Set(Uuid::new_v4().to_string()),
|
||||||
|
144
crates/jet-api/src/http/api/authentication/sign_in.rs
Normal file
144
crates/jet-api/src/http/api/authentication/sign_in.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use actix_jwt_session::SessionStorage;
|
||||||
|
use actix_web::web::{Data, Json};
|
||||||
|
use actix_web::ResponseError;
|
||||||
|
use actix_web::{post, HttpRequest, HttpResponse};
|
||||||
|
use entities::prelude::Users;
|
||||||
|
use entities::users::ActiveModel as UserModel;
|
||||||
|
use jet_contract::event_bus::{Msg, SignInMedium, Topic, UserMsg};
|
||||||
|
use jet_contract::UserId;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use rumqttc::QoS;
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
use sea_orm::*;
|
||||||
|
use validators::models::Host;
|
||||||
|
use validators::prelude::*;
|
||||||
|
|
||||||
|
use crate::config::ApplicationConfig;
|
||||||
|
use crate::extractors::RequireInstanceConfigured;
|
||||||
|
use crate::http::api::authentication::{auth_http_response, create_user, has_workspace_invites};
|
||||||
|
use crate::models::{Error, JsonError};
|
||||||
|
|
||||||
|
#[post("/sign-in")]
|
||||||
|
pub async fn sign_in(
|
||||||
|
_: RequireInstanceConfigured,
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: Json<SignInPayload>,
|
||||||
|
db: Data<DatabaseConnection>,
|
||||||
|
config: Data<ApplicationConfig>,
|
||||||
|
event_bus: Data<crate::EventBusClient>,
|
||||||
|
session: Data<SessionStorage>,
|
||||||
|
) -> Result<HttpResponse, JsonError> {
|
||||||
|
let mut t = db.begin().await.map_err(|e| {
|
||||||
|
tracing::error!("Failed to start transaction for sign-in: {e}");
|
||||||
|
Error::DatabaseError
|
||||||
|
})?;
|
||||||
|
let res = try_sign_in(req, payload, &mut t, config, event_bus, session).await?;
|
||||||
|
t.commit().await.map_err(|e| {
|
||||||
|
tracing::error!("Failed to commit transaction for sign-in: {e}");
|
||||||
|
Error::DatabaseError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn try_sign_in(
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: Json<SignInPayload>,
|
||||||
|
db: &mut DatabaseTransaction,
|
||||||
|
config: Data<ApplicationConfig>,
|
||||||
|
event_bus: Data<crate::EventBusClient>,
|
||||||
|
session: Data<SessionStorage>,
|
||||||
|
) -> Result<HttpResponse, JsonError> {
|
||||||
|
if payload.email.trim().is_empty() || payload.password.trim().is_empty() {
|
||||||
|
return Err(JsonError::new("Both email and password are required"));
|
||||||
|
}
|
||||||
|
let email = payload.email.trim().to_lowercase();
|
||||||
|
|
||||||
|
#[derive(Validator)]
|
||||||
|
#[validator(email(
|
||||||
|
comment(Allow),
|
||||||
|
ip(Allow),
|
||||||
|
local(Allow),
|
||||||
|
at_least_two_labels(Allow),
|
||||||
|
non_ascii(Allow)
|
||||||
|
))]
|
||||||
|
pub struct EmailAllowComment {
|
||||||
|
pub local_part: String,
|
||||||
|
pub need_quoted: bool,
|
||||||
|
pub domain_part: Host,
|
||||||
|
pub comment_before_local_part: Option<String>,
|
||||||
|
pub comment_after_local_part: Option<String>,
|
||||||
|
pub comment_before_domain_part: Option<String>,
|
||||||
|
pub comment_after_domain_part: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = payload.password.clone();
|
||||||
|
if let Err(e) = EmailAllowComment::validate_str(&email) {
|
||||||
|
tracing::error!("Invalid email address: {e}");
|
||||||
|
return Err(JsonError::new("Please provide a valid email address."));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (user, was_created) = match Users::find()
|
||||||
|
.filter(entities::users::Column::Email.eq(&email))
|
||||||
|
.one(&mut *db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Some(user)) => (user, false),
|
||||||
|
Ok(None) if !config.enable_signup && !has_workspace_invites(&email, &mut *db).await? => {
|
||||||
|
return Err(JsonError::new(
|
||||||
|
"New account creation is disabled. Please contact your site administrator",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(None) => (create_user(&req, &email, &password, &mut *db).await?, true),
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to load user for sign-in: {e}");
|
||||||
|
return Ok(Error::DatabaseError.error_response());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_id = user.id;
|
||||||
|
let mut user: UserModel = user.into();
|
||||||
|
|
||||||
|
let (ip, user_agent, _current_site) = crate::utils::extract_req_info(&req)?;
|
||||||
|
|
||||||
|
user.is_active = Set(true);
|
||||||
|
user.last_active = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||||
|
user.last_login_time = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||||
|
user.last_login_ip = Set(ip.clone());
|
||||||
|
user.last_login_uagent = Set(user_agent.clone());
|
||||||
|
user.token_updated_at = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||||
|
let user = Users::update(user).exec(&mut *db).await.map_err(|e| {
|
||||||
|
tracing::error!("Failed to update account for {email:?}: {e}");
|
||||||
|
Error::DatabaseError
|
||||||
|
})?;
|
||||||
|
crate::utils::invites_to_membership(user_id, &email, None, &mut *db).await?;
|
||||||
|
|
||||||
|
if let Err(e) = event_bus
|
||||||
|
.publish(
|
||||||
|
Topic::User,
|
||||||
|
Msg::User(UserMsg::SignIn {
|
||||||
|
user_id: UserId::new(user_id),
|
||||||
|
email,
|
||||||
|
user_agent,
|
||||||
|
ip,
|
||||||
|
medium: SignInMedium::Email,
|
||||||
|
first_time: false,
|
||||||
|
}),
|
||||||
|
QoS::AtLeastOnce,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::warn!("Failed to publish sign-in msg after sign in: {e}");
|
||||||
|
};
|
||||||
|
|
||||||
|
auth_http_response(user, session, StatusCode::OK)
|
||||||
|
.await
|
||||||
|
.map_err(JsonError::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct SignInPayload {
|
||||||
|
email: String,
|
||||||
|
password: String,
|
||||||
|
}
|
@ -1,30 +1,14 @@
|
|||||||
use std::env::var as env_var;
|
use std::env::var as env_var;
|
||||||
|
|
||||||
use super::AuthError;
|
use super::{auth_http_response, AuthError, AuthResponseBody};
|
||||||
use actix_jwt_session::{JwtTtl, RefreshTtl, JWT_HEADER_NAME, REFRESH_HEADER_NAME};
|
use actix_jwt_session::{JwtTtl, RefreshTtl, JWT_HEADER_NAME, REFRESH_HEADER_NAME};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
get,
|
get,
|
||||||
web::{self, Data, ServiceConfig},
|
web::{self, Data, ServiceConfig},
|
||||||
HttpRequest, HttpResponse,
|
HttpRequest, HttpResponse,
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use entities::prelude::Users;
|
||||||
use entities::project_member_invites::{
|
|
||||||
Column as ProjectMemberInviteColumn, Model as ProjectMemberInvite,
|
|
||||||
};
|
|
||||||
use entities::users::{ActiveModel as UserModel, Column as UserColumn};
|
use entities::users::{ActiveModel as UserModel, Column as UserColumn};
|
||||||
use entities::workspace_member_invites::{
|
|
||||||
Column as WorkspaceMemberInviteColumn, Model as WorkspaceMemberInvite,
|
|
||||||
};
|
|
||||||
use entities::workspace_members::ActiveModel as WorkspaceMemberModel;
|
|
||||||
use entities::{
|
|
||||||
prelude::{
|
|
||||||
ProjectMemberInvites, ProjectMembers, Users, WorkspaceMemberInvites, WorkspaceMembers,
|
|
||||||
},
|
|
||||||
sea_orm_active_enums::Roles,
|
|
||||||
};
|
|
||||||
use entities::{
|
|
||||||
project_members::ActiveModel as ProjectMemberModel, sea_orm_active_enums::ProjectMemberRoles,
|
|
||||||
};
|
|
||||||
use http_api_isahc_client::IsahcClient;
|
use http_api_isahc_client::IsahcClient;
|
||||||
use jet_contract::{
|
use jet_contract::{
|
||||||
event_bus::{Msg, SignInMedium, Topic, UserMsg},
|
event_bus::{Msg, SignInMedium, Topic, UserMsg},
|
||||||
@ -43,10 +27,10 @@ use oauth2_google::{
|
|||||||
GoogleProviderForWebServerAppsAccessType, GoogleScope,
|
GoogleProviderForWebServerAppsAccessType, GoogleScope,
|
||||||
};
|
};
|
||||||
use oauth2_signin::web_app::{SigninFlow, SigninFlowHandleCallbackByQueryConfiguration};
|
use oauth2_signin::web_app::{SigninFlow, SigninFlowHandleCallbackByQueryConfiguration};
|
||||||
use reqwest::header::{LOCATION, USER_AGENT};
|
use reqwest::{header::LOCATION, StatusCode};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
sea_query::OnConflict, ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection,
|
ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection, DatabaseTransaction,
|
||||||
DatabaseTransaction, EntityTrait, QueryFilter, Set, TransactionTrait,
|
EntityTrait, QueryFilter, Set, TransactionTrait,
|
||||||
};
|
};
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
|
||||||
@ -236,7 +220,7 @@ async fn handle_user_info(
|
|||||||
user_info: UserInfo,
|
user_info: UserInfo,
|
||||||
event_bus: Data<crate::EventBusClient>,
|
event_bus: Data<crate::EventBusClient>,
|
||||||
session: Data<actix_jwt_session::SessionStorage>,
|
session: Data<actix_jwt_session::SessionStorage>,
|
||||||
) -> std::result::Result<HttpResponse, AuthError> {
|
) -> std::result::Result<HttpResponse, Error> {
|
||||||
let UserInfo {
|
let UserInfo {
|
||||||
uid: _,
|
uid: _,
|
||||||
name: _,
|
name: _,
|
||||||
@ -273,18 +257,7 @@ async fn handle_user_info(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ip = req
|
let (ip, user_agent, _current_site) = crate::utils::extract_req_info(&req)?;
|
||||||
.peer_addr()
|
|
||||||
.ok_or(AuthError::NoPeerAddr)?
|
|
||||||
.ip()
|
|
||||||
.to_string();
|
|
||||||
let user_agent = req
|
|
||||||
.headers()
|
|
||||||
.get(USER_AGENT)
|
|
||||||
.ok_or(AuthError::NoUserAgent)?
|
|
||||||
.to_str()
|
|
||||||
.map_err(|_| AuthError::InvalidUserAgent)?
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
user.is_active = Set(true);
|
user.is_active = Set(true);
|
||||||
user.last_active = Set(Some(chrono::Utc::now().fixed_offset()));
|
user.last_active = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||||
@ -303,144 +276,7 @@ async fn handle_user_info(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace_invites = WorkspaceMemberInvites::find()
|
crate::utils::invites_to_membership(user_id, &email, Some(provider), &mut *db).await?;
|
||||||
.filter(
|
|
||||||
WorkspaceMemberInviteColumn::Accepted
|
|
||||||
.eq(true)
|
|
||||||
.and(WorkspaceMemberInviteColumn::Email.eq(&email)),
|
|
||||||
)
|
|
||||||
.all(&mut *db)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to update user {user_id:?} on oauth {provider}: {e}");
|
|
||||||
OAuthError::FetchWorkspaceInvites {
|
|
||||||
user_id,
|
|
||||||
provider: provider.to_owned(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
let project_invites = ProjectMemberInvites::find()
|
|
||||||
.filter(
|
|
||||||
ProjectMemberInviteColumn::Accepted
|
|
||||||
.eq(true)
|
|
||||||
.and(ProjectMemberInviteColumn::Email.eq(&email)),
|
|
||||||
)
|
|
||||||
.all(&mut *db)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!("Failed to update user {user_id:?} on oauth {provider}: {e}");
|
|
||||||
OAuthError::FetchProjectInvites {
|
|
||||||
user_id,
|
|
||||||
provider: provider.to_owned(),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Create workspace members
|
|
||||||
let insert = WorkspaceMembers::insert_many(workspace_invites.iter().map(
|
|
||||||
|WorkspaceMemberInvite {
|
|
||||||
role,
|
|
||||||
created_by_id,
|
|
||||||
workspace_id,
|
|
||||||
..
|
|
||||||
}| WorkspaceMemberModel {
|
|
||||||
id: NotSet,
|
|
||||||
created_at: Set(Utc::now().fixed_offset()),
|
|
||||||
updated_at: Set(Utc::now().fixed_offset()),
|
|
||||||
role: Set(match role {
|
|
||||||
Roles::Admin => Roles::Member,
|
|
||||||
_ => role.clone(),
|
|
||||||
}),
|
|
||||||
created_by_id: Set(created_by_id.clone()),
|
|
||||||
member_id: Set(user_id),
|
|
||||||
updated_by_id: NotSet,
|
|
||||||
workspace_id: Set(workspace_id.clone()),
|
|
||||||
company_role: NotSet,
|
|
||||||
view_props: NotSet,
|
|
||||||
default_props: NotSet,
|
|
||||||
issue_props: NotSet,
|
|
||||||
is_active: Set(true),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
if let Err(e) = insert
|
|
||||||
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
|
||||||
.exec(&mut *db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!("Failed to add user {user_id:?} to workspace with provider {provider}: {e}");
|
|
||||||
return Ok(HttpResponse::InternalServerError()
|
|
||||||
.json(JsonError::new("Failed to add user to workspaces")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create project members
|
|
||||||
let insert = ProjectMembers::insert_many(project_invites.iter().map(
|
|
||||||
|ProjectMemberInvite {
|
|
||||||
role,
|
|
||||||
created_by_id,
|
|
||||||
workspace_id,
|
|
||||||
..
|
|
||||||
}| ProjectMemberModel {
|
|
||||||
id: NotSet,
|
|
||||||
created_at: Set(Utc::now().fixed_offset()),
|
|
||||||
updated_at: Set(Utc::now().fixed_offset()),
|
|
||||||
role: Set(match role {
|
|
||||||
ProjectMemberRoles::Admin => ProjectMemberRoles::Member,
|
|
||||||
_ => role.clone(),
|
|
||||||
}),
|
|
||||||
created_by_id: Set(created_by_id.clone()),
|
|
||||||
member_id: Set(Some(user_id)),
|
|
||||||
updated_by_id: NotSet,
|
|
||||||
workspace_id: Set(workspace_id.clone()),
|
|
||||||
view_props: NotSet,
|
|
||||||
default_props: NotSet,
|
|
||||||
is_active: Set(true),
|
|
||||||
comment: Set(None),
|
|
||||||
project_id: NotSet,
|
|
||||||
sort_order: NotSet,
|
|
||||||
preferences: NotSet,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
if let Err(e) = insert
|
|
||||||
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
|
||||||
.exec(&mut *db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!("Failed to add user {user_id:?} to project with provider {provider}: {e}");
|
|
||||||
return Ok(HttpResponse::InternalServerError()
|
|
||||||
.json(JsonError::new("Failed to add user to projects")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanups
|
|
||||||
if let Err(e) = WorkspaceMemberInvites::delete_many()
|
|
||||||
.filter(
|
|
||||||
WorkspaceMemberInviteColumn::Id.is_in(
|
|
||||||
workspace_invites
|
|
||||||
.into_iter()
|
|
||||||
.map(|w| uuid::Uuid::from_u128(w.id.as_u128()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.exec(&mut *db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!(
|
|
||||||
"Failed clean up workspace invites for user {user_id:?} with provider {provider}: {e}"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if let Err(e) = ProjectMemberInvites::delete_many()
|
|
||||||
.filter(
|
|
||||||
ProjectMemberInviteColumn::Id.is_in(
|
|
||||||
project_invites
|
|
||||||
.into_iter()
|
|
||||||
.map(|w| uuid::Uuid::from_u128(w.id.as_u128()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.exec(&mut *db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!(
|
|
||||||
"Failed clean up project invites for user {user_id:?} with provider {provider}: {e}"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
entities::social_login_connections::ActiveModel {
|
entities::social_login_connections::ActiveModel {
|
||||||
id: NotSet,
|
id: NotSet,
|
||||||
@ -459,10 +295,10 @@ async fn handle_user_info(
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Failed to create social media connection {provider:?}: {e}");
|
error!("Failed to create social media connection {provider:?}: {e}");
|
||||||
OAuthError::ConnectSocialMedia {
|
AuthError::from(OAuthError::ConnectSocialMedia {
|
||||||
user_id,
|
user_id,
|
||||||
provider: provider.to_owned(),
|
provider: provider.to_owned(),
|
||||||
}
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Err(e) = event_bus
|
if let Err(e) = event_bus
|
||||||
@ -484,45 +320,7 @@ async fn handle_user_info(
|
|||||||
warn!("Failed to publish sign-in msg after {provider} callback: {e}");
|
warn!("Failed to publish sign-in msg after {provider} callback: {e}");
|
||||||
};
|
};
|
||||||
|
|
||||||
let access_ttl = JwtTtl(actix_jwt_session::Duration::days(9_999));
|
auth_http_response(user, session, StatusCode::CREATED).await
|
||||||
let refresh_ttl = RefreshTtl(actix_jwt_session::Duration::days(9_999));
|
|
||||||
let claims = AppClaims::from_user(user, access_ttl);
|
|
||||||
let pair = match session.store(claims, access_ttl, refresh_ttl).await {
|
|
||||||
Ok(pair) => pair,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to store session: {e}");
|
|
||||||
return Ok(HttpResponse::InternalServerError().finish());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let access_token = match pair.jwt.encode() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to store session: {e}");
|
|
||||||
return Ok(HttpResponse::InternalServerError().finish());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let refresh_token = match pair.refresh.encode() {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => {
|
|
||||||
error!("Failed to store session: {e}");
|
|
||||||
return Ok(HttpResponse::InternalServerError().finish());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
|
||||||
struct Response {
|
|
||||||
access_token: String,
|
|
||||||
refresh_token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Created()
|
|
||||||
.append_header((JWT_HEADER_NAME, access_token.clone()))
|
|
||||||
.append_header((REFRESH_HEADER_NAME, refresh_token.clone()))
|
|
||||||
.json(Response {
|
|
||||||
access_token,
|
|
||||||
refresh_token,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn github_flow(
|
fn github_flow(
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix_jwt_session::*;
|
use actix_jwt_session::*;
|
||||||
use actix_web::{web::Data, App, HttpServer};
|
use actix_web::{web::Data, App, HttpServer};
|
||||||
@ -9,9 +8,11 @@ pub use sea_orm::{Database, DatabaseConnection};
|
|||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
|
pub mod extractors;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
pub const APPLICATION_NAME: &str = "jet-api";
|
pub const APPLICATION_NAME: &str = "jet-api";
|
||||||
|
|
||||||
@ -40,21 +41,16 @@ async fn main() {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to connect to database");
|
.expect("Failed to connect to database");
|
||||||
let keys = JwtSigningKeys::load_or_create();
|
let (storage, factory) = SessionMiddlewareFactory::<session::AppClaims>::build_ed_dsa()
|
||||||
let (storage, factory) = SessionMiddlewareFactory::<session::AppClaims>::build(
|
.with_redis_pool(redis.clone())
|
||||||
Arc::new(keys.encoding_key),
|
// Check if header "Authorization" exists and contains Bearer with encoded JWT
|
||||||
Arc::new(keys.decoding_key),
|
.with_jwt_header(JWT_HEADER_NAME)
|
||||||
Algorithm::EdDSA,
|
// Check if cookie JWT exists and contains encoded JWT
|
||||||
)
|
.with_jwt_cookie(JWT_COOKIE_NAME)
|
||||||
.with_redis_pool(redis.clone())
|
.with_refresh_header(REFRESH_HEADER_NAME)
|
||||||
// Check if header "Authorization" exists and contains Bearer with encoded JWT
|
// Check if cookie JWT exists and contains encoded JWT
|
||||||
.with_jwt_header(JWT_HEADER_NAME)
|
.with_refresh_cookie(REFRESH_COOKIE_NAME)
|
||||||
// Check if cookie JWT exists and contains encoded JWT
|
.finish();
|
||||||
.with_jwt_cookie(JWT_COOKIE_NAME)
|
|
||||||
.with_refresh_header(REFRESH_HEADER_NAME)
|
|
||||||
// Check if cookie JWT exists and contains encoded JWT
|
|
||||||
.with_refresh_cookie(REFRESH_COOKIE_NAME)
|
|
||||||
.finish();
|
|
||||||
let jwt_ttl = JwtTtl(Duration::days(9999));
|
let jwt_ttl = JwtTtl(Duration::days(9999));
|
||||||
let refresh_ttl = RefreshTtl(Duration::days(3 * 31 * 999));
|
let refresh_ttl = RefreshTtl(Duration::days(3 * 31 * 999));
|
||||||
let (eb_client, eb_stream) = rumqttc::AsyncClient::new(
|
let (eb_client, eb_stream) = rumqttc::AsyncClient::new(
|
||||||
@ -76,9 +72,7 @@ async fn main() {
|
|||||||
crate::events::handle_events(eb_stream).await;
|
crate::events::handle_events(eb_stream).await;
|
||||||
|
|
||||||
let http_client = reqwest::Client::new();
|
let http_client = reqwest::Client::new();
|
||||||
let redis = Data::new(redis);
|
|
||||||
let application_config = Data::new(application_config);
|
let application_config = Data::new(application_config);
|
||||||
let db = Data::new(db);
|
|
||||||
let event_bus = Data::new(jet_contract::event_bus::Client::new(eb_client));
|
let event_bus = Data::new(jet_contract::event_bus::Client::new(eb_client));
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
@ -86,9 +80,9 @@ async fn main() {
|
|||||||
.app_data(Data::new(storage.clone()))
|
.app_data(Data::new(storage.clone()))
|
||||||
.app_data(Data::new(jwt_ttl))
|
.app_data(Data::new(jwt_ttl))
|
||||||
.app_data(Data::new(refresh_ttl))
|
.app_data(Data::new(refresh_ttl))
|
||||||
|
.app_data(Data::new(redis.clone()))
|
||||||
|
.app_data(Data::new(db.clone()))
|
||||||
.app_data(application_config.clone())
|
.app_data(application_config.clone())
|
||||||
.app_data(redis.clone())
|
|
||||||
.app_data(db.clone())
|
|
||||||
.app_data(event_bus.clone())
|
.app_data(event_bus.clone())
|
||||||
.wrap(factory.clone())
|
.wrap(factory.clone())
|
||||||
.wrap(actix_web::middleware::Logger::default())
|
.wrap(actix_web::middleware::Logger::default())
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
use derive_more::Display;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::http::AuthError;
|
use crate::http::AuthError;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Display)]
|
||||||
|
#[display(fmt = "{{\"error\":\"{error}\"}}")]
|
||||||
pub struct JsonError {
|
pub struct JsonError {
|
||||||
pub error: String,
|
pub error: String,
|
||||||
}
|
}
|
||||||
@ -16,6 +18,18 @@ impl JsonError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl actix_web::ResponseError for JsonError {
|
||||||
|
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
|
||||||
|
HttpResponse::BadRequest().json(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Error> for JsonError {
|
||||||
|
fn from(e: Error) -> Self {
|
||||||
|
Self::new(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, derive_more::Display)]
|
#[derive(Debug, Clone, derive_more::Display)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[display(fmt = "Database connection error")]
|
#[display(fmt = "Database connection error")]
|
||||||
@ -30,6 +44,10 @@ pub enum Error {
|
|||||||
Auth(AuthError),
|
Auth(AuthError),
|
||||||
#[display(fmt = "Internal server error, failed to connect to storage")]
|
#[display(fmt = "Internal server error, failed to connect to storage")]
|
||||||
RedisConnection,
|
RedisConnection,
|
||||||
|
#[display(fmt = "Failed to add user to workspaces")]
|
||||||
|
AddToWorkspace,
|
||||||
|
#[display(fmt = "Failed to add user to projects")]
|
||||||
|
AddToProject,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AuthError> for Error {
|
impl From<AuthError> for Error {
|
||||||
|
185
crates/jet-api/src/utils/mod.rs
Normal file
185
crates/jet-api/src/utils/mod.rs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
|
use chrono::Utc;
|
||||||
|
use entities::project_member_invites::{
|
||||||
|
Column as ProjectMemberInviteColumn, Model as ProjectMemberInvite,
|
||||||
|
};
|
||||||
|
use entities::workspace_member_invites::{
|
||||||
|
Column as WorkspaceMemberInviteColumn, Model as WorkspaceMemberInvite,
|
||||||
|
};
|
||||||
|
use entities::workspace_members::ActiveModel as WorkspaceMemberModel;
|
||||||
|
use entities::{
|
||||||
|
prelude::{ProjectMemberInvites, ProjectMembers, WorkspaceMemberInvites, WorkspaceMembers},
|
||||||
|
sea_orm_active_enums::Roles,
|
||||||
|
};
|
||||||
|
use entities::{
|
||||||
|
project_members::ActiveModel as ProjectMemberModel, sea_orm_active_enums::ProjectMemberRoles,
|
||||||
|
};
|
||||||
|
use reqwest::header::USER_AGENT;
|
||||||
|
use sea_orm::sea_query::OnConflict;
|
||||||
|
use sea_orm::*;
|
||||||
|
use tracing::error;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::http::OAuthError;
|
||||||
|
use crate::models::JsonError;
|
||||||
|
use crate::{http::AuthError, models::Error};
|
||||||
|
|
||||||
|
pub fn extract_req_info(req: &HttpRequest) -> Result<(String, String, String), Error> {
|
||||||
|
let ip = req.peer_addr().ok_or(AuthError::NoPeerAddr)?.ip();
|
||||||
|
let user_agent = req
|
||||||
|
.headers()
|
||||||
|
.get(USER_AGENT)
|
||||||
|
.ok_or(AuthError::NoUserAgent)?
|
||||||
|
.to_str()
|
||||||
|
.map_err(|_| AuthError::InvalidUserAgent)?
|
||||||
|
.to_string();
|
||||||
|
let current_site = req.uri().host().ok_or(Error::NoHost)?.to_owned();
|
||||||
|
Ok((ip.to_string(), user_agent, current_site))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn invites_to_membership(
|
||||||
|
user_id: Uuid,
|
||||||
|
email: &str,
|
||||||
|
provider: Option<&str>,
|
||||||
|
db: &mut DatabaseTransaction,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let workspace_invites = WorkspaceMemberInvites::find()
|
||||||
|
.filter(
|
||||||
|
WorkspaceMemberInviteColumn::Accepted
|
||||||
|
.eq(true)
|
||||||
|
.and(WorkspaceMemberInviteColumn::Email.eq(email)),
|
||||||
|
)
|
||||||
|
.all(&mut *db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to update user {user_id:?} on oauth {provider:?}: {e}");
|
||||||
|
AuthError::from(OAuthError::FetchWorkspaceInvites {
|
||||||
|
user_id,
|
||||||
|
provider: provider.to_owned().unwrap_or("--NONE--").to_owned(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let project_invites = ProjectMemberInvites::find()
|
||||||
|
.filter(
|
||||||
|
ProjectMemberInviteColumn::Accepted
|
||||||
|
.eq(true)
|
||||||
|
.and(ProjectMemberInviteColumn::Email.eq(email)),
|
||||||
|
)
|
||||||
|
.all(&mut *db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Failed to update user {user_id:?} on oauth {provider:?}: {e}");
|
||||||
|
AuthError::from(OAuthError::FetchProjectInvites {
|
||||||
|
user_id,
|
||||||
|
provider: provider.to_owned().unwrap_or("--NONE--").to_owned(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Create workspace members
|
||||||
|
let insert = WorkspaceMembers::insert_many(workspace_invites.iter().map(
|
||||||
|
|WorkspaceMemberInvite {
|
||||||
|
role,
|
||||||
|
created_by_id,
|
||||||
|
workspace_id,
|
||||||
|
..
|
||||||
|
}| WorkspaceMemberModel {
|
||||||
|
id: NotSet,
|
||||||
|
created_at: Set(Utc::now().fixed_offset()),
|
||||||
|
updated_at: Set(Utc::now().fixed_offset()),
|
||||||
|
role: Set(match role {
|
||||||
|
Roles::Admin => Roles::Member,
|
||||||
|
_ => role.clone(),
|
||||||
|
}),
|
||||||
|
created_by_id: Set(created_by_id.clone()),
|
||||||
|
member_id: Set(user_id),
|
||||||
|
updated_by_id: NotSet,
|
||||||
|
workspace_id: Set(workspace_id.clone()),
|
||||||
|
company_role: NotSet,
|
||||||
|
view_props: NotSet,
|
||||||
|
default_props: NotSet,
|
||||||
|
issue_props: NotSet,
|
||||||
|
is_active: Set(true),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
if let Err(e) = insert
|
||||||
|
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
||||||
|
.exec(&mut *db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to add user {user_id:?} to workspace with provider {provider:?}: {e}");
|
||||||
|
return Err(Error::AddToWorkspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create project members
|
||||||
|
let insert = ProjectMembers::insert_many(project_invites.iter().map(
|
||||||
|
|ProjectMemberInvite {
|
||||||
|
role,
|
||||||
|
created_by_id,
|
||||||
|
workspace_id,
|
||||||
|
..
|
||||||
|
}| ProjectMemberModel {
|
||||||
|
id: NotSet,
|
||||||
|
created_at: Set(Utc::now().fixed_offset()),
|
||||||
|
updated_at: Set(Utc::now().fixed_offset()),
|
||||||
|
role: Set(match role {
|
||||||
|
ProjectMemberRoles::Admin => ProjectMemberRoles::Member,
|
||||||
|
_ => role.clone(),
|
||||||
|
}),
|
||||||
|
created_by_id: Set(created_by_id.clone()),
|
||||||
|
member_id: Set(Some(user_id)),
|
||||||
|
updated_by_id: NotSet,
|
||||||
|
workspace_id: Set(workspace_id.clone()),
|
||||||
|
view_props: NotSet,
|
||||||
|
default_props: NotSet,
|
||||||
|
is_active: Set(true),
|
||||||
|
comment: Set(None),
|
||||||
|
project_id: NotSet,
|
||||||
|
sort_order: NotSet,
|
||||||
|
preferences: NotSet,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
if let Err(e) = insert
|
||||||
|
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
||||||
|
.exec(&mut *db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to add user {user_id:?} to project with provider {provider:?}: {e}");
|
||||||
|
return Err(Error::AddToProject);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanups
|
||||||
|
if let Err(e) = WorkspaceMemberInvites::delete_many()
|
||||||
|
.filter(
|
||||||
|
WorkspaceMemberInviteColumn::Id.is_in(
|
||||||
|
workspace_invites
|
||||||
|
.into_iter()
|
||||||
|
.map(|w| uuid::Uuid::from_u128(w.id.as_u128()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.exec(&mut *db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
"Failed clean up workspace invites for user {user_id:?} with provider {provider:?}: {e}"
|
||||||
|
);
|
||||||
|
return Err(Error::DatabaseError);
|
||||||
|
};
|
||||||
|
if let Err(e) = ProjectMemberInvites::delete_many()
|
||||||
|
.filter(
|
||||||
|
ProjectMemberInviteColumn::Id.is_in(
|
||||||
|
project_invites
|
||||||
|
.into_iter()
|
||||||
|
.map(|w| uuid::Uuid::from_u128(w.id.as_u128()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.exec(&mut *db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!(
|
||||||
|
"Failed clean up project invites for user {user_id:?} with provider {provider:?}: {e}"
|
||||||
|
);
|
||||||
|
return Err(Error::DatabaseError);
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -58,6 +58,8 @@ pub enum SignInMedium {
|
|||||||
MagicLink,
|
MagicLink,
|
||||||
#[display(fmt = "oauth")]
|
#[display(fmt = "oauth")]
|
||||||
OAuth,
|
OAuth,
|
||||||
|
#[display(fmt = "EMAIL")]
|
||||||
|
Email,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignInMedium {
|
impl SignInMedium {
|
||||||
@ -65,6 +67,7 @@ impl SignInMedium {
|
|||||||
match self {
|
match self {
|
||||||
Self::MagicLink => "MAGIC_LINK",
|
Self::MagicLink => "MAGIC_LINK",
|
||||||
Self::OAuth => "oauth",
|
Self::OAuth => "oauth",
|
||||||
|
Self::Email => "EMAIL",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user