oidc authentication
This commit is contained in:
519
backend/Cargo.lock
generated
519
backend/Cargo.lock
generated
@@ -186,6 +186,22 @@ version = "1.8.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "biscuit"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e28fc7c56c61743a01d0d1b73e4fed68b8a4f032ea3a2d4bb8c6520a33fc05a"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"data-encoding",
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
@@ -256,6 +272,7 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
@@ -275,6 +292,16 @@ version = "0.9.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -330,6 +357,47 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"darling_macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.20.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
@@ -491,6 +559,21 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||||
|
dependencies = [
|
||||||
|
"foreign-types-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foreign-types-shared"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -602,7 +685,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
"wasi 0.14.2+wasi-0.2.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -739,6 +834,23 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -747,14 +859,22 @@ version = "0.1.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
|
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"ipnet",
|
||||||
|
"libc",
|
||||||
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -867,6 +987,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -909,6 +1035,22 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ipnet"
|
||||||
|
version = "2.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iri-string"
|
||||||
|
version = "0.7.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@@ -1038,7 +1180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1059,6 +1201,23 @@ dependencies = [
|
|||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "native-tls"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"openssl",
|
||||||
|
"openssl-probe",
|
||||||
|
"openssl-sys",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
"security-framework-sys",
|
||||||
|
"tempfile",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@@ -1069,6 +1228,16 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
@@ -1137,6 +1306,69 @@ version = "1.21.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openid"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25913f03f3d3bcc2e55021067193b31f79a27283c186c91ece5f93b69b035b44"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"biscuit",
|
||||||
|
"chrono",
|
||||||
|
"lazy_static",
|
||||||
|
"mime",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"url",
|
||||||
|
"validator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl"
|
||||||
|
version = "0.10.73"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"foreign-types",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"openssl-macros",
|
||||||
|
"openssl-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-probe"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openssl-sys"
|
||||||
|
version = "0.9.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
"vcpkg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -1261,6 +1493,28 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error2"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
@@ -1279,6 +1533,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -1306,7 +1566,7 @@ version = "0.6.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1347,6 +1607,56 @@ version = "0.8.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.12.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-tls",
|
||||||
|
"hyper-util",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"native-tls",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower",
|
||||||
|
"tower-http",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rsa"
|
name = "rsa"
|
||||||
version = "0.9.8"
|
version = "0.9.8"
|
||||||
@@ -1386,6 +1696,15 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustversion"
|
name = "rustversion"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
@@ -1398,12 +1717,44 @@ version = "1.0.20"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schannel"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"core-foundation",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
"security-framework-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "security-framework-sys"
|
||||||
|
version = "2.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||||
|
dependencies = [
|
||||||
|
"core-foundation-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
@@ -1430,6 +1781,7 @@ version = "1.0.140"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -1599,7 +1951,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
@@ -1684,7 +2036,7 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -1724,7 +2076,7 @@ dependencies = [
|
|||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -1751,7 +2103,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
@@ -1775,6 +2127,12 @@ dependencies = [
|
|||||||
"unicode-properties",
|
"unicode-properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
@@ -1797,6 +2155,9 @@ name = "sync_wrapper"
|
|||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
@@ -1809,13 +2170,46 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tempfile"
|
||||||
|
version = "3.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||||
|
dependencies = [
|
||||||
|
"fastrand",
|
||||||
|
"getrandom 0.3.3",
|
||||||
|
"once_cell",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl 1.0.69",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl 2.0.12",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1925,6 +2319,16 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-native-tls"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.17"
|
version = "0.1.17"
|
||||||
@@ -1980,12 +2384,14 @@ dependencies = [
|
|||||||
"http-body-util",
|
"http-body-util",
|
||||||
"http-range-header",
|
"http-range-header",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
|
"iri-string",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -2061,6 +2467,12 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "try-lock"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
@@ -2106,6 +2518,12 @@ version = "0.2.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
@@ -2115,6 +2533,7 @@ dependencies = [
|
|||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna",
|
"idna",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2134,6 +2553,36 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "validator"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303"
|
||||||
|
dependencies = [
|
||||||
|
"idna",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
"validator_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "validator_derive"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro-error2",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "valuable"
|
name = "valuable"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -2152,6 +2601,15 @@ version = "0.9.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "want"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||||
|
dependencies = [
|
||||||
|
"try-lock",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warren"
|
name = "warren"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2168,18 +2626,20 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"hex",
|
"hex",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
"openid",
|
||||||
"regex",
|
"regex",
|
||||||
"rustix",
|
"rustix",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2189,6 +2649,15 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.14.2+wasi-0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen-rt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasite"
|
name = "wasite"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2221,6 +2690,19 @@ dependencies = [
|
|||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.100"
|
version = "0.2.100"
|
||||||
@@ -2253,6 +2735,16 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.77"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whoami"
|
name = "whoami"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@@ -2492,6 +2984,15 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen-rt"
|
||||||
|
version = "0.39.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ dotenv = "0.15.0"
|
|||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
|
openid = "0.17.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
rustix = { version = "1.0.8", features = ["fs"] }
|
rustix = { version = "1.0.8", features = ["fs"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
@@ -36,4 +37,5 @@ tower = "0.5.2"
|
|||||||
tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
tower-http = { version = "0.6.6", features = ["cors", "fs", "trace"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
|
url = "2.5.4"
|
||||||
uuid = { version = "1.17.0", features = ["serde"] }
|
uuid = { version = "1.17.0", features = ["serde"] }
|
||||||
|
|||||||
2
backend/migrations/20250808160437_users_oidc.sql
Normal file
2
backend/migrations/20250808160437_users_oidc.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE users ALTER COLUMN hash DROP NOT NULL;
|
||||||
|
ALTER TABLE users ADD COLUMN oidc_sub VARCHAR UNIQUE;
|
||||||
@@ -6,6 +6,7 @@ use warren::{
|
|||||||
file_system::{FileSystem, FileSystemConfig},
|
file_system::{FileSystem, FileSystemConfig},
|
||||||
metrics_debug_logger::MetricsDebugLogger,
|
metrics_debug_logger::MetricsDebugLogger,
|
||||||
notifier_debug_logger::NotifierDebugLogger,
|
notifier_debug_logger::NotifierDebugLogger,
|
||||||
|
oidc::{Oidc, OidcConfig},
|
||||||
postgres::{Postgres, PostgresConfig},
|
postgres::{Postgres, PostgresConfig},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -39,8 +40,20 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
fs_service.clone(),
|
fs_service.clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let auth_service =
|
let oidc_service = if let Ok(oidc_config) = OidcConfig::from_env() {
|
||||||
domain::warren::service::auth::Service::new(postgres, metrics, notifier, config.auth);
|
let repo = Oidc::new(&oidc_config).await?;
|
||||||
|
Some(domain::oidc::service::Service::new(repo, metrics, notifier))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let auth_service = domain::warren::service::auth::Service::new(
|
||||||
|
postgres,
|
||||||
|
metrics,
|
||||||
|
notifier,
|
||||||
|
config.auth,
|
||||||
|
oidc_service,
|
||||||
|
);
|
||||||
|
|
||||||
let server_config = HttpServerConfig::new(
|
let server_config = HttpServerConfig::new(
|
||||||
&config.server_address,
|
&config.server_address,
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
pub mod oidc;
|
||||||
pub mod warren;
|
pub mod warren;
|
||||||
|
|||||||
4
backend/src/lib/domain/oidc/mod.rs
Normal file
4
backend/src/lib/domain/oidc/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod models;
|
||||||
|
pub mod ports;
|
||||||
|
pub mod requests;
|
||||||
|
pub mod service;
|
||||||
69
backend/src/lib/domain/oidc/models.rs
Normal file
69
backend/src/lib/domain/oidc/models.rs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
use crate::domain::warren::models::user::{UserEmail, UserName};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct UserInfo {
|
||||||
|
sub: String,
|
||||||
|
name: UserName,
|
||||||
|
email: UserEmail,
|
||||||
|
preferred_username: Option<UserName>,
|
||||||
|
picture: Option<String>,
|
||||||
|
locale: Option<String>,
|
||||||
|
updated_at: Option<i64>,
|
||||||
|
warren_admin: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserInfo {
|
||||||
|
pub fn new(
|
||||||
|
sub: String,
|
||||||
|
name: UserName,
|
||||||
|
email: UserEmail,
|
||||||
|
preferred_username: Option<UserName>,
|
||||||
|
picture: Option<String>,
|
||||||
|
locale: Option<String>,
|
||||||
|
updated_at: Option<i64>,
|
||||||
|
warren_admin: Option<bool>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
sub,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
preferred_username,
|
||||||
|
picture,
|
||||||
|
locale,
|
||||||
|
updated_at,
|
||||||
|
warren_admin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub(&self) -> &String {
|
||||||
|
&self.sub
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &UserName {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email(&self) -> &UserEmail {
|
||||||
|
&self.email
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preferred_username(&self) -> Option<&UserName> {
|
||||||
|
self.preferred_username.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn picture(&self) -> Option<&String> {
|
||||||
|
self.picture.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn locale(&self) -> Option<&String> {
|
||||||
|
self.locale.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updated_at(&self) -> Option<i64> {
|
||||||
|
self.updated_at
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warren_admin(&self) -> Option<bool> {
|
||||||
|
self.warren_admin
|
||||||
|
}
|
||||||
|
}
|
||||||
39
backend/src/lib/domain/oidc/ports.rs
Normal file
39
backend/src/lib/domain/oidc/ports.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use super::requests::{
|
||||||
|
GetRedirectError, GetRedirectRequest, GetRedirectResponse, GetUserInfoError,
|
||||||
|
GetUserInfoRequest, GetUserInfoResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait OidcService: Clone + Send + Sync + 'static {
|
||||||
|
fn get_redirect(
|
||||||
|
&self,
|
||||||
|
request: GetRedirectRequest,
|
||||||
|
) -> impl Future<Output = Result<GetRedirectResponse, GetRedirectError>> + Send;
|
||||||
|
fn get_user_info(
|
||||||
|
&self,
|
||||||
|
request: GetUserInfoRequest,
|
||||||
|
) -> impl Future<Output = Result<GetUserInfoResponse, GetUserInfoError>> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OidcRepository: Clone + Send + Sync + 'static {
|
||||||
|
fn get_redirect(
|
||||||
|
&self,
|
||||||
|
request: GetRedirectRequest,
|
||||||
|
) -> impl Future<Output = Result<GetRedirectResponse, GetRedirectError>> + Send;
|
||||||
|
fn get_user_info(
|
||||||
|
&self,
|
||||||
|
request: GetUserInfoRequest,
|
||||||
|
) -> impl Future<Output = Result<GetUserInfoResponse, GetUserInfoError>> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OidcMetrics: Clone + Send + Sync + 'static {
|
||||||
|
fn record_get_redirect_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_get_redirect_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_get_user_info_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_get_user_info_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait OidcNotifier: Clone + Send + Sync + 'static {
|
||||||
|
fn get_redirect(&self, response: &GetRedirectResponse) -> impl Future<Output = ()> + Send;
|
||||||
|
fn get_user_info(&self, response: &GetUserInfoResponse) -> impl Future<Output = ()> + Send;
|
||||||
|
}
|
||||||
74
backend/src/lib/domain/oidc/requests.rs
Normal file
74
backend/src/lib/domain/oidc/requests.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::models::UserInfo;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct GetRedirectRequest {}
|
||||||
|
|
||||||
|
impl GetRedirectRequest {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct GetRedirectResponse {
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetRedirectResponse {
|
||||||
|
pub fn new(url: String) -> Self {
|
||||||
|
Self { url }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn url(&self) -> &String {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum GetRedirectError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct GetUserInfoRequest {
|
||||||
|
code: String,
|
||||||
|
state: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetUserInfoRequest {
|
||||||
|
pub fn new(code: String, state: Option<String>) -> Self {
|
||||||
|
Self { code, state }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code(&self) -> &String {
|
||||||
|
&self.code
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> Option<&String> {
|
||||||
|
self.state.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct GetUserInfoResponse {
|
||||||
|
info: UserInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetUserInfoResponse {
|
||||||
|
pub fn new(info: UserInfo) -> Self {
|
||||||
|
Self { info }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(&self) -> &UserInfo {
|
||||||
|
&self.info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum GetUserInfoError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
73
backend/src/lib/domain/oidc/service.rs
Normal file
73
backend/src/lib/domain/oidc/service.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use super::{
|
||||||
|
ports::{OidcMetrics, OidcNotifier, OidcRepository, OidcService},
|
||||||
|
requests::{
|
||||||
|
GetRedirectError, GetRedirectRequest, GetRedirectResponse, GetUserInfoError,
|
||||||
|
GetUserInfoRequest, GetUserInfoResponse,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Service<R, M, N>
|
||||||
|
where
|
||||||
|
R: OidcRepository,
|
||||||
|
M: OidcMetrics,
|
||||||
|
N: OidcNotifier,
|
||||||
|
{
|
||||||
|
repository: R,
|
||||||
|
metrics: M,
|
||||||
|
notifier: N,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, M, N> Service<R, M, N>
|
||||||
|
where
|
||||||
|
R: OidcRepository,
|
||||||
|
M: OidcMetrics,
|
||||||
|
N: OidcNotifier,
|
||||||
|
{
|
||||||
|
pub fn new(repository: R, metrics: M, notifier: N) -> Self {
|
||||||
|
Self {
|
||||||
|
repository,
|
||||||
|
metrics,
|
||||||
|
notifier,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R, M, N> OidcService for Service<R, M, N>
|
||||||
|
where
|
||||||
|
R: OidcRepository,
|
||||||
|
M: OidcMetrics,
|
||||||
|
N: OidcNotifier,
|
||||||
|
{
|
||||||
|
async fn get_redirect(
|
||||||
|
&self,
|
||||||
|
request: GetRedirectRequest,
|
||||||
|
) -> Result<GetRedirectResponse, GetRedirectError> {
|
||||||
|
let result = self.repository.get_redirect(request).await;
|
||||||
|
|
||||||
|
if let Ok(response) = result.as_ref() {
|
||||||
|
self.metrics.record_get_redirect_success().await;
|
||||||
|
self.notifier.get_redirect(response).await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_get_redirect_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_info(
|
||||||
|
&self,
|
||||||
|
request: GetUserInfoRequest,
|
||||||
|
) -> Result<GetUserInfoResponse, GetUserInfoError> {
|
||||||
|
let result = self.repository.get_user_info(request).await;
|
||||||
|
|
||||||
|
if let Ok(response) = result.as_ref() {
|
||||||
|
self.metrics.record_get_user_info_success().await;
|
||||||
|
self.notifier.get_user_info(response).await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_get_user_info_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,16 +11,21 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::FromRow)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::FromRow)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
oidc_sub: Option<String>,
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
name: UserName,
|
name: UserName,
|
||||||
email: UserEmail,
|
email: UserEmail,
|
||||||
hash: String,
|
hash: Option<String>,
|
||||||
admin: bool,
|
admin: bool,
|
||||||
updated_at: NaiveDateTime,
|
updated_at: NaiveDateTime,
|
||||||
created_at: NaiveDateTime,
|
created_at: NaiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
pub fn oidc_sub(&self) -> Option<&String> {
|
||||||
|
self.oidc_sub.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> &Uuid {
|
pub fn id(&self) -> &Uuid {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
@@ -33,8 +38,8 @@ impl User {
|
|||||||
&self.email
|
&self.email
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn password_hash(&self) -> &str {
|
pub fn password_hash(&self) -> Option<&String> {
|
||||||
&self.hash
|
self.hash.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn admin(&self) -> bool {
|
pub fn admin(&self) -> bool {
|
||||||
@@ -74,6 +79,7 @@ impl UserName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A valid email
|
/// A valid email
|
||||||
|
// TODO: Maybe move this somewhere else (emails are used here and for OIDC)
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, sqlx::Type)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display, sqlx::Type)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
pub struct UserEmail(String);
|
pub struct UserEmail(String);
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::domain::{
|
||||||
|
oidc::models::UserInfo,
|
||||||
|
warren::models::user::{UserEmail, UserName},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An admin request to create a new OIDC user or update an existing one if the `sub` already exists
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct CreateOrUpdateUserOidcRequest {
|
||||||
|
sub: String,
|
||||||
|
name: UserName,
|
||||||
|
email: UserEmail,
|
||||||
|
admin: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateOrUpdateUserOidcRequest {
|
||||||
|
pub fn new(sub: String, name: UserName, email: UserEmail, admin: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
sub,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
admin,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub(&self) -> &String {
|
||||||
|
&self.sub
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &UserName {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn email(&self) -> &UserEmail {
|
||||||
|
&self.email
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn admin(&self) -> bool {
|
||||||
|
self.admin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&UserInfo> for CreateOrUpdateUserOidcRequest {
|
||||||
|
fn from(value: &UserInfo) -> Self {
|
||||||
|
let name = value.preferred_username().unwrap_or(value.name()).clone();
|
||||||
|
Self::new(
|
||||||
|
value.sub().clone(),
|
||||||
|
name,
|
||||||
|
value.email().clone(),
|
||||||
|
value.warren_admin().unwrap_or(false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum CreateOrUpdateUserOidcError {
|
||||||
|
#[error("This email is already taken")]
|
||||||
|
EmailTaken,
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::domain::oidc::requests::GetRedirectError;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct GetOidcRedirectRequest {}
|
||||||
|
|
||||||
|
impl GetOidcRedirectRequest {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GetOidcRedirectRequest> for crate::domain::oidc::requests::GetRedirectRequest {
|
||||||
|
fn from(_value: GetOidcRedirectRequest) -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct GetOidcRedirectResponse {
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GetOidcRedirectResponse {
|
||||||
|
pub fn new(url: String) -> Self {
|
||||||
|
Self { url }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn url(&self) -> &String {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum GetOidcRedirectError {
|
||||||
|
#[error("OIDC is not enabled")]
|
||||||
|
Disabled,
|
||||||
|
#[error(transparent)]
|
||||||
|
GetRedirect(#[from] GetRedirectError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::domain::{
|
||||||
|
oidc::requests::{GetUserInfoError, GetUserInfoRequest},
|
||||||
|
warren::models::{
|
||||||
|
auth_session::{AuthSession, requests::CreateAuthSessionError},
|
||||||
|
user::User,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::CreateOrUpdateUserOidcError;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct LoginUserOidcRequest {
|
||||||
|
pub(super) code: String,
|
||||||
|
pub(super) state: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginUserOidcRequest {
|
||||||
|
pub fn new(code: String, state: Option<String>) -> Self {
|
||||||
|
Self { code, state }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code(&self) -> &String {
|
||||||
|
&self.code
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> Option<&String> {
|
||||||
|
self.state.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LoginUserOidcRequest> for GetUserInfoRequest {
|
||||||
|
fn from(value: LoginUserOidcRequest) -> Self {
|
||||||
|
GetUserInfoRequest::new(value.code, value.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub struct LoginUserOidcResponse {
|
||||||
|
session: AuthSession,
|
||||||
|
user: User,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoginUserOidcResponse {
|
||||||
|
pub fn new(session: AuthSession, user: User) -> Self {
|
||||||
|
Self { session, user }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session(&self) -> &AuthSession {
|
||||||
|
&self.session
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn user(&self) -> &User {
|
||||||
|
&self.user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum LoginUserOidcError {
|
||||||
|
#[error("OIDC is not enabled")]
|
||||||
|
Disabled,
|
||||||
|
#[error(transparent)]
|
||||||
|
GetUserInfo(#[from] GetUserInfoError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateOrUpdateUser(#[from] CreateOrUpdateUserOidcError),
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateAuthToken(#[from] CreateAuthSessionError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Unknown(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
mod create;
|
mod create;
|
||||||
|
mod create_or_update;
|
||||||
mod delete;
|
mod delete;
|
||||||
mod edit;
|
mod edit;
|
||||||
|
mod get_oidc_redirect;
|
||||||
mod list;
|
mod list;
|
||||||
mod list_all;
|
mod list_all;
|
||||||
mod login;
|
mod login;
|
||||||
|
mod login_oidc;
|
||||||
mod register;
|
mod register;
|
||||||
mod verify_password;
|
mod verify_password;
|
||||||
|
|
||||||
pub use create::*;
|
pub use create::*;
|
||||||
|
pub use create_or_update::*;
|
||||||
pub use delete::*;
|
pub use delete::*;
|
||||||
pub use edit::*;
|
pub use edit::*;
|
||||||
|
pub use get_oidc_redirect::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use list_all::*;
|
pub use list_all::*;
|
||||||
pub use login::*;
|
pub use login::*;
|
||||||
|
pub use login_oidc::*;
|
||||||
pub use register::*;
|
pub use register::*;
|
||||||
pub use verify_password::*;
|
pub use verify_password::*;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ impl From<LoginUserRequest> for VerifyUserPasswordRequest {
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum VerifyUserPasswordError {
|
pub enum VerifyUserPasswordError {
|
||||||
|
#[error("This user does not use password authentication")]
|
||||||
|
PasswordNotAllowed,
|
||||||
#[error("There is no user with this email: {0}")]
|
#[error("There is no user with this email: {0}")]
|
||||||
NotFound(UserEmail),
|
NotFound(UserEmail),
|
||||||
#[error("The password is incorrect")]
|
#[error("The password is incorrect")]
|
||||||
|
|||||||
@@ -86,6 +86,9 @@ pub trait AuthMetrics: Clone + Send + Sync + 'static {
|
|||||||
fn record_user_login_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_user_login_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_user_login_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_user_login_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
fn record_user_login_oidc_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
fn record_user_login_oidc_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
fn record_user_creation_success(&self) -> impl Future<Output = ()> + Send;
|
fn record_user_creation_success(&self) -> impl Future<Output = ()> + Send;
|
||||||
fn record_user_creation_failure(&self) -> impl Future<Output = ()> + Send;
|
fn record_user_creation_failure(&self) -> impl Future<Output = ()> + Send;
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ use super::models::{
|
|||||||
},
|
},
|
||||||
user::{
|
user::{
|
||||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
||||||
EditUserRequest, ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest,
|
EditUserRequest, GetOidcRedirectError, GetOidcRedirectRequest, GetOidcRedirectResponse,
|
||||||
ListAllUsersAndWarrensResponse, ListUsersError, ListUsersRequest, LoginUserError,
|
ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse,
|
||||||
LoginUserRequest, LoginUserResponse, RegisterUserError, RegisterUserRequest, User,
|
ListUsersError, ListUsersRequest, LoginUserError, LoginUserOidcError, LoginUserOidcRequest,
|
||||||
|
LoginUserOidcResponse, LoginUserRequest, LoginUserResponse, RegisterUserError,
|
||||||
|
RegisterUserRequest, User,
|
||||||
},
|
},
|
||||||
user_warren::{
|
user_warren::{
|
||||||
UserWarren,
|
UserWarren,
|
||||||
@@ -141,6 +143,11 @@ pub trait AuthService: Clone + Send + Sync + 'static {
|
|||||||
warren_service: &WS,
|
warren_service: &WS,
|
||||||
) -> impl Future<Output = Result<Warren, AuthError<DeleteWarrenError>>> + Send;
|
) -> impl Future<Output = Result<Warren, AuthError<DeleteWarrenError>>> + Send;
|
||||||
|
|
||||||
|
fn get_oidc_redirect(
|
||||||
|
&self,
|
||||||
|
request: GetOidcRedirectRequest,
|
||||||
|
) -> impl Future<Output = Result<GetOidcRedirectResponse, GetOidcRedirectError>> + Send;
|
||||||
|
|
||||||
fn register_user(
|
fn register_user(
|
||||||
&self,
|
&self,
|
||||||
request: RegisterUserRequest,
|
request: RegisterUserRequest,
|
||||||
@@ -149,6 +156,10 @@ pub trait AuthService: Clone + Send + Sync + 'static {
|
|||||||
&self,
|
&self,
|
||||||
request: LoginUserRequest,
|
request: LoginUserRequest,
|
||||||
) -> impl Future<Output = Result<LoginUserResponse, LoginUserError>> + Send;
|
) -> impl Future<Output = Result<LoginUserResponse, LoginUserError>> + Send;
|
||||||
|
fn login_user_oidc(
|
||||||
|
&self,
|
||||||
|
request: LoginUserOidcRequest,
|
||||||
|
) -> impl Future<Output = Result<LoginUserOidcResponse, LoginUserOidcError>> + Send;
|
||||||
|
|
||||||
/// An action that creates a user (MUST REQUIRE ADMIN PRIVILEGES)
|
/// An action that creates a user (MUST REQUIRE ADMIN PRIVILEGES)
|
||||||
fn create_user(
|
fn create_user(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use uuid::Uuid;
|
|||||||
use crate::domain::warren::models::{
|
use crate::domain::warren::models::{
|
||||||
auth_session::requests::FetchAuthSessionResponse,
|
auth_session::requests::FetchAuthSessionResponse,
|
||||||
file::{AbsoluteFilePath, LsResponse},
|
file::{AbsoluteFilePath, LsResponse},
|
||||||
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
|
user::{ListAllUsersAndWarrensResponse, LoginUserOidcResponse, LoginUserResponse, User},
|
||||||
user_warren::UserWarren,
|
user_warren::UserWarren,
|
||||||
warren::{
|
warren::{
|
||||||
Warren, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse,
|
Warren, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse,
|
||||||
@@ -81,6 +81,10 @@ pub trait AuthNotifier: Clone + Send + Sync + 'static {
|
|||||||
|
|
||||||
fn user_registered(&self, user: &User) -> impl Future<Output = ()> + Send;
|
fn user_registered(&self, user: &User) -> impl Future<Output = ()> + Send;
|
||||||
fn user_logged_in(&self, response: &LoginUserResponse) -> impl Future<Output = ()> + Send;
|
fn user_logged_in(&self, response: &LoginUserResponse) -> impl Future<Output = ()> + Send;
|
||||||
|
fn user_logged_in_oidc(
|
||||||
|
&self,
|
||||||
|
response: &LoginUserOidcResponse,
|
||||||
|
) -> impl Future<Output = ()> + Send;
|
||||||
fn user_created(&self, creator: &User, created: &User) -> impl Future<Output = ()> + Send;
|
fn user_created(&self, creator: &User, created: &User) -> impl Future<Output = ()> + Send;
|
||||||
fn user_edited(&self, editor: &User, edited: &User) -> impl Future<Output = ()> + Send;
|
fn user_edited(&self, editor: &User, edited: &User) -> impl Future<Output = ()> + Send;
|
||||||
fn user_deleted(&self, deleter: &User, user: &User) -> impl Future<Output = ()> + Send;
|
fn user_deleted(&self, deleter: &User, user: &User) -> impl Future<Output = ()> + Send;
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ use crate::domain::warren::models::{
|
|||||||
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
SaveRequest, SaveResponse, TouchError, TouchRequest,
|
||||||
},
|
},
|
||||||
user::{
|
user::{
|
||||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
CreateOrUpdateUserOidcError, CreateOrUpdateUserOidcRequest, CreateUserError,
|
||||||
EditUserRequest, ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest,
|
CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError, EditUserRequest,
|
||||||
ListAllUsersAndWarrensResponse, ListUsersError, ListUsersRequest, User,
|
ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse,
|
||||||
VerifyUserPasswordError, VerifyUserPasswordRequest,
|
ListUsersError, ListUsersRequest, User, VerifyUserPasswordError, VerifyUserPasswordRequest,
|
||||||
},
|
},
|
||||||
user_warren::{
|
user_warren::{
|
||||||
UserWarren,
|
UserWarren,
|
||||||
@@ -81,6 +81,10 @@ pub trait FileSystemRepository: Clone + Send + Sync + 'static {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait AuthRepository: Clone + Send + Sync + 'static {
|
pub trait AuthRepository: Clone + Send + Sync + 'static {
|
||||||
|
fn create_or_update_user_oidc(
|
||||||
|
&self,
|
||||||
|
request: CreateOrUpdateUserOidcRequest,
|
||||||
|
) -> impl Future<Output = Result<User, CreateOrUpdateUserOidcError>> + Send;
|
||||||
fn create_user(
|
fn create_user(
|
||||||
&self,
|
&self,
|
||||||
request: CreateUserRequest,
|
request: CreateUserRequest,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
domain::warren::{
|
domain::{
|
||||||
|
oidc::ports::OidcService,
|
||||||
|
warren::{
|
||||||
models::{
|
models::{
|
||||||
auth_session::{
|
auth_session::{
|
||||||
AuthError, AuthRequest, AuthSession,
|
AuthError, AuthRequest, AuthSession,
|
||||||
@@ -12,10 +14,12 @@ use crate::{
|
|||||||
file::FileStream,
|
file::FileStream,
|
||||||
user::{
|
user::{
|
||||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest,
|
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest,
|
||||||
EditUserError, EditUserRequest, ListAllUsersAndWarrensError,
|
EditUserError, EditUserRequest, GetOidcRedirectError, GetOidcRedirectRequest,
|
||||||
|
GetOidcRedirectResponse, ListAllUsersAndWarrensError,
|
||||||
ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse, ListUsersError,
|
ListAllUsersAndWarrensRequest, ListAllUsersAndWarrensResponse, ListUsersError,
|
||||||
ListUsersRequest, LoginUserError, LoginUserRequest, LoginUserResponse,
|
ListUsersRequest, LoginUserError, LoginUserOidcError, LoginUserOidcRequest,
|
||||||
RegisterUserError, RegisterUserRequest, User,
|
LoginUserOidcResponse, LoginUserRequest, LoginUserResponse, RegisterUserError,
|
||||||
|
RegisterUserRequest, User,
|
||||||
},
|
},
|
||||||
user_warren::{
|
user_warren::{
|
||||||
UserWarren,
|
UserWarren,
|
||||||
@@ -31,13 +35,14 @@ use crate::{
|
|||||||
FetchWarrensRequest, Warren, WarrenCatError, WarrenCatRequest, WarrenCpError,
|
FetchWarrensRequest, Warren, WarrenCatError, WarrenCatRequest, WarrenCpError,
|
||||||
WarrenCpRequest, WarrenCpResponse, WarrenLsError, WarrenLsRequest,
|
WarrenCpRequest, WarrenCpResponse, WarrenLsError, WarrenLsRequest,
|
||||||
WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest, WarrenMkdirResponse,
|
WarrenLsResponse, WarrenMkdirError, WarrenMkdirRequest, WarrenMkdirResponse,
|
||||||
WarrenMvError, WarrenMvRequest, WarrenMvResponse, WarrenRmError, WarrenRmRequest,
|
WarrenMvError, WarrenMvRequest, WarrenMvResponse, WarrenRmError,
|
||||||
WarrenRmResponse, WarrenSaveError, WarrenSaveRequest, WarrenSaveResponse,
|
WarrenRmRequest, WarrenRmResponse, WarrenSaveError, WarrenSaveRequest,
|
||||||
WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
WarrenSaveResponse, WarrenTouchError, WarrenTouchRequest, WarrenTouchResponse,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
ports::{AuthMetrics, AuthNotifier, AuthRepository, AuthService, WarrenService},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const AUTH_SESSION_EXPIRATION_KEY: &str = "AUTH_SESSION_EXPIRATION";
|
const AUTH_SESSION_EXPIRATION_KEY: &str = "AUTH_SESSION_EXPIRATION";
|
||||||
@@ -45,7 +50,7 @@ const AUTH_SESSION_EXPIRATION_KEY: &str = "AUTH_SESSION_EXPIRATION";
|
|||||||
/// The authentication service configuration
|
/// The authentication service configuration
|
||||||
///
|
///
|
||||||
/// * `session_lifetime`: The amount of milliseconds a client session is valid
|
/// * `session_lifetime`: The amount of milliseconds a client session is valid
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct AuthConfig {
|
pub struct AuthConfig {
|
||||||
session_lifetime: SessionExpirationTime,
|
session_lifetime: SessionExpirationTime,
|
||||||
}
|
}
|
||||||
@@ -69,39 +74,50 @@ impl AuthConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Service<R, M, N>
|
pub struct Service<R, M, N, OIDC>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
|
OIDC: OidcService,
|
||||||
{
|
{
|
||||||
repository: R,
|
repository: R,
|
||||||
metrics: M,
|
metrics: M,
|
||||||
notifier: N,
|
notifier: N,
|
||||||
|
oidc: Option<OIDC>,
|
||||||
config: AuthConfig,
|
config: AuthConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, M, N> Service<R, M, N>
|
impl<R, M, N, OIDC> Service<R, M, N, OIDC>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
|
OIDC: OidcService,
|
||||||
{
|
{
|
||||||
pub fn new(repository: R, metrics: M, notifier: N, config: AuthConfig) -> Self {
|
pub fn new(
|
||||||
|
repository: R,
|
||||||
|
metrics: M,
|
||||||
|
notifier: N,
|
||||||
|
config: AuthConfig,
|
||||||
|
oidc: Option<OIDC>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
repository,
|
repository,
|
||||||
metrics,
|
metrics,
|
||||||
notifier,
|
notifier,
|
||||||
config,
|
config,
|
||||||
|
oidc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R, M, N> AuthService for Service<R, M, N>
|
impl<R, M, N, OIDC> AuthService for Service<R, M, N, OIDC>
|
||||||
where
|
where
|
||||||
R: AuthRepository,
|
R: AuthRepository,
|
||||||
M: AuthMetrics,
|
M: AuthMetrics,
|
||||||
N: AuthNotifier,
|
N: AuthNotifier,
|
||||||
|
OIDC: OidcService,
|
||||||
{
|
{
|
||||||
async fn create_warren<WS: WarrenService>(
|
async fn create_warren<WS: WarrenService>(
|
||||||
&self,
|
&self,
|
||||||
@@ -197,6 +213,18 @@ where
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_oidc_redirect(
|
||||||
|
&self,
|
||||||
|
request: GetOidcRedirectRequest,
|
||||||
|
) -> Result<GetOidcRedirectResponse, GetOidcRedirectError> {
|
||||||
|
let oidc = self.oidc.as_ref().ok_or(GetOidcRedirectError::Disabled)?;
|
||||||
|
|
||||||
|
oidc.get_redirect(request.into())
|
||||||
|
.await
|
||||||
|
.map(|response| GetOidcRedirectResponse::new(response.url().clone()))
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
async fn register_user(&self, request: RegisterUserRequest) -> Result<User, RegisterUserError> {
|
async fn register_user(&self, request: RegisterUserRequest) -> Result<User, RegisterUserError> {
|
||||||
let result = self.repository.create_user(request.into()).await;
|
let result = self.repository.create_user(request.into()).await;
|
||||||
|
|
||||||
@@ -238,6 +266,37 @@ where
|
|||||||
result.map_err(Into::into)
|
result.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn login_user_oidc(
|
||||||
|
&self,
|
||||||
|
request: LoginUserOidcRequest,
|
||||||
|
) -> Result<LoginUserOidcResponse, LoginUserOidcError> {
|
||||||
|
let oidc = self.oidc.as_ref().ok_or(LoginUserOidcError::Disabled)?;
|
||||||
|
|
||||||
|
let user_info = oidc.get_user_info(request.into()).await?;
|
||||||
|
|
||||||
|
let user = self
|
||||||
|
.repository
|
||||||
|
.create_or_update_user_oidc(user_info.info().into())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let result = self
|
||||||
|
.create_auth_session(CreateAuthSessionRequest::new(
|
||||||
|
user.clone(),
|
||||||
|
self.config.session_lifetime(),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.map(|session| LoginUserOidcResponse::new(session, user));
|
||||||
|
|
||||||
|
if let Ok(response) = result.as_ref() {
|
||||||
|
self.metrics.record_user_login_oidc_success().await;
|
||||||
|
self.notifier.user_logged_in_oidc(response).await;
|
||||||
|
} else {
|
||||||
|
self.metrics.record_user_login_oidc_failure().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
async fn create_user(
|
async fn create_user(
|
||||||
&self,
|
&self,
|
||||||
request: AuthRequest<CreateUserRequest>,
|
request: AuthRequest<CreateUserRequest>,
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ use crate::{
|
|||||||
domain::warren::models::{
|
domain::warren::models::{
|
||||||
auth_session::{AuthError, requests::FetchAuthSessionError},
|
auth_session::{AuthError, requests::FetchAuthSessionError},
|
||||||
file::{LsError, MkdirError, RmError},
|
file::{LsError, MkdirError, RmError},
|
||||||
user::{CreateUserError, LoginUserError, RegisterUserError, VerifyUserPasswordError},
|
user::{
|
||||||
|
CreateUserError, GetOidcRedirectError, LoginUserError, LoginUserOidcError,
|
||||||
|
RegisterUserError, VerifyUserPasswordError,
|
||||||
|
},
|
||||||
user_warren::requests::FetchUserWarrenError,
|
user_warren::requests::FetchUserWarrenError,
|
||||||
warren::{
|
warren::{
|
||||||
FetchWarrenError, FetchWarrensError, WarrenLsError, WarrenMkdirError, WarrenMvError,
|
FetchWarrenError, FetchWarrensError, WarrenLsError, WarrenMkdirError, WarrenMvError,
|
||||||
@@ -117,6 +120,9 @@ impl From<LoginUserError> for ApiError {
|
|||||||
fn from(value: LoginUserError) -> Self {
|
fn from(value: LoginUserError) -> Self {
|
||||||
match value {
|
match value {
|
||||||
LoginUserError::VerifyUser(e) => match e {
|
LoginUserError::VerifyUser(e) => match e {
|
||||||
|
VerifyUserPasswordError::PasswordNotAllowed => {
|
||||||
|
Self::NotFound("This user does not use password authentication".to_string())
|
||||||
|
}
|
||||||
VerifyUserPasswordError::NotFound(_) => {
|
VerifyUserPasswordError::NotFound(_) => {
|
||||||
Self::NotFound("Could not find a user with that email".to_string())
|
Self::NotFound("Could not find a user with that email".to_string())
|
||||||
}
|
}
|
||||||
@@ -131,6 +137,18 @@ impl From<LoginUserError> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<LoginUserOidcError> for ApiError {
|
||||||
|
fn from(value: LoginUserOidcError) -> Self {
|
||||||
|
match value {
|
||||||
|
LoginUserOidcError::Disabled => Self::BadRequest("OIDC is disabled".to_string()),
|
||||||
|
LoginUserOidcError::CreateOrUpdateUser(e) => Self::InternalServerError(e.to_string()),
|
||||||
|
LoginUserOidcError::GetUserInfo(e) => Self::InternalServerError(e.to_string()),
|
||||||
|
LoginUserOidcError::CreateAuthToken(e) => Self::InternalServerError(e.to_string()),
|
||||||
|
LoginUserOidcError::Unknown(e) => Self::InternalServerError(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<FetchAuthSessionError> for ApiError {
|
impl From<FetchAuthSessionError> for ApiError {
|
||||||
fn from(value: FetchAuthSessionError) -> Self {
|
fn from(value: FetchAuthSessionError) -> Self {
|
||||||
match value {
|
match value {
|
||||||
@@ -177,3 +195,17 @@ impl From<CreateUserError> for ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<GetOidcRedirectError> for ApiError {
|
||||||
|
fn from(value: GetOidcRedirectError) -> Self {
|
||||||
|
match value {
|
||||||
|
GetOidcRedirectError::GetRedirect(e) => match e {
|
||||||
|
crate::domain::oidc::requests::GetRedirectError::Unknown(e) => {
|
||||||
|
Self::InternalServerError(e.to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
GetOidcRedirectError::Disabled => Self::BadRequest("OIDC is disabled".to_string()),
|
||||||
|
GetOidcRedirectError::Unknown(e) => Self::InternalServerError(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ impl LoginUserHttpRequestBody {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct LoginResponseBody {
|
pub struct LoginResponseBody {
|
||||||
token: String,
|
token: String,
|
||||||
user: UserData,
|
user: UserData,
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
mod fetch_session;
|
mod fetch_session;
|
||||||
mod login;
|
mod login;
|
||||||
|
mod oidc_login;
|
||||||
|
mod oidc_redirect;
|
||||||
mod register;
|
mod register;
|
||||||
|
|
||||||
use fetch_session::fetch_session;
|
use fetch_session::fetch_session;
|
||||||
use login::login;
|
use login::login;
|
||||||
|
use oidc_login::oidc_login;
|
||||||
|
use oidc_redirect::oidc_redirect;
|
||||||
use register::register;
|
use register::register;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -20,4 +25,6 @@ pub fn routes<WS: WarrenService, AS: AuthService>() -> Router<AppState<WS, AS>>
|
|||||||
.route("/register", post(register))
|
.route("/register", post(register))
|
||||||
.route("/login", post(login))
|
.route("/login", post(login))
|
||||||
.route("/session", get(fetch_session))
|
.route("/session", get(fetch_session))
|
||||||
|
.route("/oidc", get(oidc_redirect))
|
||||||
|
.route("/oidc/login", get(oidc_login))
|
||||||
}
|
}
|
||||||
|
|||||||
62
backend/src/lib/inbound/http/handlers/auth/oidc_login.rs
Normal file
62
backend/src/lib/inbound/http/handlers/auth/oidc_login.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
use axum::{
|
||||||
|
extract::{Query, State},
|
||||||
|
http::StatusCode,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
domain::warren::{
|
||||||
|
models::user::{LoginUserOidcRequest, LoginUserOidcResponse},
|
||||||
|
ports::{AuthService, WarrenService},
|
||||||
|
},
|
||||||
|
inbound::http::{
|
||||||
|
AppState,
|
||||||
|
handlers::UserData,
|
||||||
|
responses::{ApiError, ApiSuccess},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct OidcLoginRequestBody {
|
||||||
|
code: String,
|
||||||
|
state: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OidcLoginRequestBody {
|
||||||
|
pub fn into_domain(self) -> LoginUserOidcRequest {
|
||||||
|
LoginUserOidcRequest::new(self.code, self.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(super) struct OidcLoginResponseBody {
|
||||||
|
token: String,
|
||||||
|
user: UserData,
|
||||||
|
expires_at: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LoginUserOidcResponse> for OidcLoginResponseBody {
|
||||||
|
fn from(value: LoginUserOidcResponse) -> Self {
|
||||||
|
Self {
|
||||||
|
token: value.session().session_id().to_string(),
|
||||||
|
user: value.user().to_owned().into(),
|
||||||
|
expires_at: value.session().expires_at().and_utc().timestamp_millis(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn oidc_login<WS: WarrenService, AS: AuthService>(
|
||||||
|
State(state): State<AppState<WS, AS>>,
|
||||||
|
Query(request): Query<OidcLoginRequestBody>,
|
||||||
|
) -> Result<ApiSuccess<OidcLoginResponseBody>, ApiError> {
|
||||||
|
let domain_request = request.into_domain();
|
||||||
|
|
||||||
|
state
|
||||||
|
.auth_service
|
||||||
|
.login_user_oidc(domain_request)
|
||||||
|
.await
|
||||||
|
.map(|response| ApiSuccess::new(StatusCode::OK, response.into()))
|
||||||
|
.map_err(ApiError::from)
|
||||||
|
}
|
||||||
23
backend/src/lib/inbound/http/handlers/auth/oidc_redirect.rs
Normal file
23
backend/src/lib/inbound/http/handlers/auth/oidc_redirect.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
use axum::{extract::State, http::StatusCode};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
domain::warren::{
|
||||||
|
models::user::GetOidcRedirectRequest,
|
||||||
|
ports::{AuthService, WarrenService},
|
||||||
|
},
|
||||||
|
inbound::http::{
|
||||||
|
AppState,
|
||||||
|
responses::{ApiError, ApiSuccess},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn oidc_redirect<WS: WarrenService, AS: AuthService>(
|
||||||
|
State(state): State<AppState<WS, AS>>,
|
||||||
|
) -> Result<ApiSuccess<String>, ApiError> {
|
||||||
|
state
|
||||||
|
.auth_service
|
||||||
|
.get_oidc_redirect(GetOidcRedirectRequest::new())
|
||||||
|
.await
|
||||||
|
.map(|response| ApiSuccess::new(StatusCode::FOUND, response.url().clone()))
|
||||||
|
.map_err(ApiError::from)
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
use crate::domain::warren::ports::{AuthMetrics, FileSystemMetrics, WarrenMetrics};
|
use crate::domain::{
|
||||||
|
oidc::ports::OidcMetrics,
|
||||||
|
warren::ports::{AuthMetrics, FileSystemMetrics, WarrenMetrics},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct MetricsDebugLogger;
|
pub struct MetricsDebugLogger;
|
||||||
@@ -175,17 +178,17 @@ impl AuthMetrics for MetricsDebugLogger {
|
|||||||
tracing::debug!("[Metrics] Warren creation by admin failed");
|
tracing::debug!("[Metrics] Warren creation by admin failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn record_auth_warren_edit_success(&self) -> () {
|
async fn record_auth_warren_edit_success(&self) {
|
||||||
tracing::debug!("[Metrics] Warren edit by admin succeeded");
|
tracing::debug!("[Metrics] Warren edit by admin succeeded");
|
||||||
}
|
}
|
||||||
async fn record_auth_warren_edit_failure(&self) -> () {
|
async fn record_auth_warren_edit_failure(&self) {
|
||||||
tracing::debug!("[Metrics] Warren edit by admin failed");
|
tracing::debug!("[Metrics] Warren edit by admin failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn record_auth_warren_deletion_success(&self) -> () {
|
async fn record_auth_warren_deletion_success(&self) {
|
||||||
tracing::debug!("[Metrics] Warren deletion by admin succeeded");
|
tracing::debug!("[Metrics] Warren deletion by admin succeeded");
|
||||||
}
|
}
|
||||||
async fn record_auth_warren_deletion_failure(&self) -> () {
|
async fn record_auth_warren_deletion_failure(&self) {
|
||||||
tracing::debug!("[Metrics] Warren deletion by admin failed");
|
tracing::debug!("[Metrics] Warren deletion by admin failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +206,13 @@ impl AuthMetrics for MetricsDebugLogger {
|
|||||||
tracing::debug!("[Metrics] User login failed");
|
tracing::debug!("[Metrics] User login failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn record_user_login_oidc_success(&self) {
|
||||||
|
tracing::debug!("[Metrics] User login succeeded");
|
||||||
|
}
|
||||||
|
async fn record_user_login_oidc_failure(&self) {
|
||||||
|
tracing::debug!("[Metrics] User login failed");
|
||||||
|
}
|
||||||
|
|
||||||
async fn record_user_creation_success(&self) {
|
async fn record_user_creation_success(&self) {
|
||||||
tracing::debug!("[Metrics] User creation succeeded");
|
tracing::debug!("[Metrics] User creation succeeded");
|
||||||
}
|
}
|
||||||
@@ -350,3 +360,19 @@ impl AuthMetrics for MetricsDebugLogger {
|
|||||||
tracing::debug!("[Metrics] Auth warren cp failed");
|
tracing::debug!("[Metrics] Auth warren cp failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OidcMetrics for MetricsDebugLogger {
|
||||||
|
async fn record_get_redirect_success(&self) {
|
||||||
|
tracing::debug!("[Metrics] OIDC get redirect succeeded");
|
||||||
|
}
|
||||||
|
async fn record_get_redirect_failure(&self) {
|
||||||
|
tracing::debug!("[Metrics] OIDC get redirect failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn record_get_user_info_success(&self) {
|
||||||
|
tracing::debug!("[Metrics] OIDC get user info succeeded");
|
||||||
|
}
|
||||||
|
async fn record_get_user_info_failure(&self) {
|
||||||
|
tracing::debug!("[Metrics] OIDC get user info failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod file_system;
|
pub mod file_system;
|
||||||
pub mod metrics_debug_logger;
|
pub mod metrics_debug_logger;
|
||||||
pub mod notifier_debug_logger;
|
pub mod notifier_debug_logger;
|
||||||
|
pub mod oidc;
|
||||||
pub mod postgres;
|
pub mod postgres;
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::domain::warren::{
|
use crate::domain::{
|
||||||
|
oidc::{
|
||||||
|
ports::OidcNotifier,
|
||||||
|
requests::{GetRedirectResponse, GetUserInfoResponse},
|
||||||
|
},
|
||||||
|
warren::{
|
||||||
models::{
|
models::{
|
||||||
auth_session::requests::FetchAuthSessionResponse,
|
auth_session::requests::FetchAuthSessionResponse,
|
||||||
file::{AbsoluteFilePath, LsResponse},
|
file::{AbsoluteFilePath, LsResponse},
|
||||||
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
|
user::{
|
||||||
|
ListAllUsersAndWarrensResponse, LoginUserOidcResponse, LoginUserResponse, User,
|
||||||
|
},
|
||||||
user_warren::UserWarren,
|
user_warren::UserWarren,
|
||||||
warren::{
|
warren::{
|
||||||
Warren, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse,
|
Warren, WarrenCpResponse, WarrenLsResponse, WarrenMkdirResponse, WarrenMvResponse,
|
||||||
@@ -12,6 +19,7 @@ use crate::domain::warren::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
|
ports::{AuthNotifier, FileSystemNotifier, WarrenNotifier},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -220,6 +228,13 @@ impl AuthNotifier for NotifierDebugLogger {
|
|||||||
tracing::debug!("[Notifier] Logged in user {}", response.user().name());
|
tracing::debug!("[Notifier] Logged in user {}", response.user().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn user_logged_in_oidc(&self, response: &LoginUserOidcResponse) {
|
||||||
|
tracing::debug!(
|
||||||
|
"[Notifier] Logged in user {} with OIDC",
|
||||||
|
response.user().name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async fn auth_session_created(&self, user_id: &Uuid) {
|
async fn auth_session_created(&self, user_id: &Uuid) {
|
||||||
tracing::debug!("[Notifier] Created auth session for user {}", user_id);
|
tracing::debug!("[Notifier] Created auth session for user {}", user_id);
|
||||||
}
|
}
|
||||||
@@ -354,3 +369,19 @@ impl AuthNotifier for NotifierDebugLogger {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OidcNotifier for NotifierDebugLogger {
|
||||||
|
async fn get_redirect(&self, response: &GetRedirectResponse) {
|
||||||
|
tracing::debug!("[Notifier] Got OIDC redirect: {}", response.url());
|
||||||
|
}
|
||||||
|
async fn get_user_info(&self, response: &GetUserInfoResponse) {
|
||||||
|
tracing::debug!(
|
||||||
|
"[Notifier] Got OIDC user info: {} ({})",
|
||||||
|
response
|
||||||
|
.info()
|
||||||
|
.preferred_username()
|
||||||
|
.unwrap_or(response.info().name()),
|
||||||
|
response.info().sub(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
187
backend/src/lib/outbound/oidc.rs
Normal file
187
backend/src/lib/outbound/oidc.rs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
use std::{str::FromStr as _, sync::Arc};
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use openid::{Client, CompactJson, CustomClaims, Discovered, Options, StandardClaims};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::Config,
|
||||||
|
domain::{
|
||||||
|
oidc::{
|
||||||
|
models::UserInfo,
|
||||||
|
ports::OidcRepository,
|
||||||
|
requests::{
|
||||||
|
GetRedirectError, GetRedirectRequest, GetRedirectResponse, GetUserInfoError,
|
||||||
|
GetUserInfoRequest, GetUserInfoResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
warren::models::user::{UserEmail, UserName},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type OidcClient = Client<Discovered, Claims>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct OidcConfig {
|
||||||
|
issuer_url: String,
|
||||||
|
client_id: String,
|
||||||
|
client_secret: String,
|
||||||
|
redirect_url: String,
|
||||||
|
origin_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OidcConfig {
|
||||||
|
pub fn from_env() -> anyhow::Result<Self> {
|
||||||
|
let issuer_url = Config::load_env("OIDC_ISSUER_URL")?;
|
||||||
|
let client_id = Config::load_env("OIDC_CLIENT_ID")?;
|
||||||
|
let client_secret = Config::load_env("OIDC_CLIENT_SECRET")?;
|
||||||
|
let origin = Config::load_env("OIDC_ORIGIN_URL")?;
|
||||||
|
let redirect_url = Config::load_env("OIDC_REDIRECT_URL")?;
|
||||||
|
|
||||||
|
Ok(Self::new(
|
||||||
|
issuer_url,
|
||||||
|
client_id,
|
||||||
|
client_secret,
|
||||||
|
redirect_url,
|
||||||
|
origin,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
issuer_url: String,
|
||||||
|
client_id: String,
|
||||||
|
client_secret: String,
|
||||||
|
redirect_url: String,
|
||||||
|
origin_url: String,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
issuer_url,
|
||||||
|
client_id,
|
||||||
|
client_secret,
|
||||||
|
redirect_url,
|
||||||
|
origin_url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Oidc {
|
||||||
|
client: OidcClient,
|
||||||
|
|
||||||
|
auth_options: Arc<openid::Options>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Oidc {
|
||||||
|
pub async fn new(config: &OidcConfig) -> anyhow::Result<Self> {
|
||||||
|
let client = OidcClient::discover(
|
||||||
|
config.client_id.clone(),
|
||||||
|
config.client_secret.clone(),
|
||||||
|
config.redirect_url.clone(),
|
||||||
|
Url::from_str(&config.issuer_url)?,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let auth_options = Options {
|
||||||
|
scope: Some("openid profile email".into()),
|
||||||
|
state: Some(config.origin_url.clone()),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client,
|
||||||
|
auth_options: Arc::new(auth_options),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
struct Claims {
|
||||||
|
warren_admin: Option<bool>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
standard_claims: StandardClaims,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomClaims for Claims {
|
||||||
|
fn standard_claims(&self) -> &StandardClaims {
|
||||||
|
&self.standard_claims
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompactJson for Claims {}
|
||||||
|
|
||||||
|
impl OidcRepository for Oidc {
|
||||||
|
async fn get_redirect(
|
||||||
|
&self,
|
||||||
|
_request: GetRedirectRequest,
|
||||||
|
) -> Result<GetRedirectResponse, GetRedirectError> {
|
||||||
|
let url = self.client.auth_url(&self.auth_options);
|
||||||
|
|
||||||
|
Ok(GetRedirectResponse::new(url.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_info(
|
||||||
|
&self,
|
||||||
|
request: GetUserInfoRequest,
|
||||||
|
) -> Result<GetUserInfoResponse, GetUserInfoError> {
|
||||||
|
let mut token: openid::Token<Claims> = self
|
||||||
|
.client
|
||||||
|
.request_token(request.code())
|
||||||
|
.await
|
||||||
|
.context("Failed to request token")?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let id_token = token
|
||||||
|
.id_token
|
||||||
|
.as_mut()
|
||||||
|
.context("Token didn't have id_token field")?;
|
||||||
|
|
||||||
|
self.client
|
||||||
|
.decode_token(id_token)
|
||||||
|
.context("Failed to decode token")?;
|
||||||
|
|
||||||
|
id_token
|
||||||
|
.payload_mut()
|
||||||
|
.context("Failed to get id_token payload")?
|
||||||
|
.standard_claims
|
||||||
|
.azp = Some(self.client.client_id.clone());
|
||||||
|
|
||||||
|
self.client
|
||||||
|
.validate_token(id_token, None, None)
|
||||||
|
.context("Failed to validate token")?;
|
||||||
|
|
||||||
|
let user_info = try_get_user_info(
|
||||||
|
id_token
|
||||||
|
.payload()
|
||||||
|
.context("Failed to get id token payload")?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(GetUserInfoResponse::new(user_info))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_get_user_info(claims: &Claims) -> anyhow::Result<UserInfo> {
|
||||||
|
let sub = claims.standard_claims.sub.clone();
|
||||||
|
let raw = &claims.standard_claims.userinfo;
|
||||||
|
|
||||||
|
let name = UserName::new(raw.name.as_ref().context("Missing name")?)?;
|
||||||
|
let email = UserEmail::new(raw.email.as_ref().context("Missing email")?)?;
|
||||||
|
let preferred_username = if let Some(preferred_username) = raw.preferred_username.as_ref() {
|
||||||
|
Some(UserName::new(preferred_username)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let user_info = UserInfo::new(
|
||||||
|
sub,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
preferred_username,
|
||||||
|
raw.picture.as_ref().map(|url| url.clone().into()),
|
||||||
|
raw.locale.clone(),
|
||||||
|
raw.updated_at,
|
||||||
|
claims.warren_admin,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(user_info)
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Context as _, anyhow};
|
use anyhow::{Context as _, anyhow, bail};
|
||||||
use argon2::{
|
use argon2::{
|
||||||
Argon2, PasswordHash, PasswordVerifier as _,
|
Argon2, PasswordHash, PasswordVerifier as _,
|
||||||
password_hash::{
|
password_hash::{
|
||||||
@@ -23,8 +23,9 @@ use crate::domain::warren::{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
user::{
|
user::{
|
||||||
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
|
CreateOrUpdateUserOidcError, CreateOrUpdateUserOidcRequest, CreateUserError,
|
||||||
EditUserRequest, ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest,
|
CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError, EditUserRequest,
|
||||||
|
ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest,
|
||||||
ListAllUsersAndWarrensResponse, ListUsersError, ListUsersRequest, User, UserEmail,
|
ListAllUsersAndWarrensResponse, ListUsersError, ListUsersRequest, User, UserEmail,
|
||||||
UserName, UserPassword, VerifyUserPasswordError, VerifyUserPasswordRequest,
|
UserName, UserPassword, VerifyUserPasswordError, VerifyUserPasswordRequest,
|
||||||
},
|
},
|
||||||
@@ -66,6 +67,30 @@ impl AuthRepository for Postgres {
|
|||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_or_update_user_oidc(
|
||||||
|
&self,
|
||||||
|
request: CreateOrUpdateUserOidcRequest,
|
||||||
|
) -> Result<User, CreateOrUpdateUserOidcError> {
|
||||||
|
let mut connection = self
|
||||||
|
.pool
|
||||||
|
.acquire()
|
||||||
|
.await
|
||||||
|
.context("Failed to get a PostgreSQL connection")?;
|
||||||
|
|
||||||
|
let user = self
|
||||||
|
.create_or_update_user(
|
||||||
|
&mut connection,
|
||||||
|
request.sub(),
|
||||||
|
request.name(),
|
||||||
|
request.email(),
|
||||||
|
request.admin(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context(format!("Failed to create or update user"))?;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
async fn edit_user(&self, request: EditUserRequest) -> Result<User, EditUserError> {
|
async fn edit_user(&self, request: EditUserRequest) -> Result<User, EditUserError> {
|
||||||
let mut connection = self
|
let mut connection = self
|
||||||
.pool
|
.pool
|
||||||
@@ -127,7 +152,11 @@ impl AuthRepository for Postgres {
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.check_user_password_against_hash(request.password(), user.password_hash())?;
|
self.check_user_password_against_hash(
|
||||||
|
request.password(),
|
||||||
|
user.password_hash()
|
||||||
|
.ok_or(VerifyUserPasswordError::PasswordNotAllowed)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
@@ -423,6 +452,121 @@ impl Postgres {
|
|||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn create_or_update_user(
|
||||||
|
&self,
|
||||||
|
connection: &mut PgConnection,
|
||||||
|
sub: &String,
|
||||||
|
name: &UserName,
|
||||||
|
email: &UserEmail,
|
||||||
|
is_admin: bool,
|
||||||
|
) -> anyhow::Result<User> {
|
||||||
|
let mut tx = connection.begin().await?;
|
||||||
|
|
||||||
|
let existing_user: Option<User> = sqlx::query_as(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
users
|
||||||
|
WHERE
|
||||||
|
oidc_sub = $1 OR
|
||||||
|
email = $2
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(sub)
|
||||||
|
.bind(email)
|
||||||
|
.fetch_optional(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user: User = match existing_user {
|
||||||
|
Some(existing_user) => {
|
||||||
|
if let Some(existing_oidc_sub) = existing_user.oidc_sub() {
|
||||||
|
if existing_oidc_sub != sub {
|
||||||
|
// TODO: Return a proper error
|
||||||
|
bail!("The user is already linked to another OIDC subject");
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query_as(
|
||||||
|
"
|
||||||
|
UPDATE
|
||||||
|
users
|
||||||
|
SET
|
||||||
|
name = $3,
|
||||||
|
email = $4,
|
||||||
|
admin = $5
|
||||||
|
WHERE
|
||||||
|
id = $1 AND
|
||||||
|
oidc_sub = $2
|
||||||
|
RETURNING
|
||||||
|
*
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(existing_user.id())
|
||||||
|
.bind(sub)
|
||||||
|
.bind(name)
|
||||||
|
.bind(email)
|
||||||
|
.bind(is_admin)
|
||||||
|
.fetch_one(&mut *tx)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
sqlx::query_as(
|
||||||
|
"
|
||||||
|
UPDATE
|
||||||
|
users
|
||||||
|
SET
|
||||||
|
oidc_sub = $2,
|
||||||
|
name = $3,
|
||||||
|
email = $4,
|
||||||
|
admin = $5
|
||||||
|
WHERE
|
||||||
|
id = $1 AND
|
||||||
|
oidc_sub IS NULL
|
||||||
|
RETURNING
|
||||||
|
*
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(existing_user.id())
|
||||||
|
.bind(sub)
|
||||||
|
.bind(name)
|
||||||
|
.bind(email)
|
||||||
|
.bind(is_admin)
|
||||||
|
.fetch_one(&mut *tx)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
sqlx::query_as(
|
||||||
|
"
|
||||||
|
INSERT INTO users (
|
||||||
|
oidc_sub,
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
admin
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4
|
||||||
|
)
|
||||||
|
RETURNING
|
||||||
|
*
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(sub)
|
||||||
|
.bind(name)
|
||||||
|
.bind(email)
|
||||||
|
.bind(is_admin)
|
||||||
|
.fetch_one(&mut *tx)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tx.commit().await?;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
async fn edit_user(
|
async fn edit_user(
|
||||||
&self,
|
&self,
|
||||||
connection: &mut PgConnection,
|
connection: &mut PgConnection,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export async function logout() {
|
export async function logout() {
|
||||||
useAuthSession().value = null;
|
useAuthSession().value = null;
|
||||||
useAdminStore().$reset();
|
useAdminStore().$reset();
|
||||||
|
useWarrenStore().$reset();
|
||||||
await navigateTo({
|
await navigateTo({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
});
|
});
|
||||||
|
|||||||
74
frontend/lib/api/auth/oidc.ts
Normal file
74
frontend/lib/api/auth/oidc.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import type { ApiResponse } from '~/shared/types/api';
|
||||||
|
import { getApiHeaders } from '..';
|
||||||
|
import { toast } from 'vue-sonner';
|
||||||
|
import type { AuthUser } from '~/shared/types/auth';
|
||||||
|
|
||||||
|
export async function getRedirectUrl(): Promise<
|
||||||
|
{ success: true; url: string } | { success: false }
|
||||||
|
> {
|
||||||
|
const { data, error } = await useFetch<ApiResponse<string>>(
|
||||||
|
getApiUrl('auth/oidc'),
|
||||||
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: getApiHeaders(false),
|
||||||
|
responseType: 'json',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.value == null) {
|
||||||
|
toast.error('OpenID Connect', {
|
||||||
|
description: error.value?.data ?? 'Failed to get OIDC redirect',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
url: data.value.data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function oidcLoginUser(
|
||||||
|
code: string,
|
||||||
|
state: string
|
||||||
|
): Promise<{ success: boolean }> {
|
||||||
|
const { data, error } = await useFetch<
|
||||||
|
ApiResponse<{ token: string; user: AuthUser; expiresAt: number }>
|
||||||
|
>(getApiUrl(`auth/oidc/login?code=${code}&state=${state}`), {
|
||||||
|
method: 'GET',
|
||||||
|
headers: getApiHeaders(false),
|
||||||
|
responseType: 'json',
|
||||||
|
retry: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.value == null) {
|
||||||
|
toast.error('OpenID Connect', {
|
||||||
|
description: error.value?.data ?? 'Failed to login with OIDC',
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = data.value.data.token;
|
||||||
|
const { user, expiresAt } = data.value.data;
|
||||||
|
|
||||||
|
useAuthSession().value = {
|
||||||
|
type: 'WarrenAuth',
|
||||||
|
id: token,
|
||||||
|
user,
|
||||||
|
expiresAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
toast.success('OpenID Connect', {
|
||||||
|
description: `Successfully logged in`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { toTypedSchema } from '@vee-validate/yup';
|
import { toTypedSchema } from '@vee-validate/yup';
|
||||||
import { useForm } from 'vee-validate';
|
import { useForm } from 'vee-validate';
|
||||||
import { loginUser } from '~/lib/api/auth/login';
|
import { loginUser } from '~/lib/api/auth/login';
|
||||||
|
import { getRedirectUrl, oidcLoginUser } from '~/lib/api/auth/oidc';
|
||||||
import { loginSchema } from '~/lib/schemas/auth';
|
import { loginSchema } from '~/lib/schemas/auth';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -20,10 +21,31 @@ useHead({
|
|||||||
title: 'Login - Warren',
|
title: 'Login - Warren',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
// TODO: Get this from the backend
|
// TODO: Get this from the backend
|
||||||
const OPEN_ID = false;
|
const OPEN_ID = true;
|
||||||
const loggingIn = ref(false);
|
const loggingIn = ref(false);
|
||||||
|
|
||||||
|
if (
|
||||||
|
route.query.code &&
|
||||||
|
typeof route.query.code === 'string' &&
|
||||||
|
route.query.state &&
|
||||||
|
typeof route.query.state === 'string'
|
||||||
|
) {
|
||||||
|
console.log('SEND');
|
||||||
|
loggingIn.value = true;
|
||||||
|
const { success } = await oidcLoginUser(
|
||||||
|
route.query.code,
|
||||||
|
route.query.state
|
||||||
|
);
|
||||||
|
loggingIn.value = false;
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await navigateTo({ path: '/' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
validationSchema: toTypedSchema(loginSchema),
|
validationSchema: toTypedSchema(loginSchema),
|
||||||
});
|
});
|
||||||
@@ -43,6 +65,24 @@ const onSubmit = form.handleSubmit(async (values) => {
|
|||||||
|
|
||||||
loggingIn.value = false;
|
loggingIn.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function oidcClicked() {
|
||||||
|
if (loggingIn.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loggingIn.value = true;
|
||||||
|
|
||||||
|
const result = await getRedirectUrl();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
await navigateTo(result.url, {
|
||||||
|
external: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
loggingIn.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -97,7 +137,11 @@ const onSubmit = form.handleSubmit(async (values) => {
|
|||||||
:disabled="loggingIn"
|
:disabled="loggingIn"
|
||||||
>Log in</Button
|
>Log in</Button
|
||||||
>
|
>
|
||||||
<Button class="w-full" variant="outline" :disabled="!OPEN_ID"
|
<Button
|
||||||
|
class="w-full"
|
||||||
|
variant="outline"
|
||||||
|
:disabled="!OPEN_ID || loggingIn"
|
||||||
|
@click="oidcClicked"
|
||||||
>OpenID Connect</Button
|
>OpenID Connect</Button
|
||||||
>
|
>
|
||||||
<NuxtLink to="/register" class="w-full">
|
<NuxtLink to="/register" class="w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user