feat: add oauth flow base

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2025-03-27 18:39:29 +00:00
parent 4f0f8ddbe1
commit 3081fb4af8
12 changed files with 763 additions and 225 deletions

412
Cargo.lock generated
View File

@@ -100,6 +100,48 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-session"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07da4ce523b4e2ebaaf330746761df23a465b951a83d84bbce4233dabedae630"
dependencies = [
"anyhow",
"async-lock",
"async-trait",
"base64 0.13.1",
"bincode",
"blake3",
"chrono",
"hmac 0.11.0",
"log",
"rand 0.8.5",
"serde",
"serde_json",
"sha2 0.9.9",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.88" version = "0.1.88"
@@ -180,6 +222,28 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-extra"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
dependencies = [
"axum",
"axum-core",
"bytes",
"futures-util",
"headers",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"serde",
"tower",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.74" version = "0.3.74"
@@ -187,7 +251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cfg-if", "cfg-if 1.0.0",
"libc", "libc",
"miniz_oxide", "miniz_oxide",
"object", "object",
@@ -201,6 +265,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.7" version = "0.21.7"
@@ -219,6 +289,15 @@ version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "2.9.0"
@@ -228,6 +307,30 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "blake3"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if 0.1.10",
"constant_time_eq",
"crypto-mac 0.8.0",
"digest 0.9.0",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -264,6 +367,12 @@ dependencies = [
"shlex", "shlex",
] ]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@@ -352,6 +461,12 @@ 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 = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]] [[package]]
name = "core-foundation-sys" name = "core-foundation-sys"
version = "0.8.7" version = "0.8.7"
@@ -419,16 +534,36 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "crypto-mac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e"
dependencies = [
"generic-array",
"subtle",
]
[[package]] [[package]]
name = "curve25519-dalek" name = "curve25519-dalek"
version = "4.1.3" version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"cpufeatures", "cpufeatures",
"curve25519-dalek-derive", "curve25519-dalek-derive",
"digest", "digest 0.10.7",
"fiat-crypto", "fiat-crypto",
"rustc_version", "rustc_version",
"subtle", "subtle",
@@ -519,13 +654,22 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7"
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer", "block-buffer 0.10.4",
"const-oid", "const-oid",
"crypto-common", "crypto-common",
"subtle", "subtle",
@@ -561,7 +705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
dependencies = [ dependencies = [
"der", "der",
"digest", "digest 0.10.7",
"elliptic-curve", "elliptic-curve",
"rfc6979", "rfc6979",
"signature", "signature",
@@ -587,7 +731,7 @@ dependencies = [
"curve25519-dalek", "curve25519-dalek",
"ed25519", "ed25519",
"serde", "serde",
"sha2", "sha2 0.10.8",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -609,7 +753,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
dependencies = [ dependencies = [
"base16ct", "base16ct",
"crypto-bigint", "crypto-bigint",
"digest", "digest 0.10.7",
"ff", "ff",
"generic-array", "generic-array",
"group", "group",
@@ -644,11 +788,17 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"home", "home",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "event-listener"
version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.0" version = "5.4.0"
@@ -660,12 +810,6 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.3.0"
@@ -764,17 +908,6 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@@ -795,7 +928,6 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
@@ -821,7 +953,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"js-sys", "js-sys",
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
@@ -834,7 +966,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"js-sys", "js-sys",
"libc", "libc",
"r-efi", "r-efi",
@@ -885,6 +1017,30 @@ dependencies = [
"hashbrown 0.15.2", "hashbrown 0.15.2",
] ]
[[package]]
name = "headers"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9"
dependencies = [
"base64 0.21.7",
"bytes",
"headers-core",
"http",
"httpdate",
"mime",
"sha1",
]
[[package]]
name = "headers-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
"http",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@@ -903,7 +1059,17 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
dependencies = [ dependencies = [
"hmac", "hmac 0.12.1",
]
[[package]]
name = "hmac"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
dependencies = [
"crypto-mac 0.11.0",
"digest 0.9.0",
] ]
[[package]] [[package]]
@@ -912,7 +1078,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -1369,8 +1535,8 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -1473,7 +1639,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"sha2", "sha2 0.10.8",
"thiserror 1.0.69", "thiserror 1.0.69",
"url", "url",
] ]
@@ -1493,6 +1659,12 @@ version = "1.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]] [[package]]
name = "openidconnect" name = "openidconnect"
version = "4.0.0" version = "4.0.0"
@@ -1503,7 +1675,7 @@ dependencies = [
"chrono", "chrono",
"dyn-clone", "dyn-clone",
"ed25519-dalek", "ed25519-dalek",
"hmac", "hmac 0.12.1",
"http", "http",
"itertools", "itertools",
"log", "log",
@@ -1518,7 +1690,7 @@ dependencies = [
"serde_path_to_error", "serde_path_to_error",
"serde_plain", "serde_plain",
"serde_with", "serde_with",
"sha2", "sha2 0.10.8",
"subtle", "subtle",
"thiserror 1.0.69", "thiserror 1.0.69",
"url", "url",
@@ -1542,7 +1714,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"primeorder", "primeorder",
"sha2", "sha2 0.10.8",
] ]
[[package]] [[package]]
@@ -1554,7 +1726,7 @@ dependencies = [
"ecdsa", "ecdsa",
"elliptic-curve", "elliptic-curve",
"primeorder", "primeorder",
"sha2", "sha2 0.10.8",
] ]
[[package]] [[package]]
@@ -1579,7 +1751,7 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
@@ -1601,24 +1773,6 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.16" version = "0.2.16"
@@ -1658,49 +1812,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "postgres"
version = "0.19.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "363e6dfbdd780d3aa3597b6eb430db76bb315fa9bad7fae595bb8def808b8470"
dependencies = [
"bytes",
"fallible-iterator",
"futures-util",
"log",
"tokio",
"tokio-postgres",
]
[[package]]
name = "postgres-protocol"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54"
dependencies = [
"base64 0.22.1",
"byteorder",
"bytes",
"fallible-iterator",
"hmac",
"md-5",
"memchr",
"rand 0.9.0",
"sha2",
"stringprep",
]
[[package]]
name = "postgres-types"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48"
dependencies = [
"bytes",
"fallible-iterator",
"postgres-protocol",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@@ -1921,7 +2032,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
dependencies = [ dependencies = [
"hmac", "hmac 0.12.1",
"subtle", "subtle",
] ]
@@ -1932,7 +2043,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if 1.0.0",
"getrandom 0.2.15", "getrandom 0.2.15",
"libc", "libc",
"untrusted", "untrusted",
@@ -1946,7 +2057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b"
dependencies = [ dependencies = [
"const-oid", "const-oid",
"digest", "digest 0.10.7",
"num-bigint-dig", "num-bigint-dig",
"num-integer", "num-integer",
"num-traits", "num-traits",
@@ -2196,9 +2307,22 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"cpufeatures", "cpufeatures",
"digest", "digest 0.10.7",
]
[[package]]
name = "sha2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug",
] ]
[[package]] [[package]]
@@ -2207,9 +2331,9 @@ version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"cpufeatures", "cpufeatures",
"digest", "digest 0.10.7",
] ]
[[package]] [[package]]
@@ -2224,16 +2348,10 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [ dependencies = [
"digest", "digest 0.10.7",
"rand_core 0.6.4", "rand_core 0.6.4",
] ]
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"
@@ -2304,7 +2422,7 @@ dependencies = [
"crc", "crc",
"crossbeam-queue", "crossbeam-queue",
"either", "either",
"event-listener", "event-listener 5.4.0",
"futures-core", "futures-core",
"futures-intrusive", "futures-intrusive",
"futures-io", "futures-io",
@@ -2318,9 +2436,10 @@ dependencies = [
"percent-encoding", "percent-encoding",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2 0.10.8",
"smallvec", "smallvec",
"thiserror 2.0.12", "thiserror 2.0.12",
"time",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tracing", "tracing",
@@ -2355,7 +2474,7 @@ dependencies = [
"quote", "quote",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2 0.10.8",
"sqlx-core", "sqlx-core",
"sqlx-mysql", "sqlx-mysql",
"sqlx-postgres", "sqlx-postgres",
@@ -2378,7 +2497,7 @@ dependencies = [
"byteorder", "byteorder",
"bytes", "bytes",
"crc", "crc",
"digest", "digest 0.10.7",
"dotenvy", "dotenvy",
"either", "either",
"futures-channel", "futures-channel",
@@ -2388,7 +2507,7 @@ dependencies = [
"generic-array", "generic-array",
"hex", "hex",
"hkdf", "hkdf",
"hmac", "hmac 0.12.1",
"itoa", "itoa",
"log", "log",
"md-5", "md-5",
@@ -2399,11 +2518,12 @@ dependencies = [
"rsa", "rsa",
"serde", "serde",
"sha1", "sha1",
"sha2", "sha2 0.10.8",
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
"stringprep", "stringprep",
"thiserror 2.0.12", "thiserror 2.0.12",
"time",
"tracing", "tracing",
"whoami", "whoami",
] ]
@@ -2426,7 +2546,7 @@ dependencies = [
"futures-util", "futures-util",
"hex", "hex",
"hkdf", "hkdf",
"hmac", "hmac 0.12.1",
"home", "home",
"itoa", "itoa",
"log", "log",
@@ -2436,11 +2556,12 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2 0.10.8",
"smallvec", "smallvec",
"sqlx-core", "sqlx-core",
"stringprep", "stringprep",
"thiserror 2.0.12", "thiserror 2.0.12",
"time",
"tracing", "tracing",
"whoami", "whoami",
] ]
@@ -2464,6 +2585,7 @@ dependencies = [
"serde", "serde",
"serde_urlencoded", "serde_urlencoded",
"sqlx-core", "sqlx-core",
"time",
"tracing", "tracing",
"url", "url",
] ]
@@ -2685,32 +2807,6 @@ dependencies = [
"syn 2.0.100", "syn 2.0.100",
] ]
[[package]]
name = "tokio-postgres"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0"
dependencies = [
"async-trait",
"byteorder",
"bytes",
"fallible-iterator",
"futures-channel",
"futures-util",
"log",
"parking_lot",
"percent-encoding",
"phf",
"pin-project-lite",
"postgres-protocol",
"postgres-types",
"rand 0.9.0",
"socket2",
"tokio",
"tokio-util",
"whoami",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.2" version = "0.26.2"
@@ -2732,19 +2828,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-util"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -2914,14 +2997,14 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
name = "veil" name = "veil"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-session",
"axum", "axum",
"axum-extra",
"clap", "clap",
"log", "log",
"log4rs", "log4rs",
"openidconnect", "openidconnect",
"postgres",
"serde", "serde",
"serde_derive",
"serde_yaml", "serde_yaml",
"sqlx", "sqlx",
"tokio", "tokio",
@@ -2969,7 +3052,7 @@ version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"once_cell", "once_cell",
"rustversion", "rustversion",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@@ -2995,7 +3078,7 @@ version = "0.4.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
dependencies = [ dependencies = [
"cfg-if", "cfg-if 1.0.0",
"js-sys", "js-sys",
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@@ -3071,7 +3154,6 @@ checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
dependencies = [ dependencies = [
"redox_syscall", "redox_syscall",
"wasite", "wasite",
"web-sys",
] ]
[[package]] [[package]]

View File

@@ -14,14 +14,14 @@ lto = true
codegen-units = 1 codegen-units = 1
[dependencies] [dependencies]
async-session = "3.0.0"
axum = "0.8.1" axum = "0.8.1"
axum-extra = { version = "0.10.0", features = ["typed-header"] }
clap = { version = "4.5.32", features = ["derive"] } clap = { version = "4.5.32", features = ["derive"] }
log = "0.4.27" log = "0.4.27"
log4rs = "1.3.0" log4rs = "1.3.0"
openidconnect = "4.0.0" openidconnect = { version = "4.0.0", features = ["reqwest"] }
postgres = "0.19.10"
serde = "1.0.219" serde = "1.0.219"
serde_derive = "1.0.219"
serde_yaml = "0.9.34" serde_yaml = "0.9.34"
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio"] } sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio", "time"] }
tokio = { version = "1.44.1", features = ["rt-multi-thread"] } tokio = { version = "1.44.1", features = ["rt-multi-thread"] }

View File

@@ -11,6 +11,8 @@ RUN cargo fetch
RUN rm -rf src RUN rm -rf src
COPY src ./src COPY src ./src
COPY migrations ./migrations
COPY .sqlx ./.sqlx
RUN cargo build --target=x86_64-unknown-linux-musl --release RUN cargo build --target=x86_64-unknown-linux-musl --release

View File

@@ -70,6 +70,9 @@ metadata:
name: veil-config name: veil-config
data: data:
default.yml: | default.yml: |
server:
host: https://app.veil.local
database: database:
host: postgresql host: postgresql
port: 5432 port: 5432
@@ -77,10 +80,11 @@ data:
password: veil password: veil
database: veil database: veil
oidc: oauth:
issuer_url: "https://id.veil.local" issuer_url: "https://id.veil.local"
client_id: "veil" client_id: "veil"
client_secret: "insecure_secret" client_secret: "insecure_secret"
insecure: true
log4rs.yml: | log4rs.yml: |
appenders: appenders:
stdout: stdout:
@@ -167,7 +171,7 @@ data:
- client_id: "veil" - client_id: "veil"
client_secret: "$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng" # The digest of 'insecure_secret'. client_secret: "$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng" # The digest of 'insecure_secret'.
redirect_uris: redirect_uris:
- "https://app.veil.local/oauth2/callback" - "https://app.veil.local/api/auth/callback"
authorization_policy: "one_factor" authorization_policy: "one_factor"
users.yml: | users.yml: |
users: users:

View File

View File

@@ -1,3 +1,4 @@
use clap::Parser;
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
fs, fs,
@@ -5,8 +6,9 @@ use std::{
path::PathBuf, path::PathBuf,
}; };
#[derive(Deserialize)] #[derive(Clone, Deserialize)]
pub struct ServerConfig { pub struct ServerConfig {
pub host: String,
#[serde(default = "default_address")] #[serde(default = "default_address")]
pub address: std::net::IpAddr, pub address: std::net::IpAddr,
#[serde(default = "default_port")] #[serde(default = "default_port")]
@@ -23,17 +25,7 @@ const fn default_port() -> u16 {
51821 51821
} }
impl Default for ServerConfig { #[derive(Clone, Deserialize)]
fn default() -> Self {
Self {
address: default_address(),
port: default_port(),
subpath: String::default(),
}
}
}
#[derive(Deserialize)]
pub struct DatabaseConfig { pub struct DatabaseConfig {
pub user: String, pub user: String,
pub password: String, pub password: String,
@@ -43,18 +35,19 @@ pub struct DatabaseConfig {
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct OidcConfig { pub struct OAuthConfig {
pub issuer_url: String, pub issuer_url: String,
pub client_id: String, pub client_id: String,
pub client_secret: String, pub client_secret: String,
#[serde(default)]
pub insecure: bool,
} }
#[derive(Deserialize)] #[derive(Clone, Deserialize)]
pub struct Config { pub struct Config {
#[serde(default)]
pub server: ServerConfig, pub server: ServerConfig,
pub database: DatabaseConfig, pub database: DatabaseConfig,
pub oidc: OidcConfig, pub oauth: OAuthConfig,
} }
impl Config { impl Config {
@@ -64,3 +57,14 @@ impl Config {
Ok(config) Ok(config)
} }
} }
#[derive(Parser, Debug)]
#[command(version, about, long_about = None, author)]
pub struct Args {
/// Path to the YAML config file
#[arg(short, long, value_name = "FILE", default_value = "config.yaml")]
pub config: PathBuf,
/// Path to the log4rs config file
#[arg(short, long, value_name = "FILE", default_value = "log4rs.yaml")]
pub log_config: PathBuf,
}

View File

@@ -2,56 +2,38 @@
#![allow(clippy::missing_docs_in_private_items)] #![allow(clippy::missing_docs_in_private_items)]
mod config; mod config;
mod models;
mod routes; mod routes;
mod state;
use axum::{Extension, serve}; use axum::serve;
use clap::Parser; use clap::Parser;
use log::info; use log::info;
use log4rs::config::Deserializers; use log4rs::config::Deserializers;
use sqlx::postgres::PgPoolOptions; use std::net::SocketAddr;
use std::{net::SocketAddr, path::PathBuf};
use tokio::net::TcpListener; use tokio::net::TcpListener;
use config::Config; use config::{Args, Config};
use state::State;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None, author)]
struct Args {
/// Path to the YAML config file
#[arg(short, long, value_name = "FILE", default_value = "config.yaml")]
config: PathBuf,
/// Path to the log4rs config file
#[arg(short, long, value_name = "FILE", default_value = "log4rs.yaml")]
log_config: PathBuf,
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let args = Args::parse(); let args = Args::parse();
log4rs::init_file(args.log_config, Deserializers::default()).unwrap(); log4rs::init_file(args.log_config, Deserializers::default()).unwrap();
let config = Config::from_yaml(&args.config).unwrap(); let config = Config::from_yaml(&args.config).unwrap();
let state = State::from_config(config.clone()).await.unwrap();
let pgpool = PgPoolOptions::new() sqlx::migrate!("./migrations")
.max_connections(5) .run(&state.pg_pool)
.connect(&format!(
"postgres://{}:{}@{}:{}/{}",
config.database.user,
config.database.password,
config.database.host,
config.database.port,
config.database.database
))
.await .await
.unwrap(); .expect("Failed to run migrations");
let routes = routes::routes(); let routes = routes::routes(state);
let app = axum::Router::new() let app = axum::Router::new().nest(&format!("{}/api", config.server.subpath), routes);
.nest(&format!("{}/api", config.server.subpath), routes)
.layer(Extension(pgpool));
let addr = SocketAddr::from((config.server.address, config.server.port)); let addr = SocketAddr::from((config.server.address, config.server.port));
let listener = TcpListener::bind(addr).await.unwrap(); let listener = TcpListener::bind(addr).await.unwrap();
info!("Listening on {addr}."); info!("Listening on {}", listener.local_addr().unwrap());
serve(listener, app).await.unwrap(); serve(listener, app).await.unwrap();
} }

0
src/models/mod.rs Normal file
View File

317
src/routes/auth.rs Normal file
View File

@@ -0,0 +1,317 @@
use std::{borrow::Cow, convert::Infallible};
use async_session::{MemoryStore, Session, SessionStore};
use axum::{
RequestPartsExt, Router,
extract::{self, FromRef, FromRequestParts, OptionalFromRequestParts},
http::{HeaderMap, StatusCode, header, request::Parts},
response::{IntoResponse, Redirect, Response},
routing,
};
use axum_extra::{TypedHeader, headers::Cookie, typed_header::TypedHeaderRejectionReason};
use log::error;
use openidconnect::{
AccessTokenHash, AuthorizationCode, CsrfToken, EndUserEmail, EndUserUsername, Nonce,
OAuth2TokenResponse, PkceCodeChallenge, PkceCodeVerifier, Scope, SubjectIdentifier,
TokenResponse, core::CoreAuthenticationFlow, reqwest,
};
use serde::{Deserialize, Serialize};
use crate::state::{OAuthClient, State};
static COOKIE_NAME: &str = "veil_session";
#[derive(Clone, Deserialize, Serialize)]
pub struct User {
pub subject: SubjectIdentifier,
pub username: EndUserUsername,
pub email: Option<EndUserEmail>,
}
async fn login(
extract::State(oauth_client): extract::State<OAuthClient>,
extract::State(session_store): extract::State<MemoryStore>,
) -> Result<impl IntoResponse, StatusCode> {
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let (auth_url, csrf_token, nonce) = oauth_client
.authorize_url(
CoreAuthenticationFlow::AuthorizationCode,
CsrfToken::new_random,
Nonce::new_random,
)
.set_pkce_challenge(pkce_challenge)
.set_redirect_uri(Cow::Borrowed(oauth_client.redirect_uri().ok_or_else(
|| {
error!("missing redirect URI");
StatusCode::INTERNAL_SERVER_ERROR
},
)?))
.add_scope(Scope::new("profile".to_string()))
.add_scope(Scope::new("email".to_string()))
.add_scope(Scope::new("groups".to_string()))
.url();
let mut session = Session::new();
session
.insert("pkce_verifier", pkce_verifier)
.map_err(|e| {
error!("failed to insert pkce_verifier into session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
session.insert("csrf_token", csrf_token).map_err(|e| {
error!("failed to insert csrf_token into session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
session.insert("nonce", nonce).map_err(|e| {
error!("failed to insert nonce into session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let cookie = session_store
.store_session(session)
.await
.map_err(|e| {
error!("failed to store session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?
.ok_or_else(|| {
error!("failed to retrieve stored session cookie");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let cookie =
format!("{COOKIE_NAME}={cookie}; HttpOnly; SameSite=Lax; HttpOnly; Secure; Path=/");
let mut headers = HeaderMap::new();
headers.insert(
header::SET_COOKIE,
cookie.parse().map_err(|e| {
error!("failed to parse cookie: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?,
);
Ok((headers, Redirect::to(auth_url.as_str())))
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct CallbackParams {
code: String,
state: String,
}
async fn callback(
extract::Query(params): extract::Query<CallbackParams>,
extract::State(http_client): extract::State<reqwest::Client>,
extract::State(oauth_client): extract::State<OAuthClient>,
extract::State(session_store): extract::State<MemoryStore>,
TypedHeader(cookies): TypedHeader<Cookie>,
) -> Result<impl IntoResponse, StatusCode> {
let cookie = cookies
.get(COOKIE_NAME)
.ok_or(StatusCode::UNAUTHORIZED)?
.to_string();
let session = session_store
.load_session(cookie)
.await
.map_err(|e| {
error!("failed to load session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?
.ok_or(StatusCode::UNAUTHORIZED)?;
let csrf_token = session
.get::<CsrfToken>("csrf_token")
.ok_or_else(|| {
error!("failed to get csrf_token from session");
StatusCode::INTERNAL_SERVER_ERROR
})?
.clone();
let pkce_verifier = session
.get::<PkceCodeVerifier>("pkce_verifier")
.ok_or_else(|| {
error!("failed to get pkce_verifier from session");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let nonce = session
.get::<Nonce>("nonce")
.ok_or_else(|| {
error!("failed to get nonce from session");
StatusCode::INTERNAL_SERVER_ERROR
})?
.clone();
session_store.destroy_session(session).await.map_err(|e| {
error!("failed to destroy session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
if *csrf_token.secret() != params.state {
error!("csrf_token mismatch");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
let token_response = oauth_client
.exchange_code(AuthorizationCode::new(params.code))
.map_err(|e| {
error!("failed to exchange code: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?
.set_pkce_verifier(pkce_verifier)
.request_async(&http_client)
.await
.map_err(|e| {
error!("failed to request token: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let id_token = token_response.id_token().ok_or_else(|| {
error!("missing id_token in token response");
StatusCode::INTERNAL_SERVER_ERROR
})?;
let id_token_verifier = oauth_client.id_token_verifier();
let claims = id_token.claims(&id_token_verifier, &nonce).map_err(|e| {
error!("failed to verify id_token: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
if let Some(expected_access_token_hash) = claims.access_token_hash() {
let actual_access_token_hash = AccessTokenHash::from_token(
token_response.access_token(),
id_token.signing_alg().map_err(|e| {
error!("failed to get signing algorithm: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?,
id_token.signing_key(&id_token_verifier).map_err(|e| {
error!("failed to get signing key: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?,
)
.map_err(|e| {
error!("failed to compute access token hash: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
if actual_access_token_hash != *expected_access_token_hash {
error!("access token hash mismatch");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
}
let user = User {
subject: claims.subject().to_owned(),
username: claims.preferred_username().cloned().ok_or_else(|| {
error!("missing preferred_username in claims");
StatusCode::INTERNAL_SERVER_ERROR
})?,
email: claims.email().cloned(),
};
let mut session = Session::new();
session.insert("user", user).map_err(|e| {
error!("failed to insert user into session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
Ok(StatusCode::OK)
}
async fn logout(
extract::State(session_store): extract::State<MemoryStore>,
TypedHeader(cookies): TypedHeader<Cookie>,
) -> Result<impl IntoResponse, StatusCode> {
let cookie = cookies.get(COOKIE_NAME).ok_or(StatusCode::UNAUTHORIZED)?;
let Some(session) = session_store
.load_session(cookie.to_string())
.await
.map_err(|e| {
error!("failed to load session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?
else {
return Ok(StatusCode::OK);
};
session_store.destroy_session(session).await.map_err(|e| {
error!("failed to destroy session: {e}");
StatusCode::INTERNAL_SERVER_ERROR
})?;
Ok(StatusCode::OK)
}
pub fn routes(state: State) -> Router {
Router::new()
.route("/auth/login", routing::get(login))
.route("/auth/callback", routing::get(callback))
.route("/auth/logout", routing::get(logout))
.with_state(state)
}
impl<S> FromRequestParts<S> for User
where
MemoryStore: FromRef<S>,
S: Send + Sync,
{
type Rejection = Response;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let store = MemoryStore::from_ref(state);
let cookies = parts.extract::<TypedHeader<Cookie>>().await.map_err(|e| {
if *e.name() == header::COOKIE {
if matches!(e.reason(), TypedHeaderRejectionReason::Missing) {
StatusCode::UNAUTHORIZED.into_response()
} else {
error!("failed to extract cookies: {e}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
} else {
error!("failed to extract cookies: {e}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
})?;
let session_cookie = cookies
.get(COOKIE_NAME)
.ok_or_else(|| StatusCode::UNAUTHORIZED.into_response())?;
let session = store
.load_session(session_cookie.to_string())
.await
.map_err(|e| {
error!("failed to load session: {e}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
})?
.ok_or_else(|| StatusCode::UNAUTHORIZED.into_response())?;
let user = session
.get::<Self>("user")
.ok_or_else(|| StatusCode::UNAUTHORIZED.into_response())?;
Ok(user)
}
}
impl<S> OptionalFromRequestParts<S> for User
where
MemoryStore: FromRef<S>,
S: Send + Sync,
{
type Rejection = Infallible;
async fn from_request_parts(
parts: &mut Parts,
state: &S,
) -> Result<Option<Self>, Self::Rejection> {
(<Self as FromRequestParts<S>>::from_request_parts(parts, state).await)
.map_or(Ok(None), |user| Ok(Some(user)))
}
}

View File

@@ -1,9 +1,13 @@
use axum::{Router, http::StatusCode, routing}; use axum::{Router, http::StatusCode, response::IntoResponse, routing};
pub async fn get() -> Result<StatusCode, StatusCode> { use crate::state::State;
pub async fn get() -> Result<impl IntoResponse, StatusCode> {
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }
pub fn routes() -> Router { pub fn routes(state: State) -> Router {
Router::new().route("/", routing::get(get)) Router::new()
.route("/health", routing::get(get))
.with_state(state)
} }

View File

@@ -1,9 +1,13 @@
mod auth;
mod health; mod health;
use axum::Router; use axum::Router;
pub fn routes() -> Router { use crate::state::State;
let health = health::routes();
Router::new().merge(health) pub fn routes(state: State) -> Router {
let auth = auth::routes(state.clone());
let health = health::routes(state);
Router::new().merge(auth).merge(health)
} }

139
src/state.rs Normal file
View File

@@ -0,0 +1,139 @@
use async_session::MemoryStore;
use axum::extract::FromRef;
use log::error;
use openidconnect::{
ClientId, ClientSecret, EmptyAdditionalClaims, EndpointMaybeSet, EndpointNotSet, EndpointSet,
IssuerUrl, RedirectUrl, StandardErrorResponse,
core::{
CoreAuthDisplay, CoreAuthPrompt, CoreErrorResponseType, CoreGenderClaim, CoreJsonWebKey,
CoreJweContentEncryptionAlgorithm, CoreProviderMetadata, CoreRevocableToken,
CoreRevocationErrorResponse, CoreTokenIntrospectionResponse, CoreTokenResponse,
},
reqwest,
};
use tokio::{
spawn,
time::{Duration, sleep},
};
use crate::config::Config;
pub type OAuthClient<
HasAuthUrl = EndpointSet,
HasDeviceAuthUrl = EndpointNotSet,
HasIntrospectionUrl = EndpointNotSet,
HasRevocationUrl = EndpointNotSet,
HasTokenUrl = EndpointMaybeSet,
HasUserInfoUrl = EndpointMaybeSet,
> = openidconnect::Client<
EmptyAdditionalClaims,
CoreAuthDisplay,
CoreGenderClaim,
CoreJweContentEncryptionAlgorithm,
CoreJsonWebKey,
CoreAuthPrompt,
StandardErrorResponse<CoreErrorResponseType>,
CoreTokenResponse,
CoreTokenIntrospectionResponse,
CoreRevocableToken,
CoreRevocationErrorResponse,
HasAuthUrl,
HasDeviceAuthUrl,
HasIntrospectionUrl,
HasRevocationUrl,
HasTokenUrl,
HasUserInfoUrl,
>;
#[derive(Clone)]
pub struct State {
pub pg_pool: sqlx::PgPool,
pub oauth_http_client: reqwest::Client,
pub oauth_client: OAuthClient,
pub session_store: async_session::MemoryStore,
}
impl State {
pub async fn from_config(config: Config) -> Result<Self, Box<dyn std::error::Error>> {
let pg_pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(5)
.connect(&format!(
"postgres://{}:{}@{}:{}/{}",
config.database.user,
config.database.password,
config.database.host,
config.database.port,
config.database.database
))
.await?;
let mut http_client =
reqwest::ClientBuilder::new().redirect(reqwest::redirect::Policy::none());
if config.oauth.insecure {
http_client = http_client.danger_accept_invalid_certs(true);
}
let http_client = http_client.build()?;
let provider_metadata = CoreProviderMetadata::discover_async(
IssuerUrl::new(config.oauth.issuer_url)?,
&http_client,
)
.await?;
let oauth_client = openidconnect::core::CoreClient::from_provider_metadata(
provider_metadata,
ClientId::new(config.oauth.client_id),
Some(ClientSecret::new(config.oauth.client_secret)),
)
.set_redirect_uri(RedirectUrl::new(format!(
"{}{}/api/auth/callback",
config.server.host, config.server.subpath
))?);
let session_store = MemoryStore::new();
let session_store_clone = session_store.clone();
spawn(async move {
loop {
match session_store_clone.cleanup().await {
Ok(()) => {}
Err(e) => error!("Failed to clean up session store: {e}"),
}
sleep(Duration::from_secs(60)).await;
}
});
Ok(Self {
pg_pool,
oauth_http_client: http_client,
oauth_client,
session_store,
})
}
}
impl FromRef<State> for sqlx::PgPool {
fn from_ref(state: &State) -> Self {
state.pg_pool.clone()
}
}
impl FromRef<State> for reqwest::Client {
fn from_ref(state: &State) -> Self {
state.oauth_http_client.clone()
}
}
impl FromRef<State> for OAuthClient {
fn from_ref(state: &State) -> Self {
state.oauth_client.clone()
}
}
impl FromRef<State> for async_session::MemoryStore {
fn from_ref(state: &State) -> Self {
state.session_store.clone()
}
}