mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Improve cookie/token handling (#1153)
This commit is contained in:
parent
fb1a67681a
commit
db75a0b344
246
Cargo.lock
generated
246
Cargo.lock
generated
|
@ -68,7 +68,7 @@ version = "0.7.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
"getrandom 0.2.8",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -99,9 +99,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.65"
|
||||
version = "1.0.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602"
|
||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||
|
||||
[[package]]
|
||||
name = "anymap2"
|
||||
|
@ -134,7 +134,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.15",
|
||||
"time 0.3.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -173,9 +173,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "345fd392ab01f746c717b1357165b76f0b67a60192007b234058c9045fdcf695"
|
||||
checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"futures-core",
|
||||
|
@ -210,9 +210,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-global-executor"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca"
|
||||
checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
|
@ -242,16 +242,16 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7"
|
||||
checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"autocfg",
|
||||
"concurrent-queue",
|
||||
"futures-lite",
|
||||
"libc",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking",
|
||||
"polling",
|
||||
"slab",
|
||||
|
@ -262,11 +262,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "2.5.0"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
|
||||
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -583,9 +584,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.11.0"
|
||||
version = "3.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
|
@ -625,9 +626,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
version = "1.0.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
@ -750,6 +751,16 @@ dependencies = [
|
|||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
|
@ -858,7 +869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "344adc371239ef32293cb1c4fe519592fcf21206c79c02854320afcdf3ab4917"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.15",
|
||||
"time 0.3.16",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
|
@ -874,7 +885,7 @@ dependencies = [
|
|||
"publicsuffix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time 0.3.15",
|
||||
"time 0.3.16",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -1075,9 +1086,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.23"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
|
@ -1092,6 +1103,50 @@ dependencies = [
|
|||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "daemon"
|
||||
version = "1.1.0-alpha.9"
|
||||
|
@ -1113,9 +1168,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
|
||||
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
|
@ -1123,9 +1178,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f"
|
||||
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
|
@ -1137,9 +1192,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
|
||||
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
|
@ -1386,7 +1441,7 @@ checksum = "c6dedfc944f4ac38cac8b74cb1c7b4fb73c175db232d6fa98e9bd1fd81908b89"
|
|||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"byteorder",
|
||||
"getrandom 0.2.7",
|
||||
"getrandom 0.2.8",
|
||||
"openssl",
|
||||
"zeroize",
|
||||
]
|
||||
|
@ -1596,9 +1651,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
|
@ -1818,9 +1873,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.14"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
|
||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -1919,7 +1974,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa 1.0.3",
|
||||
"itoa 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1994,7 +2049,7 @@ dependencies = [
|
|||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa 1.0.3",
|
||||
"itoa 1.0.4",
|
||||
"pin-project-lite 0.2.9",
|
||||
"socket2",
|
||||
"tokio",
|
||||
|
@ -2031,17 +2086,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.50"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0"
|
||||
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
|
@ -2157,9 +2223,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
|
||||
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
|
@ -2477,9 +2543,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.135"
|
||||
version = "0.2.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
|
@ -2508,9 +2574,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.25.1"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f0455f2c1bc9a7caa792907026e469c1d91761fb0ea37cbb16427c77280cf35"
|
||||
checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
|
@ -2563,6 +2629,15 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
|
@ -2679,14 +2754,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
|
||||
checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.36.1",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2855,7 +2930,7 @@ checksum = "6d62c436394991641b970a92e23e8eeb4eb9bca74af4f5badc53bcd568daadbd"
|
|||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"chrono",
|
||||
"getrandom 0.2.7",
|
||||
"getrandom 0.2.8",
|
||||
"http",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
|
@ -2878,9 +2953,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.15.0"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "oncemutex"
|
||||
|
@ -2940,9 +3015,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
|||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.76"
|
||||
version = "0.9.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce"
|
||||
checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
|
@ -2981,9 +3056,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.3.0"
|
||||
version = "6.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
|
||||
checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
|
@ -3018,15 +3093,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.3"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
|
||||
checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-sys 0.36.1",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3154,9 +3229,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.3.0"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011"
|
||||
checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
|
@ -3226,9 +3301,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.46"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -3250,11 +3325,11 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
|||
|
||||
[[package]]
|
||||
name = "publicsuffix"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeeedb0b429dc462f30ad27ef3de97058b060016f47790c066757be38ef792b4"
|
||||
checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457"
|
||||
dependencies = [
|
||||
"idna 0.2.3",
|
||||
"idna 0.3.0",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
|
@ -3372,7 +3447,7 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
"getrandom 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3423,7 +3498,7 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
"getrandom 0.2.8",
|
||||
"redox_syscall",
|
||||
"thiserror",
|
||||
]
|
||||
|
@ -3688,6 +3763,12 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
|
@ -3823,7 +3904,7 @@ version = "1.0.87"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"itoa 1.0.4",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
@ -3855,7 +3936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa 1.0.3",
|
||||
"itoa 1.0.4",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
@ -4328,16 +4409,24 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.15"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c"
|
||||
checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca"
|
||||
dependencies = [
|
||||
"itoa 1.0.3",
|
||||
"itoa 1.0.4",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"time-macros 0.2.4",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros 0.2.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
|
@ -4350,9 +4439,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
|
@ -4588,9 +4680,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
|||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.4"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
|
@ -4663,7 +4755,7 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83"
|
||||
dependencies = [
|
||||
"getrandom 0.2.7",
|
||||
"getrandom 0.2.8",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -5156,7 +5248,7 @@ dependencies = [
|
|||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.15",
|
||||
"time 0.3.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5276,5 +5368,5 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"quick-error",
|
||||
"regex",
|
||||
"time 0.3.15",
|
||||
"time 0.3.16",
|
||||
]
|
||||
|
|
|
@ -393,7 +393,7 @@ pub struct UserAuthToken {
|
|||
pub displayname: String,
|
||||
pub spn: String,
|
||||
pub mail_primary: Option<String>,
|
||||
pub groups: Vec<Group>,
|
||||
// pub groups: Vec<Group>,
|
||||
pub ui_hints: BTreeSet<UiHint>,
|
||||
}
|
||||
|
||||
|
@ -414,9 +414,11 @@ impl fmt::Display for UserAuthToken {
|
|||
writeln!(f, "purpose: read write (expiry: {})", expiry)?
|
||||
}
|
||||
}
|
||||
/*
|
||||
for group in &self.groups {
|
||||
writeln!(f, "group: {:?}", group.spn)?;
|
||||
}
|
||||
*/
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -866,11 +868,24 @@ impl fmt::Display for AuthMech {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthIssueSession {
|
||||
Token,
|
||||
Cookie,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AuthStep {
|
||||
// name
|
||||
Init(String),
|
||||
// A new way to issue sessions. Doing this as a new init type
|
||||
// to prevent breaking existing clients.
|
||||
Init2 {
|
||||
username: String,
|
||||
issue: AuthIssueSession,
|
||||
},
|
||||
// We want to talk to you like this.
|
||||
Begin(AuthMech),
|
||||
// Step
|
||||
|
@ -959,9 +974,11 @@ pub enum AuthState {
|
|||
Continue(Vec<AuthAllowed>),
|
||||
// Something was bad, your session is terminated and no cookie.
|
||||
Denied(String),
|
||||
// Everything is good, your bearer header has been issued and is within
|
||||
// Everything is good, your bearer token has been issued and is within
|
||||
// the result.
|
||||
Success(String),
|
||||
// Everything is good, your cookie has been issued.
|
||||
SuccessCookie,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::sync::Arc;
|
|||
use kanidm_proto::v1::{
|
||||
ApiToken, AuthRequest, BackupCodesView, CURequest, CUSessionToken, CUStatus, CredentialStatus,
|
||||
Entry as ProtoEntry, OperationError, RadiusAuthToken, SearchRequest, SearchResponse, UatStatus,
|
||||
UnixGroupToken, UnixUserToken, WhoamiResponse,
|
||||
UnixGroupToken, UnixUserToken, UserAuthToken, WhoamiResponse,
|
||||
};
|
||||
use ldap3_proto::simple::*;
|
||||
use regex::Regex;
|
||||
|
@ -320,6 +320,34 @@ impl QueryServerReadV1 {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
name = "whoami_uat",
|
||||
skip(self, uat, eventid)
|
||||
fields(uuid = ?eventid)
|
||||
)]
|
||||
pub async fn handle_whoami_uat(
|
||||
&self,
|
||||
uat: Option<String>,
|
||||
eventid: Uuid,
|
||||
) -> Result<UserAuthToken, OperationError> {
|
||||
let ct = duration_from_epoch_now();
|
||||
let idms_prox_read = self.idms.proxy_read().await;
|
||||
// Make an event from the whoami request. This will process the event and
|
||||
// generate a selfuuid search.
|
||||
//
|
||||
// This current handles the unauthenticated check, and will
|
||||
// trigger the failure, but if we can manage to work out async
|
||||
// then move this to core.rs, and don't allow Option<UAT> to get
|
||||
// this far.
|
||||
idms_prox_read
|
||||
.validate_and_parse_token_to_uat(uat.as_deref(), ct)
|
||||
.map_err(|e| {
|
||||
admin_error!(?e, "Invalid identity");
|
||||
e
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(
|
||||
level = "info",
|
||||
skip_all,
|
||||
|
|
|
@ -378,11 +378,11 @@ pub fn create_https_server(
|
|||
tserver.with(sketching::middleware::TreeMiddleware::new(
|
||||
trust_x_forward_for,
|
||||
));
|
||||
// tserver.with(tide::log::LogMiddleware::new());
|
||||
|
||||
// We do not force a session ttl, because we validate this elsewhere in usage.
|
||||
tserver.with(
|
||||
// We do not force a session ttl, because we validate this elsewhere in usage.
|
||||
tide::sessions::SessionMiddleware::new(tide::sessions::MemoryStore::new(), cookie_key)
|
||||
tide::sessions::SessionMiddleware::new(tide::sessions::CookieStore::new(), cookie_key)
|
||||
.with_cookie_name("kanidm-session")
|
||||
.with_same_site_policy(tide::http::cookies::SameSite::Strict),
|
||||
);
|
||||
|
@ -566,6 +566,7 @@ pub fn create_https_server(
|
|||
|
||||
let mut self_route = appserver.at("/v1/self");
|
||||
self_route.at("/").mapped_get(&mut routemap, whoami);
|
||||
self_route.at("/_uat").mapped_get(&mut routemap, whoami_uat);
|
||||
|
||||
self_route
|
||||
.at("/_attr/:attr")
|
||||
|
|
|
@ -3,9 +3,10 @@ use std::time::Duration;
|
|||
|
||||
use compact_jwt::Jws;
|
||||
use kanidm_proto::v1::{
|
||||
AccountUnixExtend, ApiTokenGenerate, AuthRequest, AuthResponse, AuthState as ProtoAuthState,
|
||||
CUIntentToken, CURequest, CUSessionToken, CreateRequest, DeleteRequest, Entry as ProtoEntry,
|
||||
GroupUnixExtend, ModifyRequest, OperationError, SearchRequest, SingleStringRequest,
|
||||
AccountUnixExtend, ApiTokenGenerate, AuthIssueSession, AuthRequest, AuthResponse,
|
||||
AuthState as ProtoAuthState, CUIntentToken, CURequest, CUSessionToken, CreateRequest,
|
||||
DeleteRequest, Entry as ProtoEntry, GroupUnixExtend, ModifyRequest, OperationError,
|
||||
SearchRequest, SingleStringRequest,
|
||||
};
|
||||
use kanidmd_lib::filter::{Filter, FilterInvalid};
|
||||
use kanidmd_lib::idm::event::AuthResult;
|
||||
|
@ -64,10 +65,25 @@ pub async fn whoami(req: tide::Request<AppState>) -> tide::Result {
|
|||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn logout(req: tide::Request<AppState>) -> tide::Result {
|
||||
pub async fn whoami_uat(req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
let res = req.state().qe_r_ref.handle_whoami_uat(uat, eventid).await;
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
pub async fn logout(mut req: tide::Request<AppState>) -> tide::Result {
|
||||
let uat = req.get_current_uat();
|
||||
let (eventid, hvalue) = req.new_eventid();
|
||||
|
||||
// Now lets nuke any cookies for the session. We do this before the handle_logout
|
||||
// so that if any errors occur, the cookies are still removed.
|
||||
let msession = req.session_mut();
|
||||
msession.remove("auth-session-id");
|
||||
msession.remove("bearer");
|
||||
|
||||
let res = req.state().qe_w_ref.handle_logout(uat, eventid).await;
|
||||
|
||||
to_tide_response(res, hvalue)
|
||||
}
|
||||
|
||||
|
@ -1125,17 +1141,21 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
|
|||
})
|
||||
.map(|_| ProtoAuthState::Continue(allowed))
|
||||
}
|
||||
AuthState::Success(token) => {
|
||||
AuthState::Success(token, issue) => {
|
||||
debug!("🧩 -> AuthState::Success");
|
||||
// Remove the auth-session-id
|
||||
let msession = req.session_mut();
|
||||
msession.remove("auth-session-id");
|
||||
// Create a session cookie?
|
||||
msession.remove("bearer");
|
||||
msession
|
||||
.insert("bearer", token.clone())
|
||||
|
||||
match issue {
|
||||
AuthIssueSession::Cookie => msession
|
||||
.insert("bearer", token)
|
||||
.map_err(|_| OperationError::InvalidSessionState)
|
||||
.map(|_| ProtoAuthState::Success(token))
|
||||
.map(|_| ProtoAuthState::SuccessCookie),
|
||||
AuthIssueSession::Token => Ok(ProtoAuthState::Success(token)),
|
||||
}
|
||||
}
|
||||
AuthState::Denied(reason) => {
|
||||
debug!("🧩 -> AuthState::Denied");
|
||||
|
|
|
@ -213,7 +213,7 @@ impl Account {
|
|||
mail_primary: self.mail_primary.clone(),
|
||||
ui_hints: self.ui_hints.clone(),
|
||||
// application: None,
|
||||
groups: self.groups.iter().map(|g| g.to_proto()).collect(),
|
||||
// groups: self.groups.iter().map(|g| g.to_proto()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@ use std::time::Duration;
|
|||
// use webauthn_rs::proto::Credential as WebauthnCredential;
|
||||
use compact_jwt::{Jws, JwsSigner};
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech, AuthType, OperationError};
|
||||
use kanidm_proto::v1::{
|
||||
AuthAllowed, AuthCredential, AuthIssueSession, AuthMech, AuthType, OperationError,
|
||||
};
|
||||
// use crossbeam::channel::Sender;
|
||||
use tokio::sync::mpsc::UnboundedSender as Sender;
|
||||
use uuid::Uuid;
|
||||
|
@ -569,13 +571,21 @@ pub(crate) struct AuthSession {
|
|||
//
|
||||
// This handler will then handle the mfa and stepping up through to generate the auth states
|
||||
state: AuthSessionState,
|
||||
|
||||
// The type of session we will issue if successful
|
||||
issue: AuthIssueSession,
|
||||
}
|
||||
|
||||
impl AuthSession {
|
||||
/// Create a new auth session, based on the available credential handlers of the account.
|
||||
/// the session is a whole encapsulated unit of what we need to proceed, so that subsequent
|
||||
/// or interleved write operations do not cause inconsistency in this process.
|
||||
pub fn new(account: Account, webauthn: &Webauthn, ct: Duration) -> (Option<Self>, AuthState) {
|
||||
pub fn new(
|
||||
account: Account,
|
||||
issue: AuthIssueSession,
|
||||
webauthn: &Webauthn,
|
||||
ct: Duration,
|
||||
) -> (Option<Self>, AuthState) {
|
||||
// During this setup, determine the credential handler that we'll be using
|
||||
// for this session. This is currently based on presentation of an application
|
||||
// id.
|
||||
|
@ -623,7 +633,11 @@ impl AuthSession {
|
|||
(None, AuthState::Denied(reason.to_string()))
|
||||
} else {
|
||||
// We can proceed
|
||||
let auth_session = AuthSession { account, state };
|
||||
let auth_session = AuthSession {
|
||||
account,
|
||||
state,
|
||||
issue,
|
||||
};
|
||||
// Get the set of mechanisms that can proceed. This is tied
|
||||
// to the session so that it can mutate state and have progression
|
||||
// of what's next, or ordering.
|
||||
|
@ -742,8 +756,11 @@ impl AuthSession {
|
|||
CredState::Success(auth_type) => {
|
||||
security_info!("Successful cred handling");
|
||||
let session_id = Uuid::new_v4();
|
||||
let issue = self.issue;
|
||||
|
||||
security_info!(
|
||||
"Starting session {} for {} {}",
|
||||
"Issuing {:?} session {} for {} {}",
|
||||
issue,
|
||||
session_id,
|
||||
self.account.spn,
|
||||
self.account.uuid
|
||||
|
@ -805,7 +822,7 @@ impl AuthSession {
|
|||
|
||||
(
|
||||
Some(AuthSessionState::Success),
|
||||
Ok(AuthState::Success(token)),
|
||||
Ok(AuthState::Success(token, issue)),
|
||||
)
|
||||
}
|
||||
CredState::Continue(allowed) => {
|
||||
|
@ -870,7 +887,7 @@ mod tests {
|
|||
|
||||
use compact_jwt::JwsSigner;
|
||||
use hashbrown::HashSet;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech};
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthIssueSession, AuthMech};
|
||||
use tokio::sync::mpsc::unbounded_channel as unbounded;
|
||||
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
||||
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
||||
|
@ -914,7 +931,12 @@ mod tests {
|
|||
|
||||
let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1);
|
||||
|
||||
let (session, state) = AuthSession::new(anon_account, &webauthn, duration_from_epoch_now());
|
||||
let (session, state) = AuthSession::new(
|
||||
anon_account,
|
||||
AuthIssueSession::Token,
|
||||
&webauthn,
|
||||
duration_from_epoch_now(),
|
||||
);
|
||||
|
||||
if let AuthState::Choose(auth_mechs) = state {
|
||||
assert!(auth_mechs.iter().any(|x| matches!(x, AuthMech::Anonymous)));
|
||||
|
@ -942,8 +964,12 @@ mod tests {
|
|||
$account:expr,
|
||||
$webauthn:expr
|
||||
) => {{
|
||||
let (session, state) =
|
||||
AuthSession::new($account.clone(), $webauthn, duration_from_epoch_now());
|
||||
let (session, state) = AuthSession::new(
|
||||
$account.clone(),
|
||||
AuthIssueSession::Token,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
);
|
||||
let mut session = session.unwrap();
|
||||
|
||||
if let AuthState::Choose(auth_mechs) = state {
|
||||
|
@ -1013,7 +1039,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
|
@ -1066,8 +1092,12 @@ mod tests {
|
|||
$account:expr,
|
||||
$webauthn:expr
|
||||
) => {{
|
||||
let (session, state) =
|
||||
AuthSession::new($account.clone(), $webauthn, duration_from_epoch_now());
|
||||
let (session, state) = AuthSession::new(
|
||||
$account.clone(),
|
||||
AuthIssueSession::Token,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
);
|
||||
let mut session = session.expect("Session was unable to be created.");
|
||||
|
||||
if let AuthState::Choose(auth_mechs) = state {
|
||||
|
@ -1260,7 +1290,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
|
@ -1347,8 +1377,12 @@ mod tests {
|
|||
$account:expr,
|
||||
$webauthn:expr
|
||||
) => {{
|
||||
let (session, state) =
|
||||
AuthSession::new($account.clone(), $webauthn, duration_from_epoch_now());
|
||||
let (session, state) = AuthSession::new(
|
||||
$account.clone(),
|
||||
AuthIssueSession::Token,
|
||||
$webauthn,
|
||||
duration_from_epoch_now(),
|
||||
);
|
||||
let mut session = session.unwrap();
|
||||
|
||||
if let AuthState::Choose(auth_mechs) = state {
|
||||
|
@ -1485,7 +1519,7 @@ mod tests {
|
|||
None,
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
|
@ -1724,7 +1758,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
|
@ -1931,7 +1965,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
|
@ -1970,7 +2004,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
|
@ -2127,7 +2161,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
|
@ -2169,7 +2203,7 @@ mod tests {
|
|||
Some(&pw_badlist_cache),
|
||||
&jws_signer,
|
||||
) {
|
||||
Ok(AuthState::Success(_)) => {}
|
||||
Ok(AuthState::Success(_, AuthIssueSession::Token)) => {}
|
||||
_ => panic!(),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1473,7 +1473,7 @@ mod tests {
|
|||
use std::time::Duration;
|
||||
|
||||
use async_std::task;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthMech, CredentialDetailType};
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech, CredentialDetailType};
|
||||
use uuid::uuid;
|
||||
use webauthn_authenticator_rs::softpasskey::SoftPasskey;
|
||||
use webauthn_authenticator_rs::WebauthnAuthenticator;
|
||||
|
@ -1714,7 +1714,7 @@ mod tests {
|
|||
match r2 {
|
||||
Ok(AuthResult {
|
||||
sessionid: _,
|
||||
state: AuthState::Success(token),
|
||||
state: AuthState::Success(token, AuthIssueSession::Token),
|
||||
delay: _,
|
||||
}) => {
|
||||
// Process the auth session
|
||||
|
@ -1788,7 +1788,7 @@ mod tests {
|
|||
match r3 {
|
||||
Ok(AuthResult {
|
||||
sessionid: _,
|
||||
state: AuthState::Success(token),
|
||||
state: AuthState::Success(token, AuthIssueSession::Token),
|
||||
delay: _,
|
||||
}) => {
|
||||
// Process the auth session
|
||||
|
@ -1857,7 +1857,7 @@ mod tests {
|
|||
match r3 {
|
||||
Ok(AuthResult {
|
||||
sessionid: _,
|
||||
state: AuthState::Success(token),
|
||||
state: AuthState::Success(token, AuthIssueSession::Token),
|
||||
delay: _,
|
||||
}) => {
|
||||
// There now should be a backup code invalidation present
|
||||
|
@ -1934,7 +1934,7 @@ mod tests {
|
|||
match r3 {
|
||||
Ok(AuthResult {
|
||||
sessionid: _,
|
||||
state: AuthState::Success(token),
|
||||
state: AuthState::Success(token, AuthIssueSession::Token),
|
||||
delay: _,
|
||||
}) => {
|
||||
// Process the webauthn update
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::time::Duration;
|
|||
use crate::idm::AuthState;
|
||||
use crate::prelude::*;
|
||||
use kanidm_proto::v1::OperationError;
|
||||
use kanidm_proto::v1::{AuthCredential, AuthMech, AuthRequest, AuthStep};
|
||||
use kanidm_proto::v1::{AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthStep};
|
||||
|
||||
#[cfg(test)]
|
||||
use webauthn_rs::prelude::PublicKeyCredential;
|
||||
|
@ -294,8 +294,8 @@ impl LdapTokenAuthEvent {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct AuthEventStepInit {
|
||||
pub name: String,
|
||||
pub appid: Option<String>,
|
||||
pub username: String,
|
||||
pub issue: AuthIssueSession,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -320,9 +320,14 @@ pub enum AuthEventStep {
|
|||
impl AuthEventStep {
|
||||
fn from_authstep(aus: AuthStep, sid: Option<Uuid>) -> Result<Self, OperationError> {
|
||||
match aus {
|
||||
AuthStep::Init(name) => {
|
||||
Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None }))
|
||||
AuthStep::Init(username) => Ok(AuthEventStep::Init(AuthEventStepInit {
|
||||
username,
|
||||
issue: AuthIssueSession::Token,
|
||||
})),
|
||||
AuthStep::Init2 { username, issue } => {
|
||||
Ok(AuthEventStep::Init(AuthEventStepInit { username, issue }))
|
||||
}
|
||||
|
||||
AuthStep::Begin(mech) => match sid {
|
||||
Some(ssid) => Ok(AuthEventStep::Begin(AuthEventStepMech {
|
||||
sessionid: ssid,
|
||||
|
@ -347,16 +352,16 @@ impl AuthEventStep {
|
|||
#[cfg(test)]
|
||||
pub fn anonymous_init() -> Self {
|
||||
AuthEventStep::Init(AuthEventStepInit {
|
||||
name: "anonymous".to_string(),
|
||||
appid: None,
|
||||
username: "anonymous".to_string(),
|
||||
issue: AuthIssueSession::Token,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn named_init(name: &str) -> Self {
|
||||
AuthEventStep::Init(AuthEventStepInit {
|
||||
name: name.to_string(),
|
||||
appid: None,
|
||||
username: name.to_string(),
|
||||
issue: AuthIssueSession::Token,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -18,13 +18,13 @@ pub mod unix;
|
|||
|
||||
use std::fmt;
|
||||
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthMech};
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech};
|
||||
|
||||
pub enum AuthState {
|
||||
Choose(Vec<AuthMech>),
|
||||
Continue(Vec<AuthAllowed>),
|
||||
Denied(String),
|
||||
Success(String),
|
||||
Success(String, AuthIssueSession),
|
||||
}
|
||||
|
||||
impl fmt::Debug for AuthState {
|
||||
|
@ -33,7 +33,7 @@ impl fmt::Debug for AuthState {
|
|||
AuthState::Choose(mechs) => write!(f, "AuthState::Choose({:?})", mechs),
|
||||
AuthState::Continue(allow) => write!(f, "AuthState::Continue({:?})", allow),
|
||||
AuthState::Denied(reason) => write!(f, "AuthState::Denied({:?})", reason),
|
||||
AuthState::Success(_token) => write!(f, "AuthState::Success"),
|
||||
AuthState::Success(_token, issue) => write!(f, "AuthState::Success({:?})", issue),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -410,6 +410,21 @@ pub trait IdmServerTransaction<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "info", skip_all)]
|
||||
fn validate_and_parse_token_to_uat(
|
||||
&self,
|
||||
token: Option<&str>,
|
||||
ct: Duration,
|
||||
) -> Result<UserAuthToken, OperationError> {
|
||||
match self.validate_and_parse_token_to_token(token, ct)? {
|
||||
Token::UserAuthToken(uat) => Ok(uat),
|
||||
Token::ApiToken(_apit, _entry) => {
|
||||
warn!("Unable to process non user auth token");
|
||||
Err(OperationError::NotAuthenticated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_and_parse_token_to_token(
|
||||
&self,
|
||||
token: Option<&str>,
|
||||
|
@ -878,13 +893,14 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
//
|
||||
// Check anything needed? Get the current auth-session-id from request
|
||||
// because it associates to the nonce's etc which were all cached.
|
||||
let euuid = self.qs_read.name_to_uuid(init.name.as_str())?;
|
||||
let euuid = self.qs_read.name_to_uuid(init.username.as_str())?;
|
||||
|
||||
// Get the first / single entry we expect here ....
|
||||
let entry = self.qs_read.internal_search_uuid(&euuid)?;
|
||||
|
||||
security_info!(
|
||||
name = %init.name,
|
||||
username = %init.username,
|
||||
issue = ?init.issue,
|
||||
uuid = %euuid,
|
||||
"Initiating Authentication Session",
|
||||
);
|
||||
|
@ -956,7 +972,8 @@ impl<'a> IdmServerAuthTransaction<'a> {
|
|||
};
|
||||
*/
|
||||
|
||||
let (auth_session, state) = AuthSession::new(account, self.webauthn, ct);
|
||||
let (auth_session, state) =
|
||||
AuthSession::new(account, init.issue, self.webauthn, ct);
|
||||
|
||||
match auth_session {
|
||||
Some(auth_session) => {
|
||||
|
@ -2225,7 +2242,7 @@ mod tests {
|
|||
use std::time::Duration;
|
||||
|
||||
use async_std::task;
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthMech, AuthType, OperationError};
|
||||
use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech, AuthType, OperationError};
|
||||
use smartstring::alias::String as AttrString;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -2366,7 +2383,7 @@ mod tests {
|
|||
|
||||
debug_assert!(delay.is_none());
|
||||
match state {
|
||||
AuthState::Success(_uat) => {
|
||||
AuthState::Success(_uat, AuthIssueSession::Token) => {
|
||||
// Check the uat.
|
||||
}
|
||||
_ => {
|
||||
|
@ -2505,7 +2522,7 @@ mod tests {
|
|||
debug_assert!(delay.is_none());
|
||||
|
||||
match state {
|
||||
AuthState::Success(token) => {
|
||||
AuthState::Success(token, AuthIssueSession::Token) => {
|
||||
// Check the uat.
|
||||
token
|
||||
}
|
||||
|
@ -2574,7 +2591,7 @@ mod tests {
|
|||
} = ar;
|
||||
debug_assert!(delay.is_none());
|
||||
match state {
|
||||
AuthState::Success(_uat) => {
|
||||
AuthState::Success(_uat, AuthIssueSession::Token) => {
|
||||
// Check the uat.
|
||||
}
|
||||
_ => {
|
||||
|
@ -3435,7 +3452,7 @@ mod tests {
|
|||
} = ar;
|
||||
debug_assert!(delay.is_none());
|
||||
match state {
|
||||
AuthState::Success(_uat) => {
|
||||
AuthState::Success(_uat, AuthIssueSession::Token) => {
|
||||
// Check the uat.
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -1,30 +1,47 @@
|
|||
# Kanidm - Simple and Secure Identity Management
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/kanidm/kanidm/master/artwork/logo-small.png" width="20%" height="auto" />
|
||||
</p>
|
||||
|
||||
# Kanidm
|
||||
## About
|
||||
|
||||
Kanidm is an identity management platform written in rust. Our goals are:
|
||||
Kanidm is a simple and secure identity management platform, which provides services to allow
|
||||
other systems and application to authenticate against. The project aims for the highest levels
|
||||
of reliability, security and ease of use.
|
||||
|
||||
* Modern identity management platform
|
||||
* Simple to deploy and integrate with
|
||||
* Extensible for various needs
|
||||
* Correct and secure behaviour by default
|
||||
The goal of this project is to be a complete identity management provider, covering the broadest
|
||||
possible set of requirements and integrations. You should not need any other components (like Keycloak)
|
||||
when you use Kanidm. We want to create a project that will be suitable for everything
|
||||
from personal home deployments, to the largest enterprise needs.
|
||||
|
||||
Today the project is still under heavy development to achieve these goals - We have many foundational
|
||||
parts in place, and many of the required security features, but it is still an Alpha, and should be
|
||||
treated as such.
|
||||
To achieve this we rely heavily on strict defaults, simple configuration, and self-healing components.
|
||||
|
||||
The project is still growing and some areas are developing at a fast pace. The core of the server
|
||||
however is reliable and we make all effort to ensure upgrades will always work.
|
||||
|
||||
Kanidm supports:
|
||||
|
||||
* Oauth2/OIDC Authentication provider for web SSO
|
||||
* Read only LDAPS gateway
|
||||
* Linux/Unix integration (with offline authentication)
|
||||
* SSH key distribution to Linux/Unix systems
|
||||
* RADIUS for network authentication
|
||||
* Passkeys / Webauthn for secure cryptographic authentication
|
||||
* A self service web ui
|
||||
* Complete CLI tooling for administration
|
||||
|
||||
If you want to host your own centralised authentication service, then Kanidm is for you!
|
||||
|
||||
## Documentation / Getting Started / Install
|
||||
|
||||
If you want to deploy Kanidm to see what it can do, you should read the kanidm book.
|
||||
If you want to deploy Kanidm to see what it can do, you should read the Kanidm book.
|
||||
|
||||
- [Kanidm book (Latest commit)](https://kanidm.github.io/kanidm/master/)
|
||||
- [Kanidm book (Latest stable)](https://kanidm.github.io/kanidm/stable/)
|
||||
- [Kanidm book (Latest commit)](https://kanidm.github.io/kanidm/master/)
|
||||
|
||||
|
||||
We also publish limited [support guidelines](https://github.com/kanidm/kanidm/blob/master/project_docs/RELEASE_AND_SUPPORT.md).
|
||||
We also publish [support guidelines](https://github.com/kanidm/kanidm/blob/master/project_docs/RELEASE_AND_SUPPORT.md)
|
||||
for what the project will support.
|
||||
|
||||
## Code of Conduct / Ethics
|
||||
|
||||
|
@ -42,6 +59,46 @@ answer questions via email, which can be found on their github profile.
|
|||
|
||||
[gitter community channel]: https://gitter.im/kanidm/community
|
||||
|
||||
## Comparison with other services
|
||||
|
||||
### LLDAP
|
||||
|
||||
[LLDAP](https://github.com/nitnelave/lldap) is a similar project aiming for a small and easy to administer
|
||||
LDAP server with a web administration portal. Both projects use the [Kanidm LDAP bindings](https://github.com/kanidm/ldap3), and have
|
||||
many similar ideas.
|
||||
|
||||
The primary benefit of Kanidm over LLDAP is that Kanidm offers a broader set of "built in" features
|
||||
like Oauth2 and OIDC. To use these from LLDAP you need an external portal like Keycloak, where in Kanidm
|
||||
they are "built in". However that is also a strength of LLDAP is that is offers "less" which may make
|
||||
it easier to administer and deploy for you.
|
||||
|
||||
If Kanidm is too complex for your needs, you should check out LLDAP as a smaller alternative. If you
|
||||
want a project which has a broader feature set out of the box, then Kanidm might be a better fit.
|
||||
|
||||
### 389-ds / OpenLDAP
|
||||
|
||||
Both 389-ds and OpenLDAP are generic LDAP servers. This means they only provide LDAP and you need
|
||||
to bring your own IDM configuration on top.
|
||||
|
||||
If you need the highest levels of customisation possible from your LDAP deployment, then these are
|
||||
probably better alternatives. If you want a service that is easier to setup and focused on IDM, then
|
||||
Kanidm is a better choice.
|
||||
|
||||
Kanidm was originally inspired by many elements of both 389-ds and OpenLDAP. Already Kanidm is as fast
|
||||
as (or faster than) 389-ds for performance and scaling.
|
||||
|
||||
### FreeIPA
|
||||
|
||||
FreeIPA is another identity management service for Linux/Unix, and ships a huge number of features
|
||||
from LDAP, Kerberos, DNS, Certificate Authority, and more.
|
||||
|
||||
FreeIPA however is a complex system, with a huge amount of parts and configuration. This adds a lot
|
||||
of resource overhead and difficulty for administration.
|
||||
|
||||
Kanidm aims to have the features richness of FreeIPA, but without the resource and administration
|
||||
overheads. If you want a complete IDM package, but in a lighter footprint and easier to manage, then
|
||||
Kanidm is probably for you.
|
||||
|
||||
## Developer Getting Started
|
||||
|
||||
If you want to develop on the server, there is a getting started [guide for developers]. IDM
|
||||
|
@ -50,50 +107,6 @@ all backgrounds.
|
|||
|
||||
[guide for developers]: https://kanidm.github.io/kanidm/master/DEVELOPER_README.html
|
||||
|
||||
## Features
|
||||
|
||||
### Implemented
|
||||
|
||||
* SSH key distribution for servers
|
||||
* PAM/nsswitch clients (with limited offline auth)
|
||||
* MFA - TOTP
|
||||
* Highly concurrent design (MVCC, COW)
|
||||
* RADIUS integration
|
||||
* MFA - Webauthn
|
||||
|
||||
### Currently Working On
|
||||
|
||||
* CLI for administration
|
||||
* WebUI for self-service with wifi enrollment, claim management and more.
|
||||
* RBAC/Claims/Policy (limited by time and credential scope)
|
||||
* OIDC/Oauth
|
||||
|
||||
### Upcoming Focus Areas
|
||||
|
||||
* Replication (async multiple active write servers, read-only servers)
|
||||
|
||||
### Future
|
||||
|
||||
* SSH CA management
|
||||
* Sudo rule distribution via nsswitch
|
||||
* WebUI for administration
|
||||
* Account impersonation
|
||||
* Synchronisation to other IDM services
|
||||
|
||||
## Some key project ideas
|
||||
|
||||
* All people should be respected and able to be represented securely.
|
||||
* Devices represent users and their identities - they are part of the authentication.
|
||||
* Human error occurs - we should be designed to minimise human mistakes and empower people.
|
||||
* The system should be easy to understand and reason about for users and admins.
|
||||
|
||||
### Features We Want to Avoid
|
||||
|
||||
* Auditing: This is better solved by SIEM software, so we should generate data they can consume.
|
||||
* Fully synchronous behaviour: This prevents scaling and our future ability to expand.
|
||||
* Generic database: We don't want to be another NoSQL database, we want to be an IDM solution.
|
||||
* Being like LDAP/GSSAPI/Kerberos: These are all legacy protocols that are hard to use and confine our thinking - we should avoid "being like them" or using them as models.
|
||||
|
||||
## What does Kanidm mean?
|
||||
|
||||
The original project name was rsidm while it was a thought experiment. Now that it's growing
|
||||
|
|
|
@ -233,7 +233,7 @@ function addBorrowedObject(obj) {
|
|||
}
|
||||
function __wbg_adapter_48(arg0, arg1, arg2) {
|
||||
try {
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha070437c619effa2(arg0, arg1, addBorrowedObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut___A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha86bc8783d36be0a(arg0, arg1, addBorrowedObject(arg2));
|
||||
} finally {
|
||||
heap[stack_pointer++] = undefined;
|
||||
}
|
||||
|
@ -261,11 +261,11 @@ function makeClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_51(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd36b5f2296664138(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__Fn__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h36bbc8108d49feb4(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_54(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h83dbed9e96aca169(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hc4ba360e62a5fa4a(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1037,16 +1037,16 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper5892 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1425, __wbg_adapter_48);
|
||||
imports.wbg.__wbindgen_closure_wrapper5651 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1343, __wbg_adapter_48);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper6052 = function(arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1460, __wbg_adapter_51);
|
||||
imports.wbg.__wbindgen_closure_wrapper5814 = function(arg0, arg1, arg2) {
|
||||
const ret = makeClosure(arg0, arg1, 1378, __wbg_adapter_51);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper6782 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1724, __wbg_adapter_54);
|
||||
imports.wbg.__wbindgen_closure_wrapper6542 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 1635, __wbg_adapter_54);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
|
@ -9,7 +9,6 @@ use crate::components::alpha_warning_banner;
|
|||
use crate::constants::{
|
||||
CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_DT, CSS_TABLE,
|
||||
};
|
||||
use crate::models;
|
||||
use crate::utils::{do_alert_error, do_page_header, init_request};
|
||||
use crate::views::AdminRoute;
|
||||
|
||||
|
@ -83,7 +82,7 @@ pub struct AdminListAccountsProps {
|
|||
|
||||
/// Pulls all accounts (service or person-class) from the backend and returns a HashMap
|
||||
/// with the "name" field being the keys, for easy human-facing sortability.
|
||||
pub async fn get_accounts(token: &str) -> Result<AdminListAccountsMsg, GetError> {
|
||||
pub async fn get_accounts() -> Result<AdminListAccountsMsg, GetError> {
|
||||
// TODO: the actual pulling and turning into a BTreeMap in this and get_groups could *probably* be rolled up into one function? The result object differs but all the widgets are the same.
|
||||
let mut all_accounts = BTreeMap::new();
|
||||
|
||||
|
@ -94,7 +93,7 @@ pub async fn get_accounts(token: &str) -> Result<AdminListAccountsMsg, GetError>
|
|||
];
|
||||
|
||||
for (endpoint, object_type) in endpoints {
|
||||
let request = init_request(endpoint, token);
|
||||
let request = init_request(endpoint);
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
@ -146,14 +145,10 @@ impl Component for AdminListAccounts {
|
|||
fn create(ctx: &Context<Self>) -> Self {
|
||||
// TODO: work out the querystring thing so we can just show x number of elements
|
||||
// console::log!("query: {:?}", location().query);
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
|
||||
// start pulling the account data on startup
|
||||
ctx.link().send_future(async move {
|
||||
match get_accounts(token.clone().as_str()).await {
|
||||
match get_accounts().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -327,13 +322,9 @@ impl Component for AdminViewPerson {
|
|||
type Properties = AdminViewAccountProps;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
let uuid = ctx.props().uuid.clone();
|
||||
ctx.link().send_future(async move {
|
||||
match get_person(token.clone().as_str(), &uuid).await {
|
||||
match get_person(&uuid).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -480,13 +471,9 @@ impl Component for AdminViewServiceAccount {
|
|||
type Properties = AdminViewAccountProps;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
let uuid = ctx.props().uuid.clone();
|
||||
ctx.link().send_future(async move {
|
||||
match get_service_account(token.clone().as_str(), &uuid).await {
|
||||
match get_service_account(&uuid).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -553,8 +540,8 @@ impl Component for AdminViewServiceAccount {
|
|||
}
|
||||
|
||||
/// pull the details for a single person by UUID
|
||||
pub async fn get_person(token: &str, uuid: &str) -> Result<AdminViewPersonMsg, GetError> {
|
||||
let request = init_request(format!("/v1/person/{}", uuid).as_str(), token);
|
||||
pub async fn get_person(uuid: &str) -> Result<AdminViewPersonMsg, GetError> {
|
||||
let request = init_request(format!("/v1/person/{}", uuid).as_str());
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
@ -572,11 +559,8 @@ pub async fn get_person(token: &str, uuid: &str) -> Result<AdminViewPersonMsg, G
|
|||
}
|
||||
|
||||
/// pull the details for a single service_account by UUID
|
||||
pub async fn get_service_account(
|
||||
token: &str,
|
||||
uuid: &str,
|
||||
) -> Result<AdminViewServiceAccountMsg, GetError> {
|
||||
let request = init_request(format!("/v1/service_account/{}", uuid).as_str(), token);
|
||||
pub async fn get_service_account(uuid: &str) -> Result<AdminViewServiceAccountMsg, GetError> {
|
||||
let request = init_request(format!("/v1/service_account/{}", uuid).as_str());
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
|
|
@ -7,7 +7,6 @@ use yew_router::prelude::Link;
|
|||
use crate::components::adminmenu::{Entity, EntityType, GetError};
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_TABLE};
|
||||
use crate::models;
|
||||
use crate::utils::{do_alert_error, do_page_header, init_request};
|
||||
use crate::views::AdminRoute;
|
||||
|
||||
|
@ -64,14 +63,14 @@ pub struct AdminListGroupsProps {
|
|||
|
||||
/// Pulls all accounts (service or person-class) from the backend and returns a HashMap
|
||||
/// with the "name" field being the keys, for easy human-facing sortability.
|
||||
pub async fn get_groups(token: &str) -> Result<AdminListGroupsMsg, GetError> {
|
||||
pub async fn get_groups() -> Result<AdminListGroupsMsg, GetError> {
|
||||
let mut all_groups = BTreeMap::new();
|
||||
|
||||
// we iterate over these endpoints
|
||||
let endpoints = [("/v1/group", EntityType::Group)];
|
||||
|
||||
for (endpoint, object_type) in endpoints {
|
||||
let request = init_request(endpoint, token);
|
||||
let request = init_request(endpoint);
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
@ -117,14 +116,10 @@ impl Component for AdminListGroups {
|
|||
fn create(ctx: &Context<Self>) -> Self {
|
||||
// TODO: work out the querystring thing so we can just show x number of elements
|
||||
// console::log!("query: {:?}", location().query);
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
|
||||
// start pulling the account data on startup
|
||||
ctx.link().send_future(async move {
|
||||
match get_groups(token.clone().as_str()).await {
|
||||
match get_groups().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -286,14 +281,10 @@ impl Component for AdminViewGroup {
|
|||
type Properties = AdminViewGroupProps;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
let uuid = ctx.props().uuid.clone();
|
||||
// TODO: start pulling the group details then send the msg blep blep
|
||||
ctx.link().send_future(async move {
|
||||
match get_group(token.clone().as_str(), &uuid).await {
|
||||
match get_group(&uuid).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -368,8 +359,8 @@ impl Component for AdminViewGroup {
|
|||
}
|
||||
|
||||
/// pull the details for a single group by UUID
|
||||
pub async fn get_group(token: &str, groupid: &str) -> Result<AdminViewGroupMsg, GetError> {
|
||||
let request = init_request(format!("/v1/group/{}", groupid).as_str(), token);
|
||||
pub async fn get_group(groupid: &str) -> Result<AdminViewGroupMsg, GetError> {
|
||||
let request = init_request(format!("/v1/group/{}", groupid).as_str());
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
|
|
@ -7,7 +7,6 @@ use yew_router::prelude::Link;
|
|||
use crate::components::adminmenu::{Entity, EntityType, GetError};
|
||||
use crate::components::alpha_warning_banner;
|
||||
use crate::constants::{CSS_BREADCRUMB_ITEM, CSS_BREADCRUMB_ITEM_ACTIVE, CSS_CELL, CSS_TABLE};
|
||||
use crate::models;
|
||||
use crate::utils::{do_alert_error, do_page_header, init_request};
|
||||
use crate::views::AdminRoute;
|
||||
|
||||
|
@ -64,7 +63,7 @@ pub struct AdminListOAuth2Props {
|
|||
|
||||
/// Pulls all OAuth2 RPs from the backend and returns a HashMap
|
||||
/// with the "name" field being the keys, for easy human-facing sortability.
|
||||
pub async fn get_entities(token: &str) -> Result<AdminListOAuth2Msg, GetError> {
|
||||
pub async fn get_entities() -> Result<AdminListOAuth2Msg, GetError> {
|
||||
// TODO: the actual pulling and turning into a BTreeMap across the admin systems could *probably* be rolled up into one function? The result object differs but all the query bits are the same.
|
||||
let mut oauth2_objects = BTreeMap::new();
|
||||
|
||||
|
@ -72,7 +71,7 @@ pub async fn get_entities(token: &str) -> Result<AdminListOAuth2Msg, GetError> {
|
|||
let endpoints = [("/v1/oauth2", EntityType::OAuth2RP)];
|
||||
|
||||
for (endpoint, object_type) in endpoints {
|
||||
let request = init_request(endpoint, token);
|
||||
let request = init_request(endpoint);
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
@ -113,14 +112,10 @@ impl Component for AdminListOAuth2 {
|
|||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
// TODO: work out the querystring thing so we can just show x number of elements
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
|
||||
// start pulling the data on startup
|
||||
ctx.link().send_future(async move {
|
||||
match get_entities(token.clone().as_str()).await {
|
||||
match get_entities().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -299,15 +294,11 @@ impl Component for AdminViewOAuth2 {
|
|||
type Properties = AdminViewOAuth2Props;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
let token = match models::get_bearer_token() {
|
||||
Some(value) => value,
|
||||
None => String::from(""),
|
||||
};
|
||||
let rs_name = ctx.props().rs_name.clone();
|
||||
|
||||
// start pulling the data on startup
|
||||
ctx.link().send_future(async move {
|
||||
match get_oauth2_rp(token.clone().as_str(), &rs_name).await {
|
||||
match get_oauth2_rp(&rs_name).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -405,8 +396,8 @@ impl Component for AdminViewOAuth2 {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn get_oauth2_rp(token: &str, rs_name: &str) -> Result<AdminViewOAuth2Msg, GetError> {
|
||||
let request = init_request(format!("/v1/oauth2/{}", rs_name).as_str(), token);
|
||||
pub async fn get_oauth2_rp(rs_name: &str) -> Result<AdminViewOAuth2Msg, GetError> {
|
||||
let request = init_request(format!("/v1/oauth2/{}", rs_name).as_str());
|
||||
let response = match request.send().await {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use compact_jwt::{Jws, JwsUnverified};
|
||||
use kanidm_proto::v1::{SingleStringRequest, UserAuthToken};
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{FormData, HtmlFormElement, Request, RequestInit, RequestMode, Response};
|
||||
|
@ -69,7 +67,7 @@ pub enum State {
|
|||
|
||||
#[derive(PartialEq, Eq, Properties)]
|
||||
pub struct ChangeUnixPasswordProps {
|
||||
pub token: String,
|
||||
pub uat: UserAuthToken,
|
||||
}
|
||||
|
||||
impl Component for ChangeUnixPassword {
|
||||
|
@ -98,9 +96,11 @@ impl Component for ChangeUnixPassword {
|
|||
},
|
||||
);
|
||||
}
|
||||
let tk = ctx.props().token.clone();
|
||||
ctx.link().send_future(async {
|
||||
match Self::update_unix_password(tk, fd.password_input).await {
|
||||
|
||||
let id = ctx.props().uat.uuid;
|
||||
|
||||
ctx.link().send_future(async move {
|
||||
match Self::update_unix_password(id, fd.password_input).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -257,14 +257,7 @@ impl Component for ChangeUnixPassword {
|
|||
}
|
||||
|
||||
impl ChangeUnixPassword {
|
||||
async fn update_unix_password(token: String, new_password: String) -> Result<Msg, FetchError> {
|
||||
let jwtu = JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse");
|
||||
|
||||
let uat: Jws<UserAuthToken> = jwtu
|
||||
.unsafe_release_without_verification()
|
||||
.expect_throw("Invalid UAT, unable to release ");
|
||||
|
||||
let id = uat.into_inner().uuid.to_string();
|
||||
async fn update_unix_password(id: Uuid, new_password: String) -> Result<Msg, FetchError> {
|
||||
let changereq_jsvalue = serde_json::to_string(&SingleStringRequest {
|
||||
value: new_password,
|
||||
})
|
||||
|
@ -283,10 +276,6 @@ impl ChangeUnixPassword {
|
|||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
// use anyhow::Error;
|
||||
use gloo::console;
|
||||
use kanidm_proto::v1::{
|
||||
AuthAllowed, AuthCredential, AuthMech, AuthRequest, AuthResponse, AuthState, AuthStep,
|
||||
AuthAllowed, AuthCredential, AuthIssueSession, AuthMech, AuthRequest, AuthResponse, AuthState,
|
||||
AuthStep,
|
||||
};
|
||||
use kanidm_proto::webauthn::PublicKeyCredential;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen_futures::{spawn_local, JsFuture};
|
||||
use web_sys::{CredentialRequestOptions, Request, RequestInit, RequestMode, Response};
|
||||
use web_sys::{
|
||||
CredentialRequestOptions, Request, RequestCredentials, RequestInit, RequestMode, Response,
|
||||
};
|
||||
use yew::prelude::*;
|
||||
use yew::virtual_dom::VNode;
|
||||
use yew_router::prelude::*;
|
||||
|
@ -78,7 +81,10 @@ impl From<FetchError> for LoginAppMsg {
|
|||
impl LoginApp {
|
||||
async fn auth_init(username: String) -> Result<LoginAppMsg, FetchError> {
|
||||
let authreq = AuthRequest {
|
||||
step: AuthStep::Init(username),
|
||||
step: AuthStep::Init2 {
|
||||
username,
|
||||
issue: AuthIssueSession::Cookie,
|
||||
},
|
||||
};
|
||||
let authreq_jsvalue = serde_json::to_string(&authreq)
|
||||
.map(|s| JsValue::from(&s))
|
||||
|
@ -87,6 +93,7 @@ impl LoginApp {
|
|||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
opts.body(Some(&authreq_jsvalue));
|
||||
|
||||
|
@ -141,6 +148,7 @@ impl LoginApp {
|
|||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
opts.body(Some(&authreq_jsvalue));
|
||||
|
||||
|
@ -542,6 +550,7 @@ impl Component for LoginApp {
|
|||
#[cfg(debug)]
|
||||
console::debug!("create".to_string());
|
||||
// Assume we are here for a good reason.
|
||||
// -- clear the bearer to prevent conflict
|
||||
models::clear_bearer_token();
|
||||
// Do we have a login hint?
|
||||
let inputvalue = models::pop_login_hint().unwrap_or_else(|| "".to_string());
|
||||
|
@ -835,11 +844,22 @@ impl Component for LoginApp {
|
|||
self.state = LoginState::Denied(reason);
|
||||
true
|
||||
}
|
||||
AuthState::Success(bearer_token) => {
|
||||
AuthState::Success(_bearer_token) => {
|
||||
// Store the bearer here!
|
||||
/*
|
||||
models::set_bearer_token(bearer_token);
|
||||
self.state = LoginState::Authenticated;
|
||||
true
|
||||
*/
|
||||
self.state = LoginState::Error {
|
||||
emsg: "Invalid Issued Session Type, expected cookie".to_string(),
|
||||
kopid: None,
|
||||
};
|
||||
true
|
||||
}
|
||||
AuthState::SuccessCookie => {
|
||||
self.state = LoginState::Authenticated;
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,19 +12,6 @@ use yew_router::prelude::{AnyHistory, History};
|
|||
use crate::manager::Route;
|
||||
use crate::views::ViewRoute;
|
||||
|
||||
pub fn get_bearer_token() -> Option<String> {
|
||||
let prev_session: Result<String, _> = PersistentStorage::get("kanidm_bearer_token");
|
||||
#[cfg(debug)]
|
||||
console::debug!(format!("kanidm_bearer_token -> {:?}", prev_session).as_str());
|
||||
|
||||
prev_session.ok()
|
||||
}
|
||||
|
||||
pub fn set_bearer_token(bearer_token: String) {
|
||||
PersistentStorage::set("kanidm_bearer_token", bearer_token)
|
||||
.expect_throw("failed to set header");
|
||||
}
|
||||
|
||||
pub fn clear_bearer_token() {
|
||||
PersistentStorage::delete("kanidm_bearer_token");
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ pub use kanidm_proto::oauth2::{
|
|||
};
|
||||
use wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, RequestMode, RequestRedirect, Response};
|
||||
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, RequestRedirect, Response};
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
|
@ -15,14 +15,12 @@ use crate::manager::Route;
|
|||
use crate::{models, utils};
|
||||
|
||||
enum State {
|
||||
// We don't have a token, or something is invalid.
|
||||
LoginRequired,
|
||||
// We are in the process of check the auth token to be sure we can proceed.
|
||||
TokenCheck(String),
|
||||
TokenCheck,
|
||||
// Token check done, lets do it.
|
||||
SubmitAuthReq(String),
|
||||
SubmitAuthReq,
|
||||
Consent {
|
||||
token: String,
|
||||
client_name: String,
|
||||
#[allow(dead_code)]
|
||||
scopes: Vec<String>,
|
||||
|
@ -39,6 +37,7 @@ pub struct Oauth2App {
|
|||
}
|
||||
|
||||
pub enum Oauth2Msg {
|
||||
LoginRequired,
|
||||
LoginProceed,
|
||||
ConsentGranted(String),
|
||||
TokenValid,
|
||||
|
@ -68,20 +67,17 @@ impl From<FetchError> for Oauth2Msg {
|
|||
}
|
||||
|
||||
impl Oauth2App {
|
||||
async fn fetch_token_valid(token: String) -> Result<Oauth2Msg, FetchError> {
|
||||
async fn fetch_session_valid() -> Result<Oauth2Msg, FetchError> {
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
let request = Request::new_with_str_and_init("/v1/auth/valid", &opts)?;
|
||||
|
||||
request
|
||||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
@ -91,7 +87,7 @@ impl Oauth2App {
|
|||
if status == 200 {
|
||||
Ok(Oauth2Msg::TokenValid)
|
||||
} else if status == 401 {
|
||||
Ok(Oauth2Msg::LoginProceed)
|
||||
Ok(Oauth2Msg::LoginRequired)
|
||||
} else {
|
||||
let headers = resp.headers();
|
||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||
|
@ -102,10 +98,7 @@ impl Oauth2App {
|
|||
}
|
||||
}
|
||||
|
||||
async fn fetch_authreq(
|
||||
token: String,
|
||||
authreq: AuthorisationRequest,
|
||||
) -> Result<Oauth2Msg, FetchError> {
|
||||
async fn fetch_authreq(authreq: AuthorisationRequest) -> Result<Oauth2Msg, FetchError> {
|
||||
let authreq_jsvalue = serde_json::to_string(&authreq)
|
||||
.map(|s| JsValue::from(&s))
|
||||
.expect_throw("Failed to serialise authreq");
|
||||
|
@ -113,6 +106,7 @@ impl Oauth2App {
|
|||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
opts.body(Some(&authreq_jsvalue));
|
||||
|
||||
|
@ -121,10 +115,6 @@ impl Oauth2App {
|
|||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
@ -173,10 +163,7 @@ impl Oauth2App {
|
|||
}
|
||||
}
|
||||
|
||||
async fn fetch_consent_token(
|
||||
token: String,
|
||||
consent_token: String,
|
||||
) -> Result<Oauth2Msg, FetchError> {
|
||||
async fn fetch_consent_token(consent_token: String) -> Result<Oauth2Msg, FetchError> {
|
||||
let consentreq_jsvalue = serde_json::to_string(&consent_token)
|
||||
.map(|s| JsValue::from(&s))
|
||||
.expect_throw("Failed to serialise consent_req");
|
||||
|
@ -185,6 +172,7 @@ impl Oauth2App {
|
|||
opts.method("POST");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.redirect(RequestRedirect::Manual);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
opts.body(Some(&consentreq_jsvalue));
|
||||
|
||||
|
@ -193,10 +181,6 @@ impl Oauth2App {
|
|||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
@ -274,25 +258,17 @@ impl Component for Oauth2App {
|
|||
// Push the request down. This covers if we move to LoginRequired.
|
||||
models::push_oauth2_authorisation_request(query);
|
||||
|
||||
match models::get_bearer_token() {
|
||||
Some(token) => {
|
||||
// Start the fetch req.
|
||||
// Put the fetch handle into the consent type.
|
||||
let token_c = token.clone();
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_token_valid(token_c).await {
|
||||
match Self::fetch_session_valid().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
|
||||
Oauth2App {
|
||||
state: State::TokenCheck(token),
|
||||
}
|
||||
}
|
||||
None => Oauth2App {
|
||||
state: State::LoginRequired,
|
||||
},
|
||||
state: State::TokenCheck,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,6 +283,10 @@ impl Component for Oauth2App {
|
|||
console::debug!("oauth2::update");
|
||||
|
||||
match msg {
|
||||
Oauth2Msg::LoginRequired => {
|
||||
self.state = State::LoginRequired;
|
||||
true
|
||||
}
|
||||
Oauth2Msg::LoginProceed => {
|
||||
models::push_return_location(models::Location::Manager(Route::Oauth2));
|
||||
|
||||
|
@ -322,15 +302,14 @@ impl Component for Oauth2App {
|
|||
let ar = models::pop_oauth2_authorisation_request();
|
||||
|
||||
self.state = match (&self.state, ar) {
|
||||
(State::TokenCheck(token), Some(ar)) => {
|
||||
let token_c = token.clone();
|
||||
(State::TokenCheck, Some(ar)) => {
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_authreq(token_c, ar).await {
|
||||
match Self::fetch_authreq(ar).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
State::SubmitAuthReq(token.clone())
|
||||
State::SubmitAuthReq
|
||||
}
|
||||
_ => {
|
||||
console::error!("Invalid state transition");
|
||||
|
@ -346,8 +325,7 @@ impl Component for Oauth2App {
|
|||
consent_token,
|
||||
} => {
|
||||
self.state = match &self.state {
|
||||
State::SubmitAuthReq(token) => State::Consent {
|
||||
token: token.clone(),
|
||||
State::SubmitAuthReq => State::Consent {
|
||||
client_name,
|
||||
scopes,
|
||||
pii_scopes,
|
||||
|
@ -363,15 +341,13 @@ impl Component for Oauth2App {
|
|||
Oauth2Msg::ConsentGranted(_) => {
|
||||
self.state = match &self.state {
|
||||
State::Consent {
|
||||
token,
|
||||
consent_token,
|
||||
client_name,
|
||||
..
|
||||
} => {
|
||||
let token_c = token.clone();
|
||||
let cr_c = consent_token.clone();
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_consent_token(token_c, cr_c).await {
|
||||
match Self::fetch_consent_token(cr_c).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -453,7 +429,6 @@ impl Component for Oauth2App {
|
|||
}
|
||||
}
|
||||
State::Consent {
|
||||
token: _,
|
||||
client_name,
|
||||
scopes: _,
|
||||
pii_scopes,
|
||||
|
@ -509,7 +484,7 @@ impl Component for Oauth2App {
|
|||
</div>
|
||||
}
|
||||
}
|
||||
State::SubmitAuthReq(_) | State::TokenCheck(_) => {
|
||||
State::SubmitAuthReq | State::TokenCheck => {
|
||||
html! {
|
||||
<div class="alert alert-light" role="alert">
|
||||
<h2 class="text-center">{ "Processing ... " }</h2>
|
||||
|
|
|
@ -3,7 +3,9 @@ use gloo_net::http::Request;
|
|||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
pub use web_sys::InputEvent;
|
||||
use web_sys::{Document, Event, HtmlElement, HtmlInputElement, RequestMode, Window};
|
||||
use web_sys::{
|
||||
Document, Event, HtmlElement, HtmlInputElement, RequestCredentials, RequestMode, Window,
|
||||
};
|
||||
use yew::virtual_dom::VNode;
|
||||
use yew::{html, Html};
|
||||
|
||||
|
@ -85,11 +87,11 @@ pub fn do_footer() -> VNode {
|
|||
}
|
||||
|
||||
/// Builds a request object to a server-local endpoint with the usual requirements
|
||||
pub fn init_request(endpoint: &str, token: &str) -> gloo_net::http::Request {
|
||||
pub fn init_request(endpoint: &str) -> gloo_net::http::Request {
|
||||
Request::new(endpoint)
|
||||
.mode(RequestMode::SameOrigin)
|
||||
.credentials(RequestCredentials::SameOrigin)
|
||||
.header("content-type", "application/json")
|
||||
.header("authorization", format!("Bearer {}", token).as_str())
|
||||
}
|
||||
|
||||
pub fn do_alert_error(alert_title: &str, alert_message: Option<&str>) -> Html {
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use compact_jwt::{Jws, JwsUnverified};
|
||||
use gloo::console;
|
||||
use kanidm_proto::v1::UserAuthToken;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
|
@ -72,28 +70,24 @@ enum State {
|
|||
LoginRequired,
|
||||
LoggingOut,
|
||||
Verifying,
|
||||
Authenticated(String),
|
||||
Authenticated(UserAuthToken),
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Properties)]
|
||||
pub struct ViewProps {
|
||||
pub token: String,
|
||||
// pub current_user_entry: Option<Entry>,
|
||||
pub current_user_uat: Option<UserAuthToken>,
|
||||
pub current_user_uat: UserAuthToken,
|
||||
}
|
||||
|
||||
pub struct ViewsApp {
|
||||
state: State,
|
||||
// pub current_user_entry: Option<Entry>,
|
||||
pub current_user_uat: Option<UserAuthToken>,
|
||||
}
|
||||
|
||||
pub enum ViewsMsg {
|
||||
Verified(String),
|
||||
Verified,
|
||||
ProfileInfoRecieved { uat: UserAuthToken },
|
||||
Logout,
|
||||
LogoutComplete,
|
||||
ProfileInfoRecieved { uat: UserAuthToken },
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
|
@ -117,25 +111,18 @@ impl Component for ViewsApp {
|
|||
// Ensure the token is valid before we proceed. Could be
|
||||
// due to a session expiry or something else, but we want to make
|
||||
// sure we are really authenticated before we proceed.
|
||||
let state = match models::get_bearer_token() {
|
||||
Some(token) => {
|
||||
|
||||
// Send off the validation event.
|
||||
ctx.link().send_future(async {
|
||||
match Self::check_token_valid(token).await {
|
||||
match Self::check_session_valid().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
|
||||
State::Verifying
|
||||
}
|
||||
None => State::LoginRequired,
|
||||
};
|
||||
let state = State::Verifying;
|
||||
|
||||
ViewsApp {
|
||||
state,
|
||||
current_user_uat: None,
|
||||
}
|
||||
ViewsApp { state }
|
||||
}
|
||||
|
||||
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
||||
|
@ -148,42 +135,34 @@ impl Component for ViewsApp {
|
|||
#[cfg(debug)]
|
||||
console::debug!("views::update");
|
||||
match msg {
|
||||
ViewsMsg::Verified(token) => {
|
||||
let tk = token.clone();
|
||||
self.state = State::Authenticated(token);
|
||||
// Populate the user profile
|
||||
ViewsMsg::Verified => {
|
||||
// Populate the user profile now we know their session is valid.
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_user_data(tk).await {
|
||||
match Self::fetch_user_data().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
true
|
||||
}
|
||||
ViewsMsg::ProfileInfoRecieved { uat } => {
|
||||
self.state = State::Authenticated(uat);
|
||||
true
|
||||
}
|
||||
ViewsMsg::Logout => {
|
||||
match models::get_bearer_token() {
|
||||
Some(tk) => {
|
||||
models::clear_bearer_token();
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_logout(tk).await {
|
||||
match Self::fetch_logout().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
self.state = State::LoggingOut;
|
||||
}
|
||||
None => self.state = State::LoginRequired,
|
||||
}
|
||||
true
|
||||
}
|
||||
ViewsMsg::LogoutComplete => {
|
||||
self.state = State::LoginRequired;
|
||||
true
|
||||
}
|
||||
ViewsMsg::ProfileInfoRecieved { uat } => {
|
||||
self.current_user_uat = Some(uat);
|
||||
true
|
||||
}
|
||||
ViewsMsg::Error { emsg, kopid } => {
|
||||
self.state = State::Error { emsg, kopid };
|
||||
true
|
||||
|
@ -227,7 +206,7 @@ impl Component for ViewsApp {
|
|||
</main>
|
||||
}
|
||||
}
|
||||
State::Authenticated(_) => self.view_authenticated(ctx),
|
||||
State::Authenticated(uat) => self.view_authenticated(ctx, uat),
|
||||
State::Error { emsg, kopid } => {
|
||||
html! {
|
||||
<main class="form-signin">
|
||||
|
@ -254,8 +233,8 @@ impl Component for ViewsApp {
|
|||
|
||||
impl ViewsApp {
|
||||
/// The base page for the user dashboard
|
||||
fn view_authenticated(&self, ctx: &Context<Self>) -> Html {
|
||||
let current_user_uat = self.current_user_uat.clone();
|
||||
fn view_authenticated(&self, ctx: &Context<Self>, uat: &UserAuthToken) -> Html {
|
||||
let current_user_uat = uat.clone();
|
||||
|
||||
// WARN set dash-body against body here?
|
||||
html! {
|
||||
|
@ -337,17 +316,13 @@ impl ViewsApp {
|
|||
<main class="p-3 x-auto">
|
||||
<Switch<ViewRoute> render={ Switch::render(move |route: &ViewRoute| {
|
||||
// safety - can't panic because to get to this location we MUST be authenticated!
|
||||
let token =
|
||||
models::get_bearer_token().expect_throw("Invalid state, bearer token must be present!");
|
||||
|
||||
match route {
|
||||
|
||||
ViewRoute::Admin => html!{
|
||||
<Switch<AdminRoute> render={ Switch::render(admin_routes) } />
|
||||
},
|
||||
ViewRoute::Apps => html! { <AppsApp /> },
|
||||
ViewRoute::Profile => html! { <ProfileApp token={ token } current_user_uat={ current_user_uat.clone() } /> },
|
||||
ViewRoute::Security => html! { <SecurityApp token={ token } current_user_uat={ current_user_uat.clone() } /> },
|
||||
ViewRoute::Profile => html! { <ProfileApp current_user_uat={ current_user_uat.clone() } /> },
|
||||
ViewRoute::Security => html! { <SecurityApp current_user_uat={ current_user_uat.clone() } /> },
|
||||
ViewRoute::NotFound => html! {
|
||||
<Redirect<Route> to={Route::NotFound}/>
|
||||
},
|
||||
|
@ -358,10 +333,11 @@ impl ViewsApp {
|
|||
}
|
||||
}
|
||||
|
||||
async fn check_token_valid(token: String) -> Result<ViewsMsg, FetchError> {
|
||||
async fn check_session_valid() -> Result<ViewsMsg, FetchError> {
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
let request = Request::new_with_str_and_init("/v1/auth/valid", &opts)?;
|
||||
|
||||
|
@ -369,10 +345,6 @@ impl ViewsApp {
|
|||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
@ -380,9 +352,8 @@ impl ViewsApp {
|
|||
let status = resp.status();
|
||||
|
||||
if status == 200 {
|
||||
Ok(ViewsMsg::Verified(token))
|
||||
Ok(ViewsMsg::Verified)
|
||||
} else if status == 401 {
|
||||
// Not valid, re-auth
|
||||
Ok(ViewsMsg::LogoutComplete)
|
||||
} else {
|
||||
let headers = resp.headers();
|
||||
|
@ -393,25 +364,48 @@ impl ViewsApp {
|
|||
}
|
||||
}
|
||||
|
||||
async fn fetch_user_data(token: String) -> Result<ViewsMsg, FetchError> {
|
||||
let jwtu = JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse");
|
||||
|
||||
let uat: Jws<UserAuthToken> = jwtu
|
||||
.unsafe_release_without_verification()
|
||||
.expect_throw("Invalid UAT, unable to release");
|
||||
|
||||
// We could get rid of this since the token is all we need?
|
||||
//
|
||||
// How will we manage this on changes?
|
||||
Ok(ViewsMsg::ProfileInfoRecieved {
|
||||
uat: uat.into_inner(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn fetch_logout(token: String) -> Result<ViewsMsg, FetchError> {
|
||||
async fn fetch_user_data() -> Result<ViewsMsg, FetchError> {
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
let request = Request::new_with_str_and_init("/v1/self/_uat", &opts)?;
|
||||
|
||||
request
|
||||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
||||
let status = resp.status();
|
||||
|
||||
if status == 200 {
|
||||
let jsval = JsFuture::from(resp.json()?).await?;
|
||||
let uat: UserAuthToken = serde_wasm_bindgen::from_value(jsval)
|
||||
.map_err(|e| {
|
||||
let e_msg = format!("serde error -> {:?}", e);
|
||||
console::error!(e_msg.as_str());
|
||||
})
|
||||
.expect_throw("Invalid response type");
|
||||
|
||||
Ok(ViewsMsg::ProfileInfoRecieved { uat })
|
||||
} else {
|
||||
let headers = resp.headers();
|
||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||
let text = JsFuture::from(resp.text()?).await?;
|
||||
let emsg = text.as_string().unwrap_or_else(|| "".to_string());
|
||||
Ok(ViewsMsg::Error { emsg, kopid })
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_logout() -> Result<ViewsMsg, FetchError> {
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
let request = Request::new_with_str_and_init("/v1/logout", &opts)?;
|
||||
|
||||
|
@ -419,10 +413,6 @@ impl ViewsApp {
|
|||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
|
|
@ -1,57 +1,202 @@
|
|||
use gloo::console;
|
||||
use wasm_bindgen::UnwrapThrowExt;
|
||||
use kanidm_proto::v1::{Entry, WhoamiResponse};
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
|
||||
use yew::prelude::*;
|
||||
use yew::virtual_dom::VNode;
|
||||
|
||||
use crate::constants::CSS_PAGE_HEADER;
|
||||
use crate::error::FetchError;
|
||||
use crate::utils;
|
||||
use crate::views::ViewProps;
|
||||
|
||||
struct Profile {
|
||||
mail_primary: Option<String>,
|
||||
spn: String,
|
||||
displayname: String,
|
||||
groups: Vec<String>,
|
||||
uuid: Uuid,
|
||||
}
|
||||
|
||||
impl TryFrom<Entry> for Profile {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(entry: Entry) -> Result<Self, Self::Error> {
|
||||
console::error!("Entry Dump", format!("{:?}", entry).to_string());
|
||||
|
||||
let uuid = entry
|
||||
.attrs
|
||||
.get("uuid")
|
||||
.and_then(|list| list.get(0))
|
||||
.ok_or_else(|| "Missing UUID".to_string())
|
||||
.and_then(|uuid_str| {
|
||||
Uuid::parse_str(uuid_str).map_err(|_| "Invalid UUID".to_string())
|
||||
})?;
|
||||
|
||||
let spn = entry
|
||||
.attrs
|
||||
.get("spn")
|
||||
.and_then(|list| list.get(0))
|
||||
.cloned()
|
||||
.ok_or_else(|| "Missing SPN".to_string())?;
|
||||
|
||||
let displayname = entry
|
||||
.attrs
|
||||
.get("displayname")
|
||||
.and_then(|list| list.get(0))
|
||||
.cloned()
|
||||
.ok_or_else(|| "Missing displayname".to_string())?;
|
||||
|
||||
let groups = entry.attrs.get("memberof").cloned().unwrap_or_default();
|
||||
|
||||
let mail_primary = entry
|
||||
.attrs
|
||||
.get("mail_primary")
|
||||
.and_then(|list| list.get(0))
|
||||
.cloned();
|
||||
|
||||
Ok(Profile {
|
||||
mail_primary,
|
||||
spn,
|
||||
displayname,
|
||||
groups,
|
||||
uuid,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
enum State {
|
||||
Loading,
|
||||
Ready(Profile),
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
pub enum Msg {
|
||||
Profile { entry: Entry },
|
||||
Error { emsg: String, kopid: Option<String> },
|
||||
}
|
||||
|
||||
impl From<FetchError> for Msg {
|
||||
fn from(fe: FetchError) -> Self {
|
||||
Msg::Error {
|
||||
emsg: fe.as_string(),
|
||||
kopid: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User Profile UI
|
||||
pub struct ProfileApp {}
|
||||
pub struct ProfileApp {
|
||||
state: State,
|
||||
}
|
||||
|
||||
impl Component for ProfileApp {
|
||||
type Message = ();
|
||||
type Message = Msg;
|
||||
type Properties = ViewProps;
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
#[cfg(debug)]
|
||||
console::debug!("views::profile::create");
|
||||
|
||||
ProfileApp {}
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_profile_data().await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
});
|
||||
|
||||
ProfileApp {
|
||||
state: State::Loading,
|
||||
}
|
||||
}
|
||||
|
||||
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
||||
console::debug!(format!(
|
||||
"views::profile::changed current_user: {:?}",
|
||||
ctx.props().current_user_uat,
|
||||
));
|
||||
fn changed(&mut self, _ctx: &Context<Self>) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &Context<Self>, _msg: Self::Message) -> bool {
|
||||
console::debug!(format!(
|
||||
"views::profile::update current_user: {:?}",
|
||||
ctx.props().current_user_uat,
|
||||
));
|
||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
#[cfg(debug)]
|
||||
console::debug!("profile::update");
|
||||
match msg {
|
||||
Msg::Profile { entry } => {
|
||||
self.state = match Profile::try_from(entry) {
|
||||
Ok(profile) => State::Ready(profile),
|
||||
Err(emsg) => State::Error { emsg, kopid: None },
|
||||
};
|
||||
true
|
||||
}
|
||||
Msg::Error { emsg, kopid } => {
|
||||
self.state = State::Error { emsg, kopid };
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
|
||||
#[cfg(debug)]
|
||||
console::debug!("views::profile::rendered");
|
||||
}
|
||||
|
||||
/// UI view for the user profile
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let pagecontent = match &ctx.props().current_user_uat {
|
||||
None => {
|
||||
match &self.state {
|
||||
State::Loading => {
|
||||
html! {
|
||||
<h2>
|
||||
{"Loading user info..."}
|
||||
</h2>
|
||||
<main class="text-center form-signin h-100">
|
||||
<div class="vert-center">
|
||||
<div class="spinner-border text-dark" role="status">
|
||||
<span class="visually-hidden">{ "Loading..." }</span>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
Some(uat) => {
|
||||
let mail_primary = match uat.mail_primary.as_ref() {
|
||||
State::Ready(profile) => self.view_profile(ctx, &profile),
|
||||
State::Error { emsg, kopid } => self.do_alert_error(
|
||||
"An error has occured 😔 ",
|
||||
Some(
|
||||
format!(
|
||||
"{}\n\n{}",
|
||||
emsg.as_str(),
|
||||
if let Some(opid) = kopid.as_ref() {
|
||||
format!("Operation ID: {}", opid.clone())
|
||||
} else {
|
||||
"Error occurred client-side.".to_string()
|
||||
}
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
ctx,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProfileApp {
|
||||
fn do_alert_error(
|
||||
&self,
|
||||
alert_title: &str,
|
||||
alert_message: Option<&str>,
|
||||
_ctx: &Context<Self>,
|
||||
) -> VNode {
|
||||
html! {
|
||||
<div class="container">
|
||||
<div class="row justify-content-md-center">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<p><strong>{ alert_title }</strong></p>
|
||||
if let Some(value) = alert_message {
|
||||
<p>{ value }</p>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
/// UI view for the user profile
|
||||
fn view_profile(&self, _ctx: &Context<Self>, profile: &Profile) -> Html {
|
||||
let mail_primary = match profile.mail_primary.as_ref() {
|
||||
Some(email_address) => {
|
||||
html! {
|
||||
<a href={ format!("mailto:{}", &email_address)}>
|
||||
|
@ -62,21 +207,21 @@ impl Component for ProfileApp {
|
|||
None => html! { {"<primary email is unset>"}},
|
||||
};
|
||||
|
||||
let spn = &uat.spn.to_owned();
|
||||
let spn = &profile.spn.to_owned();
|
||||
let spn_split = spn.split('@');
|
||||
let username = &spn_split.clone().next().unwrap_throw();
|
||||
let domain = &spn_split.clone().last().unwrap_throw();
|
||||
let display_name = uat.displayname.to_owned();
|
||||
let user_groups: Vec<String> = uat
|
||||
let display_name = profile.displayname.to_owned();
|
||||
let user_groups: Vec<String> = profile
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| {
|
||||
.map(|group_spn| {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
group.spn.split('@').next().unwrap().to_string()
|
||||
group_spn.split('@').next().unwrap().to_string()
|
||||
})
|
||||
.collect();
|
||||
|
||||
html! {
|
||||
let pagecontent = html! {
|
||||
<dl class="row">
|
||||
<dt class="col-6">{ "Display Name" }</dt>
|
||||
<dd class="col">{ display_name }</dd>
|
||||
|
@ -118,12 +263,10 @@ impl Component for ProfileApp {
|
|||
{ "User's UUID" }
|
||||
</dt>
|
||||
<dd class="col">
|
||||
{ format!("{}", &uat.uuid ) }
|
||||
{ format!("{}", &profile.uuid ) }
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
}
|
||||
}
|
||||
};
|
||||
html! {
|
||||
<>
|
||||
|
@ -135,4 +278,43 @@ impl Component for ProfileApp {
|
|||
</>
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_profile_data() -> Result<Msg, FetchError> {
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
let request = Request::new_with_str_and_init("/v1/self", &opts)?;
|
||||
|
||||
request
|
||||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
let resp: Response = resp_value.dyn_into().expect_throw("Invalid response type");
|
||||
let status = resp.status();
|
||||
|
||||
if status == 200 {
|
||||
let jsval = JsFuture::from(resp.json()?).await?;
|
||||
let state: WhoamiResponse = serde_wasm_bindgen::from_value(jsval)
|
||||
.map_err(|e| {
|
||||
let e_msg = format!("serde error -> {:?}", e);
|
||||
console::error!(e_msg.as_str());
|
||||
})
|
||||
.expect_throw("Invalid response type");
|
||||
|
||||
Ok(Msg::Profile {
|
||||
entry: state.youare,
|
||||
})
|
||||
} else {
|
||||
let headers = resp.headers();
|
||||
let kopid = headers.get("x-kanidm-opid").ok().flatten();
|
||||
let text = JsFuture::from(resp.text()?).await?;
|
||||
let emsg = text.as_string().unwrap_or_else(|| "".to_string());
|
||||
Ok(Msg::Error { emsg, kopid })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use compact_jwt::{Jws, JwsUnverified};
|
||||
#[cfg(debug)]
|
||||
use gloo::console;
|
||||
use kanidm_proto::v1::{CUSessionToken, CUStatus, UiHint, UserAuthToken};
|
||||
use kanidm_proto::v1::{CUSessionToken, CUStatus, UiHint};
|
||||
use wasm_bindgen::{JsCast, UnwrapThrowExt};
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
use web_sys::{Request, RequestCredentials, RequestInit, RequestMode, Response};
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
|
@ -74,19 +71,12 @@ impl Component for SecurityApp {
|
|||
Msg::RequestCredentialUpdate => {
|
||||
// Submit a req to init the session.
|
||||
// The uuid we want to submit against - hint, it's us.
|
||||
let token = ctx.props().token.clone();
|
||||
|
||||
let jwtu =
|
||||
JwsUnverified::from_str(&token).expect_throw("Invalid UAT, unable to parse");
|
||||
|
||||
let uat: Jws<UserAuthToken> = jwtu
|
||||
.unsafe_release_without_verification()
|
||||
.expect_throw("Unvalid UAT, unable to release ");
|
||||
|
||||
let id = uat.into_inner().uuid.to_string();
|
||||
let uat = &ctx.props().current_user_uat;
|
||||
let id = uat.uuid.to_string();
|
||||
|
||||
ctx.link().send_future(async {
|
||||
match Self::fetch_token_valid(id, token).await {
|
||||
match Self::fetch_token_valid(id).await {
|
||||
Ok(v) => v,
|
||||
Err(v) => v.into(),
|
||||
}
|
||||
|
@ -142,7 +132,7 @@ impl Component for SecurityApp {
|
|||
_ => html! { <></> },
|
||||
};
|
||||
|
||||
let current_user_uat = ctx.props().current_user_uat.clone();
|
||||
let uat = ctx.props().current_user_uat.clone();
|
||||
|
||||
html! {
|
||||
<>
|
||||
|
@ -166,25 +156,24 @@ impl Component for SecurityApp {
|
|||
</p>
|
||||
</div>
|
||||
<hr/>
|
||||
if let Some(uat) = current_user_uat {
|
||||
if uat.ui_hints.contains(&UiHint::PosixAccount) {
|
||||
<div>
|
||||
<p>
|
||||
<ChangeUnixPassword token={ctx.props().token.clone()}></ChangeUnixPassword>
|
||||
<ChangeUnixPassword uat={ uat }/>
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SecurityApp {
|
||||
async fn fetch_token_valid(id: String, token: String) -> Result<Msg, FetchError> {
|
||||
async fn fetch_token_valid(id: String) -> Result<Msg, FetchError> {
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("GET");
|
||||
opts.mode(RequestMode::SameOrigin);
|
||||
opts.credentials(RequestCredentials::SameOrigin);
|
||||
|
||||
let uri = format!("/v1/person/{}/_credential/_update", id);
|
||||
|
||||
|
@ -194,10 +183,6 @@ impl SecurityApp {
|
|||
.headers()
|
||||
.set("content-type", "application/json")
|
||||
.expect_throw("failed to set header");
|
||||
request
|
||||
.headers()
|
||||
.set("authorization", format!("Bearer {}", token).as_str())
|
||||
.expect_throw("failed to set header");
|
||||
|
||||
let window = utils::window();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?;
|
||||
|
|
Loading…
Reference in a new issue