Compare commits
12 Commits
20251028
...
66ea004705
| Author | SHA1 | Date | |
|---|---|---|---|
| 66ea004705 | |||
| 3bc53b8f91 | |||
| 2fe1b4a8b2 | |||
| 16ef969e29 | |||
| 0fa58f4d3a | |||
| a0325fefbf | |||
| 92d04f3312 | |||
| abd86ff22d | |||
| f64f01a958 | |||
| 96ffc669d7 | |||
| d9f659ce98 | |||
| e73b5b8e5b |
333
Cargo.lock
generated
333
Cargo.lock
generated
@@ -317,9 +317,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.20"
|
version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -568,6 +568,7 @@ dependencies = [
|
|||||||
"mime_guess",
|
"mime_guess",
|
||||||
"qrcode-generator",
|
"qrcode-generator",
|
||||||
"rand",
|
"rand",
|
||||||
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
@@ -664,9 +665,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.12.0"
|
version = "3.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytemuck"
|
name = "bytemuck"
|
||||||
@@ -737,9 +738,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.2.2"
|
version = "4.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b802d85aaf3a1cdb02b224ba472ebdea62014fccfcb269b95a4d76443b5ee5a"
|
checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@@ -748,9 +749,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.2.2"
|
version = "4.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14a1a858f532119338887a4b8e1af9c60de8249cd7bafd68036a489e261e37b6"
|
checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@@ -858,6 +859,16 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
@@ -866,9 +877,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.6"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181"
|
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
@@ -1137,6 +1148,15 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fastrand"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fdeflate"
|
name = "fdeflate"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
@@ -1196,6 +1216,15 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@@ -1283,9 +1312,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.17"
|
version = "0.3.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f"
|
checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -1401,6 +1430,17 @@ dependencies = [
|
|||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -1428,6 +1468,43 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"hyper",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.56"
|
version = "0.1.56"
|
||||||
@@ -1514,6 +1591,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
@@ -1525,6 +1611,12 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnet"
|
||||||
|
version = "2.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-terminal"
|
name = "is-terminal"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
@@ -1641,9 +1733,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.141"
|
version = "0.2.142"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5"
|
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libm"
|
name = "libm"
|
||||||
@@ -1662,9 +1754,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.3.1"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f"
|
checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-channel"
|
name = "local-channel"
|
||||||
@@ -1762,6 +1854,24 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -1875,9 +1985,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.50"
|
version = "0.10.51"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1"
|
checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
@@ -1900,10 +2010,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-probe"
|
||||||
version = "0.9.85"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0"
|
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1953,7 +2069,7 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall 0.2.16",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
@@ -2134,10 +2250,19 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "redox_syscall"
|
||||||
version = "1.7.3"
|
version = "0.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -2146,9 +2271,46 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.29"
|
version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27b71749df584b7f4cac2c426c127a7c785a5106cc98f7a8feb044115f0fa254"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.0",
|
||||||
|
"bytes",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"hyper-tls",
|
||||||
|
"ipnet",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"mime",
|
||||||
|
"native-tls",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"winreg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rfc6979"
|
name = "rfc6979"
|
||||||
@@ -2217,9 +2379,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.37.11"
|
version = "0.37.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77"
|
checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
@@ -2235,6 +2397,15 @@ version = "1.0.13"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.42.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2261,6 +2432,29 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
@@ -2477,6 +2671,19 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fastrand",
|
||||||
|
"redox_syscall 0.3.5",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -2576,6 +2783,16 @@ dependencies = [
|
|||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.7"
|
version = "0.7.7"
|
||||||
@@ -2599,6 +2816,12 @@ dependencies = [
|
|||||||
"ring",
|
"ring",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-service"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.37"
|
version = "0.1.37"
|
||||||
@@ -2632,6 +2855,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "try-lock"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
@@ -2754,6 +2983,16 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "want"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"try-lock",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.0+wasi-snapshot-preview1"
|
version = "0.10.0+wasi-snapshot-preview1"
|
||||||
@@ -2791,6 +3030,18 @@ dependencies = [
|
|||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.84"
|
version = "0.2.84"
|
||||||
@@ -2920,6 +3171,21 @@ dependencies = [
|
|||||||
"windows-targets 0.48.0",
|
"windows-targets 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.42.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.42.2",
|
||||||
|
"windows_aarch64_msvc 0.42.2",
|
||||||
|
"windows_i686_gnu 0.42.2",
|
||||||
|
"windows_i686_msvc 0.42.2",
|
||||||
|
"windows_x86_64_gnu 0.42.2",
|
||||||
|
"windows_x86_64_gnullvm 0.42.2",
|
||||||
|
"windows_x86_64_msvc 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
@@ -3052,6 +3318,15 @@ version = "0.48.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x509-parser"
|
name = "x509-parser"
|
||||||
version = "0.13.2"
|
version = "0.13.2"
|
||||||
|
|||||||
@@ -38,3 +38,4 @@ aes-gcm = { version = "0.10.1", features = ["aes"] }
|
|||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
chrono = "0.4.24"
|
chrono = "0.4.24"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
reqwest = { version = "0.11.16", features = ["json"] }
|
||||||
12
assets/img/brands/gitea.svg
Normal file
12
assets/img/brands/gitea.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg version="1.1" id="main_outline" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 640 640" style="enable-background:new 0 0 640 640;" xml:space="preserve"><link xmlns="" type="text/css" rel="stylesheet" id="dark-mode-custom-link"/><link xmlns="" type="text/css" rel="stylesheet" id="dark-mode-general-link"/><style xmlns="" lang="en" type="text/css" id="dark-mode-custom-style"/><style xmlns="" lang="en" type="text/css" id="dark-mode-native-style"/><style xmlns="" lang="en" type="text/css" id="dark-mode-native-sheet"/>
|
||||||
|
<g>
|
||||||
|
<path id="teabag" style="fill:#FFFFFF" d="M395.9,484.2l-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5,21.2-17.9,33.8-11.8 c17.2,8.3,27.1,13,27.1,13l-0.1-109.2l16.7-0.1l0.1,117.1c0,0,57.4,24.2,83.1,40.1c3.7,2.3,10.2,6.8,12.9,14.4 c2.1,6.1,2,13.1-1,19.3l-61,126.9C423.6,484.9,408.4,490.3,395.9,484.2z"/>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#609926" d="M622.7,149.8c-4.1-4.1-9.6-4-9.6-4s-117.2,6.6-177.9,8c-13.3,0.3-26.5,0.6-39.6,0.7c0,39.1,0,78.2,0,117.2 c-5.5-2.6-11.1-5.3-16.6-7.9c0-36.4-0.1-109.2-0.1-109.2c-29,0.4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5 c-9.8-0.6-22.5-2.1-39,1.5c-8.7,1.8-33.5,7.4-53.8,26.9C-4.9,212.4,6.6,276.2,8,285.8c1.7,11.7,6.9,44.2,31.7,72.5 c45.8,56.1,144.4,54.8,144.4,54.8s12.1,28.9,30.6,55.5c25,33.1,50.7,58.9,75.7,62c63,0,188.9-0.1,188.9-0.1s12,0.1,28.3-10.3 c14-8.5,26.5-23.4,26.5-23.4s12.9-13.8,30.9-45.3c5.5-9.7,10.1-19.1,14.1-28c0,0,55.2-117.1,55.2-231.1 C633.2,157.9,624.7,151.8,622.7,149.8z M125.6,353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6,321.8,60,295.4 c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5,38.5-30c13.8-3.7,31-3.1,31-3.1s7.1,59.4,15.7,94.2c7.2,29.2,24.8,77.7,24.8,77.7 S142.5,359.9,125.6,353.9z M425.9,461.5c0,0-6.1,14.5-19.6,15.4c-5.8,0.4-10.3-1.2-10.3-1.2s-0.3-0.1-5.3-2.1l-112.9-55 c0,0-10.9-5.7-12.8-15.6c-2.2-8.1,2.7-18.1,2.7-18.1L322,273c0,0,4.8-9.7,12.2-13c0.6-0.3,2.3-1,4.5-1.5c8.1-2.1,18,2.8,18,2.8 l110.7,53.7c0,0,12.6,5.7,15.3,16.2c1.9,7.4-0.5,14-1.8,17.2C474.6,363.8,425.9,461.5,425.9,461.5z"/>
|
||||||
|
<path style="fill:#609926" d="M326.8,380.1c-8.2,0.1-15.4,5.8-17.3,13.8c-1.9,8,2,16.3,9.1,20c7.7,4,17.5,1.8,22.7-5.4 c5.1-7.1,4.3-16.9-1.8-23.1l24-49.1c1.5,0.1,3.7,0.2,6.2-0.5c4.1-0.9,7.1-3.6,7.1-3.6c4.2,1.8,8.6,3.8,13.2,6.1 c4.8,2.4,9.3,4.9,13.4,7.3c0.9,0.5,1.8,1.1,2.8,1.9c1.6,1.3,3.4,3.1,4.7,5.5c1.9,5.5-1.9,14.9-1.9,14.9 c-2.3,7.6-18.4,40.6-18.4,40.6c-8.1-0.2-15.3,5-17.7,12.5c-2.6,8.1,1.1,17.3,8.9,21.3c7.8,4,17.4,1.7,22.5-5.3 c5-6.8,4.6-16.3-1.1-22.6c1.9-3.7,3.7-7.4,5.6-11.3c5-10.4,13.5-30.4,13.5-30.4c0.9-1.7,5.7-10.3,2.7-21.3 c-2.5-11.4-12.6-16.7-12.6-16.7c-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3c4.7-9.7,9.4-19.3,14.1-29 c-4.1-2-8.1-4-12.2-6.1c-4.8,9.8-9.7,19.7-14.5,29.5c-6.7-0.1-12.9,3.5-16.1,9.4c-3.4,6.3-2.7,14.1,1.9,19.8 C343.2,346.5,335,363.3,326.8,380.1z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
1
assets/img/brands/github.svg
Normal file
1
assets/img/brands/github.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 960 B |
1
assets/img/brands/gitlab.svg
Normal file
1
assets/img/brands/gitlab.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 380 380"><defs><style>.cls-1{fill:#e24329;}.cls-2{fill:#fc6d26;}.cls-3{fill:#fca326;}</style></defs><g id="LOGO"><path class="cls-1" d="M282.83,170.73l-.27-.69-26.14-68.22a6.81,6.81,0,0,0-2.69-3.24,7,7,0,0,0-8,.43,7,7,0,0,0-2.32,3.52l-17.65,54H154.29l-17.65-54A6.86,6.86,0,0,0,134.32,99a7,7,0,0,0-8-.43,6.87,6.87,0,0,0-2.69,3.24L97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82,19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91,40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/><path class="cls-2" d="M282.83,170.73l-.27-.69a88.3,88.3,0,0,0-35.15,15.8L190,229.25c19.55,14.79,36.57,27.64,36.57,27.64l40.06-30,.1-.08A48.56,48.56,0,0,0,282.83,170.73Z"/><path class="cls-3" d="M153.43,256.89l19.7,14.91,12,9.06a8.07,8.07,0,0,0,9.76,0l12-9.06,19.7-14.91S209.55,244,190,229.25C170.45,244,153.43,256.89,153.43,256.89Z"/><path class="cls-2" d="M132.58,185.84A88.19,88.19,0,0,0,97.44,170l-.26.69a48.54,48.54,0,0,0,16.1,56.1l.09.07.24.17,39.82,29.82s17-12.85,36.57-27.64Z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
1
assets/img/brands/google.svg
Normal file
1
assets/img/brands/google.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="705.6" height="720" viewBox="0 0 186.69 190.5" xmlns:v="https://vecta.io/nano"><link xmlns="" type="text/css" rel="stylesheet" id="dark-mode-custom-link"/><link xmlns="" type="text/css" rel="stylesheet" id="dark-mode-general-link"/><style xmlns="" lang="en" type="text/css" id="dark-mode-custom-style"/><style xmlns="" lang="en" type="text/css" id="dark-mode-native-style"/><style xmlns="" lang="en" type="text/css" id="dark-mode-native-sheet"/><g transform="translate(1184.583 765.171)"><path clip-path="none" mask="none" d="M-1089.333-687.239v36.888h51.262c-2.251 11.863-9.006 21.908-19.137 28.662l30.913 23.986c18.011-16.625 28.402-41.044 28.402-70.052 0-6.754-.606-13.249-1.732-19.483z" fill="#4285f4"/><path clip-path="none" mask="none" d="M-1142.714-651.791l-6.972 5.337-24.679 19.223h0c15.673 31.086 47.796 52.561 85.03 52.561 25.717 0 47.278-8.486 63.038-23.033l-30.913-23.986c-8.486 5.715-19.31 9.179-32.125 9.179-24.765 0-45.806-16.712-53.34-39.226z" fill="#34a853"/><path clip-path="none" mask="none" d="M-1174.365-712.61c-6.494 12.815-10.217 27.276-10.217 42.689s3.723 29.874 10.217 42.689c0 .086 31.693-24.592 31.693-24.592-1.905-5.715-3.031-11.776-3.031-18.098s1.126-12.383 3.031-18.098z" fill="#fbbc05"/><path d="M-1089.333-727.244c14.028 0 26.497 4.849 36.455 14.201l27.276-27.276c-16.539-15.413-38.013-24.852-63.731-24.852-37.234 0-69.359 21.388-85.032 52.561l31.692 24.592c7.533-22.514 28.575-39.226 53.34-39.226z" fill="#ea4335" clip-path="none" mask="none"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
assets/img/brands/microsoft.svg
Normal file
1
assets/img/brands/microsoft.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><path fill="#f35325" d="M0 0h10v10H0z"/><path fill="#81bc06" d="M11 0h10v10H11z"/><path fill="#05a6f0" d="M0 11h10v10H0z"/><path fill="#ffba08" d="M11 11h10v10H11z"/></svg>
|
||||||
|
After Width: | Height: | Size: 232 B |
@@ -1,3 +1,4 @@
|
|||||||
pub mod bruteforce_actor;
|
pub mod bruteforce_actor;
|
||||||
pub mod openid_sessions_actor;
|
pub mod openid_sessions_actor;
|
||||||
|
pub mod providers_states_actor;
|
||||||
pub mod users_actor;
|
pub mod users_actor;
|
||||||
|
|||||||
130
src/actors/providers_states_actor.rs
Normal file
130
src/actors/providers_states_actor.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
//! # Providers state actor
|
||||||
|
//!
|
||||||
|
//! This actor stores the content of the states
|
||||||
|
//! during authentication with upstream providers
|
||||||
|
|
||||||
|
use crate::constants::{
|
||||||
|
MAX_OIDC_PROVIDERS_STATES, OIDC_PROVIDERS_STATE_DURATION, OIDC_PROVIDERS_STATE_LEN,
|
||||||
|
OIDC_STATES_CLEANUP_INTERVAL,
|
||||||
|
};
|
||||||
|
use actix::{Actor, AsyncContext, Context, Handler, Message};
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use crate::data::login_redirect::LoginRedirect;
|
||||||
|
use crate::data::provider::ProviderID;
|
||||||
|
use crate::utils::string_utils::rand_str;
|
||||||
|
use crate::utils::time::time;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProviderLoginState {
|
||||||
|
pub provider_id: ProviderID,
|
||||||
|
pub state_id: String,
|
||||||
|
pub redirect: LoginRedirect,
|
||||||
|
pub expire: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProviderLoginState {
|
||||||
|
pub fn new(prov_id: &ProviderID, redirect: LoginRedirect) -> Self {
|
||||||
|
Self {
|
||||||
|
provider_id: prov_id.clone(),
|
||||||
|
state_id: rand_str(OIDC_PROVIDERS_STATE_LEN),
|
||||||
|
redirect,
|
||||||
|
expire: time() + OIDC_PROVIDERS_STATE_DURATION,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "()")]
|
||||||
|
pub struct RecordState {
|
||||||
|
pub ip: IpAddr,
|
||||||
|
pub state: ProviderLoginState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "Option<ProviderLoginState>")]
|
||||||
|
pub struct ConsumeState {
|
||||||
|
pub ip: IpAddr,
|
||||||
|
pub state_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ProvidersStatesActor {
|
||||||
|
states: HashMap<IpAddr, Vec<ProviderLoginState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProvidersStatesActor {
|
||||||
|
/// Clean outdated states
|
||||||
|
fn clean_old_states(&mut self) {
|
||||||
|
#[allow(clippy::map_clone)]
|
||||||
|
let keys = self.states.keys().map(|i| *i).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for ip in keys {
|
||||||
|
// Remove old states
|
||||||
|
let states = self.states.get_mut(&ip).unwrap();
|
||||||
|
states.retain(|i| i.expire < time());
|
||||||
|
|
||||||
|
// Remove empty entry keys
|
||||||
|
if states.is_empty() {
|
||||||
|
self.states.remove(&ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a new provider login state
|
||||||
|
pub fn insert_state(&mut self, ip: IpAddr, state: ProviderLoginState) {
|
||||||
|
if let Entry::Vacant(e) = self.states.entry(ip) {
|
||||||
|
e.insert(vec![state]);
|
||||||
|
} else {
|
||||||
|
let states = self.states.get_mut(&ip).unwrap();
|
||||||
|
|
||||||
|
// We limit the number of states per IP address
|
||||||
|
if states.len() > MAX_OIDC_PROVIDERS_STATES {
|
||||||
|
states.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
states.push(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get & consume a login state
|
||||||
|
pub fn consume_state(&mut self, ip: IpAddr, state_id: &str) -> Option<ProviderLoginState> {
|
||||||
|
let idx = self
|
||||||
|
.states
|
||||||
|
.get(&ip)?
|
||||||
|
.iter()
|
||||||
|
.position(|val| val.state_id.as_str() == state_id)?;
|
||||||
|
|
||||||
|
Some(self.states.get_mut(&ip)?.remove(idx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for ProvidersStatesActor {
|
||||||
|
type Context = Context<Self>;
|
||||||
|
|
||||||
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
|
// Clean up at a regular interval failed attempts
|
||||||
|
ctx.run_interval(OIDC_STATES_CLEANUP_INTERVAL, |act, _ctx| {
|
||||||
|
log::trace!("Cleaning up old states");
|
||||||
|
act.clean_old_states();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<RecordState> for ProvidersStatesActor {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
|
fn handle(&mut self, req: RecordState, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
self.insert_state(req.ip, req.state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<ConsumeState> for ProvidersStatesActor {
|
||||||
|
type Result = Option<ProviderLoginState>;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: ConsumeState, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
self.consume_state(req.ip, &req.state_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use crate::data::provider::ProviderID;
|
||||||
use actix::{Actor, Context, Handler, Message, MessageResult};
|
use actix::{Actor, Context, Handler, Message, MessageResult};
|
||||||
|
|
||||||
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
|
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
|
||||||
@@ -19,6 +20,11 @@ pub trait UsersSyncBackend {
|
|||||||
fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> Res;
|
fn save_new_successful_2fa_authentication(&mut self, id: &UserID, ip: IpAddr) -> Res;
|
||||||
fn clear_2fa_login_history(&mut self, id: &UserID) -> Res;
|
fn clear_2fa_login_history(&mut self, id: &UserID) -> Res;
|
||||||
fn delete_account(&mut self, id: &UserID) -> Res;
|
fn delete_account(&mut self, id: &UserID) -> Res;
|
||||||
|
fn set_authorized_authentication_sources(
|
||||||
|
&mut self,
|
||||||
|
id: &UserID,
|
||||||
|
sources: AuthorizedAuthenticationSources,
|
||||||
|
) -> Res;
|
||||||
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res;
|
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,12 +34,13 @@ pub enum LoginResult {
|
|||||||
AccountNotFound,
|
AccountNotFound,
|
||||||
InvalidPassword,
|
InvalidPassword,
|
||||||
AccountDisabled,
|
AccountDisabled,
|
||||||
|
LocalAuthForbidden,
|
||||||
Success(Box<User>),
|
Success(Box<User>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(LoginResult)]
|
#[rtype(LoginResult)]
|
||||||
pub struct LoginRequest {
|
pub struct LocalLoginRequest {
|
||||||
pub login: String,
|
pub login: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
@@ -88,6 +95,16 @@ pub struct AddSuccessful2FALogin(pub UserID, pub IpAddr);
|
|||||||
#[rtype(result = "bool")]
|
#[rtype(result = "bool")]
|
||||||
pub struct Clear2FALoginHistory(pub UserID);
|
pub struct Clear2FALoginHistory(pub UserID);
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Debug, Clone)]
|
||||||
|
pub struct AuthorizedAuthenticationSources {
|
||||||
|
pub local: bool,
|
||||||
|
pub upstream: Vec<ProviderID>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "bool")]
|
||||||
|
pub struct SetAuthorizedAuthenticationSources(pub UserID, pub AuthorizedAuthenticationSources);
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "bool")]
|
#[rtype(result = "bool")]
|
||||||
pub struct SetGrantedClients(pub UserID, pub GrantedClients);
|
pub struct SetGrantedClients(pub UserID, pub GrantedClients);
|
||||||
@@ -119,10 +136,10 @@ impl Actor for UsersActor {
|
|||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<LoginRequest> for UsersActor {
|
impl Handler<LocalLoginRequest> for UsersActor {
|
||||||
type Result = MessageResult<LoginRequest>;
|
type Result = MessageResult<LocalLoginRequest>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: LoginRequest, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: LocalLoginRequest, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
match self.manager.find_by_username_or_email(&msg.login) {
|
match self.manager.find_by_username_or_email(&msg.login) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to find user! {}", e);
|
log::error!("Failed to find user! {}", e);
|
||||||
@@ -142,6 +159,10 @@ impl Handler<LoginRequest> for UsersActor {
|
|||||||
return MessageResult(LoginResult::AccountDisabled);
|
return MessageResult(LoginResult::AccountDisabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !user.allow_local_login {
|
||||||
|
return MessageResult(LoginResult::LocalAuthForbidden);
|
||||||
|
}
|
||||||
|
|
||||||
MessageResult(LoginResult::Success(Box::new(user)))
|
MessageResult(LoginResult::Success(Box::new(user)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,6 +262,29 @@ impl Handler<Clear2FALoginHistory> for UsersActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Handler<SetAuthorizedAuthenticationSources> for UsersActor {
|
||||||
|
type Result = <SetAuthorizedAuthenticationSources as actix::Message>::Result;
|
||||||
|
fn handle(
|
||||||
|
&mut self,
|
||||||
|
msg: SetAuthorizedAuthenticationSources,
|
||||||
|
_ctx: &mut Self::Context,
|
||||||
|
) -> Self::Result {
|
||||||
|
match self
|
||||||
|
.manager
|
||||||
|
.set_authorized_authentication_sources(&msg.0, msg.1)
|
||||||
|
{
|
||||||
|
Ok(_) => true,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to set authorized authentication sources for user! {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Handler<SetGrantedClients> for UsersActor {
|
impl Handler<SetGrantedClients> for UsersActor {
|
||||||
type Result = <SetGrantedClients as actix::Message>::Result;
|
type Result = <SetGrantedClients as actix::Message>::Result;
|
||||||
fn handle(&mut self, msg: SetGrantedClients, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: SetGrantedClients, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ pub const USERS_LIST_FILE: &str = "users.json";
|
|||||||
/// File in storage containing clients list
|
/// File in storage containing clients list
|
||||||
pub const CLIENTS_LIST_FILE: &str = "clients.yaml";
|
pub const CLIENTS_LIST_FILE: &str = "clients.yaml";
|
||||||
|
|
||||||
|
/// File in storage containing providers list
|
||||||
|
pub const PROVIDERS_LIST_FILE: &str = "providers.yaml";
|
||||||
|
|
||||||
/// Default built-in credentials
|
/// Default built-in credentials
|
||||||
pub const DEFAULT_ADMIN_USERNAME: &str = "admin";
|
pub const DEFAULT_ADMIN_USERNAME: &str = "admin";
|
||||||
pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
|
pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
|
||||||
@@ -68,3 +71,15 @@ pub const OPEN_ID_REFRESH_TOKEN_TIMEOUT: u64 = 360000;
|
|||||||
/// Webauthn constants
|
/// Webauthn constants
|
||||||
pub const WEBAUTHN_REGISTER_CHALLENGE_EXPIRE: u64 = 3600;
|
pub const WEBAUTHN_REGISTER_CHALLENGE_EXPIRE: u64 = 3600;
|
||||||
pub const WEBAUTHN_LOGIN_CHALLENGE_EXPIRE: u64 = 3600;
|
pub const WEBAUTHN_LOGIN_CHALLENGE_EXPIRE: u64 = 3600;
|
||||||
|
|
||||||
|
/// OpenID providers login state constants
|
||||||
|
pub const OIDC_STATES_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
|
||||||
|
pub const MAX_OIDC_PROVIDERS_STATES: usize = 10;
|
||||||
|
pub const OIDC_PROVIDERS_STATE_LEN: usize = 40;
|
||||||
|
pub const OIDC_PROVIDERS_STATE_DURATION: u64 = 60 * 15;
|
||||||
|
|
||||||
|
/// OpenID providers configuration constants
|
||||||
|
pub const OIDC_PROVIDERS_LIFETIME: u64 = 3600;
|
||||||
|
|
||||||
|
/// OpenID provider callback URI
|
||||||
|
pub const OIDC_PROVIDER_CB_URI: &str = "/prov_cb";
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ use actix_web::{web, HttpResponse, Responder};
|
|||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
|
||||||
use crate::actors::users_actor;
|
use crate::actors::users_actor;
|
||||||
use crate::actors::users_actor::UsersActor;
|
use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersActor};
|
||||||
use crate::constants::TEMPORARY_PASSWORDS_LEN;
|
use crate::constants::TEMPORARY_PASSWORDS_LEN;
|
||||||
use crate::controllers::settings_controller::BaseSettingsPage;
|
use crate::controllers::settings_controller::BaseSettingsPage;
|
||||||
use crate::data::action_logger::{Action, ActionLogger};
|
use crate::data::action_logger::{Action, ActionLogger};
|
||||||
use crate::data::client::{Client, ClientID, ClientManager};
|
use crate::data::client::{Client, ClientID, ClientManager};
|
||||||
use crate::data::current_user::CurrentUser;
|
use crate::data::current_user::CurrentUser;
|
||||||
|
use crate::data::provider::{Provider, ProviderID, ProvidersManager};
|
||||||
use crate::data::user::{GeneralSettings, GrantedClients, User, UserID};
|
use crate::data::user::{GeneralSettings, GrantedClients, User, UserID};
|
||||||
use crate::utils::string_utils::rand_str;
|
use crate::utils::string_utils::rand_str;
|
||||||
|
|
||||||
@@ -22,6 +23,13 @@ struct ClientsListTemplate {
|
|||||||
clients: Vec<Client>,
|
clients: Vec<Client>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "settings/providers_list.html")]
|
||||||
|
struct ProvidersListTemplate {
|
||||||
|
_p: BaseSettingsPage,
|
||||||
|
providers: Vec<Provider>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/users_list.html")]
|
#[template(path = "settings/users_list.html")]
|
||||||
struct UsersListTemplate {
|
struct UsersListTemplate {
|
||||||
@@ -35,6 +43,7 @@ struct EditUserTemplate {
|
|||||||
_p: BaseSettingsPage,
|
_p: BaseSettingsPage,
|
||||||
u: User,
|
u: User,
|
||||||
clients: Vec<Client>,
|
clients: Vec<Client>,
|
||||||
|
providers: Vec<Provider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn clients_route(
|
pub async fn clients_route(
|
||||||
@@ -51,6 +60,20 @@ pub async fn clients_route(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn providers_route(
|
||||||
|
user: CurrentUser,
|
||||||
|
providers: web::Data<Arc<ProvidersManager>>,
|
||||||
|
) -> impl Responder {
|
||||||
|
HttpResponse::Ok().body(
|
||||||
|
ProvidersListTemplate {
|
||||||
|
_p: BaseSettingsPage::get("OpenID Providers list", &user, None, None),
|
||||||
|
providers: providers.cloned(),
|
||||||
|
}
|
||||||
|
.render()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
pub struct UpdateUserQuery {
|
pub struct UpdateUserQuery {
|
||||||
uid: UserID,
|
uid: UserID,
|
||||||
@@ -62,6 +85,8 @@ pub struct UpdateUserQuery {
|
|||||||
enabled: Option<String>,
|
enabled: Option<String>,
|
||||||
two_factor_exemption_after_successful_login: Option<String>,
|
two_factor_exemption_after_successful_login: Option<String>,
|
||||||
admin: Option<String>,
|
admin: Option<String>,
|
||||||
|
allow_local_login: Option<String>,
|
||||||
|
authorized_sources: String,
|
||||||
grant_type: String,
|
grant_type: String,
|
||||||
granted_clients: String,
|
granted_clients: String,
|
||||||
two_factor: String,
|
two_factor: String,
|
||||||
@@ -136,6 +161,29 @@ pub async fn users_route(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the list of authorized authentication sources
|
||||||
|
let auth_sources = AuthorizedAuthenticationSources {
|
||||||
|
local: update.0.allow_local_login.is_some(),
|
||||||
|
upstream: match update.0.authorized_sources.as_str() {
|
||||||
|
"" => vec![],
|
||||||
|
s => s.split(',').map(|s| ProviderID(s.to_string())).collect(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if edited_user.authorized_authentication_sources() != auth_sources {
|
||||||
|
logger.log(Action::AdminSetAuthorizedAuthenticationSources(
|
||||||
|
&edited_user,
|
||||||
|
&auth_sources,
|
||||||
|
));
|
||||||
|
users
|
||||||
|
.send(users_actor::SetAuthorizedAuthenticationSources(
|
||||||
|
edited_user.uid.clone(),
|
||||||
|
auth_sources,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// Update list of granted clients
|
// Update list of granted clients
|
||||||
let granted_clients = match update.0.grant_type.as_str() {
|
let granted_clients = match update.0.grant_type.as_str() {
|
||||||
"all_clients" => GrantedClients::AllClients,
|
"all_clients" => GrantedClients::AllClients,
|
||||||
@@ -240,6 +288,7 @@ pub async fn users_route(
|
|||||||
pub async fn create_user(
|
pub async fn create_user(
|
||||||
admin: CurrentUser,
|
admin: CurrentUser,
|
||||||
clients: web::Data<Arc<ClientManager>>,
|
clients: web::Data<Arc<ClientManager>>,
|
||||||
|
providers: web::Data<Arc<ProvidersManager>>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
let user = User {
|
let user = User {
|
||||||
authorized_clients: Some(
|
authorized_clients: Some(
|
||||||
@@ -257,6 +306,7 @@ pub async fn create_user(
|
|||||||
_p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
|
_p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
|
||||||
u: user,
|
u: user,
|
||||||
clients: clients.cloned(),
|
clients: clients.cloned(),
|
||||||
|
providers: providers.cloned(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
@@ -271,6 +321,7 @@ pub struct EditUserQuery {
|
|||||||
pub async fn edit_user(
|
pub async fn edit_user(
|
||||||
admin: CurrentUser,
|
admin: CurrentUser,
|
||||||
clients: web::Data<Arc<ClientManager>>,
|
clients: web::Data<Arc<ClientManager>>,
|
||||||
|
providers: web::Data<Arc<ProvidersManager>>,
|
||||||
users: web::Data<Addr<UsersActor>>,
|
users: web::Data<Addr<UsersActor>>,
|
||||||
query: web::Query<EditUserQuery>,
|
query: web::Query<EditUserQuery>,
|
||||||
) -> impl Responder {
|
) -> impl Responder {
|
||||||
@@ -293,6 +344,7 @@ pub async fn edit_user(
|
|||||||
),
|
),
|
||||||
u: edited_account.unwrap_or_default(),
|
u: edited_account.unwrap_or_default(),
|
||||||
clients: clients.cloned(),
|
clients: clients.cloned(),
|
||||||
|
providers: providers.cloned(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use actix::Addr;
|
|||||||
use actix_identity::Identity;
|
use actix_identity::Identity;
|
||||||
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::actors::bruteforce_actor::BruteForceActor;
|
use crate::actors::bruteforce_actor::BruteForceActor;
|
||||||
use crate::actors::users_actor::{LoginResult, UsersActor};
|
use crate::actors::users_actor::{LoginResult, UsersActor};
|
||||||
@@ -12,6 +13,7 @@ use crate::controllers::base_controller::{
|
|||||||
};
|
};
|
||||||
use crate::data::action_logger::{Action, ActionLogger};
|
use crate::data::action_logger::{Action, ActionLogger};
|
||||||
use crate::data::login_redirect::LoginRedirect;
|
use crate::data::login_redirect::LoginRedirect;
|
||||||
|
use crate::data::provider::{Provider, ProvidersManager};
|
||||||
use crate::data::remote_ip::RemoteIP;
|
use crate::data::remote_ip::RemoteIP;
|
||||||
use crate::data::session_identity::{SessionIdentity, SessionStatus};
|
use crate::data::session_identity::{SessionIdentity, SessionStatus};
|
||||||
use crate::data::user::User;
|
use crate::data::user::User;
|
||||||
@@ -30,6 +32,7 @@ struct BaseLoginPage<'a> {
|
|||||||
struct LoginTemplate<'a> {
|
struct LoginTemplate<'a> {
|
||||||
_p: BaseLoginPage<'a>,
|
_p: BaseLoginPage<'a>,
|
||||||
login: String,
|
login: String,
|
||||||
|
providers: Vec<Provider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
@@ -77,6 +80,7 @@ pub struct LoginRequestQuery {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn login_route(
|
pub async fn login_route(
|
||||||
remote_ip: RemoteIP,
|
remote_ip: RemoteIP,
|
||||||
|
providers: web::Data<Arc<ProvidersManager>>,
|
||||||
users: web::Data<Addr<UsersActor>>,
|
users: web::Data<Addr<UsersActor>>,
|
||||||
bruteforce: web::Data<Addr<BruteForceActor>>,
|
bruteforce: web::Data<Addr<BruteForceActor>>,
|
||||||
query: web::Query<LoginRequestQuery>,
|
query: web::Query<LoginRequestQuery>,
|
||||||
@@ -121,7 +125,7 @@ pub async fn login_route(
|
|||||||
query.redirect.get_encoded()
|
query.redirect.get_encoded()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Check if the user has to valide a second factor
|
// Check if the user has to validate a second factor
|
||||||
else if SessionIdentity(id.as_ref()).need_2fa_auth() {
|
else if SessionIdentity(id.as_ref()).need_2fa_auth() {
|
||||||
return redirect_user(&format!(
|
return redirect_user(&format!(
|
||||||
"/2fa_auth?redirect={}",
|
"/2fa_auth?redirect={}",
|
||||||
@@ -132,7 +136,7 @@ pub async fn login_route(
|
|||||||
else if let Some(req) = &req {
|
else if let Some(req) = &req {
|
||||||
login = req.login.clone();
|
login = req.login.clone();
|
||||||
let response: LoginResult = users
|
let response: LoginResult = users
|
||||||
.send(users_actor::LoginRequest {
|
.send(users_actor::LocalLoginRequest {
|
||||||
login: login.clone(),
|
login: login.clone(),
|
||||||
password: req.password.clone(),
|
password: req.password.clone(),
|
||||||
})
|
})
|
||||||
@@ -163,6 +167,12 @@ pub async fn login_route(
|
|||||||
danger = Some("Your account is disabled!".to_string());
|
danger = Some("Your account is disabled!".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoginResult::LocalAuthForbidden => {
|
||||||
|
log::warn!("Failed login for username {} : attempted to use local auth, but it is forbidden", &login);
|
||||||
|
logger.log(Action::TryLocalLoginFromUnauthorizedAccount(&login));
|
||||||
|
danger = Some("You cannot login from local auth with your account!".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
LoginResult::Error => {
|
LoginResult::Error => {
|
||||||
danger = Some("An unkown error occured while trying to sign you in!".to_string());
|
danger = Some("An unkown error occured while trying to sign you in!".to_string());
|
||||||
}
|
}
|
||||||
@@ -197,6 +207,7 @@ pub async fn login_route(
|
|||||||
redirect_uri: &query.redirect,
|
redirect_uri: &query.redirect,
|
||||||
},
|
},
|
||||||
login,
|
login,
|
||||||
|
providers: providers.cloned(),
|
||||||
}
|
}
|
||||||
.render()
|
.render()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub mod base_controller;
|
|||||||
pub mod login_api;
|
pub mod login_api;
|
||||||
pub mod login_controller;
|
pub mod login_controller;
|
||||||
pub mod openid_controller;
|
pub mod openid_controller;
|
||||||
|
pub mod providers_controller;
|
||||||
pub mod settings_controller;
|
pub mod settings_controller;
|
||||||
pub mod two_factor_api;
|
pub mod two_factor_api;
|
||||||
pub mod two_factors_controller;
|
pub mod two_factors_controller;
|
||||||
|
|||||||
73
src/controllers/providers_controller.rs
Normal file
73
src/controllers/providers_controller.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use actix::Addr;
|
||||||
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
|
||||||
|
use crate::actors::providers_states_actor;
|
||||||
|
use crate::actors::providers_states_actor::{ProviderLoginState, ProvidersStatesActor};
|
||||||
|
use crate::controllers::base_controller::{build_fatal_error_page, redirect_user};
|
||||||
|
use crate::data::action_logger::{Action, ActionLogger};
|
||||||
|
use crate::data::login_redirect::LoginRedirect;
|
||||||
|
use crate::data::provider::{ProviderID, ProvidersManager};
|
||||||
|
use crate::data::provider_configuration::ProviderConfigurationHelper;
|
||||||
|
use crate::data::remote_ip::RemoteIP;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct StartLoginQuery {
|
||||||
|
#[serde(default)]
|
||||||
|
redirect: LoginRedirect,
|
||||||
|
id: ProviderID,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start user authentication using a provider
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub async fn start_login(
|
||||||
|
remote_ip: RemoteIP,
|
||||||
|
providers: web::Data<Arc<ProvidersManager>>,
|
||||||
|
states: web::Data<Addr<ProvidersStatesActor>>,
|
||||||
|
query: web::Query<StartLoginQuery>,
|
||||||
|
logger: ActionLogger,
|
||||||
|
) -> impl Responder {
|
||||||
|
// Get provider information
|
||||||
|
let provider = match providers.find_by_id(&query.id) {
|
||||||
|
None => {
|
||||||
|
return HttpResponse::NotFound()
|
||||||
|
.body(build_fatal_error_page("Login provider not found!"));
|
||||||
|
}
|
||||||
|
Some(p) => p,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate & save state
|
||||||
|
let state = ProviderLoginState::new(&provider.id, query.redirect.clone());
|
||||||
|
states
|
||||||
|
.send(providers_states_actor::RecordState {
|
||||||
|
ip: remote_ip.0,
|
||||||
|
state: state.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
logger.log(Action::StartLoginAttemptWithOpenIDProvider {
|
||||||
|
provider_id: &provider.id,
|
||||||
|
state: &state.state_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get provider configuration
|
||||||
|
let config = match ProviderConfigurationHelper::get_configuration(&provider).await {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to load provider configuration! {}", e);
|
||||||
|
return HttpResponse::InternalServerError().body(build_fatal_error_page(
|
||||||
|
"Failed to load provider configuration!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
log::debug!("Provider configuration: {:?}", config);
|
||||||
|
|
||||||
|
let url = config.auth_url(&provider, &state);
|
||||||
|
log::debug!("Redirect user on {url} for authentication",);
|
||||||
|
|
||||||
|
// Redirect user
|
||||||
|
redirect_user(&url)
|
||||||
|
}
|
||||||
@@ -8,8 +8,9 @@ use actix_web::dev::Payload;
|
|||||||
use actix_web::{web, Error, FromRequest, HttpRequest};
|
use actix_web::{web, Error, FromRequest, HttpRequest};
|
||||||
|
|
||||||
use crate::actors::users_actor;
|
use crate::actors::users_actor;
|
||||||
use crate::actors::users_actor::UsersActor;
|
use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersActor};
|
||||||
use crate::data::client::Client;
|
use crate::data::client::Client;
|
||||||
|
use crate::data::provider::ProviderID;
|
||||||
use crate::data::remote_ip::RemoteIP;
|
use crate::data::remote_ip::RemoteIP;
|
||||||
use crate::data::session_identity::SessionIdentity;
|
use crate::data::session_identity::SessionIdentity;
|
||||||
use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID};
|
use crate::data::user::{FactorID, GrantedClients, TwoFactor, User, UserID};
|
||||||
@@ -20,22 +21,38 @@ pub enum Action<'a> {
|
|||||||
AdminDeleteUser(&'a User),
|
AdminDeleteUser(&'a User),
|
||||||
AdminResetUserPassword(&'a User),
|
AdminResetUserPassword(&'a User),
|
||||||
AdminRemoveUserFactor(&'a User, &'a TwoFactor),
|
AdminRemoveUserFactor(&'a User, &'a TwoFactor),
|
||||||
|
AdminSetAuthorizedAuthenticationSources(&'a User, &'a AuthorizedAuthenticationSources),
|
||||||
AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
|
AdminSetNewGrantedClientsList(&'a User, &'a GrantedClients),
|
||||||
AdminClear2FAHistory(&'a User),
|
AdminClear2FAHistory(&'a User),
|
||||||
LoginWebauthnAttempt { success: bool, user_id: UserID },
|
LoginWebauthnAttempt {
|
||||||
|
success: bool,
|
||||||
|
user_id: UserID,
|
||||||
|
},
|
||||||
|
StartLoginAttemptWithOpenIDProvider {
|
||||||
|
provider_id: &'a ProviderID,
|
||||||
|
state: &'a str,
|
||||||
|
},
|
||||||
Signout,
|
Signout,
|
||||||
UserNeed2FAOnLogin(&'a User),
|
UserNeed2FAOnLogin(&'a User),
|
||||||
UserSuccessfullyAuthenticated(&'a User),
|
UserSuccessfullyAuthenticated(&'a User),
|
||||||
UserNeedNewPasswordOnLogin(&'a User),
|
UserNeedNewPasswordOnLogin(&'a User),
|
||||||
TryLoginWithDisabledAccount(&'a str),
|
TryLoginWithDisabledAccount(&'a str),
|
||||||
|
TryLocalLoginFromUnauthorizedAccount(&'a str),
|
||||||
FailedLoginWithBadCredentials(&'a str),
|
FailedLoginWithBadCredentials(&'a str),
|
||||||
UserChangedPasswordOnLogin(&'a UserID),
|
UserChangedPasswordOnLogin(&'a UserID),
|
||||||
OTPLoginAttempt { user: &'a User, success: bool },
|
OTPLoginAttempt {
|
||||||
NewOpenIDSession { client: &'a Client },
|
user: &'a User,
|
||||||
|
success: bool,
|
||||||
|
},
|
||||||
|
NewOpenIDSession {
|
||||||
|
client: &'a Client,
|
||||||
|
},
|
||||||
ChangedHisPassword,
|
ChangedHisPassword,
|
||||||
ClearedHisLoginHistory,
|
ClearedHisLoginHistory,
|
||||||
AddNewFactor(&'a TwoFactor),
|
AddNewFactor(&'a TwoFactor),
|
||||||
Removed2FAFactor { factor_id: &'a FactorID },
|
Removed2FAFactor {
|
||||||
|
factor_id: &'a FactorID,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Action<'a> {
|
impl<'a> Action<'a> {
|
||||||
@@ -64,6 +81,11 @@ impl<'a> Action<'a> {
|
|||||||
Action::AdminClear2FAHistory(user) => {
|
Action::AdminClear2FAHistory(user) => {
|
||||||
format!("cleared 2FA history of {}", user.quick_identity())
|
format!("cleared 2FA history of {}", user.quick_identity())
|
||||||
}
|
}
|
||||||
|
Action::AdminSetAuthorizedAuthenticationSources(user, sources) => format!(
|
||||||
|
"update authorized authentication sources ({:?}) for user ({})",
|
||||||
|
sources,
|
||||||
|
user.quick_identity()
|
||||||
|
),
|
||||||
Action::AdminSetNewGrantedClientsList(user, clients) => format!(
|
Action::AdminSetNewGrantedClientsList(user, clients) => format!(
|
||||||
"set new granted clients list ({:?}) for user ({})",
|
"set new granted clients list ({:?}) for user ({})",
|
||||||
clients,
|
clients,
|
||||||
@@ -73,6 +95,9 @@ impl<'a> Action<'a> {
|
|||||||
true => format!("successfully performed webauthn attempt for user {user_id:?}"),
|
true => format!("successfully performed webauthn attempt for user {user_id:?}"),
|
||||||
false => format!("performed FAILED webauthn attempt for user {user_id:?}"),
|
false => format!("performed FAILED webauthn attempt for user {user_id:?}"),
|
||||||
},
|
},
|
||||||
|
Action::StartLoginAttemptWithOpenIDProvider { provider_id, state } => format!(
|
||||||
|
"started new authentication attempt through an OpenID provider (prov={} / state={state})", provider_id.0
|
||||||
|
),
|
||||||
Action::Signout => "signed out".to_string(),
|
Action::Signout => "signed out".to_string(),
|
||||||
Action::UserNeed2FAOnLogin(user) => {
|
Action::UserNeed2FAOnLogin(user) => {
|
||||||
format!(
|
format!(
|
||||||
@@ -90,6 +115,9 @@ impl<'a> Action<'a> {
|
|||||||
Action::TryLoginWithDisabledAccount(login) => {
|
Action::TryLoginWithDisabledAccount(login) => {
|
||||||
format!("successfully authenticated as {login}, but this is a DISABLED ACCOUNT")
|
format!("successfully authenticated as {login}, but this is a DISABLED ACCOUNT")
|
||||||
}
|
}
|
||||||
|
Action::TryLocalLoginFromUnauthorizedAccount(login) => {
|
||||||
|
format!("successfully locally authenticated as {login}, but this is a FORBIDDEN for this account!")
|
||||||
|
}
|
||||||
Action::FailedLoginWithBadCredentials(login) => {
|
Action::FailedLoginWithBadCredentials(login) => {
|
||||||
format!("attempted to authenticate as {login} but with a WRONG PASSWORD")
|
format!("attempted to authenticate as {login} but with a WRONG PASSWORD")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::constants::{APP_NAME, CLIENTS_LIST_FILE, USERS_LIST_FILE};
|
use crate::constants::{APP_NAME, CLIENTS_LIST_FILE, PROVIDERS_LIST_FILE, USERS_LIST_FILE};
|
||||||
|
|
||||||
/// Basic OIDC provider
|
/// Basic OIDC provider
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
@@ -72,6 +72,10 @@ impl AppConfig {
|
|||||||
self.storage_path().join(CLIENTS_LIST_FILE)
|
self.storage_path().join(CLIENTS_LIST_FILE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn providers_file(&self) -> PathBuf {
|
||||||
|
self.storage_path().join(PROVIDERS_LIST_FILE)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn full_url(&self, uri: &str) -> String {
|
pub fn full_url(&self, uri: &str) -> String {
|
||||||
if uri.starts_with('/') {
|
if uri.starts_with('/') {
|
||||||
format!("{}{}", self.website_origin, uri)
|
format!("{}{}", self.website_origin, uri)
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ use base64::Engine as _;
|
|||||||
use crate::utils::err::Res;
|
use crate::utils::err::Res;
|
||||||
use crate::utils::string_utils::rand_str;
|
use crate::utils::string_utils::rand_str;
|
||||||
|
|
||||||
|
const JWK_USE_SIGN: &str = "sig";
|
||||||
|
|
||||||
/// Json Web Key <https://datatracker.ietf.org/doc/html/rfc7517>
|
/// Json Web Key <https://datatracker.ietf.org/doc/html/rfc7517>
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct JsonWebKey {
|
pub struct JsonWebKey {
|
||||||
#[serde(rename = "alg")]
|
#[serde(rename = "alg")]
|
||||||
algorithm: String,
|
algorithm: String,
|
||||||
@@ -24,6 +26,8 @@ pub struct JsonWebKey {
|
|||||||
modulus: String,
|
modulus: String,
|
||||||
#[serde(rename = "e")]
|
#[serde(rename = "e")]
|
||||||
public_exponent: String,
|
public_exponent: String,
|
||||||
|
#[serde(rename = "use", skip_serializing_if = "Option::is_none")]
|
||||||
|
usage: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -44,6 +48,7 @@ impl JWTSigner {
|
|||||||
key_id: self.0.key_id().as_ref().unwrap().to_string(),
|
key_id: self.0.key_id().as_ref().unwrap().to_string(),
|
||||||
public_exponent: BASE64_URL_URL_SAFE.encode(components.e),
|
public_exponent: BASE64_URL_URL_SAFE.encode(components.e),
|
||||||
modulus: BASE64_URL_SAFE_NO_PAD.encode(components.n),
|
modulus: BASE64_URL_SAFE_NO_PAD.encode(components.n),
|
||||||
|
usage: Some(JWK_USE_SIGN.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ pub mod jwt_signer;
|
|||||||
pub mod login_redirect;
|
pub mod login_redirect;
|
||||||
pub mod open_id_user_info;
|
pub mod open_id_user_info;
|
||||||
pub mod openid_config;
|
pub mod openid_config;
|
||||||
|
pub mod provider;
|
||||||
|
pub mod provider_configuration;
|
||||||
pub mod remote_ip;
|
pub mod remote_ip;
|
||||||
pub mod session_identity;
|
pub mod session_identity;
|
||||||
pub mod totp_key;
|
pub mod totp_key;
|
||||||
|
|||||||
89
src/data/provider.rs
Normal file
89
src/data/provider.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use crate::data::entity_manager::EntityManager;
|
||||||
|
use crate::data::login_redirect::LoginRedirect;
|
||||||
|
use crate::utils::string_utils::apply_env_vars;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||||
|
pub struct ProviderID(pub String);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Provider {
|
||||||
|
/// The ID of the provider
|
||||||
|
pub id: ProviderID,
|
||||||
|
|
||||||
|
/// The human-readable name of the client
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// A logo presented to the users of the provider
|
||||||
|
pub logo: String,
|
||||||
|
|
||||||
|
/// The registration id of BasicOIDC on the provider
|
||||||
|
pub client_id: String,
|
||||||
|
|
||||||
|
/// The registration secret of BasicOIDC on the provider
|
||||||
|
pub client_secret: String,
|
||||||
|
|
||||||
|
/// Specify the URL of the OpenID configuration URL
|
||||||
|
///
|
||||||
|
/// (.well-known/openid-configuration endpoint)
|
||||||
|
pub configuration_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Provider {
|
||||||
|
/// Get URL-encoded provider id
|
||||||
|
pub fn id_encoded(&self) -> String {
|
||||||
|
urlencoding::encode(&self.id.0).to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the URL where the logo can be located
|
||||||
|
pub fn logo_url(&self) -> &str {
|
||||||
|
match self.logo.as_str() {
|
||||||
|
"gitea" => "/assets/img/brands/gitea.svg",
|
||||||
|
"gitlab" => "/assets/img/brands/gitlab.svg",
|
||||||
|
"github" => "/assets/img/brands/github.svg",
|
||||||
|
"microsoft" => "/assets/img/brands/microsoft.svg",
|
||||||
|
"google" => "/assets/img/brands/google.svg",
|
||||||
|
s => s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the URL to use to login with the provider
|
||||||
|
pub fn login_url(&self, redirect_url: &LoginRedirect) -> String {
|
||||||
|
format!(
|
||||||
|
"/login_with_prov?id={}&redirect_url={}",
|
||||||
|
self.id_encoded(),
|
||||||
|
redirect_url.get_encoded()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Provider {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id.eq(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Provider {}
|
||||||
|
|
||||||
|
pub type ProvidersManager = EntityManager<Provider>;
|
||||||
|
|
||||||
|
impl EntityManager<Provider> {
|
||||||
|
pub fn find_by_id(&self, u: &ProviderID) -> Option<Provider> {
|
||||||
|
for entry in self.iter() {
|
||||||
|
if entry.id.eq(u) {
|
||||||
|
return Some(entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_environment_variables(&mut self) {
|
||||||
|
for c in self.iter_mut() {
|
||||||
|
c.id = ProviderID(apply_env_vars(&c.id.0));
|
||||||
|
c.name = apply_env_vars(&c.name);
|
||||||
|
c.logo = apply_env_vars(&c.logo);
|
||||||
|
c.client_id = apply_env_vars(&c.client_id);
|
||||||
|
c.client_secret = apply_env_vars(&c.client_secret);
|
||||||
|
c.configuration_url = apply_env_vars(&c.configuration_url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/data/provider_configuration.rs
Normal file
89
src/data/provider_configuration.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use crate::actors::providers_states_actor::ProviderLoginState;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::constants::{OIDC_PROVIDERS_LIFETIME, OIDC_PROVIDER_CB_URI};
|
||||||
|
use crate::data::app_config::AppConfig;
|
||||||
|
use crate::data::jwt_signer::JsonWebKey;
|
||||||
|
use crate::data::provider::Provider;
|
||||||
|
use crate::utils::err::Res;
|
||||||
|
use crate::utils::time::time;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Deserialize)]
|
||||||
|
pub struct ProviderDiscovery {
|
||||||
|
pub issuer: String,
|
||||||
|
pub authorization_endpoint: String,
|
||||||
|
pub token_endpoint: String,
|
||||||
|
pub userinfo_endpoint: Option<String>,
|
||||||
|
pub jwks_uri: String,
|
||||||
|
pub claims_supported: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct ProviderJWKs {
|
||||||
|
pub keys: Vec<JsonWebKey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provider configuration
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProviderConfiguration {
|
||||||
|
pub discovery: ProviderDiscovery,
|
||||||
|
pub keys: ProviderJWKs,
|
||||||
|
pub expire: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProviderConfiguration {
|
||||||
|
/// Get the URL where a user should be redirected to authenticate
|
||||||
|
pub fn auth_url(&self, provider: &Provider, state: &ProviderLoginState) -> String {
|
||||||
|
let authorization_url = &self.discovery.authorization_endpoint;
|
||||||
|
let client_id = urlencoding::encode(&provider.client_id).to_string();
|
||||||
|
let state = urlencoding::encode(&state.state_id).to_string();
|
||||||
|
let callback_url = AppConfig::get().full_url(OIDC_PROVIDER_CB_URI);
|
||||||
|
|
||||||
|
format!("{authorization_url}?response_type=code&scope=openid%20profile%20email&client_id={client_id}&state={state}&redirect_uri={callback_url}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static THREAD_CACHE: RefCell<HashMap<String, ProviderConfiguration>> = RefCell::new(Default::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProviderConfigurationHelper {}
|
||||||
|
|
||||||
|
impl ProviderConfigurationHelper {
|
||||||
|
/// Get or refresh the configuration for a provider
|
||||||
|
pub async fn get_configuration(provider: &Provider) -> Res<ProviderConfiguration> {
|
||||||
|
let config = THREAD_CACHE.with(|i| i.borrow().get(&provider.configuration_url).cloned());
|
||||||
|
|
||||||
|
// Refresh config cache if needed
|
||||||
|
if config.is_none() || config.as_ref().unwrap().expire < time() {
|
||||||
|
let conf = Self::fetch_configuration(provider).await?;
|
||||||
|
|
||||||
|
THREAD_CACHE.with(|i| {
|
||||||
|
i.borrow_mut()
|
||||||
|
.insert(provider.configuration_url.clone(), conf.clone())
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can return immediately previously extracted value
|
||||||
|
Ok(config.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get fresh configuration from provider
|
||||||
|
async fn fetch_configuration(provider: &Provider) -> Res<ProviderConfiguration> {
|
||||||
|
let discovery: ProviderDiscovery = reqwest::get(&provider.configuration_url)
|
||||||
|
.await?
|
||||||
|
.json()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let keys: ProviderJWKs = reqwest::get(&discovery.jwks_uri).await?.json().await?;
|
||||||
|
|
||||||
|
Ok(ProviderConfiguration {
|
||||||
|
discovery,
|
||||||
|
keys,
|
||||||
|
expire: time() + OIDC_PROVIDERS_LIFETIME,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use crate::actors::users_actor::AuthorizedAuthenticationSources;
|
||||||
use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN;
|
use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN;
|
||||||
use crate::data::client::{Client, ClientID};
|
use crate::data::client::{Client, ClientID};
|
||||||
use crate::data::login_redirect::LoginRedirect;
|
use crate::data::login_redirect::LoginRedirect;
|
||||||
|
use crate::data::provider::{Provider, ProviderID};
|
||||||
use crate::data::totp_key::TotpKey;
|
use crate::data::totp_key::TotpKey;
|
||||||
use crate::data::webauthn_manager::WebauthnPubKey;
|
use crate::data::webauthn_manager::WebauthnPubKey;
|
||||||
use crate::utils::time::{fmt_time, time};
|
use crate::utils::time::{fmt_time, time};
|
||||||
@@ -114,6 +116,10 @@ impl Successful2FALogin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub uid: UserID,
|
pub uid: UserID,
|
||||||
@@ -142,6 +148,14 @@ pub struct User {
|
|||||||
/// None = all services
|
/// None = all services
|
||||||
/// Some([]) = no service
|
/// Some([]) = no service
|
||||||
pub authorized_clients: Option<Vec<ClientID>>,
|
pub authorized_clients: Option<Vec<ClientID>>,
|
||||||
|
|
||||||
|
/// Authorize connection through local login
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub allow_local_login: bool,
|
||||||
|
|
||||||
|
/// Allowed third party providers
|
||||||
|
#[serde(default)]
|
||||||
|
pub allow_login_from_providers: Vec<ProviderID>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
@@ -162,6 +176,19 @@ impl User {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the list of sources from which a user can authenticate from
|
||||||
|
pub fn authorized_authentication_sources(&self) -> AuthorizedAuthenticationSources {
|
||||||
|
AuthorizedAuthenticationSources {
|
||||||
|
local: self.allow_local_login,
|
||||||
|
upstream: self.allow_login_from_providers.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a user can authenticate using a givne provider or not
|
||||||
|
pub fn can_login_from_provider(&self, provider: &Provider) -> bool {
|
||||||
|
self.allow_login_from_providers.contains(&provider.id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn granted_clients(&self) -> GrantedClients {
|
pub fn granted_clients(&self) -> GrantedClients {
|
||||||
match self.authorized_clients.as_deref() {
|
match self.authorized_clients.as_deref() {
|
||||||
None => GrantedClients::AllClients,
|
None => GrantedClients::AllClients,
|
||||||
@@ -296,6 +323,8 @@ impl Default for User {
|
|||||||
two_factor_exemption_after_successful_login: false,
|
two_factor_exemption_after_successful_login: false,
|
||||||
last_successful_2fa: Default::default(),
|
last_successful_2fa: Default::default(),
|
||||||
authorized_clients: Some(Vec::new()),
|
authorized_clients: Some(Vec::new()),
|
||||||
|
allow_local_login: true,
|
||||||
|
allow_login_from_providers: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::net::IpAddr;
|
use std::net::IpAddr;
|
||||||
|
|
||||||
use crate::actors::users_actor::UsersSyncBackend;
|
use crate::actors::users_actor::{AuthorizedAuthenticationSources, UsersSyncBackend};
|
||||||
use crate::data::entity_manager::EntityManager;
|
use crate::data::entity_manager::EntityManager;
|
||||||
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
|
use crate::data::user::{FactorID, GeneralSettings, GrantedClients, TwoFactor, User, UserID};
|
||||||
use crate::utils::err::{new_error, Res};
|
use crate::utils::err::{new_error, Res};
|
||||||
@@ -143,6 +143,18 @@ impl UsersSyncBackend for EntityManager<User> {
|
|||||||
self.remove(&user)
|
self.remove(&user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_authorized_authentication_sources(
|
||||||
|
&mut self,
|
||||||
|
id: &UserID,
|
||||||
|
sources: AuthorizedAuthenticationSources,
|
||||||
|
) -> Res {
|
||||||
|
self.update_user(id, |mut user| {
|
||||||
|
user.allow_local_login = sources.local;
|
||||||
|
user.allow_login_from_providers = sources.upstream;
|
||||||
|
user
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res {
|
fn set_granted_2fa_clients(&mut self, id: &UserID, clients: GrantedClients) -> Res {
|
||||||
self.update_user(id, |mut user| {
|
self.update_user(id, |mut user| {
|
||||||
user.authorized_clients = clients.to_user();
|
user.authorized_clients = clients.to_user();
|
||||||
|
|||||||
23
src/main.rs
23
src/main.rs
@@ -12,6 +12,7 @@ use actix_web::{get, middleware, web, App, HttpResponse, HttpServer};
|
|||||||
|
|
||||||
use basic_oidc::actors::bruteforce_actor::BruteForceActor;
|
use basic_oidc::actors::bruteforce_actor::BruteForceActor;
|
||||||
use basic_oidc::actors::openid_sessions_actor::OpenIDSessionsActor;
|
use basic_oidc::actors::openid_sessions_actor::OpenIDSessionsActor;
|
||||||
|
use basic_oidc::actors::providers_states_actor::ProvidersStatesActor;
|
||||||
use basic_oidc::actors::users_actor::{UsersActor, UsersSyncBackend};
|
use basic_oidc::actors::users_actor::{UsersActor, UsersSyncBackend};
|
||||||
use basic_oidc::constants::*;
|
use basic_oidc::constants::*;
|
||||||
use basic_oidc::controllers::assets_controller::assets_route;
|
use basic_oidc::controllers::assets_controller::assets_route;
|
||||||
@@ -20,6 +21,7 @@ use basic_oidc::data::app_config::AppConfig;
|
|||||||
use basic_oidc::data::client::ClientManager;
|
use basic_oidc::data::client::ClientManager;
|
||||||
use basic_oidc::data::entity_manager::EntityManager;
|
use basic_oidc::data::entity_manager::EntityManager;
|
||||||
use basic_oidc::data::jwt_signer::JWTSigner;
|
use basic_oidc::data::jwt_signer::JWTSigner;
|
||||||
|
use basic_oidc::data::provider::ProvidersManager;
|
||||||
use basic_oidc::data::user::User;
|
use basic_oidc::data::user::User;
|
||||||
use basic_oidc::data::webauthn_manager::WebAuthManager;
|
use basic_oidc::data::webauthn_manager::WebAuthManager;
|
||||||
use basic_oidc::middlewares::auth_middleware::AuthMiddleware;
|
use basic_oidc::middlewares::auth_middleware::AuthMiddleware;
|
||||||
@@ -68,6 +70,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
let users_actor = UsersActor::new(users).start();
|
let users_actor = UsersActor::new(users).start();
|
||||||
let bruteforce_actor = BruteForceActor::default().start();
|
let bruteforce_actor = BruteForceActor::default().start();
|
||||||
|
let providers_states_actor = ProvidersStatesActor::default().start();
|
||||||
let openid_sessions_actor = OpenIDSessionsActor::default().start();
|
let openid_sessions_actor = OpenIDSessionsActor::default().start();
|
||||||
let jwt_signer = JWTSigner::gen_from_memory().expect("Failed to generate JWKS key");
|
let jwt_signer = JWTSigner::gen_from_memory().expect("Failed to generate JWKS key");
|
||||||
let webauthn_manager = Arc::new(WebAuthManager::init(config));
|
let webauthn_manager = Arc::new(WebAuthManager::init(config));
|
||||||
@@ -77,6 +80,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
clients.apply_environment_variables();
|
clients.apply_environment_variables();
|
||||||
let clients = Arc::new(clients);
|
let clients = Arc::new(clients);
|
||||||
|
|
||||||
|
let mut providers = ProvidersManager::open_or_create(config.providers_file())
|
||||||
|
.expect("Failed to load providers list!");
|
||||||
|
providers.apply_environment_variables();
|
||||||
|
let providers = Arc::new(providers);
|
||||||
|
|
||||||
log::info!("Server will listen on {}", config.listen_address);
|
log::info!("Server will listen on {}", config.listen_address);
|
||||||
let listen_address = config.listen_address.to_string();
|
let listen_address = config.listen_address.to_string();
|
||||||
|
|
||||||
@@ -99,8 +107,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(users_actor.clone()))
|
.app_data(web::Data::new(users_actor.clone()))
|
||||||
.app_data(web::Data::new(bruteforce_actor.clone()))
|
.app_data(web::Data::new(bruteforce_actor.clone()))
|
||||||
|
.app_data(web::Data::new(providers_states_actor.clone()))
|
||||||
.app_data(web::Data::new(openid_sessions_actor.clone()))
|
.app_data(web::Data::new(openid_sessions_actor.clone()))
|
||||||
.app_data(web::Data::new(clients.clone()))
|
.app_data(web::Data::new(clients.clone()))
|
||||||
|
.app_data(web::Data::new(providers.clone()))
|
||||||
.app_data(web::Data::new(jwt_signer.clone()))
|
.app_data(web::Data::new(jwt_signer.clone()))
|
||||||
.app_data(web::Data::new(webauthn_manager.clone()))
|
.app_data(web::Data::new(webauthn_manager.clone()))
|
||||||
.wrap(
|
.wrap(
|
||||||
@@ -110,7 +120,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.wrap(AuthMiddleware {})
|
.wrap(AuthMiddleware {})
|
||||||
.wrap(identity_middleware)
|
.wrap(identity_middleware)
|
||||||
.wrap(session_mw)
|
.wrap(session_mw)
|
||||||
// main route
|
// Main route
|
||||||
.route(
|
.route(
|
||||||
"/",
|
"/",
|
||||||
web::get().to(|| async {
|
web::get().to(|| async {
|
||||||
@@ -120,7 +130,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.route("/robots.txt", web::get().to(assets_controller::robots_txt))
|
.route("/robots.txt", web::get().to(assets_controller::robots_txt))
|
||||||
// health route
|
// Health route
|
||||||
.service(health)
|
.service(health)
|
||||||
// Assets serving
|
// Assets serving
|
||||||
.route("/assets/{path:.*}", web::get().to(assets_route))
|
.route("/assets/{path:.*}", web::get().to(assets_route))
|
||||||
@@ -151,6 +161,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/login/api/auth_webauthn",
|
"/login/api/auth_webauthn",
|
||||||
web::post().to(login_api::auth_webauthn),
|
web::post().to(login_api::auth_webauthn),
|
||||||
)
|
)
|
||||||
|
// Providers controller
|
||||||
|
.route(
|
||||||
|
"/login_with_prov",
|
||||||
|
web::get().to(providers_controller::start_login),
|
||||||
|
)
|
||||||
// Settings routes
|
// Settings routes
|
||||||
.route(
|
.route(
|
||||||
"/settings",
|
"/settings",
|
||||||
@@ -207,6 +222,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/admin/clients",
|
"/admin/clients",
|
||||||
web::get().to(admin_controller::clients_route),
|
web::get().to(admin_controller::clients_route),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/admin/providers",
|
||||||
|
web::get().to(admin_controller::providers_route),
|
||||||
|
)
|
||||||
.route("/admin/users", web::get().to(admin_controller::users_route))
|
.route("/admin/users", web::get().to(admin_controller::users_route))
|
||||||
.route(
|
.route(
|
||||||
"/admin/users",
|
"/admin/users",
|
||||||
|
|||||||
@@ -30,8 +30,6 @@
|
|||||||
font-size: 3.5rem;
|
font-size: 3.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
@@ -45,7 +43,7 @@
|
|||||||
|
|
||||||
<main class="form-signin">
|
<main class="form-signin">
|
||||||
|
|
||||||
<h1 class="h3 mb-3 fw-normal">{{ _p.page_title }}</h1>
|
<h1 class="h3 mb-3 fw-normal" style="margin-bottom: 2rem !important;">{{ _p.page_title }}</h1>
|
||||||
|
|
||||||
{% if let Some(danger) = _p.danger %}
|
{% if let Some(danger) = _p.danger %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="alert alert-danger" role="alert">
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
{% extends "base_login_page.html" %}
|
{% extends "base_login_page.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
#providers {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-button {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.provider-button img {
|
||||||
|
margin-right: 1em;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
<form action="/login?redirect={{ _p.redirect_uri.get_encoded() }}" method="post">
|
<form action="/login?redirect={{ _p.redirect_uri.get_encoded() }}" method="post">
|
||||||
<div>
|
<div>
|
||||||
<div class="form-floating">
|
<div class="form-floating">
|
||||||
@@ -19,4 +37,18 @@
|
|||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<!-- Upstream providers -->
|
||||||
|
{% if !providers.is_empty() %}
|
||||||
|
<div id="providers">
|
||||||
|
{% for prov in providers %}
|
||||||
|
<a class="btn btn-secondary btn-lg provider-button" href="{{ prov.login_url(_p.redirect_uri) }}">
|
||||||
|
<img src="{{ prov.logo_url() }}" alt="Provider icon"/>
|
||||||
|
<div style="text-align: left;">
|
||||||
|
Login using {{ prov.name }} <br/>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -42,6 +42,11 @@
|
|||||||
Clients
|
Clients
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/providers" class="nav-link link-dark">
|
||||||
|
Providers
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/users" class="nav-link link-dark">
|
<a href="/admin/users" class="nav-link link-dark">
|
||||||
Users
|
Users
|
||||||
@@ -83,6 +88,7 @@
|
|||||||
if(el.href === location.href) el.classList.add("active");
|
if(el.href === location.href) el.classList.add("active");
|
||||||
else el.classList.remove("active")
|
else el.classList.remove("active")
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% if _p.ip_location_api.is_some() %}
|
{% if _p.ip_location_api.is_some() %}
|
||||||
<script>const IP_LOCATION_API = "{{ _p.ip_location_api.unwrap() }}"</script>
|
<script>const IP_LOCATION_API = "{{ _p.ip_location_api.unwrap() }}"</script>
|
||||||
|
|||||||
@@ -113,27 +113,60 @@
|
|||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{% for e in u.get_formatted_2fa_successful_logins() %}
|
{% for e in u.get_formatted_2fa_successful_logins() %}
|
||||||
{% if e.can_bypass_2fa %}<li style="font-weight: bold;">{{ e.ip }} - {{ e.fmt_time() }} - BYPASS 2FA</li>
|
{% if e.can_bypass_2fa %}
|
||||||
{% else %}<li>{{ e.ip }} - {{ e.fmt_time() }}</li>{% endif %}
|
<li style="font-weight: bold;">{{ e.ip }} - {{ e.fmt_time() }} - BYPASS 2FA</li>
|
||||||
|
{% else %}
|
||||||
|
<li>{{ e.ip }} - {{ e.fmt_time() }}</li>
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Authorized authentication sources -->
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<legend class="mt-4">Authorized authentication sources</legend>
|
||||||
|
|
||||||
|
<!-- Local login -->
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="allow_local_login" id="allow_local_login"
|
||||||
|
{% if u.allow_local_login %} checked="" {% endif %}>
|
||||||
|
<label class="form-check-label" for="allow_local_login">
|
||||||
|
Allow local login
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Upstream providers -->
|
||||||
|
<input type="hidden" name="authorized_sources" id="authorized_sources"/>
|
||||||
|
{% for prov in providers %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input authorized_provider" type="checkbox" name="prov-{{ prov.id.0 }}"
|
||||||
|
id="prov-{{ prov.id.0 }}"
|
||||||
|
data-id="{{ prov.id.0 }}"
|
||||||
|
{% if u.can_login_from_provider(prov) %} checked="" {% endif %}>
|
||||||
|
<label class="form-check-label" for="prov-{{ prov.id.0 }}">
|
||||||
|
Allow login from {{ prov.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<!-- Granted clients -->
|
<!-- Granted clients -->
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<legend class="mt-4">Granted clients</legend>
|
<legend class="mt-4">Granted clients</legend>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label class="form-check-label">
|
<label class="form-check-label">
|
||||||
<input type="radio" class="form-check-input" name="grant_type"
|
<input type="radio" class="form-check-input" name="grant_type"
|
||||||
value="all_clients" {% if u.granted_clients() == GrantedClients::AllClients %} checked="" {% endif %}>
|
value="all_clients" {% if u.granted_clients()== GrantedClients::AllClients %} checked="" {% endif
|
||||||
|
%}>
|
||||||
Grant all clients
|
Grant all clients
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label class="form-check-label">
|
<label class="form-check-label">
|
||||||
<input type="radio" class="form-check-input" name="grant_type"
|
<input type="radio" class="form-check-input" name="grant_type"
|
||||||
value="custom_clients" {% if matches!(self.u.granted_clients(), GrantedClients::SomeClients(_)) %} checked="checked" {% endif %}>
|
value="custom_clients" {% if matches!(self.u.granted_clients(), GrantedClients::SomeClients(_))
|
||||||
|
%} checked="checked" {% endif %}>
|
||||||
Manually specify allowed clients
|
Manually specify allowed clients
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,7 +188,8 @@
|
|||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<label class="form-check-label">
|
<label class="form-check-label">
|
||||||
<input type="radio" class="form-check-input" name="grant_type"
|
<input type="radio" class="form-check-input" name="grant_type"
|
||||||
value="no_client" {% if u.granted_clients() == GrantedClients::NoClient %} checked="checked" {% endif %}>
|
value="no_client" {% if u.granted_clients()== GrantedClients::NoClient %} checked="checked" {%
|
||||||
|
endif %}>
|
||||||
Do not grant any client
|
Do not grant any client
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -215,6 +249,13 @@
|
|||||||
form.addEventListener("submit", (ev) => {
|
form.addEventListener("submit", (ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
|
const authorized_sources = [...document.querySelectorAll(".authorized_provider")]
|
||||||
|
.filter(e => e.checked)
|
||||||
|
.map(e => e.getAttribute("data-id")).join(",")
|
||||||
|
|
||||||
|
document.querySelector("input[name=authorized_sources]").value = authorized_sources;
|
||||||
|
|
||||||
|
|
||||||
const authorized_clients = [...document.querySelectorAll(".authorize_client_checkbox")]
|
const authorized_clients = [...document.querySelectorAll(".authorize_client_checkbox")]
|
||||||
.filter(e => e.checked)
|
.filter(e => e.checked)
|
||||||
.map(e => e.getAttribute("data-id")).join(",")
|
.map(e => e.getAttribute("data-id")).join(",")
|
||||||
@@ -231,6 +272,9 @@
|
|||||||
form.submit();
|
form.submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
37
templates/settings/providers_list.html
Normal file
37
templates/settings/providers_list.html
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{% extends "base_settings_page.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#providers td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<table id="providers" class="table table-hover" style="max-width: 800px;" aria-describedby="OpenID providers list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Configuration URL</th>
|
||||||
|
<th scope="col">Client ID</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in providers %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img src="{{ c.logo_url() }}" alt="{{ c.name }} logo" width="30px"/>
|
||||||
|
</td>
|
||||||
|
<td>{{ c.id.0 }}</td>
|
||||||
|
<td>{{ c.name }}</td>
|
||||||
|
<td><a href="{{ c.configuration_url }}" target="_blank" rel="noreferrer">
|
||||||
|
{{ c.configuration_url }}
|
||||||
|
</a></td>
|
||||||
|
<td>{{ c.client_id }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
Reference in New Issue
Block a user