From b0667729a32b6fab28c9b5dd7ab467d85a1dbf09 Mon Sep 17 00:00:00 2001 From: cduvray Date: Sun, 8 Jan 2023 13:45:21 +0100 Subject: [PATCH] chore: fmt --- .gitignore | 9 +- Cargo.lock | 1977 ++++++++++++++++++ Cargo.toml | 5 + README.md | 40 + config/ec256-private.pem | 5 + config/ec256-public.pem | 4 + config/ed25519-private.pem | 3 + config/ed25519-public.pem | 3 + config/ed256-jwk.json | 8 + config/jwks.json | 55 + config/jwtRS256.key | 51 + config/jwtRS256.key.pub | 14 + config/private_ecdsa_key.pem | 5 + config/private_ed25519_key.pem | 3 + config/private_rsa_key_pkcs8.pem | 28 + config/public_ecdsa_key.pem | 4 + config/public_ed25519_key.pem | 3 + demo-server/Cargo.toml | 23 + demo-server/request.http | 35 + demo-server/src/main.rs | 70 + demo-server/src/oidc_provider/mod.rs | 240 +++ jwt-authorizer/Cargo.toml | 33 + jwt-authorizer/clippy.toml | 0 jwt-authorizer/docs/README.md | 36 + jwt-authorizer/src/authorizer.rs | 170 ++ jwt-authorizer/src/error.rs | 60 + jwt-authorizer/src/jwks/key_store_manager.rs | 373 ++++ jwt-authorizer/src/jwks/mod.rs | 24 + jwt-authorizer/src/layer.rs | 286 +++ jwt-authorizer/src/lib.rs | 34 + jwt-authorizer/src/tests.rs | 1 + rustfmt.toml | 1 + 32 files changed, 3596 insertions(+), 7 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 config/ec256-private.pem create mode 100644 config/ec256-public.pem create mode 100644 config/ed25519-private.pem create mode 100644 config/ed25519-public.pem create mode 100644 config/ed256-jwk.json create mode 100644 config/jwks.json create mode 100644 config/jwtRS256.key create mode 100644 config/jwtRS256.key.pub create mode 100644 config/private_ecdsa_key.pem create mode 100644 config/private_ed25519_key.pem create mode 100644 config/private_rsa_key_pkcs8.pem create mode 100644 config/public_ecdsa_key.pem create mode 100644 config/public_ed25519_key.pem create mode 100644 demo-server/Cargo.toml create mode 100644 demo-server/request.http create mode 100644 demo-server/src/main.rs create mode 100644 demo-server/src/oidc_provider/mod.rs create mode 100644 jwt-authorizer/Cargo.toml create mode 100644 jwt-authorizer/clippy.toml create mode 100644 jwt-authorizer/docs/README.md create mode 100644 jwt-authorizer/src/authorizer.rs create mode 100644 jwt-authorizer/src/error.rs create mode 100644 jwt-authorizer/src/jwks/key_store_manager.rs create mode 100644 jwt-authorizer/src/jwks/mod.rs create mode 100644 jwt-authorizer/src/layer.rs create mode 100644 jwt-authorizer/src/lib.rs create mode 100644 jwt-authorizer/src/tests.rs create mode 100644 rustfmt.toml diff --git a/.gitignore b/.gitignore index 088ba6b..d964947 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,5 @@ -# Generated by Cargo -# will have compiled files and executables /target/ - -# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries -# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock +.idea/ +.vscode -# These are backup files generated by rustfmt -**/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c9109cf --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1977 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-channel" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "headers", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deadpool" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421fe0f90f2ab22016f32a9881be5134fdd71c65298917084b0c7477cbc3856e" +dependencies = [ + "async-trait", + "deadpool-runtime", + "num_cpus", + "retain_mut", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa37046cc0f6c3cc6090fbdbf73ef0b8ef4cfcc37f6befc0020f63e8cf121e1" + +[[package]] +name = "demo-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "headers", + "josekit", + "jsonwebtoken", + "jwt-authorizer", + "once_cell", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[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.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "http-types" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" +dependencies = [ + "anyhow", + "async-channel", + "base64", + "futures-lite", + "http", + "infer", + "pin-project-lite", + "rand", + "serde", + "serde_json", + "serde_qs", + "serde_urlencoded", + "url", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "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 = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "infer" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "josekit" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee6af62ad98bdf699ad2ecc8323479a1fdc7aa5faa6043d93119d83f6c5fca8" +dependencies = [ + "anyhow", + "base64", + "flate2", + "once_cell", + "openssl", + "regex", + "serde", + "serde_json", + "thiserror", + "time", +] + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "8.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f4f04699947111ec1733e71778d763555737579e44b85844cae8e1940a1828" +dependencies = [ + "base64", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "jwt-authorizer" +version = "0.2.0" +dependencies = [ + "axum", + "futures-core", + "futures-util", + "headers", + "http", + "jsonwebtoken", + "pin-project", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tokio", + "tower-http", + "tower-layer", + "tower-service", + "tracing", + "tracing-subscriber", + "wiremock", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "openssl" +version = "0.10.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.42.0", +] + +[[package]] +name = "pem" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +dependencies = [ + "base64", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "reqwest" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" +dependencies = [ + "base64", + "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 = "retain_mut" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bb801831d812c562ae7d2bfb531f26e66e4e1f6b17307ba4149c5064710e5b" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "base64", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "valuable" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "wiremock" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249dc68542861d17eae4b4e5e8fb381c2f9e8f255a84f6771d5fdf8b6c03ce3c" +dependencies = [ + "assert-json-diff", + "async-trait", + "base64", + "deadpool", + "futures", + "futures-timer", + "http-types", + "hyper", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a8d11fe --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] +members = [ + "demo-server", + "jwt-authorizer", +] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..edc2f46 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# jwt-authorizer + +JWT authorizer Layer for Axum. + +## Features + +- JWT token verification (Bearer) +- Claims extraction +- JWKS endpoint support (with refresh) +- algoritms: ECDSA, RSA, EdDSA, HS + +## Usage + +See documentation of the [`jwt-authorizer`](./jwt-authorizer/docs/README.md) module or the [`demo-server`](./demo-server/) example. + +## Development + +### Key generation + +EC (ECDSA) - (algorigthm ES256 - ECDSA using SHA-256) + +curve name: prime256v1 (secp256r1, secp384r1) + +> openssl ecparam -genkey -noout -name prime256v1 | openssl pkcs8 -topk8 -nocrypt -out ec-private.pem + +> openssl ec -in ec-private.pem -pubout -out ec-public-key.pem + +EdDSA (Edwards-curve Digital Signature Algorithm) + +(Ed25519 - implémentation spécifique de EdDSA, utilisant la Courbe d'Edwards tordue) + +> openssl genpkey -algorithm ed25519 + +## Contributing + +Contributions are wellcome! + +## License + +MIT \ No newline at end of file diff --git a/config/ec256-private.pem b/config/ec256-private.pem new file mode 100644 index 0000000..66712bd --- /dev/null +++ b/config/ec256-private.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgH614zrAA67VgIGav +JQU718PqSvV95Z/QO0I4oxxpekmhRANCAAQxmLBzkRU/8Te+R3agp52viVZUw32+ +B3IEGkEhUUmPBtZ6S1O+QejNJm9Nc1AFaHmCUgzpUCZr4wXaiz4yb8t2 +-----END PRIVATE KEY----- diff --git a/config/ec256-public.pem b/config/ec256-public.pem new file mode 100644 index 0000000..666def8 --- /dev/null +++ b/config/ec256-public.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEMZiwc5EVP/E3vkd2oKedr4lWVMN9 +vgdyBBpBIVFJjwbWektTvkHozSZvTXNQBWh5glIM6VAma+MF2os+Mm/Ldg== +-----END PUBLIC KEY----- diff --git a/config/ed25519-private.pem b/config/ed25519-private.pem new file mode 100644 index 0000000..a1b6ad7 --- /dev/null +++ b/config/ed25519-private.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIEoAdOduNX+lt7bOPEkqAOwYabpsGhyxiAaUbMw184Ca +-----END PRIVATE KEY----- diff --git a/config/ed25519-public.pem b/config/ed25519-public.pem new file mode 100644 index 0000000..473ebbd --- /dev/null +++ b/config/ed25519-public.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAuWtSkE+I9aTMYTTvuTE1rtu0rNdxp3DU33cJ/ksL1Gk= +-----END PUBLIC KEY----- diff --git a/config/ed256-jwk.json b/config/ed256-jwk.json new file mode 100644 index 0000000..5f0611d --- /dev/null +++ b/config/ed256-jwk.json @@ -0,0 +1,8 @@ +{ + "kty": "OKP", + "use": "sig", + "crv": "Ed25519", + "x": "uWtSkE-I9aTMYTTvuTE1rtu0rNdxp3DU33cJ_ksL1Gk", + "kid": "key-ed", + "alg": "EdDSA" + } \ No newline at end of file diff --git a/config/jwks.json b/config/jwks.json new file mode 100644 index 0000000..5a09e06 --- /dev/null +++ b/config/jwks.json @@ -0,0 +1,55 @@ + +{ + "keys": [ + { + "kty": "RSA", + "n": "2pQeZdxa7q093K7bj5h6-leIpxfTnuAxzXdhjfGEJHxmt2ekHyCBWWWXCBiDn2RTcEBcy6gZqOW45Uy_tw-5e-Px1xFj1PykGEkRlOpYSAeWsNaAWvvpGB9m4zQ0PgZeMDDXE5IIBrY6YAzmGQxV-fcGGLhJnXl0-5_z7tKC7RvBoT3SGwlc_AmJqpFtTpEBn_fDnyqiZbpcjXYLExFpExm41xDitRKHWIwfc3dV8_vlNntlxCPGy_THkjdXJoHv2IJmlhvmr5_h03iGMLWDKSywxOol_4Wc1BT7Hb6byMxW40GKwSJJ4p7W8eI5mqggRHc8jlwSsTN9LZ2VOvO-XiVShZRVg7JeraGAfWwaIgIJ1D8C1h5Pi0iFpp2suxpHAXHfyLMJXuVotpXbDh4NDX-A4KRMgaxcfAcui_x6gybksq6gF90-9nfQfmVMVJctZ6M-FvRr-itd1Nef5WAtwUp1qyZygAXU3cH3rarscajmurOsP6dE1OHl3grY_eZhQxk33VBK9lavqNKPg6Q_PLiq1ojbYBj3bcYifJrsNeQwxldQP83aWt5rGtgZTehKVJwa40Uy_Grae1iRnsDtdSy5sTJIJ6EiShnWAdMoGejdiI8vpkjrdU8SWH8lv1KXI54DsbyAuke2cYz02zPWc6JEotQqI0HwhzU0KHyoY4s", + "e": "AQAB", + "kid": "key-rsa", + "alg": "RS256", + "use": "sig" + }, + { + "kty": "RSA", + "n": "yRE6rHuNR0QbHO3H3Kt2pOKGVhQqGZXInOduQNxXzuKlvQTLUTv4l4sggh5_CYYi_cvI-SXVT9kPWSKXxJXBXd_4LkvcPuUakBoAkfh-eiFVMh2VrUyWyj3MFl0HTVF9KwRXLAcwkREiS3npThHRyIxuy0ZMeZfxVL5arMhw1SRELB8HoGfG_AtH89BIE9jDBHZ9dLelK9a184zAf8LwoPLxvJb3Il5nncqPcSfKDDodMFBIMc4lQzDKL5gvmiXLXB1AGLm8KBjfE8s3L5xqi-yUod-j8MtvIj812dkS4QMiRVN_by2h3ZY8LYVGrqZXZTcgn2ujn8uKjXLZVD5TdQ", + "e": "AQAB", + "kid": "rsa01", + "alg": "RS256", + "use": "sig" + }, + { + "kty": "EC", + "crv": "P-256", + "x": "MZiwc5EVP_E3vkd2oKedr4lWVMN9vgdyBBpBIVFJjwY", + "y": "1npLU75B6M0mb01zUAVoeYJSDOlQJmvjBdqLPjJvy3Y", + "kid": "key-ec", + "alg": "ES256", + "use": "sig" + }, + { + "kty": "EC", + "crv": "P-256", + "x": "w7JAoU_gJbZJvV-zCOvU9yFJq0FNC_edCMRM78P8eQQ", + "y": "wQg1EytcsEmGrM70Gb53oluoDbVhCZ3Uq3hHMslHVb4", + "kid": "ec01", + "alg": "ES256", + "use": "sig" + }, + { + "kty": "OKP", + "use": "sig", + "crv": "Ed25519", + "x": "uWtSkE-I9aTMYTTvuTE1rtu0rNdxp3DU33cJ_ksL1Gk", + "kid": "key-ed", + "alg": "EdDSA" + }, + { + "kty": "OKP", + "use": "sig", + "crv": "Ed25519", + "x": "2-Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8", + "kid": "ed01", + "alg": "EdDSA" + } + ] + } \ No newline at end of file diff --git a/config/jwtRS256.key b/config/jwtRS256.key new file mode 100644 index 0000000..91b3390 --- /dev/null +++ b/config/jwtRS256.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJJwIBAAKCAgEA2pQeZdxa7q093K7bj5h6+leIpxfTnuAxzXdhjfGEJHxmt2ek +HyCBWWWXCBiDn2RTcEBcy6gZqOW45Uy/tw+5e+Px1xFj1PykGEkRlOpYSAeWsNaA +WvvpGB9m4zQ0PgZeMDDXE5IIBrY6YAzmGQxV+fcGGLhJnXl0+5/z7tKC7RvBoT3S +Gwlc/AmJqpFtTpEBn/fDnyqiZbpcjXYLExFpExm41xDitRKHWIwfc3dV8/vlNntl +xCPGy/THkjdXJoHv2IJmlhvmr5/h03iGMLWDKSywxOol/4Wc1BT7Hb6byMxW40GK +wSJJ4p7W8eI5mqggRHc8jlwSsTN9LZ2VOvO+XiVShZRVg7JeraGAfWwaIgIJ1D8C +1h5Pi0iFpp2suxpHAXHfyLMJXuVotpXbDh4NDX+A4KRMgaxcfAcui/x6gybksq6g +F90+9nfQfmVMVJctZ6M+FvRr+itd1Nef5WAtwUp1qyZygAXU3cH3rarscajmurOs +P6dE1OHl3grY/eZhQxk33VBK9lavqNKPg6Q/PLiq1ojbYBj3bcYifJrsNeQwxldQ +P83aWt5rGtgZTehKVJwa40Uy/Grae1iRnsDtdSy5sTJIJ6EiShnWAdMoGejdiI8v +pkjrdU8SWH8lv1KXI54DsbyAuke2cYz02zPWc6JEotQqI0HwhzU0KHyoY4sCAwEA +AQKCAgA6UAG8Ewl/W2CBm3Sf3oIQf4HJciXW4ODoe8ze3Wvvf/C3RUMXushHXT7p +vgB/aXiJHeKjwnj2AjNNmSgcYmmNj8ZZJh6IF85/XB8Ap3Rd4whkrRUZMNOCx/3e +53J4iaJfIOiAJBlYEQ2Jymcoj43wXeKWfbPF+z0mVAnz0N10/E6wAZon9FuGMdU0 +WA/dQfo4/xSFRg6FLS673p4dvCtYGSii17JjtEm/acKKP3AC41THMCx6I0FJ8Ee9 +zl3FvCyMil1r9o2YlQLeM+042XPgbDfMkNsKTE8GlYJY8R0GeN1FS5sE42zqtI2L +glrz056oJVdWc2HZPG9M2BmT3KsQXE1C2Bgp6yVLjzL0WtLU3S2M3XM1lXQm+MiA +fZWNastrmRnMA8b+NcEWAsKtl5xgXtyIOTQn6L4n0rJdwyh84LYIOfdT0q/dmc4Y +fl88iJp7w3AZwZN1PH+wJDNYuIHsL2CucOx2vui5zoWSVfb4fxCwv3UCSXe1FHrj +Xlk15Loqz4VzBa9V8eNlUdxtYB9MC0lkQ/u6qj4peBbcHoflCMxCp7WMNhye1HLL +4utz4FgiXIdNXVLAMIsoyK1ZWx3MFCrR58jbu2smFdqIcnVq5UJHdD9yz3BUjl0x +cicgDpSNt9QeTUrLutAsu08rdAfl9o6Og2QFk80hrxMX1B0eAQKCAQEA/OKzTtnw +BK5ZKze22FYBr+Zpa+J4rD7jLlrbD4AGRRsx3FGI6f7JiW3U98v/uOi8IEgAeGZw +z4C0JUI7jL6LzB67o4L7IqP7fjEegWZlnC36UGLGtldyj2lGgON67vZxw9hq/tat +aIoMSkMVsOXDIMy7p5S1zodF426FC2D8UF+759dZ716U0wjYGAMKZrVIwa8fhEVu +CflUTntq17tPbpr/vjvrC2jqi8hk8ma+lH1vHDCsTiNmm69auGiqEKjTLUJY7AiA +2r0f7yfAdtpSgOnKQ2+kcOsSVO00EjqD6VpV7/MDok36pLwdiqZ/TrWAjjsYqPHs +9OSmb2liuCW8ywKCAQEA3UVBS9KURjYALPqnGaoCc3yWD/oxZJGs5B3kPY4tiTJG +4RZ2K/oYxkp6e1FojlhEjoghn/l+fRHjoZVkj7znHR4JjQYRoU8DgGQ+99+uyt6Q +q6XD1LLqHvU/3As8DwSFrpp4pjCXAGWSwKGp57vax3RegrmC9TvaIkBpye3mUtPp +F/RrFewI8Rhj1SvoCu0qGilYhyItPeSvbTkEcnv9lV5R7mjnehmDwG0stM2TyA6v +5oBa26ZCEU0TzTnaJodpc4FUXux+AfmctfIpClcSRm8ipOgF17ikH6lVKdmwHJ8x +alV3Uh1MPARZlJwnuQlgRXX93s9cjGCLnKqEiN/cQQKCAQAoeD8px0bZ+OzcNbZV +OK5ccAs+8KdPKWFB8dhMyrg2Jvv7vjCjAdtO2vzSCxuJg/VXVS5+FibHjllF/St6 +gqPsrp5otHVsPcHpmALBwplQPStp4eTbGXOD790Qk1cBFv9t0ByPW9u0dyMwXzwB +a0Om5BzD3NCblJpiozU3dPXsBuYTXCtQW1qFy0yJyzLG7QwPsu7gRBwwDG6pgKbA +j4FOug9jakNbOBcQ96jwAfFN4iT95ewtNQ0erRlfmaBduibRf2SroVC9sLaDl2D9 +pEK/zqpH0H4IdBYi8TL8F9E0bviBxeo29zO9WT2BCtQkzHceS+bOYqkBJ/ZargrW +XXOxAoIBAHzOoYQJJUVtFDBKuZJKSNOnRGWCs/WMDb8l9SWbWqf2SfCQYNtxWCQQ +woFoa9dOhmz28DBx5BzbyE/OGkjRPnM4DB8Ve0BHdywmXzYlX0xiuat39ru0p0YL +A5g0Zg36eQUBcGgdJC8/G8W36kQhu8ehJeYKiYmV1vZW6tTRcYbqrKGsZfKZjnmf +TkBhYaM4HvVeuOaQKoCsyx6KeK2yrlhgOUqGtXozhhM2AW+CPYcscZ9MavNWFhH4 +LeEmbpwo6RwTqOlZ78FhcDlYfDmu30oHSb1GenUxWrHZK4ZNmX6rdI4L4x/YErYP +pg+i/OzsEvdbFHVm9Ubg9h7KN7OUwYECggEAGPsNQTDp9cRle0sGXF5ExaATzLHA +rWtCMN2cv66dq6aDY0qZwdwOSBUoy3lzCxq4nOpSb0hKu1aT2J5pA7dLrLMgQA1N +G0fhVKpGgJh7RtTT+W2CvY6fDB5EjZ4W4o4YBJa0Pgmcrh5waKaDQQtk4dD39iCw +r0h5ahrduf64C5COMQFZiDElRQqY9zxkdWTfHN/3wcVzqmfEX5/kOvteRNNHlVBp +6NkTKDlY+TKgb3VHic0+KyD7/x7pmTEnbEDkWGWLF3GThKQbBgr2nIvuUpSMleTR +d62haX3C90QHcUBJHhIxcujvwVhbIm3iDA/D9cyswq0iaOM0JKW1Hjk+fA== +-----END RSA PRIVATE KEY----- diff --git a/config/jwtRS256.key.pub b/config/jwtRS256.key.pub new file mode 100644 index 0000000..900f615 --- /dev/null +++ b/config/jwtRS256.key.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2pQeZdxa7q093K7bj5h6 ++leIpxfTnuAxzXdhjfGEJHxmt2ekHyCBWWWXCBiDn2RTcEBcy6gZqOW45Uy/tw+5 +e+Px1xFj1PykGEkRlOpYSAeWsNaAWvvpGB9m4zQ0PgZeMDDXE5IIBrY6YAzmGQxV ++fcGGLhJnXl0+5/z7tKC7RvBoT3SGwlc/AmJqpFtTpEBn/fDnyqiZbpcjXYLExFp +Exm41xDitRKHWIwfc3dV8/vlNntlxCPGy/THkjdXJoHv2IJmlhvmr5/h03iGMLWD +KSywxOol/4Wc1BT7Hb6byMxW40GKwSJJ4p7W8eI5mqggRHc8jlwSsTN9LZ2VOvO+ +XiVShZRVg7JeraGAfWwaIgIJ1D8C1h5Pi0iFpp2suxpHAXHfyLMJXuVotpXbDh4N +DX+A4KRMgaxcfAcui/x6gybksq6gF90+9nfQfmVMVJctZ6M+FvRr+itd1Nef5WAt +wUp1qyZygAXU3cH3rarscajmurOsP6dE1OHl3grY/eZhQxk33VBK9lavqNKPg6Q/ +PLiq1ojbYBj3bcYifJrsNeQwxldQP83aWt5rGtgZTehKVJwa40Uy/Grae1iRnsDt +dSy5sTJIJ6EiShnWAdMoGejdiI8vpkjrdU8SWH8lv1KXI54DsbyAuke2cYz02zPW +c6JEotQqI0HwhzU0KHyoY4sCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/config/private_ecdsa_key.pem b/config/private_ecdsa_key.pem new file mode 100644 index 0000000..4613a0d --- /dev/null +++ b/config/private_ecdsa_key.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgWTFfCGljY6aw3Hrt +kHmPRiazukxPLb6ilpRAewjW8nihRANCAATDskChT+Altkm9X7MI69T3IUmrQU0L +950IxEzvw/x5BMEINRMrXLBJhqzO9Bm+d6JbqA21YQmd1Kt4RzLJR1W+ +-----END PRIVATE KEY----- diff --git a/config/private_ed25519_key.pem b/config/private_ed25519_key.pem new file mode 100644 index 0000000..84b5e06 --- /dev/null +++ b/config/private_ed25519_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIGrD/e7uKYqSY4twDEsRfMMuLSrODf14dpTiTK6K1YI0 +-----END PRIVATE KEY----- diff --git a/config/private_rsa_key_pkcs8.pem b/config/private_rsa_key_pkcs8.pem new file mode 100644 index 0000000..2df7451 --- /dev/null +++ b/config/private_rsa_key_pkcs8.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJETqse41HRBsc +7cfcq3ak4oZWFCoZlcic525A3FfO4qW9BMtRO/iXiyCCHn8JhiL9y8j5JdVP2Q9Z +IpfElcFd3/guS9w+5RqQGgCR+H56IVUyHZWtTJbKPcwWXQdNUX0rBFcsBzCRESJL +eelOEdHIjG7LRkx5l/FUvlqsyHDVJEQsHwegZ8b8C0fz0EgT2MMEdn10t6Ur1rXz +jMB/wvCg8vG8lvciXmedyo9xJ8oMOh0wUEgxziVDMMovmC+aJctcHUAYubwoGN8T +yzcvnGqL7JSh36Pwy28iPzXZ2RLhAyJFU39vLaHdljwthUaupldlNyCfa6Ofy4qN +ctlUPlN1AgMBAAECggEAdESTQjQ70O8QIp1ZSkCYXeZjuhj081CK7jhhp/4ChK7J +GlFQZMwiBze7d6K84TwAtfQGZhQ7km25E1kOm+3hIDCoKdVSKch/oL54f/BK6sKl +qlIzQEAenho4DuKCm3I4yAw9gEc0DV70DuMTR0LEpYyXcNJY3KNBOTjN5EYQAR9s +2MeurpgK2MdJlIuZaIbzSGd+diiz2E6vkmcufJLtmYUT/k/ddWvEtz+1DnO6bRHh +xuuDMeJA/lGB/EYloSLtdyCF6sII6C6slJJtgfb0bPy7l8VtL5iDyz46IKyzdyzW +tKAn394dm7MYR1RlUBEfqFUyNK7C+pVMVoTwCC2V4QKBgQD64syfiQ2oeUlLYDm4 +CcKSP3RnES02bcTyEDFSuGyyS1jldI4A8GXHJ/lG5EYgiYa1RUivge4lJrlNfjyf +dV230xgKms7+JiXqag1FI+3mqjAgg4mYiNjaao8N8O3/PD59wMPeWYImsWXNyeHS +55rUKiHERtCcvdzKl4u35ZtTqQKBgQDNKnX2bVqOJ4WSqCgHRhOm386ugPHfy+8j +m6cicmUR46ND6ggBB03bCnEG9OtGisxTo/TuYVRu3WP4KjoJs2LD5fwdwJqpgtHl +yVsk45Y1Hfo+7M6lAuR8rzCi6kHHNb0HyBmZjysHWZsn79ZM+sQnLpgaYgQGRbKV +DZWlbw7g7QKBgQCl1u+98UGXAP1jFutwbPsx40IVszP4y5ypCe0gqgon3UiY/G+1 +zTLp79GGe/SjI2VpQ7AlW7TI2A0bXXvDSDi3/5Dfya9ULnFXv9yfvH1QwWToySpW +Kvd1gYSoiX84/WCtjZOr0e0HmLIb0vw0hqZA4szJSqoxQgvF22EfIWaIaQKBgQCf +34+OmMYw8fEvSCPxDxVvOwW2i7pvV14hFEDYIeZKW2W1HWBhVMzBfFB5SE8yaCQy +pRfOzj9aKOCm2FjjiErVNpkQoi6jGtLvScnhZAt/lr2TXTrl8OwVkPrIaN0bG/AS +aUYxmBPCpXu3UjhfQiWqFq/mFyzlqlgvuCc9g95HPQKBgAscKP8mLxdKwOgX8yFW +GcZ0izY/30012ajdHY+/QK5lsMoxTnn0skdS+spLxaS5ZEO4qvPVb8RAoCkWMMal +2pOhmquJQVDPDLuZHdrIiKiDM20dy9sMfHygWcZjQ4WSxf/J7T9canLZIXFhHAZT +3wc9h4G8BBCtWN2TN/LsGZdB +-----END PRIVATE KEY----- diff --git a/config/public_ecdsa_key.pem b/config/public_ecdsa_key.pem new file mode 100644 index 0000000..f8b9c8e --- /dev/null +++ b/config/public_ecdsa_key.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEw7JAoU/gJbZJvV+zCOvU9yFJq0FN +C/edCMRM78P8eQTBCDUTK1ywSYaszvQZvneiW6gNtWEJndSreEcyyUdVvg== +-----END PUBLIC KEY----- diff --git a/config/public_ed25519_key.pem b/config/public_ed25519_key.pem new file mode 100644 index 0000000..9ea2b43 --- /dev/null +++ b/config/public_ed25519_key.pem @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA2+Jj2UvNCvQiUPNYRgSi0cJSPiJI6Rs6D0UTeEpQVj8= +-----END PUBLIC KEY----- diff --git a/demo-server/Cargo.toml b/demo-server/Cargo.toml new file mode 100644 index 0000000..f678b25 --- /dev/null +++ b/demo-server/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "demo-server" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.64" +axum = { version = "0.6.1", features = ["headers"] } +headers = "0.3" +josekit = "0.8.1" +jsonwebtoken = "8.2.0" +once_cell = "1.8" +reqwest = { version = "0.11.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0.34" +tokio = { version = "1.0", features = ["full"] } +tower-http = { version = "0.3.4", features = ["trace"] } +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +jwt-authorizer = { path = "../jwt-authorizer" } \ No newline at end of file diff --git a/demo-server/request.http b/demo-server/request.http new file mode 100644 index 0000000..62ec8bb --- /dev/null +++ b/demo-server/request.http @@ -0,0 +1,35 @@ +### +POST http://localhost:3000/oidc/authorize +Content-Type: application/json + +{"client_id":"foo","client_secret":"bar"} + +### Protected RSA +GET http://localhost:3000/api/protected +Content-Type: application/json +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImtleS1yc2EifQ.eyJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwfQ.K9QFjvVquRF2-Wt1QRfipOGwiYsmRs7SAwqKskHemFb9BRRZutpfV4oEoHaXMLomTUe8rH0TMjpKcweYK_H1I8D4r-mAN216oUfxCQiFWDB8T2VBI8um-efUg67i2myDZJr5VXdZH8ywj7bn9LyNS4I_xT-J3XvsngeCpuxVSRiYu4FkcUkLrPzbu2sDyBXFqYO9FOorZ8sl0Ninc93fWT2uUrEG8jRyWCa4xpoqbKbm7CN7T2tOKF7mx_xdSPTeSM-U9mUiHsMOrXi1S05IM0hvNJrBduLS6sMTFWrVhis6zqnuxDOirwZS-aN0_SgMDnZTFPsCh8dkqFde1Pv1IYjZfr5OOHjQ9QWj6UDjam6M1eWVPK6QLlxv5bU_gnlAiHm9wJX38-REwmVhIJIBzKxsgJAu1gnRBxe36OM3rkgYxpB86YvfDyOoFlqx8erdxYv38AtvJibe4HB6KLndp_QMm5XXQsbfyEXWGe8hzDwozdhGeQsJXz7PcI3KPlv19PrUM8njElFpOiyfAEXwbtp1EZTzMZ4ZNF6LLFy1fpLcosgyp05o_2YMvngltSnN3v0IPncJx50StdYsoxPN9Ac_nH8VbNlHfmPHMklD1plof0pYf5SiL8yCQP9Uiw9NrN2PeQzbveMKF1T1UNbn2tefxoxr3k6sgWiMH_g_kkk + +### Protected EC +GET http://localhost:3000/api/protected +Content-Type: application/json +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6ImtleS1lYyJ9.eyJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwfQ.r0qaeYJWhjEybhNPgrrFwLBTeLMYtL4IJqOxZyxH9m-JYWqzV0R0WyYQIkf_tQ1UmzqHc9_xzUZnzSjTeEwDHw + +### Protected Ed +GET http://localhost:3000/api/protected +Content-Type: application/json +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImtleS1lZCJ9.eyJzdWIiOiJiQGIuY29tIiwiZXhwIjoyMDAwMDAwMDAwfQ.XAx9msioheXEH1XUEIWMHGBg25JOpBHqcgL_ou_S3fwVht2TbKRiDZ4G6ZyEtn57hCbOy250zTD_g0EbaMGwAg + +### +GET http://localhost:3000/api/protected +Content-Type: application/json +Authorization: Bearer blablabla.xxxx.xxxx + + +### +GET http://localhost:3000/oidc/jwks +Content-Type: application/json + + +### +GET http://localhost:3000/oidc/tokens +Content-Type: application/json diff --git a/demo-server/src/main.rs b/demo-server/src/main.rs new file mode 100644 index 0000000..7b8b6bc --- /dev/null +++ b/demo-server/src/main.rs @@ -0,0 +1,70 @@ +use axum::{ + routing::{get, post}, + Router, +}; +use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims}; +use serde::Deserialize; +use std::{fmt::Display, net::SocketAddr}; +use tower_http::trace::TraceLayer; +use tracing::info; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +mod oidc_provider; + +#[tokio::main] +async fn main() { + tracing_subscriber::registry() + .with(tracing_subscriber::EnvFilter::new( + std::env::var("RUST_LOG").unwrap_or_else(|_| "info,axum_poc=debug,tower_http=debug".into()), + )) + .with(tracing_subscriber::fmt::layer()) + .init(); + + fn claim_checker(u: &User) -> bool { + info!("checking claims: {} -> {}", u.sub, u.sub.contains('@')); + + u.sub.contains('@') // must be an email + } + + // First let's create an authorizer builder from a JWKS Endpoint + // User is a struct deserializable from JWT claims representing the authorized user + let jwt_auth: JwtAuthorizer = JwtAuthorizer::new() + .from_jwks_url("http://localhost:3000/oidc/jwks") + .with_check(claim_checker); + + let oidc = Router::new() + .route("/authorize", post(oidc_provider::authorize)) + .route("/jwks", get(oidc_provider::jwks)) + .route("/tokens", get(oidc_provider::tokens)); + + let api = Router::new() + .route("/protected", get(protected)) + .layer(jwt_auth.layer()); + // .layer(jwt_auth.check_claims(|_: User| true)); + + let app = Router::new() + .nest("/oidc/", oidc) + .nest("/api", api) + .layer(TraceLayer::new_for_http()); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::info!("listening on {}", addr); + + axum::Server::bind(&addr).serve(app.into_make_service()).await.unwrap(); +} + +async fn protected(JwtClaims(user): JwtClaims) -> Result { + // Send the protected data to the user + Ok(format!("Welcome: {}", user.sub)) +} + +#[derive(Debug, Deserialize, Clone)] +struct User { + sub: String, +} + +impl Display for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "User: {:?}", self.sub) + } +} diff --git a/demo-server/src/oidc_provider/mod.rs b/demo-server/src/oidc_provider/mod.rs new file mode 100644 index 0000000..919842b --- /dev/null +++ b/demo-server/src/oidc_provider/mod.rs @@ -0,0 +1,240 @@ +use axum::{ + async_trait, + extract::{FromRequestParts, TypedHeader}, + headers::{authorization::Bearer, Authorization}, + http::{request::Parts, StatusCode}, + response::{IntoResponse, Response}, + Json, +}; +use josekit::jwk::{ + alg::{ec::EcCurve, ec::EcKeyPair, ed::EdKeyPair, rsa::RsaKeyPair}, + Jwk, +}; +use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::fmt::Display; + +pub static KEYS: Lazy = Lazy::new(|| { + //let secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"); + // Keys::new("xxxxx".as_bytes()) + Keys::load_rsa() +}); + +pub struct Keys { + pub alg: Algorithm, + pub encoding: EncodingKey, + pub decoding: DecodingKey, +} + +impl Keys { + fn new(secret: &[u8]) -> Self { + Self { + alg: Algorithm::HS256, + encoding: EncodingKey::from_secret(secret), + decoding: DecodingKey::from_secret(secret), + } + } + fn load_rsa() -> Self { + Self { + alg: Algorithm::RS256, + encoding: EncodingKey::from_rsa_pem(include_bytes!("../../../config/jwtRS256.key")).unwrap(), + decoding: DecodingKey::from_rsa_pem(include_bytes!("../../../config/jwtRS256.key.pub")).unwrap(), + } + } +} + +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +struct JwkSet { + keys: Vec, +} + +pub async fn jwks() -> Json { + // let mut ksmap = serde_json::Map::new(); + + let mut kset = JwkSet { + keys: Vec::::new(), + }; + + let keypair = RsaKeyPair::from_pem(include_bytes!("../../../config/jwtRS256.key")).unwrap(); + let mut pk = keypair.to_jwk_public_key(); + pk.set_key_id("key-rsa"); + pk.set_algorithm("RS256"); + pk.set_key_use("sig"); + kset.keys.push(pk); + + let keypair = RsaKeyPair::from_pem(include_bytes!("../../../config/private_rsa_key_pkcs8.pem")).unwrap(); + let mut pk = keypair.to_jwk_public_key(); + pk.set_key_id("rsa01"); + pk.set_algorithm("RS256"); + pk.set_key_use("sig"); + kset.keys.push(pk); + + let keypair = + EcKeyPair::from_pem(include_bytes!("../../../config/ec256-private.pem"), Some(EcCurve::P256)).unwrap(); + let mut pk = keypair.to_jwk_public_key(); + pk.set_key_id("key-ec"); + pk.set_algorithm("ES256"); + pk.set_key_use("sig"); + kset.keys.push(pk); + + let keypair = EcKeyPair::from_pem( + include_bytes!("../../../config/private_ecdsa_key.pem"), + Some(EcCurve::P256), + ) + .unwrap(); + let mut pk = keypair.to_jwk_public_key(); + pk.set_key_id("ec01"); + pk.set_algorithm("ES256"); + pk.set_key_use("sig"); + kset.keys.push(pk); + + let keypair = EdKeyPair::from_pem(include_bytes!("../../../config/ed25519-private.pem")).unwrap(); + let mut pk = keypair.to_jwk_public_key(); + pk.set_key_id("key-ed"); + pk.set_algorithm("EdDSA"); + pk.set_key_use("sig"); + kset.keys.push(pk); + + let keypair = EdKeyPair::from_pem(include_bytes!("../../../config/private_ed25519_key.pem")).unwrap(); + let mut pk = keypair.to_jwk_public_key(); + pk.set_key_id("ed01"); + pk.set_algorithm("EdDSA"); + pk.set_key_use("sig"); + kset.keys.push(pk); + + Json(json!(kset)) +} + +fn build_header(alg: Algorithm, kid: &str) -> Header { + Header { + typ: Some("JWT".to_string()), + alg, + kid: Some(kid.to_owned()), + cty: None, + jku: None, + jwk: None, + x5u: None, + x5c: None, + x5t: None, + x5t_s256: None, + } +} + +pub async fn tokens() -> Json { + let claims = Claims { + sub: "b@b.com".to_owned(), + exp: 2000000000, // May 2033 + }; + + let rsa_key = EncodingKey::from_rsa_pem(include_bytes!("../../../config/jwtRS256.key")).unwrap(); + let ec_key = EncodingKey::from_ec_pem(include_bytes!("../../../config/ec256-private.pem")).unwrap(); + let ed_key = EncodingKey::from_ed_pem(include_bytes!("../../../config/ed25519-private.pem")).unwrap(); + + let rsa_token = encode(&build_header(Algorithm::RS256, "key-rsa"), &claims, &rsa_key).unwrap(); + let ec_token = encode(&build_header(Algorithm::ES256, "key-ec"), &claims, &ec_key).unwrap(); + let ed_token = encode(&build_header(Algorithm::EdDSA, "key-ed"), &claims, &ed_key).unwrap(); + + Json(json!({ + "rsa": rsa_token, + "ec": ec_token, + "ed": ed_token + })) +} + +pub async fn authorize(Json(payload): Json) -> Result, AuthError> { + tracing::info!("authorizing ..."); + if payload.client_id.is_empty() || payload.client_secret.is_empty() { + return Err(AuthError::MissingCredentials); + } + // Here you can check the user credentials from a database + if payload.client_id != "foo" || payload.client_secret != "bar" { + return Err(AuthError::WrongCredentials); + } + let claims = Claims { + sub: "b@b.com".to_owned(), + // Mandatory expiry time as UTC timestamp + exp: 2000000000, // May 2033 + }; + // Create the authorization token + let token = encode(&Header::new(KEYS.alg), &claims, &KEYS.encoding).map_err(|_| AuthError::TokenCreation)?; + + // Send the authorized token + Ok(Json(AuthBody::new(token))) +} + +#[derive(Debug, Serialize, Deserialize)] +struct Claims { + sub: String, + exp: usize, +} + +#[derive(Debug, Serialize)] +pub struct AuthBody { + access_token: String, + token_type: String, +} + +#[derive(Debug, Deserialize)] +pub struct AuthPayload { + client_id: String, + client_secret: String, +} + +#[derive(Debug)] +pub enum AuthError { + WrongCredentials, + MissingCredentials, + TokenCreation, + InvalidToken, +} + +impl Display for Claims { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "sub: {}", self.sub) + } +} + +impl AuthBody { + fn new(access_token: String) -> Self { + Self { + access_token, + token_type: "Bearer".to_string(), + } + } +} + +impl IntoResponse for AuthError { + fn into_response(self) -> Response { + let (status, error_message) = match self { + AuthError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"), + AuthError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"), + AuthError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"), + AuthError::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"), + }; + let body = Json(json!({ + "error": error_message, + })); + (status, body).into_response() + } +} + +#[async_trait] +impl FromRequestParts for Claims +where + S: Send + Sync, +{ + type Rejection = AuthError; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + // Extract the token from the authorization header + let TypedHeader(Authorization(bearer)) = TypedHeader::>::from_request_parts(parts, state) + .await + .map_err(|_| AuthError::InvalidToken)?; + let token_data = decode::(bearer.token(), &KEYS.decoding, &Validation::default()) + .map_err(|_| AuthError::InvalidToken)?; + + Ok(token_data.claims) + } +} diff --git a/jwt-authorizer/Cargo.toml b/jwt-authorizer/Cargo.toml new file mode 100644 index 0000000..dbd3fed --- /dev/null +++ b/jwt-authorizer/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "jwt-authorizer" +description = "jwt authorizer middleware for axum" +version = "0.2.0" +edition = "2021" +authors = ["cduvray "] +license = "MIT" +readme = "docs/README.md" +repository = "https://github.com/cduvray/jwt-authorizer" +keywords = ["jwt","axum","authorisation"] + +[dependencies] +axum = { version = "0.6.1", features = ["headers"] } +futures-util = "0.3.25" +futures-core = "0.3.25" +headers = "0.3" +jsonwebtoken = "8.2.0" +http = "0.2.8" +# pin-project-lite = "0.2.9" +pin-project = "1.0.12" +reqwest = { version = "0.11.13", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0.37" +tokio = { version = "1.0", features = ["full"] } +tower-http = { version = "0.3.4", features = ["trace", "auth"] } +tower-layer = "0.3" +tower-service = "0.3" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[dev-dependencies] +wiremock = "0.5" \ No newline at end of file diff --git a/jwt-authorizer/clippy.toml b/jwt-authorizer/clippy.toml new file mode 100644 index 0000000..e69de29 diff --git a/jwt-authorizer/docs/README.md b/jwt-authorizer/docs/README.md new file mode 100644 index 0000000..d3beb4f --- /dev/null +++ b/jwt-authorizer/docs/README.md @@ -0,0 +1,36 @@ +# jwt-authorizer + +JWT authoriser Layer for Axum. + +Example: + +```rust + use jwt_authorizer::{AuthError, JwtAuthorizer, JwtClaims}; + use axum::{routing::get, Router}; + use serde::Deserialize; + + // Authorized entity, struct deserializable from JWT claims + #[derive(Debug, Deserialize, Clone)] + struct User { + sub: String, + } + + // let's create an authorizer builder from a JWKS Endpoint + let jwt_auth: JwtAuthorizer = JwtAuthorizer::new() + .from_jwks_url("http://localhost:3000/oidc/jwks"); + + // adding the authorization layer + let app = Router::new().route("/protected", get(protected)) + .layer(jwt_auth.layer()); + + // proteced handler with user injection (mapping some jwt claims) + async fn protected(JwtClaims(user): JwtClaims) -> Result { + // Send the protected data to the user + Ok(format!("Welcome: {}", user.sub)) + } + + # async { + axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) + .serve(app.into_make_service()).await.expect("server failed"); + # }; +``` \ No newline at end of file diff --git a/jwt-authorizer/src/authorizer.rs b/jwt-authorizer/src/authorizer.rs new file mode 100644 index 0000000..bb52c82 --- /dev/null +++ b/jwt-authorizer/src/authorizer.rs @@ -0,0 +1,170 @@ +use std::{io::Read, time::Duration}; + +use jsonwebtoken::{decode, decode_header, jwk::JwkSet, DecodingKey, TokenData, Validation}; +use serde::de::DeserializeOwned; + +use crate::{ + error::AuthError, + jwks::{key_store_manager::KeyStoreManager, KeySource}, +}; + +pub trait ClaimsChecker { + fn check(&self, claims: &C) -> bool; +} + +#[derive(Clone)] +pub struct FnClaimsChecker +where + C: Clone, +{ + pub checker_fn: fn(&C) -> bool, +} + +impl ClaimsChecker for FnClaimsChecker +where + C: Clone, +{ + fn check(&self, claims: &C) -> bool { + (self.checker_fn)(claims) + } +} + +pub struct Authorizer +where + C: Clone, +{ + pub key_source: KeySource, + pub claims_checker: Option>, +} + +fn read_data(path: &str) -> Result, AuthError> { + let mut data = Vec::::new(); + let mut f = std::fs::File::open(path)?; + f.read_to_end(&mut data)?; + Ok(data) +} + +impl Authorizer +where + C: DeserializeOwned + Clone + Send + Sync, +{ + pub fn from_jwks(jwks: &str, claims_checker: Option>) -> Result, AuthError> { + let set: JwkSet = serde_json::from_str(jwks)?; + let k = DecodingKey::from_jwk(&set.keys[0])?; + + Ok(Authorizer { + key_source: KeySource::DecodingKeySource(k), + claims_checker, + }) + } + + pub fn from_rsa_file(path: &str) -> Result, AuthError> { + Ok(Authorizer { + key_source: KeySource::DecodingKeySource(DecodingKey::from_rsa_pem(&read_data(path)?)?), + claims_checker: None, + }) + } + + pub fn from_ec_file(path: &str) -> Result, AuthError> { + let k = DecodingKey::from_ec_der(&read_data(path)?); + Ok(Authorizer { + key_source: KeySource::DecodingKeySource(k), + claims_checker: None, + }) + } + + pub fn from_ed_file(path: &str) -> Result, AuthError> { + let k = DecodingKey::from_ed_der(&read_data(path)?); + Ok(Authorizer { + key_source: KeySource::DecodingKeySource(k), + claims_checker: None, + }) + } + + pub fn from_secret(secret: &str) -> Result, AuthError> { + let k = DecodingKey::from_secret(secret.as_bytes()); + Ok(Authorizer { + key_source: KeySource::DecodingKeySource(k), + claims_checker: None, + }) + } + + pub fn from_jwks_url(url: &str, claims_checker: Option>) -> Result, AuthError> { + let key_store_manager = KeyStoreManager::with_refresh_interval(url, Duration::from_secs(60)); + Ok(Authorizer { + key_source: KeySource::KeyStoreSource(key_store_manager), + claims_checker, + }) + } + + pub async fn check_auth(&self, token: &str) -> Result, AuthError> { + let header = decode_header(token)?; + let validation = Validation::new(header.alg); + let decoding_key = self.key_source.get_key(header).await?; + let token_data = decode::(token, &decoding_key, &validation)?; + + if let Some(ref checker) = self.claims_checker { + if !checker.check(&token_data.claims) { + return Err(AuthError::InvalidClaims()); + } + } + + Ok(token_data) + } +} + +#[cfg(test)] +mod tests { + + use jsonwebtoken::{Algorithm, Header}; + use serde_json::Value; + + use super::Authorizer; + + #[tokio::test] + async fn from_secret() { + let h = Header::new(Algorithm::HS256); + let a = Authorizer::::from_secret("xxxxxx").unwrap(); + let k = a.key_source.get_key(h); + assert!(k.await.is_ok()); + } + + #[tokio::test] + async fn from_jwks() { + let jwks = r#" + {"keys": [{ + "kid": "1", + "kty": "RSA", + "alg": "RS256", + "use": "sig", + "n": "2pQeZdxa7q093K7bj5h6-leIpxfTnuAxzXdhjfGEJHxmt2ekHyCBWWWXCBiDn2RTcEBcy6gZqOW45Uy_tw-5e-Px1xFj1PykGEkRlOpYSAeWsNaAWvvpGB9m4zQ0PgZeMDDXE5IIBrY6YAzmGQxV-fcGGLhJnXl0-5_z7tKC7RvBoT3SGwlc_AmJqpFtTpEBn_fDnyqiZbpcjXYLExFpExm41xDitRKHWIwfc3dV8_vlNntlxCPGy_THkjdXJoHv2IJmlhvmr5_h03iGMLWDKSywxOol_4Wc1BT7Hb6byMxW40GKwSJJ4p7W8eI5mqggRHc8jlwSsTN9LZ2VOvO-XiVShZRVg7JeraGAfWwaIgIJ1D8C1h5Pi0iFpp2suxpHAXHfyLMJXuVotpXbDh4NDX-A4KRMgaxcfAcui_x6gybksq6gF90-9nfQfmVMVJctZ6M-FvRr-itd1Nef5WAtwUp1qyZygAXU3cH3rarscajmurOsP6dE1OHl3grY_eZhQxk33VBK9lavqNKPg6Q_PLiq1ojbYBj3bcYifJrsNeQwxldQP83aWt5rGtgZTehKVJwa40Uy_Grae1iRnsDtdSy5sTJIJ6EiShnWAdMoGejdiI8vpkjrdU8SWH8lv1KXI54DsbyAuke2cYz02zPWc6JEotQqI0HwhzU0KHyoY4s", + "e": "AQAB" + }]} + "#; + let a = Authorizer::::from_jwks(jwks, None).unwrap(); + let k = a.key_source.get_key(Header::new(Algorithm::RS256)); + assert!(k.await.is_ok()); + } + + #[tokio::test] + async fn from_file() { + let a = Authorizer::::from_rsa_file("../config/jwtRS256.key.pub").unwrap(); + let k = a.key_source.get_key(Header::new(Algorithm::RS256)); + assert!(k.await.is_ok()); + + let a = Authorizer::::from_ec_file("../config/ec256-public.pem").unwrap(); + let k = a.key_source.get_key(Header::new(Algorithm::ES256)); + assert!(k.await.is_ok()); + + let a = Authorizer::::from_ed_file("../config/ed25519-public.pem").unwrap(); + let k = a.key_source.get_key(Header::new(Algorithm::EdDSA)); + assert!(k.await.is_ok()); + } + + #[tokio::test] + async fn from_file_errors() { + let a = Authorizer::::from_rsa_file("./config/does-not-exist.pem"); + println!("{:?}", a.as_ref().err()); + assert!(a.is_err()); + } +} diff --git a/jwt-authorizer/src/error.rs b/jwt-authorizer/src/error.rs new file mode 100644 index 0000000..46f46e0 --- /dev/null +++ b/jwt-authorizer/src/error.rs @@ -0,0 +1,60 @@ +use axum::{ + extract::rejection::TypedHeaderRejection, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use jsonwebtoken::Algorithm; +use thiserror::Error; + +use tracing::log::warn; + +#[derive(Debug, Error)] +pub enum AuthError { + #[error(transparent)] + JwksSerialisationError(#[from] serde_json::Error), + + #[error(transparent)] + JwksRefreshError(#[from] reqwest::Error), + + #[error(transparent)] + KeyFileError(#[from] std::io::Error), + + #[error("InvalidKey {0}")] + InvalidKey(String), + + #[error("Invalid Kid {0}")] + InvalidKid(String), + + #[error("Invalid Key Algorithm {0:?}")] + InvalidKeyAlg(Algorithm), + + #[error(transparent)] + InvalidTokenHeader(#[from] TypedHeaderRejection), + + #[error(transparent)] + InvalidToken(#[from] jsonwebtoken::errors::Error), + + #[error("Invalid Claim")] + InvalidClaims(), +} + +impl IntoResponse for AuthError { + fn into_response(self) -> Response { + warn!("AuthError: {}", &self); + let (status, error_message) = match self { + AuthError::JwksRefreshError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + AuthError::KeyFileError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + AuthError::InvalidKid(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), + AuthError::InvalidTokenHeader(_) => (StatusCode::BAD_REQUEST, self.to_string()), + AuthError::InvalidToken(_) => (StatusCode::BAD_REQUEST, self.to_string()), + AuthError::InvalidKey(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg), + AuthError::JwksSerialisationError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + AuthError::InvalidKeyAlg(_) => (StatusCode::BAD_REQUEST, self.to_string()), + AuthError::InvalidClaims() => (StatusCode::FORBIDDEN, self.to_string()), + }; + let body = axum::Json(serde_json::json!({ + "error": error_message, + })); + (status, body).into_response() + } +} diff --git a/jwt-authorizer/src/jwks/key_store_manager.rs b/jwt-authorizer/src/jwks/key_store_manager.rs new file mode 100644 index 0000000..4388270 --- /dev/null +++ b/jwt-authorizer/src/jwks/key_store_manager.rs @@ -0,0 +1,373 @@ +use jsonwebtoken::{ + jwk::{Jwk, JwkSet}, + Algorithm, DecodingKey, +}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::sync::Mutex; + +use crate::error::AuthError; + +#[derive(Clone)] +pub enum RefreshStrategy { + /// refresh periodicaly + Interval(Duration), + /// when kid not found in the store + KeyNotFound, + // other strategies? KeyNotFoundOrInterval(Duration), Once, +} + +#[derive(Clone)] +pub struct KeyStoreManager { + key_url: String, + refresh: RefreshStrategy, + /// in case of fail loading (error or key not found), minimal interval + minimal_refresh_interval: Duration, + keystore: Arc>, +} + +pub struct KeyStore { + /// key set + jwks: JwkSet, + /// time of the last successfully loaded jwkset + load_time: Option, + /// time of the last failed load + fail_time: Option, +} + +impl KeyStoreManager { + pub(crate) fn new(url: &str, refresh: RefreshStrategy) -> KeyStoreManager { + KeyStoreManager { + key_url: url.to_owned(), + refresh, + minimal_refresh_interval: Duration::from_secs(5), // TODO: make configurable + keystore: Arc::new(Mutex::new(KeyStore { + jwks: JwkSet { keys: vec![] }, + load_time: None, + fail_time: None, + })), + } + } + + pub(crate) fn with_refresh(url: &str) -> KeyStoreManager { + KeyStoreManager::new(url, RefreshStrategy::KeyNotFound) + } + + pub(crate) fn with_refresh_interval(url: &str, interval: Duration) -> KeyStoreManager { + KeyStoreManager::new(url, RefreshStrategy::Interval(interval)) + } + + pub(crate) async fn get_key(&self, header: &jsonwebtoken::Header) -> Result { + let kstore = self.keystore.clone(); + let mut ks_gard = kstore.lock().await; + let key = match self.refresh { + RefreshStrategy::Interval(refresh_interval) => { + if ks_gard.should_refresh(refresh_interval) && ks_gard.can_refresh(self.minimal_refresh_interval) { + ks_gard.refresh(&self.key_url, &[]).await?; + } + if let Some(ref kid) = header.kid { + ks_gard + .find_kid(kid) + .ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))? + } else { + ks_gard + .find_alg(&header.alg) + .ok_or(AuthError::InvalidKeyAlg(header.alg))? + } + } + RefreshStrategy::KeyNotFound => { + if let Some(ref kid) = header.kid { + let jwk_opt = ks_gard.find_kid(kid); + if let Some(jwk) = jwk_opt { + jwk + } else if ks_gard.can_refresh(self.minimal_refresh_interval) { + ks_gard.refresh(&self.key_url, &[("kid", kid)]).await?; + ks_gard + .find_kid(kid) + .ok_or_else(|| AuthError::InvalidKid(kid.to_owned()))? + } else { + return Err(AuthError::InvalidKid(kid.to_owned())); + } + } else { + let jwk_opt = ks_gard.find_alg(&header.alg); + // .ok_or(AuthError::InvalidKeyAlg(header.alg))? + if let Some(jwk) = jwk_opt { + jwk + } else if ks_gard.can_refresh(self.minimal_refresh_interval) { + ks_gard + .refresh( + &self.key_url, + &[( + "alg", + &serde_json::to_string(&header.alg) + .map_err(|_| AuthError::InvalidKeyAlg(header.alg))?, + )], + ) + .await?; + ks_gard + .find_alg(&header.alg) + .ok_or_else(|| AuthError::InvalidKeyAlg(header.alg))? + } else { + return Err(AuthError::InvalidKeyAlg(header.alg)); + } + } + } + }; + + DecodingKey::from_jwk(key).map_err(|err| AuthError::InvalidKey(err.to_string())) + } +} + +impl KeyStore { + fn should_refresh(&self, refresh_interval: Duration) -> bool { + if let Some(t) = self.load_time { + t.elapsed() > refresh_interval + } else { + true + } + } + + fn can_refresh(&self, minimal_refresh_interval: Duration) -> bool { + if let Some(ft) = self.fail_time { + if let Some(lt) = self.load_time { + ft.elapsed() > minimal_refresh_interval && lt.elapsed() > minimal_refresh_interval + } else { + ft.elapsed() > minimal_refresh_interval + } + } else if let Some(lt) = self.load_time { + lt.elapsed() > minimal_refresh_interval + } else { + true + } + } + + async fn refresh(&mut self, key_url: &str, qparam: &[(&str, &str)]) -> Result<(), AuthError> { + reqwest::Client::new() + .get(key_url) + .query(qparam) + .send() + .await + .map_err(AuthError::JwksRefreshError)? + .json::() + .await + .map(|jwks| { + self.load_time = Some(Instant::now()); + self.jwks = jwks; + Ok(()) + }) + .map_err(|e| { + self.fail_time = Some(Instant::now()); + AuthError::JwksRefreshError(e) + })? + } + + /// Find the key in the set that matches the given key id, if any. + pub fn find_kid(&self, kid: &str) -> Option<&Jwk> { + self.jwks.find(kid) + } + + /// Find the key in the set that matches the given key id, if any. + pub fn find_alg(&self, alg: &Algorithm) -> Option<&Jwk> { + self.jwks.keys.iter().find(|jwk| { + if let Some(ref a) = jwk.common.algorithm { + alg == a + } else { + false + } + }) + } + + /// Find first key. + pub fn find_first(&self) -> Option<&Jwk> { + self.jwks.keys.get(0) + } +} + +#[cfg(test)] +mod tests { + use std::time::{Duration, Instant}; + + use jsonwebtoken::Algorithm; + use jsonwebtoken::{jwk::Jwk, Header}; + use wiremock::{ + matchers::{method, path}, + Mock, MockServer, ResponseTemplate, + }; + + use crate::jwks::key_store_manager::{KeyStore, KeyStoreManager}; + + #[test] + fn keystore_should_refresh() { + let ks = KeyStore { + jwks: jsonwebtoken::jwk::JwkSet { keys: vec![] }, + fail_time: None, + load_time: Some(Instant::now()), + }; + + assert!(!ks.should_refresh(Duration::from_secs(5))); + + let ks = KeyStore { + jwks: jsonwebtoken::jwk::JwkSet { keys: vec![] }, + fail_time: None, + load_time: Some(Instant::now() - Duration::from_secs(6)), + }; + + assert!(ks.should_refresh(Duration::from_secs(5))); + } + + #[test] + fn keystore_can_refresh() { + let ks = KeyStore { + jwks: jsonwebtoken::jwk::JwkSet { keys: vec![] }, + fail_time: Some(Instant::now() - Duration::from_secs(5)), + load_time: None, + }; + assert!(ks.can_refresh(Duration::from_secs(4))); + assert!(!ks.can_refresh(Duration::from_secs(6))); + + let ks = KeyStore { + jwks: jsonwebtoken::jwk::JwkSet { keys: vec![] }, + fail_time: None, + load_time: Some(Instant::now() - Duration::from_secs(5)), + }; + assert!(ks.can_refresh(Duration::from_secs(4))); + assert!(!ks.can_refresh(Duration::from_secs(6))); + } + + #[test] + fn find_kid() { + let jwk0: Jwk = serde_json::from_str(r#"{"kid":"1","kty":"RSA","alg":"RS256","n":"xxxx","e":"AQAB"}"#).unwrap(); + let jwk1: Jwk = serde_json::from_str(r#"{"kid":"2","kty":"RSA","alg":"RS256","n":"xxxx","e":"AQAB"}"#).unwrap(); + let ks = KeyStore { + load_time: None, + fail_time: None, + jwks: jsonwebtoken::jwk::JwkSet { keys: vec![jwk0, jwk1] }, + }; + assert!(ks.find_kid("1").is_some()); + assert!(ks.find_kid("2").is_some()); + assert!(ks.find_kid("3").is_none()); + } + + #[test] + fn find_alg() { + let jwk0: Jwk = serde_json::from_str(r#"{"kty": "RSA", "alg": "RS256", "n": "xxx","e": "yyy"}"#).unwrap(); + let ks = KeyStore { + load_time: None, + fail_time: None, + jwks: jsonwebtoken::jwk::JwkSet { keys: vec![jwk0] }, + }; + assert!(ks.find_alg(&Algorithm::RS256).is_some()); + assert!(ks.find_alg(&Algorithm::EdDSA).is_none()); + } + + async fn mock_jwks_response_once(mock_server: &MockServer, jwk: &str) { + let jwk0: Jwk = serde_json::from_str(jwk).unwrap(); + let jwks = jsonwebtoken::jwk::JwkSet { keys: vec![jwk0] }; + Mock::given(method("GET")) + .and(path("/")) + .respond_with(ResponseTemplate::new(200).set_body_json(&jwks)) + .expect(1) + .mount(&mock_server) + .await; + } + + fn build_header(kid: &str, alg: Algorithm) -> Header { + let mut header = Header::new(alg); + header.kid = Some(kid.to_owned()); + header + } + + #[tokio::test] + async fn keystore_manager_find_key_with_refresh_interval() { + let mock_server = MockServer::start().await; + mock_jwks_response_once( + &mock_server, + r#"{ + "kty": "OKP", + "use": "sig", + "crv": "Ed25519", + "x": "uWtSkE-I9aTMYTTvuTE1rtu0rNdxp3DU33cJ_ksL1Gk", + "kid": "key-ed", + "alg": "EdDSA" + }"#, + ) + .await; + + let ksm = KeyStoreManager::with_refresh_interval(&mock_server.uri(), Duration::from_secs(3000)); + let r = ksm.get_key(&Header::new(Algorithm::EdDSA)).await; + assert!(r.is_ok()); + mock_server.verify().await; + } + + #[tokio::test] + async fn keystore_manager_find_key_with_refresh() { + let mock_server = MockServer::start().await; + mock_jwks_response_once( + &mock_server, + r#"{ + "kty": "OKP", + "use": "sig", + "crv": "Ed25519", + "x": "uWtSkE-I9aTMYTTvuTE1rtu0rNdxp3DU33cJ_ksL1Gk", + "kid": "key-ed", + "alg": "EdDSA" + }"#, + ) + .await; + + let mut ksm = KeyStoreManager::with_refresh(&mock_server.uri()); + + // STEP 1: initial (lazy) reloading + let r = ksm.get_key(&build_header("key-ed", Algorithm::EdDSA)).await; + assert!(r.is_ok()); + mock_server.verify().await; + + // STEP2: new kid -> reloading ksm + mock_server.reset().await; + mock_jwks_response_once( + &mock_server, + r#"{ + "kty": "OKP", + "use": "sig", + "crv": "Ed25519", + "x": "uWtSkE-I9aTMYTTvuTE1rtu0rNdxp3DU33cJ_ksL1Gk", + "kid": "key-ed02", + "alg": "EdDSA" + }"#, + ) + .await; + let h = build_header("key-ed02", Algorithm::EdDSA); + assert!(ksm.get_key(&h).await.is_err()); + + ksm.minimal_refresh_interval = Duration::from_millis(100); + tokio::time::sleep(Duration::from_millis(101)).await; + assert!(ksm.get_key(&h).await.is_ok()); + + mock_server.verify().await; + + // STEP3: new algorithm -> try to reload + mock_server.reset().await; + mock_jwks_response_once( + &mock_server, + r#"{ + "kty": "EC", + "crv": "P-256", + "x": "w7JAoU_gJbZJvV-zCOvU9yFJq0FNC_edCMRM78P8eQQ", + "y": "wQg1EytcsEmGrM70Gb53oluoDbVhCZ3Uq3hHMslHVb4", + "kid": "ec01", + "alg": "ES256", + "use": "sig" + }"#, + ) + .await; + let h = Header::new(Algorithm::ES256); + assert!(ksm.get_key(&h).await.is_err()); + + tokio::time::sleep(Duration::from_millis(101)).await; + assert!(ksm.get_key(&h).await.is_ok()); + + mock_server.verify().await; + } +} diff --git a/jwt-authorizer/src/jwks/mod.rs b/jwt-authorizer/src/jwks/mod.rs new file mode 100644 index 0000000..82d124c --- /dev/null +++ b/jwt-authorizer/src/jwks/mod.rs @@ -0,0 +1,24 @@ +use jsonwebtoken::{DecodingKey, Header}; + +use crate::error::AuthError; + +use self::key_store_manager::KeyStoreManager; + +pub mod key_store_manager; + +#[derive(Clone)] +pub enum KeySource { + KeyStoreSource(KeyStoreManager), + DecodingKeySource(DecodingKey), +} + +impl KeySource { + pub async fn get_key(&self, header: Header) -> Result { + match self { + KeySource::KeyStoreSource(kstore) => kstore.get_key(&header).await, + KeySource::DecodingKeySource(key) => { + Ok(key.clone()) // TODO: clone -> & + } + } + } +} diff --git a/jwt-authorizer/src/layer.rs b/jwt-authorizer/src/layer.rs new file mode 100644 index 0000000..5f3ffec --- /dev/null +++ b/jwt-authorizer/src/layer.rs @@ -0,0 +1,286 @@ +use axum::http::Request; +use axum::response::IntoResponse; +use axum::{body::Body, response::Response}; +use futures_core::ready; +use futures_util::future::BoxFuture; +use headers::authorization::Bearer; +use headers::{Authorization, HeaderMapExt}; +use http::StatusCode; +use pin_project::pin_project; +use serde::de::DeserializeOwned; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task::{Context, Poll}; +use tower_layer::Layer; +use tower_service::Service; + +use crate::authorizer::{Authorizer, FnClaimsChecker}; + +/// Authorizer Layer builder +/// +/// - initialisation of the Authorizer from jwks, rsa, ed, ec or secret +/// - can define a checker (jwt claims check) +pub struct JwtAuthorizer +where + C: Clone + DeserializeOwned, +{ + url: Option<&'static str>, + claims_checker: Option>, +} + +/// layer builder +impl JwtAuthorizer +where + C: Clone + DeserializeOwned + Send + Sync, +{ + pub fn new() -> Self { + JwtAuthorizer { + url: None, + claims_checker: None, + } + } + + pub fn from_jwks_url(mut self, url: &'static str) -> JwtAuthorizer { + self.url = Some(url); + + self + } + + pub fn from_rsa_pem(mut self, path: &'static str) -> JwtAuthorizer { + // TODO + self + } + + pub fn from_ec_der(mut self, path: &'static str) -> JwtAuthorizer { + // TODO + self + } + + pub fn from_ed_der(mut self, path: &'static str) -> JwtAuthorizer { + // TODO + self + } + + pub fn from_secret(mut self, path: &'static str) -> JwtAuthorizer { + // TODO + self + } + + /// layer that checks token validity and claim constraints (custom function) + pub fn with_check(mut self, checker_fn: fn(&C) -> bool) -> JwtAuthorizer { + self.claims_checker = Some(FnClaimsChecker { checker_fn }); + + self + } + + /// build axum layer + pub fn layer(&self) -> AsyncAuthorizationLayer { + // TODO: replace unwrap + let auth = Arc::new(Authorizer::from_jwks_url(self.url.unwrap(), self.claims_checker.clone()).unwrap()); + + AsyncAuthorizationLayer::new(auth) + } +} + +/// Trait for authorizing requests. +pub trait AsyncAuthorizer { + type RequestBody; + type ResponseBody; + type Future: Future, Response>>; + + /// Authorize the request. + /// + /// If the future resolves to `Ok(request)` then the request is allowed through, otherwise not. + fn authorize(&self, request: Request) -> Self::Future; +} + +impl AsyncAuthorizer for AsyncAuthorizationService +where + B: Send + Sync + 'static, + C: Clone + DeserializeOwned + Send + Sync + 'static, +{ + type RequestBody = B; + type ResponseBody = Body; + type Future = BoxFuture<'static, Result, Response>>; + + fn authorize(&self, mut request: Request) -> Self::Future { + let authorizer = self.auth.clone(); + let h = request.headers(); + let bearer: Authorization = h.typed_get().unwrap(); + Box::pin(async move { + if let Ok(token_data) = authorizer.check_auth(bearer.token()).await { + // Set `token_data` as a request extension so it can be accessed by other + // services down the stack. + request.extensions_mut().insert(token_data); + + Ok(request) + } else { + let unauthorized_response = Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .unwrap(); + + Err(unauthorized_response) + } + }) + } +} + +// -------------- Layer ----------------- + +#[derive(Clone)] +pub struct AsyncAuthorizationLayer +where + C: Clone + DeserializeOwned + Send, +{ + auth: Arc>, +} + +impl AsyncAuthorizationLayer +where + C: Clone + DeserializeOwned + Send, +{ + pub fn new(auth: Arc>) -> AsyncAuthorizationLayer { + Self { auth } + } +} + +impl Layer for AsyncAuthorizationLayer +where + C: Clone + DeserializeOwned + Send + Sync, +{ + type Service = AsyncAuthorizationService; + + fn layer(&self, inner: S) -> Self::Service { + AsyncAuthorizationService::new(inner, self.auth.clone()) + } +} + +// ---------- AsyncAuthorizationService -------- + +#[derive(Clone)] +pub struct AsyncAuthorizationService +where + C: Clone + DeserializeOwned + Send + Sync, +{ + pub inner: S, + pub auth: Arc>, +} + +impl AsyncAuthorizationService +where + C: Clone + DeserializeOwned + Send + Sync, +{ + pub fn get_ref(&self) -> &S { + &self.inner + } + + /// Gets a mutable reference to the underlying service. + pub fn get_mut(&mut self) -> &mut S { + &mut self.inner + } + + /// Consumes `self`, returning the underlying service. + pub fn into_inner(self) -> S { + self.inner + } +} + +impl AsyncAuthorizationService +where + C: Clone + DeserializeOwned + Send + Sync, +{ + /// Authorize requests using a custom scheme. + /// + /// The `Authorization` header is required to have the value provided. + pub fn new(inner: S, auth: Arc>) -> AsyncAuthorizationService { + Self { inner, auth } + } +} + +impl Service> for AsyncAuthorizationService +where + ReqBody: Send + Sync + 'static, + S: Service, Response = Response> + Clone, + C: Clone + DeserializeOwned + Send + Sync + 'static, +{ + type Response = S::Response; + type Error = S::Error; + type Future = ResponseFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: Request) -> Self::Future { + let inner = self.inner.clone(); + let auth_fut = self.authorize(req); + + ResponseFuture { + state: State::Authorize { auth_fut }, + service: inner, + } + } +} + +#[pin_project] +/// Response future for [`AsyncAuthorizationService`]. +pub struct ResponseFuture +where + S: Service, Response = Response>, + ReqBody: Send + Sync + 'static, + C: Clone + DeserializeOwned + Send + Sync + 'static, +{ + #[pin] + state: State< as AsyncAuthorizer>::Future, S::Future>, + service: S, +} + +#[pin_project(project = StateProj)] +enum State { + Authorize { + #[pin] + auth_fut: A, + }, + Authorized { + #[pin] + svc_fut: SFut, + }, +} + +impl Future for ResponseFuture +where + S: Service, Response = Response>, + ReqBody: Send + Sync + 'static, + C: Clone + DeserializeOwned + Send + Sync, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + loop { + match this.state.as_mut().project() { + StateProj::Authorize { auth_fut } => { + let auth = ready!(auth_fut.poll(cx)); + match auth { + Ok(req) => { + let svc_fut = this.service.call(req); + this.state.set(State::Authorized { svc_fut }) + } + Err(res) => { + tracing::info!("err: {:?}", res); + let r = (StatusCode::FORBIDDEN, format!("Unauthorized : {:?}", res)).into_response(); + // TODO: replace r by res (type problems: res should be already a 403 error response) + return Poll::Ready(Ok(r)); + } + }; + } + StateProj::Authorized { svc_fut } => { + return svc_fut.poll(cx); + } + } + } + } +} diff --git a/jwt-authorizer/src/lib.rs b/jwt-authorizer/src/lib.rs new file mode 100644 index 0000000..34b6c81 --- /dev/null +++ b/jwt-authorizer/src/lib.rs @@ -0,0 +1,34 @@ +#![doc = include_str!("../docs/README.md")] + +use axum::{async_trait, extract::FromRequestParts, http::request::Parts}; +use jsonwebtoken::TokenData; +use serde::de::DeserializeOwned; + +pub use self::error::AuthError; +pub use layer::JwtAuthorizer; + +pub mod authorizer; +pub mod error; +pub mod jwks; +pub mod layer; + +/// Claims serialized using T +#[derive(Debug, Clone, Copy, Default)] +pub struct JwtClaims(pub T); + +#[async_trait] +impl FromRequestParts for JwtClaims +where + T: DeserializeOwned + Send + Sync + Clone + 'static, + S: Send + Sync, +{ + type Rejection = error::AuthError; + + async fn from_request_parts(parts: &mut Parts, _: &S) -> Result { + let claims = parts.extensions.get::>().unwrap(); // TODO: unwrap -> err + Ok(JwtClaims(claims.claims.clone())) // TODO: unwrap -> err + } +} + +#[cfg(test)] +mod tests; diff --git a/jwt-authorizer/src/tests.rs b/jwt-authorizer/src/tests.rs new file mode 100644 index 0000000..ec521da --- /dev/null +++ b/jwt-authorizer/src/tests.rs @@ -0,0 +1 @@ +// TODO: tests diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..3ccd3c9 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width=120 \ No newline at end of file