mirror of
https://github.com/LeNei/axum-sqlx-template.git
synced 2026-02-13 22:56:19 +00:00
add base auth implementation
This commit is contained in:
352
Cargo.lock
generated
352
Cargo.lock
generated
@@ -127,12 +127,15 @@ dependencies = [
|
||||
"config",
|
||||
"http",
|
||||
"hyper",
|
||||
"jsonwebtoken",
|
||||
"log",
|
||||
"reqwest",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"serde-aux",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
@@ -238,6 +241,16 @@ dependencies = [
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
@@ -387,18 +400,72 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
@@ -546,6 +613,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -634,6 +707,19 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.56"
|
||||
@@ -687,6 +773,23 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
@@ -711,6 +814,18 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "8.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@@ -738,6 +853,12 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
@@ -811,6 +932,24 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@@ -856,7 +995,7 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"hermit-abi 0.2.6",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -866,6 +1005,50 @@ version = "1.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
@@ -892,7 +1075,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"smallvec",
|
||||
"winapi",
|
||||
]
|
||||
@@ -947,6 +1130,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@@ -1010,6 +1199,15 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
@@ -1017,7 +1215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -1051,6 +1249,43 @@ version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
@@ -1066,6 +1301,20 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.8"
|
||||
@@ -1099,6 +1348,15 @@ version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
||||
dependencies = [
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@@ -1131,6 +1389,29 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.162"
|
||||
@@ -1409,6 +1690,19 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
@@ -1529,6 +1823,16 @@ dependencies = [
|
||||
"syn 2.0.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.4"
|
||||
@@ -1773,6 +2077,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
@@ -1826,6 +2136,18 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.84"
|
||||
@@ -1934,6 +2256,21 @@ dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
@@ -2066,6 +2403,15 @@ version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
|
||||
@@ -31,3 +31,6 @@ tower-http = { version = "0.4.0", features = ["trace", "cors"] }
|
||||
http = "0.2"
|
||||
hyper = { version = "0.14", features = ["full"] }
|
||||
anyhow = "1.0"
|
||||
jsonwebtoken = {version = "8", default-features = false }
|
||||
thiserror = "1.0"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
112
src/auth/claims.rs
Normal file
112
src/auth/claims.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use crate::routes::ApiContext;
|
||||
|
||||
use super::token::{Token, TokenError};
|
||||
use axum::{
|
||||
async_trait,
|
||||
extract::{FromRef, FromRequestParts},
|
||||
http::request::Parts,
|
||||
response::IntoResponse,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub(crate) struct Claims<C: DeserializeOwned + ParseTokenClaims>(pub C);
|
||||
|
||||
/// Trait indicating that the type can be parsed from a request.
|
||||
///
|
||||
/// Implementing this trait for your claims data means it can be used as an
|
||||
/// [extractor][axum::extract] in your request handlers. Assuming you have a
|
||||
/// struct `TokenClaims` with the attributes you want to parse from a JWT,
|
||||
/// implementing [`ParseTokenClaims`] will allow you to write a request handler
|
||||
/// like:
|
||||
/// ```ignore
|
||||
/// async fn my_request_handler(Claims(claims): Claims<TokenClaims>) -> Response {
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The alternative to implementing this trait is to implement
|
||||
/// [`FromRequestParts`][axum::extract::FromRequestParts] directly for your
|
||||
/// token claims.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use axum::{
|
||||
/// http::status::StatusCode,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// Json,
|
||||
/// };
|
||||
/// use axum_jwks::{ParseTokenClaims, TokenError};
|
||||
/// use serde::Deserialize;
|
||||
/// use serde_json::json;
|
||||
///
|
||||
/// #[derive(Deserialize)]
|
||||
/// struct TokenClaims {
|
||||
/// sub: String,
|
||||
/// }
|
||||
///
|
||||
/// impl ParseTokenClaims for TokenClaims {
|
||||
/// type Rejection = TokenClaimsError;
|
||||
/// }
|
||||
///
|
||||
/// enum TokenClaimsError {
|
||||
/// Invalid,
|
||||
/// Missing,
|
||||
/// }
|
||||
///
|
||||
/// impl From<TokenError> for TokenClaimsError {
|
||||
/// fn from(error: TokenError) -> Self {
|
||||
/// match error {
|
||||
/// TokenError::Missing => Self::Missing,
|
||||
/// other => Self::Invalid,
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl IntoResponse for TokenClaimsError {
|
||||
/// fn into_response(self) -> Response {
|
||||
/// let body = match self {
|
||||
/// Self::Invalid => json!({ "message": "Invalid token." }),
|
||||
/// Self::Missing => json!({ "message": "No token provided." }),
|
||||
/// };
|
||||
///
|
||||
/// (StatusCode::UNAUTHORIZED, Json(body)).into_response()
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub(crate) trait ParseTokenClaims {
|
||||
/// The type of error returned if the token claims cannot be parsed and
|
||||
/// validated from the request.
|
||||
type Rejection: IntoResponse + From<TokenError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<S, C> FromRequestParts<S> for Claims<C>
|
||||
where
|
||||
C: DeserializeOwned + ParseTokenClaims,
|
||||
ApiContext: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = C::Rejection;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let jwks = ApiContext::from_ref(state).jwks;
|
||||
let token = Token::from_request_parts(parts)?;
|
||||
|
||||
let token_data = jwks.validate_claims(token.value())?;
|
||||
|
||||
Ok(Claims(token_data.claims))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub(crate) struct KeycloakClaims {
|
||||
pub sub: String,
|
||||
exp: i64,
|
||||
iat: i64,
|
||||
email: String,
|
||||
}
|
||||
|
||||
impl ParseTokenClaims for KeycloakClaims {
|
||||
type Rejection = TokenError;
|
||||
}
|
||||
25
src/auth/mod.rs
Normal file
25
src/auth/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
pub mod claims;
|
||||
pub mod token;
|
||||
|
||||
use crate::routes::ApiContext;
|
||||
use axum::extract::State;
|
||||
use axum::http::Request;
|
||||
use axum::middleware::Next;
|
||||
use axum::response::Response;
|
||||
use claims::{Claims, KeycloakClaims};
|
||||
use hyper::StatusCode;
|
||||
|
||||
pub(crate) async fn auth_user<B>(
|
||||
State(ctx): State<ApiContext>,
|
||||
Claims(claims): Claims<KeycloakClaims>,
|
||||
mut req: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Result<Response, StatusCode> {
|
||||
/*
|
||||
let user = AuthUser::get_from_db(&claims.sub, &ctx.db, None)
|
||||
.await
|
||||
.map_err(|_| StatusCode::UNAUTHORIZED)?;
|
||||
req.extensions_mut().insert(user);
|
||||
*/
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
61
src/auth/token.rs
Normal file
61
src/auth/token.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use axum::http::request::Parts;
|
||||
use axum::response::IntoResponse;
|
||||
use hyper::header::AUTHORIZATION;
|
||||
use hyper::StatusCode;
|
||||
use thiserror::Error;
|
||||
|
||||
/// A JWT provided as a bearer token in an `Authorization` header.
|
||||
#[derive(PartialEq)]
|
||||
pub(crate) struct Token(String);
|
||||
|
||||
impl Token {
|
||||
pub fn value(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn from_request_parts(parts: &mut Parts) -> Result<Self, TokenError> {
|
||||
let auth_header = parts
|
||||
.headers
|
||||
.get(AUTHORIZATION)
|
||||
.ok_or(TokenError::Missing)?
|
||||
.to_str()
|
||||
.map_err(|_| TokenError::Missing)?;
|
||||
|
||||
let token = auth_header
|
||||
.strip_prefix("Bearer ")
|
||||
.ok_or(TokenError::Missing)?;
|
||||
|
||||
Ok(Token(token.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// An error with a JWT.
|
||||
#[derive(Debug, Error, PartialEq)]
|
||||
pub(crate) enum TokenError {
|
||||
/// The token is either malformed or did not pass validation.
|
||||
#[error("the token is invalid or malformed: {0:?}")]
|
||||
Invalid(jsonwebtoken::errors::Error),
|
||||
|
||||
/// The token header could not be decoded because it was malformed.
|
||||
#[error("the token header is malformed: {0:?}")]
|
||||
InvalidHeader(jsonwebtoken::errors::Error),
|
||||
|
||||
/// No bearer token found in the `Authorization` header.
|
||||
#[error("no bearer token found")]
|
||||
Missing,
|
||||
|
||||
/// The token's header does not contain the `kid` attribute used to identify
|
||||
/// which decoding key should be used.
|
||||
#[error("the token header does not specify a `kid`")]
|
||||
MissingKeyId,
|
||||
|
||||
/// The token's `kid` attribute specifies a key that is unknown.
|
||||
#[error("token uses the unknown key {0:?}")]
|
||||
UnknownKeyId(String),
|
||||
}
|
||||
|
||||
impl IntoResponse for TokenError {
|
||||
fn into_response(self) -> axum::response::Response {
|
||||
StatusCode::UNAUTHORIZED.into_response()
|
||||
}
|
||||
}
|
||||
158
src/config/jwks.rs
Normal file
158
src/config/jwks.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use crate::auth::token::TokenError;
|
||||
use jsonwebtoken::{
|
||||
decode, decode_header,
|
||||
jwk::{self, AlgorithmParameters},
|
||||
DecodingKey, TokenData, Validation,
|
||||
};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, info};
|
||||
|
||||
/// A container for a set of JWT decoding keys.
|
||||
///
|
||||
/// The container can be used to validate any JWT that identifies a known key
|
||||
/// through the `kid` attribute in the token's header.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Jwks {
|
||||
keys: HashMap<String, Jwk>,
|
||||
}
|
||||
|
||||
impl Jwks {
|
||||
/// Pull a JSON Web Key Set from a specific authority.
|
||||
pub async fn from_authority(authority: &str, audience: String) -> Result<Self, JwksError> {
|
||||
Self::from_authority_with_client(&reqwest::Client::default(), authority, audience).await
|
||||
}
|
||||
|
||||
/// A version of [`from_authority`][Self::from_authority] that allows for
|
||||
/// passing in a custom [`Client`][reqwest::Client].
|
||||
pub async fn from_authority_with_client(
|
||||
client: &reqwest::Client,
|
||||
authority: &str,
|
||||
audience: String,
|
||||
) -> Result<Self, JwksError> {
|
||||
let jwks_url = format!("{}/protocol/openid-connect/certs", authority);
|
||||
debug!(%authority, %jwks_url, "Fetching JSON Web Key Set.");
|
||||
let jwks: jwk::JwkSet = client.get(jwks_url).send().await?.json().await?;
|
||||
|
||||
info!(
|
||||
%authority,
|
||||
count = jwks.keys.len(),
|
||||
"Successfully pulled JSON Web Key Set."
|
||||
);
|
||||
|
||||
let mut keys = HashMap::new();
|
||||
for jwk in jwks.keys {
|
||||
let kid = jwk.common.key_id.ok_or(JwkError::MissingKeyId)?;
|
||||
|
||||
match &jwk.algorithm {
|
||||
jwk::AlgorithmParameters::RSA(rsa) => {
|
||||
let decoding_key =
|
||||
DecodingKey::from_rsa_components(&rsa.n, &rsa.e).map_err(|err| {
|
||||
JwkError::DecodingError {
|
||||
key_id: kid.clone(),
|
||||
error: err,
|
||||
}
|
||||
})?;
|
||||
let mut validation = Validation::new(jwk.common.algorithm.ok_or(
|
||||
JwkError::MissingAlgorithm {
|
||||
key_id: kid.clone(),
|
||||
},
|
||||
)?);
|
||||
validation.set_audience(&[audience.clone()]);
|
||||
|
||||
keys.insert(
|
||||
kid,
|
||||
Jwk {
|
||||
decoding: decoding_key,
|
||||
validation,
|
||||
},
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
info!(%kid, "Ignoring unsupported key.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { keys })
|
||||
}
|
||||
|
||||
pub fn validate_claims<T>(&self, token: &str) -> Result<TokenData<T>, TokenError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let header = decode_header(token).map_err(|error| {
|
||||
debug!(?error, "Received token with invalid header.");
|
||||
|
||||
TokenError::InvalidHeader(error)
|
||||
})?;
|
||||
let kid = header.kid.as_ref().ok_or_else(|| {
|
||||
debug!(?header, "Header is missing the `kid` attribute.");
|
||||
|
||||
TokenError::MissingKeyId
|
||||
})?;
|
||||
|
||||
let key = self.keys.get(kid).ok_or_else(|| {
|
||||
debug!(%kid, "Token refers to an unknown key.");
|
||||
|
||||
TokenError::UnknownKeyId(kid.to_owned())
|
||||
})?;
|
||||
|
||||
let decoded_token: TokenData<T> =
|
||||
decode(token, &key.decoding, &key.validation).map_err(|error| {
|
||||
println!("error: {:?}", error);
|
||||
debug!(?error, "Token is malformed or does not pass validation.");
|
||||
|
||||
TokenError::Invalid(error)
|
||||
})?;
|
||||
|
||||
Ok(decoded_token)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Jwk {
|
||||
decoding: DecodingKey,
|
||||
validation: Validation,
|
||||
}
|
||||
|
||||
/// An error with the overall set of JSON Web Keys.
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum JwksError {
|
||||
/// There was an error fetching the JWKS from the specified authority.
|
||||
#[error("could not fetch JWKS from authority: {0}")]
|
||||
FetchError(#[from] reqwest::Error),
|
||||
|
||||
/// An error with an individual key caused the processing of the JWKS to
|
||||
/// fail.
|
||||
#[error("there was an error with an individual key: {0}")]
|
||||
KeyError(#[from] JwkError),
|
||||
}
|
||||
|
||||
/// An error with a specific key from a JWKS.
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum JwkError {
|
||||
/// There was an error constructing the decoding key from the RSA components
|
||||
/// provided by the key.
|
||||
#[error("could not construct a decoding key for {key_id:?}: {error:?}")]
|
||||
DecodingError {
|
||||
key_id: String,
|
||||
error: jsonwebtoken::errors::Error,
|
||||
},
|
||||
|
||||
/// The key does not specify an algorithm to use.
|
||||
#[error("the key {key_id:?} does not specify an algorithm")]
|
||||
MissingAlgorithm { key_id: String },
|
||||
|
||||
/// The key is missing the `kid` attribute.
|
||||
#[error("the key is missing the `kid` attribute")]
|
||||
MissingKeyId,
|
||||
|
||||
/// The key uses an unexpected algorithm type.
|
||||
#[error("the key {key_id:?} uses a non-RSA algorithm {algorithm:?}")]
|
||||
UnexpectedAlgorithm {
|
||||
algorithm: AlgorithmParameters,
|
||||
key_id: String,
|
||||
},
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod app;
|
||||
pub mod database;
|
||||
pub mod jwks;
|
||||
pub mod logging;
|
||||
|
||||
use app::ApplicationSettings;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod routes;
|
||||
pub mod startup;
|
||||
|
||||
@@ -4,7 +4,7 @@ use axum_sqlx_template::startup::build;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let subscriber = get_subscriber("api".into(), "info".into(), std::io::stdout);
|
||||
let subscriber = get_subscriber("api".into(), "debug".into(), std::io::stdout);
|
||||
init_subscriber(subscriber);
|
||||
|
||||
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use axum::middleware;
|
||||
use axum::{routing::get, Router};
|
||||
use http::Method;
|
||||
use hyper::StatusCode;
|
||||
@@ -5,17 +6,21 @@ use sqlx::PgPool;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
use crate::auth::auth_user;
|
||||
use crate::config::jwks::Jwks;
|
||||
|
||||
#[tracing::instrument(name = "Ping")]
|
||||
async fn ping() -> StatusCode {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ApiContext {
|
||||
pub(crate) struct ApiContext {
|
||||
pub db: PgPool,
|
||||
pub jwks: Jwks,
|
||||
}
|
||||
|
||||
pub fn build_routes(api_context: ApiContext) -> Router {
|
||||
pub(crate) fn build_routes(api_context: ApiContext) -> Router {
|
||||
let cors = CorsLayer::new()
|
||||
// allow `GET` and `POST` when accessing the resource
|
||||
.allow_methods([Method::GET, Method::POST])
|
||||
@@ -23,7 +28,17 @@ pub fn build_routes(api_context: ApiContext) -> Router {
|
||||
.allow_origin(Any);
|
||||
Router::new()
|
||||
.route("/ping", get(ping))
|
||||
.nest("/auth", build_auth_routes(&api_context))
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(cors)
|
||||
.with_state(api_context)
|
||||
}
|
||||
|
||||
fn build_auth_routes(api_context: &ApiContext) -> Router<ApiContext> {
|
||||
Router::new()
|
||||
.route("/ping", get(ping))
|
||||
.route_layer(middleware::from_fn_with_state(
|
||||
api_context.clone(),
|
||||
auth_user,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::config::jwks::Jwks;
|
||||
use crate::config::Settings;
|
||||
use crate::routes::{build_routes, ApiContext};
|
||||
use anyhow::Context;
|
||||
@@ -7,6 +8,8 @@ use std::net::TcpListener;
|
||||
pub async fn build(settings: Settings) -> anyhow::Result<()> {
|
||||
let api_context = ApiContext {
|
||||
db: settings.database.get_connection_pool(),
|
||||
jwks: Jwks::from_authority("http://localhost:8088/realms/test", "account".to_string())
|
||||
.await?,
|
||||
};
|
||||
let api_router = build_routes(api_context);
|
||||
let address = format!(
|
||||
|
||||
Reference in New Issue
Block a user