auth in infra
This commit is contained in:
418
Cargo.lock
generated
418
Cargo.lock
generated
@@ -378,12 +378,24 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base16ct"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -692,6 +704,18 @@ version = "0.2.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-bigint"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@@ -715,6 +739,7 @@ dependencies = [
|
|||||||
"fiat-crypto",
|
"fiat-crypto",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -734,8 +759,18 @@ version = "0.20.11"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.20.11",
|
||||||
"darling_macro",
|
"darling_macro 0.20.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling"
|
||||||
|
version = "0.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.21.3",
|
||||||
|
"darling_macro 0.21.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -752,13 +787,38 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_core"
|
||||||
|
version = "0.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"ident_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"strsim",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling_macro"
|
name = "darling_macro"
|
||||||
version = "0.20.11"
|
version = "0.20.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core 0.20.11",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "darling_macro"
|
||||||
|
version = "0.21.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
|
||||||
|
dependencies = [
|
||||||
|
"darling_core 0.21.3",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
@@ -814,7 +874,7 @@ version = "0.20.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.20.11",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -880,12 +940,33 @@ version = "0.15.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecdsa"
|
||||||
|
version = "0.16.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
|
||||||
|
dependencies = [
|
||||||
|
"der",
|
||||||
|
"digest",
|
||||||
|
"elliptic-curve",
|
||||||
|
"rfc6979",
|
||||||
|
"signature",
|
||||||
|
"spki",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"pkcs8",
|
||||||
"signature",
|
"signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -897,9 +978,11 @@ checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"ed25519",
|
"ed25519",
|
||||||
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"signature",
|
"signature",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -911,6 +994,27 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "elliptic-curve"
|
||||||
|
version = "0.13.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"crypto-bigint",
|
||||||
|
"digest",
|
||||||
|
"ff",
|
||||||
|
"generic-array",
|
||||||
|
"group",
|
||||||
|
"hkdf",
|
||||||
|
"pem-rfc7468",
|
||||||
|
"pkcs8",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"sec1",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "email_address"
|
name = "email_address"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
@@ -1065,6 +1169,16 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ff"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fiat-crypto"
|
name = "fiat-crypto"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
@@ -1254,6 +1368,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
"version_check",
|
"version_check",
|
||||||
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1293,6 +1408,17 @@ dependencies = [
|
|||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "group"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
|
||||||
|
dependencies = [
|
||||||
|
"ff",
|
||||||
|
"rand_core 0.6.4",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
@@ -1735,6 +1861,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1745,6 +1872,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1787,6 +1916,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.10.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -1822,6 +1960,29 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "10.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c76e1c7d7df3e34443b3621b459b066a7b79644f059fc8b2db7070c825fd417e"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"ed25519-dalek",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"hmac",
|
||||||
|
"js-sys",
|
||||||
|
"p256",
|
||||||
|
"p384",
|
||||||
|
"pem",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"rsa",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"signature",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "k-core"
|
name = "k-core"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
@@ -2221,18 +2382,24 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"axum-login",
|
||||||
"chrono",
|
"chrono",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"jsonwebtoken",
|
||||||
"k-core",
|
"k-core",
|
||||||
"notes-domain",
|
"notes-domain",
|
||||||
|
"openidconnect",
|
||||||
|
"password-auth",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-sessions",
|
||||||
"tower-sessions-sqlx-store",
|
"tower-sessions-sqlx-store",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2375,6 +2542,26 @@ 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 = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oauth2"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"http",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"reqwest",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"sha2",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.3"
|
version = "1.21.3"
|
||||||
@@ -2403,6 +2590,37 @@ dependencies = [
|
|||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "openidconnect"
|
||||||
|
version = "4.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d8c6709ba2ea764bbed26bce1adf3c10517113ddea6f2d4196e4851757ef2b2"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"chrono",
|
||||||
|
"dyn-clone",
|
||||||
|
"ed25519-dalek",
|
||||||
|
"hmac",
|
||||||
|
"http",
|
||||||
|
"itertools 0.10.5",
|
||||||
|
"log",
|
||||||
|
"oauth2",
|
||||||
|
"p256",
|
||||||
|
"p384",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"rsa",
|
||||||
|
"serde",
|
||||||
|
"serde-value",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"serde_plain",
|
||||||
|
"serde_with",
|
||||||
|
"sha2",
|
||||||
|
"subtle",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl"
|
name = "openssl"
|
||||||
version = "0.10.75"
|
version = "0.10.75"
|
||||||
@@ -2459,6 +2677,15 @@ 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 = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ordered-float"
|
||||||
|
version = "2.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ort"
|
name = "ort"
|
||||||
version = "2.0.0-rc.10"
|
version = "2.0.0-rc.10"
|
||||||
@@ -2484,6 +2711,30 @@ dependencies = [
|
|||||||
"ureq 3.1.4",
|
"ureq 3.1.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p256"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
|
||||||
|
dependencies = [
|
||||||
|
"ecdsa",
|
||||||
|
"elliptic-curve",
|
||||||
|
"primeorder",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "p384"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
|
||||||
|
dependencies = [
|
||||||
|
"ecdsa",
|
||||||
|
"elliptic-curve",
|
||||||
|
"primeorder",
|
||||||
|
"sha2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
version = "2.2.1"
|
version = "2.2.1"
|
||||||
@@ -2548,6 +2799,16 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pem-rfc7468"
|
name = "pem-rfc7468"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -2674,6 +2935,15 @@ dependencies = [
|
|||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primeorder"
|
||||||
|
version = "0.13.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
|
||||||
|
dependencies = [
|
||||||
|
"elliptic-curve",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error-attr2"
|
name = "proc-macro-error-attr2"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -2741,7 +3011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools",
|
"itertools 0.14.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -2947,7 +3217,7 @@ dependencies = [
|
|||||||
"built",
|
"built",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"interpolate_name",
|
"interpolate_name",
|
||||||
"itertools",
|
"itertools 0.14.0",
|
||||||
"libc",
|
"libc",
|
||||||
"libfuzzer-sys",
|
"libfuzzer-sys",
|
||||||
"log",
|
"log",
|
||||||
@@ -3004,7 +3274,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
|
checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
"itertools",
|
"itertools 0.14.0",
|
||||||
"rayon",
|
"rayon",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3047,6 +3317,26 @@ dependencies = [
|
|||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast"
|
||||||
|
version = "1.0.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
|
||||||
|
dependencies = [
|
||||||
|
"ref-cast-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ref-cast-impl"
|
||||||
|
version = "1.0.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.2"
|
version = "1.12.2"
|
||||||
@@ -3123,6 +3413,16 @@ dependencies = [
|
|||||||
"webpki-roots 1.0.4",
|
"webpki-roots 1.0.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rfc6979"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
|
||||||
|
dependencies = [
|
||||||
|
"hmac",
|
||||||
|
"subtle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rgb"
|
name = "rgb"
|
||||||
version = "0.8.52"
|
version = "0.8.52"
|
||||||
@@ -3321,12 +3621,50 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schemars"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
|
||||||
|
dependencies = [
|
||||||
|
"dyn-clone",
|
||||||
|
"ref-cast",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "schemars"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2"
|
||||||
|
dependencies = [
|
||||||
|
"dyn-clone",
|
||||||
|
"ref-cast",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[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 = "sec1"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||||
|
dependencies = [
|
||||||
|
"base16ct",
|
||||||
|
"der",
|
||||||
|
"generic-array",
|
||||||
|
"pkcs8",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.11.1"
|
version = "2.11.1"
|
||||||
@@ -3379,6 +3717,16 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-value"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -3432,6 +3780,15 @@ dependencies = [
|
|||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_plain"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_repr"
|
name = "serde_repr"
|
||||||
version = "0.1.20"
|
version = "0.1.20"
|
||||||
@@ -3455,6 +3812,37 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with"
|
||||||
|
version = "3.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"chrono",
|
||||||
|
"hex",
|
||||||
|
"indexmap 1.9.3",
|
||||||
|
"indexmap 2.12.1",
|
||||||
|
"schemars 0.9.0",
|
||||||
|
"schemars 1.2.0",
|
||||||
|
"serde_core",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with_macros",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_with_macros"
|
||||||
|
version = "3.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
|
||||||
|
dependencies = [
|
||||||
|
"darling 0.21.3",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
@@ -3539,6 +3927,18 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror 2.0.17",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
@@ -4065,7 +4465,7 @@ dependencies = [
|
|||||||
"derive_builder",
|
"derive_builder",
|
||||||
"esaxx-rs",
|
"esaxx-rs",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"itertools",
|
"itertools 0.14.0",
|
||||||
"log",
|
"log",
|
||||||
"macro_rules_attribute",
|
"macro_rules_attribute",
|
||||||
"monostate",
|
"monostate",
|
||||||
@@ -4630,7 +5030,7 @@ version = "0.20.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
|
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling 0.20.11",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro-error2",
|
"proc-macro-error2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ pub mod services;
|
|||||||
pub mod value_objects;
|
pub mod value_objects;
|
||||||
|
|
||||||
// Re-export commonly used types at crate root
|
// Re-export commonly used types at crate root
|
||||||
pub use entities::{MAX_TAGS_PER_NOTE, Note, NoteFilter, NoteVersion, Tag, User};
|
pub use entities::*;
|
||||||
pub use errors::{DomainError, DomainResult};
|
pub use errors::{DomainError, DomainResult};
|
||||||
pub use ports::MessageBroker;
|
pub use ports::*;
|
||||||
pub use repositories::{NoteRepository, TagRepository, UserRepository};
|
pub use repositories::*;
|
||||||
pub use services::{CreateNoteRequest, NoteService, TagService, UpdateNoteRequest, UserService};
|
pub use services::*;
|
||||||
pub use value_objects::{
|
pub use value_objects::*;
|
||||||
Email, MAX_NOTE_TITLE_LENGTH, MAX_TAG_NAME_LENGTH, MIN_PASSWORD_LENGTH, NoteTitle, Password,
|
|
||||||
TagName, ValidationError,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -4,18 +4,38 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sqlite", "smart-features", "broker-nats"]
|
default = [
|
||||||
sqlite = ["sqlx/sqlite", "k-core/sqlite", "tower-sessions-sqlx-store", "k-core/sessions-db"]
|
"sqlite",
|
||||||
postgres = ["sqlx/postgres", "k-core/postgres", "tower-sessions-sqlx-store", "k-core/sessions-db"]
|
"smart-features",
|
||||||
|
"broker-nats",
|
||||||
|
"auth-jwt",
|
||||||
|
"auth-oidc",
|
||||||
|
"auth-axum-login",
|
||||||
|
]
|
||||||
|
sqlite = [
|
||||||
|
"sqlx/sqlite",
|
||||||
|
"k-core/sqlite",
|
||||||
|
"tower-sessions-sqlx-store",
|
||||||
|
"k-core/sessions-db",
|
||||||
|
]
|
||||||
|
postgres = [
|
||||||
|
"sqlx/postgres",
|
||||||
|
"k-core/postgres",
|
||||||
|
"tower-sessions-sqlx-store",
|
||||||
|
"k-core/sessions-db",
|
||||||
|
]
|
||||||
smart-features = ["k-core/ai"]
|
smart-features = ["k-core/ai"]
|
||||||
broker-nats = ["dep:futures-util", "k-core/broker-nats"]
|
broker-nats = ["dep:futures-util", "k-core/broker-nats"]
|
||||||
|
auth-axum-login = ["dep:axum-login", "dep:password-auth"]
|
||||||
|
auth-oidc = ["dep:openidconnect", "dep:url"]
|
||||||
|
auth-jwt = ["dep:jsonwebtoken"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
k-core = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-core", features = [
|
k-core = { git = "https://git.gabrielkaszewski.dev/GKaszewski/k-core", features = [
|
||||||
"logging",
|
"logging",
|
||||||
"db-sqlx",
|
"db-sqlx",
|
||||||
"sessions-db"
|
"sessions-db",
|
||||||
], version = "*"}
|
], version = "*" }
|
||||||
notes-domain = { path = "../notes-domain" }
|
notes-domain = { path = "../notes-domain" }
|
||||||
|
|
||||||
chrono = { version = "0.4.42", features = ["serde"] }
|
chrono = { version = "0.4.42", features = ["serde"] }
|
||||||
@@ -31,4 +51,18 @@ futures-util = { version = "0.3", optional = true }
|
|||||||
futures-core = "0.3"
|
futures-core = "0.3"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
tower-sessions-sqlx-store = { version = "0.15.0", optional = true}
|
tower-sessions-sqlx-store = { version = "0.15.0", optional = true }
|
||||||
|
tower-sessions = "0.14"
|
||||||
|
|
||||||
|
# Auth dependencies (optional)
|
||||||
|
axum-login = { version = "0.18", optional = true }
|
||||||
|
password-auth = { version = "1.0", optional = true }
|
||||||
|
openidconnect = { version = "4.0.1", optional = true }
|
||||||
|
url = { version = "2.5.8", optional = true }
|
||||||
|
jsonwebtoken = { version = "10.2.0", features = [
|
||||||
|
"sha2",
|
||||||
|
"p256",
|
||||||
|
"hmac",
|
||||||
|
"rsa",
|
||||||
|
"rust_crypto",
|
||||||
|
], optional = true }
|
||||||
|
|||||||
110
notes-infra/src/auth/axum_login.rs
Normal file
110
notes-infra/src/auth/axum_login.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum_login::{AuthnBackend, UserId};
|
||||||
|
use password_auth::verify_password;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tower_sessions::SessionManagerLayer;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use notes_domain::{User, UserRepository};
|
||||||
|
|
||||||
|
use crate::session_store::InfraSessionStore;
|
||||||
|
|
||||||
|
/// Wrapper around domain User to implement AuthUser
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AuthUser(pub User);
|
||||||
|
|
||||||
|
impl axum_login::AuthUser for AuthUser {
|
||||||
|
type Id = Uuid;
|
||||||
|
|
||||||
|
fn id(&self) -> Self::Id {
|
||||||
|
self.0.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn session_auth_hash(&self) -> &[u8] {
|
||||||
|
// Use password hash to invalidate sessions if password changes
|
||||||
|
self.0
|
||||||
|
.password_hash
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.as_bytes())
|
||||||
|
.unwrap_or(&[])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AuthBackend {
|
||||||
|
pub user_repo: Arc<dyn UserRepository>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthBackend {
|
||||||
|
pub fn new(user_repo: Arc<dyn UserRepository>) -> Self {
|
||||||
|
Self { user_repo }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Credentials {
|
||||||
|
pub email: notes_domain::Email,
|
||||||
|
pub password: notes_domain::Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AuthError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Anyhow(#[from] anyhow::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthnBackend for AuthBackend {
|
||||||
|
type User = AuthUser;
|
||||||
|
type Credentials = Credentials;
|
||||||
|
type Error = AuthError;
|
||||||
|
|
||||||
|
async fn authenticate(
|
||||||
|
&self,
|
||||||
|
creds: Self::Credentials,
|
||||||
|
) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
let user = self
|
||||||
|
.user_repo
|
||||||
|
.find_by_email(creds.email.as_ref())
|
||||||
|
.await
|
||||||
|
.map_err(|e| AuthError::Anyhow(anyhow::anyhow!(e)))?;
|
||||||
|
|
||||||
|
if let Some(user) = user {
|
||||||
|
if let Some(hash) = &user.password_hash {
|
||||||
|
// Verify password
|
||||||
|
if verify_password(creds.password.as_ref(), hash).is_ok() {
|
||||||
|
return Ok(Some(AuthUser(user)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
let user = self
|
||||||
|
.user_repo
|
||||||
|
.find_by_id(*user_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| AuthError::Anyhow(anyhow::anyhow!(e)))?;
|
||||||
|
|
||||||
|
Ok(user.map(AuthUser))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AuthSession = axum_login::AuthSession<AuthBackend>;
|
||||||
|
pub type AuthManagerLayer = axum_login::AuthManagerLayer<AuthBackend, InfraSessionStore>;
|
||||||
|
|
||||||
|
pub async fn setup_auth_layer(
|
||||||
|
session_layer: SessionManagerLayer<InfraSessionStore>,
|
||||||
|
user_repo: Arc<dyn UserRepository>,
|
||||||
|
) -> Result<AuthManagerLayer, AuthError> {
|
||||||
|
let backend = AuthBackend::new(user_repo);
|
||||||
|
|
||||||
|
let auth_layer = axum_login::AuthManagerLayerBuilder::new(backend, session_layer).build();
|
||||||
|
Ok(auth_layer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_password(password: &str) -> String {
|
||||||
|
password_auth::generate_hash(password)
|
||||||
|
}
|
||||||
278
notes-infra/src/auth/jwt.rs
Normal file
278
notes-infra/src/auth/jwt.rs
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
//! JWT Authentication Infrastructure
|
||||||
|
//!
|
||||||
|
//! Provides JWT token creation and validation using HS256 (secret-based).
|
||||||
|
//! For OIDC/JWKS validation, see the `oidc` module.
|
||||||
|
|
||||||
|
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
|
||||||
|
use notes_domain::User;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
/// Minimum secret length for production (256 bits = 32 bytes)
|
||||||
|
const MIN_SECRET_LENGTH: usize = 32;
|
||||||
|
|
||||||
|
/// JWT configuration
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct JwtConfig {
|
||||||
|
/// Secret key for HS256 signing/verification
|
||||||
|
pub secret: String,
|
||||||
|
/// Expected issuer (for validation)
|
||||||
|
pub issuer: Option<String>,
|
||||||
|
/// Expected audience (for validation)
|
||||||
|
pub audience: Option<String>,
|
||||||
|
/// Token expiry in hours (default: 24)
|
||||||
|
pub expiry_hours: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwtConfig {
|
||||||
|
/// Create a new JWT config with validation
|
||||||
|
///
|
||||||
|
/// In production mode, this will reject weak secrets.
|
||||||
|
pub fn new(
|
||||||
|
secret: String,
|
||||||
|
issuer: Option<String>,
|
||||||
|
audience: Option<String>,
|
||||||
|
expiry_hours: Option<u64>,
|
||||||
|
is_production: bool,
|
||||||
|
) -> Result<Self, JwtError> {
|
||||||
|
// Validate secret strength in production
|
||||||
|
if is_production && secret.len() < MIN_SECRET_LENGTH {
|
||||||
|
return Err(JwtError::WeakSecret {
|
||||||
|
min_length: MIN_SECRET_LENGTH,
|
||||||
|
actual_length: secret.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
secret,
|
||||||
|
issuer,
|
||||||
|
audience,
|
||||||
|
expiry_hours: expiry_hours.unwrap_or(24),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create config without validation (for testing)
|
||||||
|
pub fn new_unchecked(secret: String) -> Self {
|
||||||
|
Self {
|
||||||
|
secret,
|
||||||
|
issuer: None,
|
||||||
|
audience: None,
|
||||||
|
expiry_hours: 24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JWT claims structure
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct JwtClaims {
|
||||||
|
/// Subject - the user's unique identifier (user ID as string)
|
||||||
|
pub sub: String,
|
||||||
|
/// User's email address
|
||||||
|
pub email: String,
|
||||||
|
/// Expiry timestamp (seconds since UNIX epoch)
|
||||||
|
pub exp: usize,
|
||||||
|
/// Issued at timestamp (seconds since UNIX epoch)
|
||||||
|
pub iat: usize,
|
||||||
|
/// Issuer
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub iss: Option<String>,
|
||||||
|
/// Audience
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub aud: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JWT-related errors
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum JwtError {
|
||||||
|
#[error("JWT secret is too weak: minimum {min_length} bytes required, got {actual_length}")]
|
||||||
|
WeakSecret {
|
||||||
|
min_length: usize,
|
||||||
|
actual_length: usize,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[error("Token creation failed: {0}")]
|
||||||
|
CreationFailed(#[from] jsonwebtoken::errors::Error),
|
||||||
|
|
||||||
|
#[error("Token validation failed: {0}")]
|
||||||
|
ValidationFailed(String),
|
||||||
|
|
||||||
|
#[error("Token expired")]
|
||||||
|
Expired,
|
||||||
|
|
||||||
|
#[error("Invalid token format")]
|
||||||
|
InvalidFormat,
|
||||||
|
|
||||||
|
#[error("Missing configuration")]
|
||||||
|
MissingConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// JWT token validator and generator
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct JwtValidator {
|
||||||
|
config: JwtConfig,
|
||||||
|
encoding_key: EncodingKey,
|
||||||
|
decoding_key: DecodingKey,
|
||||||
|
validation: Validation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwtValidator {
|
||||||
|
/// Create a new JWT validator with the given configuration
|
||||||
|
pub fn new(config: JwtConfig) -> Self {
|
||||||
|
let encoding_key = EncodingKey::from_secret(config.secret.as_bytes());
|
||||||
|
let decoding_key = DecodingKey::from_secret(config.secret.as_bytes());
|
||||||
|
|
||||||
|
let mut validation = Validation::new(Algorithm::HS256);
|
||||||
|
|
||||||
|
// Configure issuer validation if set
|
||||||
|
if let Some(ref issuer) = config.issuer {
|
||||||
|
validation.set_issuer(&[issuer]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure audience validation if set
|
||||||
|
if let Some(ref audience) = config.audience {
|
||||||
|
validation.set_audience(&[audience]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
encoding_key,
|
||||||
|
decoding_key,
|
||||||
|
validation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a JWT token for the given user
|
||||||
|
pub fn create_token(&self, user: &User) -> Result<String, JwtError> {
|
||||||
|
let now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards")
|
||||||
|
.as_secs() as usize;
|
||||||
|
|
||||||
|
let expiry = now + (self.config.expiry_hours as usize * 3600);
|
||||||
|
|
||||||
|
let claims = JwtClaims {
|
||||||
|
sub: user.id.to_string(),
|
||||||
|
email: user.email.as_ref().to_string(),
|
||||||
|
exp: expiry,
|
||||||
|
iat: now,
|
||||||
|
iss: self.config.issuer.clone(),
|
||||||
|
aud: self.config.audience.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let header = Header::new(Algorithm::HS256);
|
||||||
|
encode(&header, &claims, &self.encoding_key).map_err(JwtError::CreationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validate a JWT token and return the claims
|
||||||
|
pub fn validate_token(&self, token: &str) -> Result<JwtClaims, JwtError> {
|
||||||
|
let token_data = decode::<JwtClaims>(token, &self.decoding_key, &self.validation).map_err(
|
||||||
|
|e| match e.kind() {
|
||||||
|
jsonwebtoken::errors::ErrorKind::ExpiredSignature => JwtError::Expired,
|
||||||
|
jsonwebtoken::errors::ErrorKind::InvalidToken => JwtError::InvalidFormat,
|
||||||
|
_ => JwtError::ValidationFailed(e.to_string()),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(token_data.claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the user ID (subject) from a token without full validation
|
||||||
|
/// Useful for logging/debugging, but should not be trusted for auth
|
||||||
|
pub fn decode_unverified(&self, token: &str) -> Result<JwtClaims, JwtError> {
|
||||||
|
let mut validation = Validation::new(Algorithm::HS256);
|
||||||
|
validation.insecure_disable_signature_validation();
|
||||||
|
validation.validate_exp = false;
|
||||||
|
|
||||||
|
let token_data = decode::<JwtClaims>(token, &self.decoding_key, &validation)
|
||||||
|
.map_err(|_| JwtError::InvalidFormat)?;
|
||||||
|
|
||||||
|
Ok(token_data.claims)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for JwtValidator {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("JwtValidator")
|
||||||
|
.field("issuer", &self.config.issuer)
|
||||||
|
.field("audience", &self.config.audience)
|
||||||
|
.field("expiry_hours", &self.config.expiry_hours)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use notes_domain::Email;
|
||||||
|
|
||||||
|
fn create_test_user() -> User {
|
||||||
|
let email = Email::try_from("test@example.com").unwrap();
|
||||||
|
User::new("test-subject", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_and_validate_token() {
|
||||||
|
let config = JwtConfig::new_unchecked("test-secret-key-that-is-long-enough".to_string());
|
||||||
|
let validator = JwtValidator::new(config);
|
||||||
|
let user = create_test_user();
|
||||||
|
|
||||||
|
let token = validator.create_token(&user).expect("Should create token");
|
||||||
|
let claims = validator
|
||||||
|
.validate_token(&token)
|
||||||
|
.expect("Should validate token");
|
||||||
|
|
||||||
|
assert_eq!(claims.sub, user.id.to_string());
|
||||||
|
assert_eq!(claims.email, "test@example.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_weak_secret_rejected_in_production() {
|
||||||
|
let result = JwtConfig::new(
|
||||||
|
"short".to_string(), // Too short
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
true, // Production mode
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(matches!(result, Err(JwtError::WeakSecret { .. })));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_weak_secret_allowed_in_development() {
|
||||||
|
let result = JwtConfig::new(
|
||||||
|
"short".to_string(), // Too short but OK in dev
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
false, // Development mode
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_token_rejected() {
|
||||||
|
let config = JwtConfig::new_unchecked("test-secret-key-that-is-long-enough".to_string());
|
||||||
|
let validator = JwtValidator::new(config);
|
||||||
|
|
||||||
|
let result = validator.validate_token("invalid.token.here");
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_wrong_secret_rejected() {
|
||||||
|
let config1 = JwtConfig::new_unchecked("secret-one-that-is-long-enough".to_string());
|
||||||
|
let config2 = JwtConfig::new_unchecked("secret-two-that-is-long-enough".to_string());
|
||||||
|
|
||||||
|
let validator1 = JwtValidator::new(config1);
|
||||||
|
let validator2 = JwtValidator::new(config2);
|
||||||
|
|
||||||
|
let user = create_test_user();
|
||||||
|
let token = validator1.create_token(&user).unwrap();
|
||||||
|
|
||||||
|
// Token from validator1 should fail on validator2
|
||||||
|
let result = validator2.validate_token(&token);
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
6
notes-infra/src/auth/mod.rs
Normal file
6
notes-infra/src/auth/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#[cfg(feature = "auth-axum-login")]
|
||||||
|
mod axum_login;
|
||||||
|
#[cfg(feature = "auth-jwt")]
|
||||||
|
mod jwt;
|
||||||
|
#[cfg(feature = "auth-oidc")]
|
||||||
|
mod oidc;
|
||||||
202
notes-infra/src/auth/oidc.rs
Normal file
202
notes-infra/src/auth/oidc.rs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
use anyhow::anyhow;
|
||||||
|
use notes_domain::{
|
||||||
|
AuthorizationCode, AuthorizationUrlData, ClientId, ClientSecret, CsrfToken, IssuerUrl,
|
||||||
|
OidcNonce, PkceVerifier, RedirectUrl, ResourceId,
|
||||||
|
};
|
||||||
|
use openidconnect::{
|
||||||
|
AccessTokenHash, Client, EmptyAdditionalClaims, EndpointMaybeSet, EndpointNotSet, EndpointSet,
|
||||||
|
OAuth2TokenResponse, PkceCodeChallenge, Scope, StandardErrorResponse, TokenResponse,
|
||||||
|
UserInfoClaims,
|
||||||
|
core::{
|
||||||
|
CoreAuthDisplay, CoreAuthPrompt, CoreAuthenticationFlow, CoreClient, CoreErrorResponseType,
|
||||||
|
CoreGenderClaim, CoreJsonWebKey, CoreJweContentEncryptionAlgorithm, CoreProviderMetadata,
|
||||||
|
CoreRevocableToken, CoreRevocationErrorResponse, CoreTokenIntrospectionResponse,
|
||||||
|
CoreTokenResponse,
|
||||||
|
},
|
||||||
|
reqwest,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type OidcClient = Client<
|
||||||
|
EmptyAdditionalClaims,
|
||||||
|
CoreAuthDisplay,
|
||||||
|
CoreGenderClaim,
|
||||||
|
CoreJweContentEncryptionAlgorithm,
|
||||||
|
CoreJsonWebKey,
|
||||||
|
CoreAuthPrompt,
|
||||||
|
StandardErrorResponse<CoreErrorResponseType>,
|
||||||
|
CoreTokenResponse,
|
||||||
|
CoreTokenIntrospectionResponse,
|
||||||
|
CoreRevocableToken,
|
||||||
|
CoreRevocationErrorResponse,
|
||||||
|
EndpointSet, // HasAuthUrl (Required and guaranteed by discovery)
|
||||||
|
EndpointNotSet, // HasDeviceAuthUrl
|
||||||
|
EndpointNotSet, // HasIntrospectionUrl
|
||||||
|
EndpointNotSet, // HasRevocationUrl
|
||||||
|
EndpointMaybeSet, // HasTokenUrl (Discovered, might be missing)
|
||||||
|
EndpointMaybeSet, // HasUserInfoUrl (Discovered, might be missing)
|
||||||
|
>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct OidcService {
|
||||||
|
client: OidcClient,
|
||||||
|
resource_id: Option<ResourceId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OidcUser {
|
||||||
|
pub subject: String,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OidcService {
|
||||||
|
/// Create a new OIDC service with validated configuration newtypes
|
||||||
|
pub async fn new(
|
||||||
|
issuer: IssuerUrl,
|
||||||
|
client_id: ClientId,
|
||||||
|
client_secret: Option<ClientSecret>,
|
||||||
|
redirect_url: RedirectUrl,
|
||||||
|
resource_id: Option<ResourceId>,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
tracing::debug!("🔵 OIDC Setup: Client ID = '{}'", client_id);
|
||||||
|
tracing::debug!("🔵 OIDC Setup: Redirect = '{}'", redirect_url);
|
||||||
|
tracing::debug!(
|
||||||
|
"🔵 OIDC Setup: Secret = {:?}",
|
||||||
|
if client_secret.is_some() {
|
||||||
|
"SET"
|
||||||
|
} else {
|
||||||
|
"NONE"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let http_client = reqwest::ClientBuilder::new()
|
||||||
|
.redirect(reqwest::redirect::Policy::none())
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let provider_metadata = CoreProviderMetadata::discover_async(
|
||||||
|
openidconnect::IssuerUrl::new(issuer.as_ref().to_string())?,
|
||||||
|
&http_client,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Convert to openidconnect types
|
||||||
|
let oidc_client_id = openidconnect::ClientId::new(client_id.as_ref().to_string());
|
||||||
|
let oidc_client_secret = client_secret
|
||||||
|
.as_ref()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| openidconnect::ClientSecret::new(s.as_ref().to_string()));
|
||||||
|
let oidc_redirect_url = openidconnect::RedirectUrl::new(redirect_url.as_ref().to_string())?;
|
||||||
|
|
||||||
|
let client = CoreClient::from_provider_metadata(
|
||||||
|
provider_metadata,
|
||||||
|
oidc_client_id,
|
||||||
|
oidc_client_secret,
|
||||||
|
)
|
||||||
|
.set_redirect_uri(oidc_redirect_url);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client,
|
||||||
|
resource_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the authorization URL and associated state for OIDC login
|
||||||
|
///
|
||||||
|
/// Returns structured data instead of a raw tuple for better type safety
|
||||||
|
pub fn get_authorization_url(&self) -> AuthorizationUrlData {
|
||||||
|
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
|
||||||
|
|
||||||
|
let (auth_url, csrf_token, nonce) = self
|
||||||
|
.client
|
||||||
|
.authorize_url(
|
||||||
|
CoreAuthenticationFlow::AuthorizationCode,
|
||||||
|
openidconnect::CsrfToken::new_random,
|
||||||
|
openidconnect::Nonce::new_random,
|
||||||
|
)
|
||||||
|
.add_scope(Scope::new("profile".to_string()))
|
||||||
|
.add_scope(Scope::new("email".to_string()))
|
||||||
|
.set_pkce_challenge(pkce_challenge)
|
||||||
|
.url();
|
||||||
|
|
||||||
|
AuthorizationUrlData {
|
||||||
|
url: auth_url.into(),
|
||||||
|
csrf_token: CsrfToken::new(csrf_token.secret().to_string()),
|
||||||
|
nonce: OidcNonce::new(nonce.secret().to_string()),
|
||||||
|
pkce_verifier: PkceVerifier::new(pkce_verifier.secret().to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the OIDC callback with type-safe parameters
|
||||||
|
pub async fn resolve_callback(
|
||||||
|
&self,
|
||||||
|
code: AuthorizationCode,
|
||||||
|
nonce: OidcNonce,
|
||||||
|
pkce_verifier: PkceVerifier,
|
||||||
|
) -> anyhow::Result<OidcUser> {
|
||||||
|
let http_client = reqwest::ClientBuilder::new()
|
||||||
|
.redirect(reqwest::redirect::Policy::none())
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let oidc_pkce_verifier =
|
||||||
|
openidconnect::PkceCodeVerifier::new(pkce_verifier.as_ref().to_string());
|
||||||
|
let oidc_nonce = openidconnect::Nonce::new(nonce.as_ref().to_string());
|
||||||
|
|
||||||
|
let token_response = self
|
||||||
|
.client
|
||||||
|
.exchange_code(openidconnect::AuthorizationCode::new(
|
||||||
|
code.as_ref().to_string(),
|
||||||
|
))?
|
||||||
|
.set_pkce_verifier(oidc_pkce_verifier)
|
||||||
|
.request_async(&http_client)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let id_token = token_response
|
||||||
|
.id_token()
|
||||||
|
.ok_or_else(|| anyhow!("Server did not return an ID token"))?;
|
||||||
|
|
||||||
|
let mut id_token_verifier = self.client.id_token_verifier().clone();
|
||||||
|
|
||||||
|
if let Some(resource_id) = &self.resource_id {
|
||||||
|
let trusted_resource_id = resource_id.as_ref().to_string();
|
||||||
|
id_token_verifier = id_token_verifier
|
||||||
|
.set_other_audience_verifier_fn(move |aud| aud.as_str() == trusted_resource_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let claims = id_token.claims(&id_token_verifier, &oidc_nonce)?;
|
||||||
|
|
||||||
|
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()?,
|
||||||
|
id_token.signing_key(&id_token_verifier)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if actual_access_token_hash != *expected_access_token_hash {
|
||||||
|
return Err(anyhow!("Invalid access token"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let email = if let Some(email) = claims.email() {
|
||||||
|
Some(email.as_str().to_string())
|
||||||
|
} else {
|
||||||
|
// Fallback: Call UserInfo Endpoint using the Access Token
|
||||||
|
tracing::debug!("🔵 Email missing in ID Token, fetching UserInfo...");
|
||||||
|
|
||||||
|
let user_info: UserInfoClaims<EmptyAdditionalClaims, CoreGenderClaim> = self
|
||||||
|
.client
|
||||||
|
.user_info(token_response.access_token().clone(), None)?
|
||||||
|
.request_async(&http_client)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
user_info.email().map(|e| e.as_str().to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
// If email is still missing, we must error out because your app requires valid emails
|
||||||
|
let email =
|
||||||
|
email.ok_or_else(|| anyhow!("User has no verified email address in ZITADEL"))?;
|
||||||
|
|
||||||
|
Ok(OidcUser {
|
||||||
|
subject: claims.subject().to_string(),
|
||||||
|
email,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
//!
|
//!
|
||||||
//! - [`db::run_migrations`] - Run database migrations
|
//! - [`db::run_migrations`] - Run database migrations
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
#[cfg(feature = "broker-nats")]
|
#[cfg(feature = "broker-nats")]
|
||||||
pub mod broker;
|
pub mod broker;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
|||||||
Reference in New Issue
Block a user