diff --git a/Cargo.lock b/Cargo.lock index d914180f..e47f6e39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,12 +24,19 @@ dependencies = [ "agent_issuance", "agent_store", "axum", + "axum-auth", "axum-macros", "hyper", + "lazy_static", "mime", + "oid4vc-core", + "oid4vci", + "serde", "serde_json", "tokio", "tower", + "url", + "uuid", ] [[package]] @@ -40,33 +47,46 @@ dependencies = [ "agent_issuance", "agent_store", "axum", + "lazy_static", + "oid4vci", "serde_json", "tokio", + "url", ] [[package]] name = "agent_issuance" version = "0.1.0" dependencies = [ - "agent_store", "async-trait", "cqrs-es", - "identity_credential", + "derivative", + "did-key", "jsonschema", + "jsonwebtoken", + "lazy_static", + "oid4vc-core", + "oid4vc-manager", + "oid4vci", "serde", "serde_json", - "sqlx", + "thiserror", "tokio", + "url", + "uuid", ] [[package]] name = "agent_store" version = "0.1.0" dependencies = [ + "agent_issuance", + "async-trait", "config", "cqrs-es", "dotenvy", "postgres-es", + "serde_json", "sqlx", ] @@ -76,7 +96,7 @@ version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" dependencies = [ - "getrandom", + "getrandom 0.2.11", "once_cell", "version_check", ] @@ -88,7 +108,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.2.11", "once_cell", "serde", "version_check", @@ -110,6 +130,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.4" @@ -164,6 +199,12 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + [[package]] name = "async-trait" version = "0.1.74" @@ -184,6 +225,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -222,6 +272,18 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-auth" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42cdd03ddd244e9e2f5ceb7c6e1d6a123db07cdb8570250392459ef635a016" +dependencies = [ + "async-trait", + "axum-core", + "base64 0.13.1", + "http", +] + [[package]] name = "axum-core" version = "0.3.4" @@ -272,6 +334,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base16ct" version = "0.2.0" @@ -290,6 +358,15 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64-url" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5b0a88aa36e9f095ee2e2b13fb8c5e4313e022783aedacc123328c0084916d" +dependencies = [ + "base64 0.21.5", +] + [[package]] name = "base64ct" version = "1.6.0" @@ -326,6 +403,27 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -335,6 +433,29 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bls12_381_plus" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c681aa947677ec0c5ccfa6f14c0dd039ddbaa7b12952bf146bd5226a5f9880" +dependencies = [ + "digest 0.9.0", + "ff 0.12.1", + "group 0.12.1", + "heapless", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + [[package]] name = "bumpalo" version = "3.14.0" @@ -374,6 +495,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "clap" version = "4.4.8" @@ -422,9 +558,9 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "config" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +checksum = "23738e11972c7643e4ec947840fc463b6a571afcd3e735bdfce7d03c7a784aca" dependencies = [ "async-trait", "json5", @@ -445,6 +581,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -498,6 +640,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -517,6 +665,24 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -539,6 +705,26 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -580,17 +766,52 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.39", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.39", +] + [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "data-encoding-macro" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" +checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -598,14 +819,24 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" +checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" dependencies = [ "data-encoding", "syn 1.0.109", ] +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der" version = "0.7.8" @@ -627,6 +858,54 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "did-key" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed21f9ed50f9d3f79b6ba84f2cf8a536399c3500bc99406c1bbc1e0c598524e" +dependencies = [ + "arrayref", + "base64 0.13.1", + "bls12_381_plus", + "bs58", + "curve25519-dalek 3.2.0", + "did_url", + "ed25519-dalek", + "getrandom 0.2.11", + "hkdf 0.11.0", + "json-patch", + "libsecp256k1", + "p256", + "serde", + "serde_json", + "sha2 0.9.9", + "x25519-dalek", +] + [[package]] name = "did_url" version = "0.1.0" @@ -637,6 +916,20 @@ dependencies = [ "serde", ] +[[package]] +name = "dif-presentation-exchange" +version = "0.1.0" +source = "git+https://git@github.com/impierce/openid4vc.git?branch=feat/refactor-request#68f45f65316eacf8847ee35da82957d33757957c" +dependencies = [ + "getset", + "jsonpath_lib", + "jsonschema", + "serde", + "serde_json", + "serde_with 3.4.0", + "url", +] + [[package]] name = "digest" version = "0.9.0" @@ -652,7 +945,7 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", "const-oid", "crypto-common", "subtle", @@ -670,18 +963,39 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der", + "der 0.7.8", "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "elliptic-curve 0.13.8", + "rfc6979 0.4.0", + "signature 2.2.0", + "spki 0.7.2", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", ] [[package]] @@ -690,7 +1004,21 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "signature", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", ] [[package]] @@ -700,11 +1028,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ "curve25519-dalek 4.1.1", - "ed25519", + "ed25519 2.2.3", "hashbrown 0.14.2", "hex", "rand_core 0.6.4", - "sha2", + "sha2 0.10.8", "zeroize", ] @@ -717,21 +1045,42 @@ dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array", + "group 0.12.1", + "hkdf 0.12.3", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle", + "zeroize", +] + [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct", - "crypto-bigint", + "base16ct 0.2.0", + "crypto-bigint 0.5.5", "digest 0.10.7", - "ff", + "ff 0.13.0", "generic-array", - "group", - "pkcs8", + "group 0.13.0", + "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.3", "subtle", "zeroize", ] @@ -794,6 +1143,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "ff" version = "0.13.0" @@ -835,9 +1195,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] @@ -853,8 +1213,14 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.29" +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ @@ -902,7 +1268,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.12.1", ] [[package]] @@ -963,6 +1329,17 @@ dependencies = [ "zeroize", ] +[[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 = "getrandom" version = "0.2.11" @@ -972,23 +1349,46 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "gimli" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff", + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] @@ -1005,13 +1405,22 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.1.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1040,6 +1449,19 @@ dependencies = [ "hashbrown 0.14.2", ] +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -1061,13 +1483,43 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + [[package]] name = "hkdf" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.0", + "digest 0.9.0", ] [[package]] @@ -1079,6 +1531,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + [[package]] name = "home" version = "0.5.5" @@ -1110,6 +1573,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.8.0" @@ -1146,10 +1615,54 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "identity_core" version = "1.0.0" -source = "git+https://github.com/iotaledger/identity.rs.git#b96fd9a234119d9744ede2e3bb0dfeb68ef1d319" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aee1810a89ab737d3a4dd660867f4a4d2ef5f57c18bb4afba578254a8c9b4b3" dependencies = [ "iota-crypto", "js-sys", @@ -1166,15 +1679,18 @@ dependencies = [ [[package]] name = "identity_credential" version = "1.0.0" -source = "git+https://github.com/iotaledger/identity.rs.git#b96fd9a234119d9744ede2e3bb0dfeb68ef1d319" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd5f343f5d67ba5398a4ab087c6981b0a869ddc1e63b6b59b5e36d0c9c38e1c" dependencies = [ "identity_core", "identity_did", "identity_document", "identity_verification", - "indexmap", + "indexmap 2.1.0", + "itertools", "once_cell", "serde", + "serde_repr", "strum", "thiserror", "url", @@ -1183,7 +1699,8 @@ dependencies = [ [[package]] name = "identity_did" version = "1.0.0" -source = "git+https://github.com/iotaledger/identity.rs.git#b96fd9a234119d9744ede2e3bb0dfeb68ef1d319" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70a1b3b93ec45f45239964fc484764ff252055a9ef00db90c89a4bd80e60761" dependencies = [ "did_url", "form_urlencoded", @@ -1196,13 +1713,14 @@ dependencies = [ [[package]] name = "identity_document" version = "1.0.0" -source = "git+https://github.com/iotaledger/identity.rs.git#b96fd9a234119d9744ede2e3bb0dfeb68ef1d319" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6095569f56c25e47cc12ffd17ec580fe44d71a7aaa52471d2c8aa89f6a38407a" dependencies = [ "did_url", "identity_core", "identity_did", "identity_verification", - "indexmap", + "indexmap 2.1.0", "serde", "strum", "thiserror", @@ -1211,7 +1729,8 @@ dependencies = [ [[package]] name = "identity_jose" version = "1.0.0" -source = "git+https://github.com/iotaledger/identity.rs.git#b96fd9a234119d9744ede2e3bb0dfeb68ef1d319" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8890438d085777f7370032bb805d5db98ebec1291bb2e04e4af2a8dc4e46224" dependencies = [ "identity_core", "iota-crypto", @@ -1225,7 +1744,8 @@ dependencies = [ [[package]] name = "identity_verification" version = "1.0.0" -source = "git+https://github.com/iotaledger/identity.rs.git#b96fd9a234119d9744ede2e3bb0dfeb68ef1d319" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87a47637a711e8ba9dea4939c92f622464b11563b6a511923fa6ce1e68c9153c" dependencies = [ "identity_core", "identity_did", @@ -1237,14 +1757,25 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -1256,6 +1787,18 @@ dependencies = [ "serde", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "iota-crypto" version = "0.23.0" @@ -1266,11 +1809,11 @@ dependencies = [ "curve25519-dalek 3.2.0", "digest 0.10.7", "ed25519-zebra", - "getrandom", + "getrandom 0.2.11", "k256", - "rand", + "rand 0.8.5", "serde", - "sha2", + "sha2 0.10.8", "x25519-dalek", "zeroize", ] @@ -1281,6 +1824,26 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "is_empty" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a936154b7f653894729875d5593b3251d90619c90994581dceec44334df4021" +dependencies = [ + "is_empty_derive", +] + +[[package]] +name = "is_empty_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8903009eceffe882e7cb6adadd29001f4f0e46f67616247f40841a5604f1610" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "iso8601" version = "0.6.1" @@ -1314,6 +1877,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json-patch" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + [[package]] name = "json5" version = "0.4.1" @@ -1325,6 +1899,17 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + [[package]] name = "jsonschema" version = "0.17.1" @@ -1338,13 +1923,13 @@ dependencies = [ "clap", "fancy-regex", "fraction", - "getrandom", + "getrandom 0.2.11", "iso8601", "itoa", "memchr", "num-cmp", "once_cell", - "parking_lot", + "parking_lot 0.12.1", "percent-encoding", "regex", "reqwest", @@ -1355,6 +1940,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.5", + "pem", + "ring 0.16.20", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" version = "0.13.2" @@ -1362,10 +1961,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", "once_cell", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -1389,6 +1988,54 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libsqlite3-sys" version = "0.26.0" @@ -1456,6 +2103,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1478,7 +2135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys", ] @@ -1540,7 +2197,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1622,12 +2279,119 @@ dependencies = [ "memchr", ] +[[package]] +name = "oid4vc-core" +version = "0.1.0" +source = "git+https://git@github.com/impierce/openid4vc.git?branch=feat/refactor-request#68f45f65316eacf8847ee35da82957d33757957c" +dependencies = [ + "anyhow", + "async-trait", + "base64-url", + "derive_more", + "did_url", + "futures", + "getset", + "is_empty", + "jsonwebtoken", + "rand 0.8.5", + "reqwest-middleware", + "serde", + "serde_json", + "serde_urlencoded", + "serde_with 2.3.3", + "url", +] + +[[package]] +name = "oid4vc-manager" +version = "0.1.0" +source = "git+https://git@github.com/impierce/openid4vc.git?branch=feat/refactor-request#68f45f65316eacf8847ee35da82957d33757957c" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "axum-auth", + "chrono", + "did-key", + "did_url", + "futures", + "getset", + "identity_core", + "identity_credential", + "jsonwebtoken", + "oid4vc-core", + "oid4vci", + "oid4vp", + "paste", + "reqwest", + "serde", + "serde_json", + "serde_urlencoded", + "serde_with 3.4.0", + "siopv2", + "tokio", + "tower-http", + "url", +] + +[[package]] +name = "oid4vci" +version = "0.1.0" +source = "git+https://git@github.com/impierce/openid4vc.git?branch=feat/refactor-request#68f45f65316eacf8847ee35da82957d33757957c" +dependencies = [ + "anyhow", + "derivative", + "dif-presentation-exchange", + "getset", + "jsonwebtoken", + "lazy_static", + "oid4vc-core", + "paste", + "reqwest", + "reqwest-middleware", + "reqwest-retry", + "serde", + "serde_json", + "serde_urlencoded", + "serde_with 3.4.0", + "tokio", +] + +[[package]] +name = "oid4vp" +version = "0.1.0" +source = "git+https://git@github.com/impierce/openid4vc.git?branch=feat/refactor-request#68f45f65316eacf8847ee35da82957d33757957c" +dependencies = [ + "anyhow", + "chrono", + "dif-presentation-exchange", + "futures", + "getset", + "identity_core", + "identity_credential", + "is_empty", + "jsonwebtoken", + "oid4vc-core", + "oid4vci", + "serde", + "serde_json", + "serde_with 3.4.0", + "tokio", + "url", +] + [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -1638,6 +2402,37 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", + "sha2 0.10.8", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1645,7 +2440,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1656,7 +2465,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "smallvec", "windows-targets", ] @@ -1673,6 +2482,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1684,9 +2502,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" @@ -1730,7 +2548,7 @@ checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" dependencies = [ "once_cell", "pest", - "sha2", + "sha2 0.10.8", ] [[package]] @@ -1771,9 +2589,19 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.8", + "pkcs8 0.10.2", + "spki 0.7.2", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", ] [[package]] @@ -1782,8 +2610,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.8", + "spki 0.7.2", ] [[package]] @@ -1825,6 +2653,30 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -1843,6 +2695,25 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + [[package]] name = "rand" version = "0.8.5" @@ -1850,10 +2721,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[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 0.5.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -1869,6 +2750,9 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] [[package]] name = "rand_core" @@ -1876,7 +2760,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.11", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", ] [[package]] @@ -1932,36 +2834,117 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", + "mime_guess", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots 0.25.3", "winreg", ] +[[package]] +name = "reqwest-middleware" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a3e86aa6053e59030e7ce2d2a3b258dd08fc2d337d52f73f6cb480f5858690" +dependencies = [ + "anyhow", + "async-trait", + "http", + "reqwest", + "serde", + "task-local-extensions", + "thiserror", +] + +[[package]] +name = "reqwest-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af20b65c2ee9746cc575acb6bd28a05ffc0d15e25c992a8f4462d8686aacb4f" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "futures", + "getrandom 0.2.11", + "http", + "hyper", + "parking_lot 0.11.2", + "reqwest", + "reqwest-middleware", + "retry-policies", + "task-local-extensions", + "tokio", + "tracing", + "wasm-timer", +] + +[[package]] +name = "retry-policies" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17dd00bff1d737c40dbcd47d4375281bf4c17933f9eef0a185fc7bacca23ecbd" +dependencies = [ + "anyhow", + "chrono", + "rand 0.8.5", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac 0.12.1", + "zeroize", +] + [[package]] name = "rfc6979" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] +[[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 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + [[package]] name = "ring" version = "0.17.5" @@ -1969,10 +2952,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ "cc", - "getrandom", + "getrandom 0.2.11", "libc", "spin 0.9.8", - "untrusted", + "untrusted 0.9.0", "windows-sys", ] @@ -1989,9 +2972,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ef35bf3e7fe15a53c4ab08a998e42271eab13eb0db224126bc7bc4c4bad96d" +checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f" dependencies = [ "const-oid", "digest 0.10.7", @@ -1999,10 +2982,10 @@ dependencies = [ "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.2", "subtle", "zeroize", ] @@ -2051,7 +3034,8 @@ version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ - "ring", + "log", + "ring 0.17.5", "rustls-webpki", "sct", ] @@ -2071,8 +3055,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -2099,8 +3083,22 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct 0.1.1", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", ] [[package]] @@ -2109,10 +3107,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct", - "der", + "base16ct 0.2.0", + "der 0.7.8", "generic-array", - "pkcs8", + "pkcs8 0.10.2", "subtle", "zeroize", ] @@ -2125,18 +3123,18 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -2149,6 +3147,7 @@ version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -2164,6 +3163,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2176,6 +3186,63 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "serde_with_macros 2.3.3", + "time", +] + +[[package]] +name = "serde_with" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23" +dependencies = [ + "base64 0.21.5", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.1.0", + "serde", + "serde_json", + "serde_with_macros 3.4.0", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_with_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2187,6 +3254,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2207,6 +3287,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "signature" version = "2.2.0" @@ -2217,6 +3307,47 @@ dependencies = [ "rand_core 0.6.4", ] +[[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 = "siopv2" +version = "0.1.0" +source = "git+https://git@github.com/impierce/openid4vc.git?branch=feat/refactor-request#68f45f65316eacf8847ee35da82957d33757957c" +dependencies = [ + "anyhow", + "async-trait", + "base64-url", + "chrono", + "did_url", + "futures", + "getset", + "identity_credential", + "is_empty", + "jsonwebtoken", + "oid4vc-core", + "oid4vci", + "oid4vp", + "reqwest", + "reqwest-middleware", + "reqwest-retry", + "serde", + "serde_json", + "serde_urlencoded", + "serde_with 3.4.0", + "tokio", + "url", +] + [[package]] name = "slab" version = "0.4.9" @@ -2267,6 +3398,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + [[package]] name = "spki" version = "0.7.2" @@ -2274,7 +3415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" dependencies = [ "base64ct", - "der", + "der 0.7.8", ] [[package]] @@ -2323,7 +3464,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap", + "indexmap 2.1.0", "log", "memchr", "once_cell", @@ -2333,7 +3474,7 @@ dependencies = [ "rustls-pemfile", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "smallvec", "sqlformat", "thiserror", @@ -2341,7 +3482,7 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots", + "webpki-roots 0.24.0", ] [[package]] @@ -2372,7 +3513,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -2404,19 +3545,19 @@ dependencies = [ "futures-util", "generic-array", "hex", - "hkdf", - "hmac", + "hkdf 0.12.3", + "hmac 0.12.1", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", - "sha2", + "sha2 0.10.8", "smallvec", "sqlx-core", "stringprep", @@ -2443,19 +3584,19 @@ dependencies = [ "futures-io", "futures-util", "hex", - "hkdf", - "hmac", + "hkdf 0.12.3", + "hmac 0.12.1", "home", "itoa", "log", "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha1", - "sha2", + "sha2 0.10.8", "smallvec", "sqlx-core", "stringprep", @@ -2486,6 +3627,12 @@ dependencies = [ "url", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stringprep" version = "0.1.4" @@ -2580,6 +3727,21 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "task-local-extensions" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" +dependencies = [ + "pin-utils", +] + [[package]] name = "tempfile" version = "3.8.1" @@ -2588,7 +3750,7 @@ checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.4.1", "rustix", "windows-sys", ] @@ -2668,7 +3830,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot", + "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", "socket2 0.5.5", @@ -2687,6 +3849,16 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -2737,6 +3909,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" @@ -2781,6 +3971,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -2799,6 +3998,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2832,6 +4040,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -2840,9 +4054,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -2858,9 +4072,14 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58fe91d841bc04822c9801002db4ea904b9e4b8e6bbad25127b46eff8dc516b" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +dependencies = [ + "getrandom 0.2.11", + "rand 0.8.5", + "serde", +] [[package]] name = "vcpkg" @@ -2883,6 +4102,12 @@ dependencies = [ "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" @@ -2955,6 +4180,21 @@ version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +[[package]] +name = "wasm-timer" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.11.2", + "pin-utils", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.65" @@ -2974,6 +4214,12 @@ dependencies = [ "rustls-webpki", ] +[[package]] +name = "webpki-roots" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" + [[package]] name = "whoami" version = "1.4.1" @@ -3002,6 +4248,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3078,6 +4333,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "1.1.1" diff --git a/Cargo.toml b/Cargo.toml index d18b55cd..d8a8d3da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,9 @@ members = [ ] [workspace.dependencies] +lazy_static = "1.4" +uuid = { version = "1.4", features = ["v4", "fast-rng", "serde"] } serde = { version = "1.0", default-features = false, features = ["derive"] } serde_json = { version = "1.0" } +thiserror = "1.0" +url = "2.5" diff --git a/agent_api_rest/Cargo.toml b/agent_api_rest/Cargo.toml index afed3827..4344310f 100644 --- a/agent_api_rest/Cargo.toml +++ b/agent_api_rest/Cargo.toml @@ -5,13 +5,24 @@ edition = "2021" [dependencies] agent_issuance = { path = "../agent_issuance" } -agent_store = { path = "../agent_store" } + +oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } +oid4vc-core = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } + axum = "0.6" +axum-auth = "0.4" axum-macros = "0.3" -serde_json = "1.0" +hyper = { version = "0.14", features = ["full"] } +serde.workspace = true +serde_json.workspace = true +uuid.workspace = true [dev-dependencies] -hyper = { version = "0.14", features = ["full"] } +agent_store = { path = "../agent_store" } + +lazy_static.workspace = true mime = { version = "0.3" } tokio = { version = "1.34", features = ["full"] } tower = { version = "0.4" } +url.workspace = true + diff --git a/agent_api_rest/postman/ssi-agent.postman_collection.json b/agent_api_rest/postman/ssi-agent.postman_collection.json new file mode 100644 index 00000000..5bd04071 --- /dev/null +++ b/agent_api_rest/postman/ssi-agent.postman_collection.json @@ -0,0 +1,251 @@ +{ + "info": { + "_postman_id": "53b46e18-de7f-4973-8304-8238844a71ce", + "name": "ssi-agent", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "24972330" + }, + "item": [ + { + "name": "Issuance", + "item": [ + { + "name": "subjects", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const jsonData = JSON.parse(responseBody);", + "", + "const subjectId = jsonData?.id;", + "", + "if(subjectId){", + " pm.collectionVariables.set(\"subjectId\",subjectId)", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"value\": \"{{preAuthorizedCode}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "http://{{host}}/v1/subjects" + }, + "response": [] + }, + { + "name": "credentials", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"subjectId\": \"{{subjectId}}\",\n \"credential\": {\n \"credentialSubject\": {\n \"id\": {},\n \"type\": \"AchievementSubject\",\n \"achievement\": {\n \"id\": \"https://example.com/achievements/21st-century-skills/teamwork\",\n \"type\": \"Achievement\",\n \"criteria\": {\n \"narrative\": \"Team members are nominated for this badge by their peers and recognized upon review by Example Corp management.\"\n },\n \"description\": \"This badge recognizes the development of the capacity to collaborate within a group environment.\",\n \"name\": \"Teamwork\"\n }\n }\n }\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "http://{{host}}/v1/credentials" + }, + "response": [] + }, + { + "name": "offers", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"subjectId\": \"{{subjectId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "http://{{host}}/v1/offers" + }, + "response": [] + } + ] + }, + { + "name": "oid4vci", + "item": [ + { + "name": "oauth-authorization-server", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const jsonData = JSON.parse(responseBody);", + "", + "const issuer = jsonData?.issuer;", + "const tokenEndpoint = jsonData?.token_endpoint;", + "", + "if(issuer){", + " pm.collectionVariables.set(\"issuer\",issuer)", + "}", + "", + "if(tokenEndpoint){", + " pm.collectionVariables.set(\"tokenEndpoint\",tokenEndpoint)", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": "http://{{host}}/.well-known/oauth-authorization-server" + }, + "response": [] + }, + { + "name": "openid-credential-issuer", + "request": { + "method": "GET", + "header": [], + "url": "http://{{host}}/.well-known/openid-credential-issuer" + }, + "response": [] + }, + { + "name": "token", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "const jsonData = JSON.parse(responseBody);", + "", + "const accessToken = jsonData?.access_token;", + "", + "if(accessToken){", + " pm.collectionVariables.set(\"accessToken\",accessToken)", + "}", + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "grant_type", + "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code", + "type": "text" + }, + { + "key": "pre-authorized_code", + "value": "{{preAuthorizedCode}}", + "type": "text" + } + ] + }, + "url": "http://{{host}}/v1/oauth/token" + }, + "response": [] + }, + { + "name": "credential", + "request": { + "method": "POST", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{accessToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"format\": \"jwt_vc_json\",\n \"credential_definition\": {\n \"type\": [\n \"VerifiableCredential\",\n \"OpenBadgeCredential\"\n ]\n },\n \"proof\": {\n \"proof_type\": \"jwt\",\n \"jwt\": \"eyJ0eXAiOiJvcGVuaWQ0dmNpLXByb29mK2p3dCIsImFsZyI6IkVkRFNBIiwia2lkIjoiZGlkOmtleTp6Nk1rdWlSS3ExZktyekFYZVNOaUd3cnBKUFB1Z1k4QXhKWUE1Y3BDdlpDWUJEN0IjejZNa3VpUktxMWZLcnpBWGVTTmlHd3JwSlBQdWdZOEF4SllBNWNwQ3ZaQ1lCRDdCIn0.eyJpc3MiOiJkaWQ6a2V5Ono2TWt1aVJLcTFmS3J6QVhlU05pR3dycEpQUHVnWThBeEpZQTVjcEN2WkNZQkQ3QiIsImF1ZCI6Imh0dHA6Ly8xOTIuMTY4LjEuMTI3OjMwMzMvIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjE1NzEzMjQ4MDAsIm5vbmNlIjoidW5zYWZlX2Nfbm9uY2UifQ.wR2e4VUnVjG6IK9cntcqvc_8KEJQUd3SEjsPZwDYDlYqijZ4ZaQLxyHtzNmLkIS3FpChLrZrcvIUJrZxrWcKAg\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "http://{{host}}/v1/openid4vci/credential" + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "host", + "value": "INITIAL_VALUE", + "type": "string" + }, + { + "key": "preAuthorizedCode", + "value": "unique_subject_string", + "type": "string" + }, + { + "key": "subjectId", + "value": "INITIAL_VALUE", + "type": "string" + }, + { + "key": "issuer", + "value": "INITIAL_VALUE", + "type": "string" + }, + { + "key": "tokenEndpoint", + "value": "INITIAL_VALUE", + "type": "string" + }, + { + "key": "accessToken", + "value": "INITIAL_VALUE", + "type": "string" + } + ] +} \ No newline at end of file diff --git a/agent_api_rest/src/credential_issuer/credential.rs b/agent_api_rest/src/credential_issuer/credential.rs new file mode 100644 index 00000000..c43cbc14 --- /dev/null +++ b/agent_api_rest/src/credential_issuer/credential.rs @@ -0,0 +1,56 @@ +use agent_issuance::{ + command::IssuanceCommand, + handlers::{command_handler, query_handler}, + model::aggregate::IssuanceData, + queries::IssuanceDataView, + state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, +}; +use axum_auth::AuthBearer; +use oid4vci::credential_request::CredentialRequest; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn credential( + State(state): State>, + AuthBearer(access_token): AuthBearer, + Json(credential_request): Json, +) -> impl IntoResponse { + let command = IssuanceCommand::CreateCredentialResponse { + access_token: access_token.clone(), + credential_request, + }; + + match command_handler(AGGREGATE_ID.to_string(), &state, command).await { + Ok(_) => StatusCode::NO_CONTENT.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + }; + + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) => { + // TODO: This is a non-idiomatic way of finding the subject by using the access token. We should use a aggregate/query instead. + let subject = view + .subjects + .iter() + .find(|subject| subject.token_response.as_ref().unwrap().access_token == access_token); + if let Some(subject) = subject { + (StatusCode::OK, Json(subject.credential_response.clone())).into_response() + } else { + StatusCode::NOT_FOUND.into_response() + } + } + Ok(None) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} diff --git a/agent_api_rest/src/credential_issuer/mod.rs b/agent_api_rest/src/credential_issuer/mod.rs new file mode 100644 index 00000000..df6bc6fe --- /dev/null +++ b/agent_api_rest/src/credential_issuer/mod.rs @@ -0,0 +1,3 @@ +pub mod credential; +pub mod token; +pub mod well_known; diff --git a/agent_api_rest/src/credential_issuer/token.rs b/agent_api_rest/src/credential_issuer/token.rs new file mode 100644 index 00000000..c05a7e39 --- /dev/null +++ b/agent_api_rest/src/credential_issuer/token.rs @@ -0,0 +1,58 @@ +use agent_issuance::{ + command::IssuanceCommand, + handlers::{command_handler, query_handler}, + model::aggregate::IssuanceData, + queries::IssuanceDataView, + state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, + Form, +}; +use oid4vci::token_request::TokenRequest; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn token( + State(state): State>, + Form(token_request): Form, +) -> impl IntoResponse { + let pre_authorized_code = match token_request.clone() { + TokenRequest::PreAuthorizedCode { + pre_authorized_code, .. + } => pre_authorized_code, + _ => return StatusCode::BAD_REQUEST.into_response(), + }; + let command = IssuanceCommand::CreateTokenResponse { token_request }; + + match command_handler(AGGREGATE_ID.to_string(), &state, command).await { + Ok(_) => StatusCode::NO_CONTENT.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + }; + + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) => { + // TODO: This is a non-idiomatic way of finding the subject by using the pre-authorized_code in the token_request. We should use a aggregate/query instead. + let subject = view + .subjects + .iter() + .find(|subject| subject.pre_authorized_code == pre_authorized_code); + if let Some(subject) = subject { + (StatusCode::OK, Json(subject.token_response.clone())).into_response() + } else { + StatusCode::NOT_FOUND.into_response() + } + } + Ok(None) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} diff --git a/agent_api_rest/src/credential_issuer/well_known/mod.rs b/agent_api_rest/src/credential_issuer/well_known/mod.rs new file mode 100644 index 00000000..3e7935ad --- /dev/null +++ b/agent_api_rest/src/credential_issuer/well_known/mod.rs @@ -0,0 +1,2 @@ +pub mod oauth_authorization_server; +pub mod openid_credential_issuer; diff --git a/agent_api_rest/src/credential_issuer/well_known/oauth_authorization_server.rs b/agent_api_rest/src/credential_issuer/well_known/oauth_authorization_server.rs new file mode 100644 index 00000000..03eb3616 --- /dev/null +++ b/agent_api_rest/src/credential_issuer/well_known/oauth_authorization_server.rs @@ -0,0 +1,26 @@ +use agent_issuance::{ + handlers::query_handler, model::aggregate::IssuanceData, queries::IssuanceDataView, state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, +}; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn oauth_authorization_server( + State(state): State>, +) -> impl IntoResponse { + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) if view.oid4vci_data.authorization_server_metadata.is_some() => { + (StatusCode::OK, Json(view.oid4vci_data.authorization_server_metadata)).into_response() + } + Ok(_) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} diff --git a/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs b/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs new file mode 100644 index 00000000..3ca8f14c --- /dev/null +++ b/agent_api_rest/src/credential_issuer/well_known/openid_credential_issuer.rs @@ -0,0 +1,26 @@ +use agent_issuance::{ + handlers::query_handler, model::aggregate::IssuanceData, queries::IssuanceDataView, state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, +}; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn openid_credential_issuer( + State(state): State>, +) -> impl IntoResponse { + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) if view.oid4vci_data.credential_issuer_metadata.is_some() => { + (StatusCode::OK, Json(view.oid4vci_data.credential_issuer_metadata)).into_response() + } + Ok(_) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} diff --git a/agent_api_rest/src/credentials.rs b/agent_api_rest/src/credentials.rs new file mode 100644 index 00000000..136d500b --- /dev/null +++ b/agent_api_rest/src/credentials.rs @@ -0,0 +1,150 @@ +use agent_issuance::{ + command::IssuanceCommand, handlers::query_handler, model::aggregate::IssuanceData, + model::command_handler_without_id, queries::IssuanceDataView, state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, +}; +use hyper::header; +use serde_json::Value; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn credentials( + State(state): State>, + Json(payload): Json, +) -> impl IntoResponse { + // TODO: This should be removed once we know how to use aggregate ID's. + let subject_id: uuid::Uuid = payload["subjectId"].as_str().unwrap().parse().unwrap(); + let command = IssuanceCommand::CreateUnsignedCredential { + subject_id: subject_id.clone(), + credential: payload["credential"].clone(), + }; + + match command_handler_without_id(&state, command).await { + Ok(_) => {} + Err(err) => { + println!("Error: {:#?}\n", err); + return (StatusCode::BAD_REQUEST, err.to_string()).into_response(); + } + }; + + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) => { + match view.subjects.iter().find_map(|subject| { + (subject.id == subject_id) + .then(|| { + subject + .credentials + .as_ref() + .map(|credential| credential.unsigned_credential.clone()) + }) + .flatten() + }) { + Some(unsigned_credential) => ( + StatusCode::CREATED, + [(header::LOCATION, format!("/v1/credentials/{}", AGGREGATE_ID))], + Json(unsigned_credential), + ) + .into_response(), + None => StatusCode::NOT_FOUND.into_response(), + } + } + Ok(None) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + app, + tests::{create_subject, load_credential_format_template}, + }; + + use super::*; + use agent_issuance::services::IssuanceServices; + use agent_store::in_memory; + use axum::{ + body::Body, + http::{self, Request}, + }; + use serde_json::json; + use std::sync::Arc; + use tower::ServiceExt; + + #[tokio::test] + async fn test_credentials_endpoint() { + let state = Arc::new(in_memory::ApplicationState::new(vec![], IssuanceServices {}).await) + as ApplicationState; + + load_credential_format_template(state.clone()).await; + let subject_id = create_subject(state.clone()).await; + + let app = app(state); + + let response = app + .oneshot( + Request::builder() + .method(http::Method::POST) + .uri("/v1/credentials") + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) + .body(Body::from( + serde_json::to_vec(&json!({ + "subjectId": subject_id, + "credential": {"credentialSubject": { + "first_name": "Ferris", + "last_name": "Rustacean", + }}, + })) + .unwrap(), + )) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::CREATED); + + assert_eq!( + response.headers().get(http::header::LOCATION).unwrap(), + "/v1/credentials/agg-id-F39A0C" + ); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let body: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!( + body, + serde_json::from_str::( + r#" + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" + ], + "id": "http://example.com/credentials/3527", + "type": ["VerifiableCredential", "OpenBadgeCredential"], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": "Profile", + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "first_name": "Ferris", + "last_name": "Rustacean" + } + } + "# + ) + .unwrap() + ); + } +} diff --git a/agent_api_rest/src/lib.rs b/agent_api_rest/src/lib.rs index 1fe7a2bd..141e42d5 100644 --- a/agent_api_rest/src/lib.rs +++ b/agent_api_rest/src/lib.rs @@ -1,123 +1,131 @@ -use agent_issuance::{ - command::IssuanceCommand, - model::{aggregate::Credential, create_credential}, - queries::CredentialView, -}; -use agent_store::state::ApplicationState; +mod credential_issuer; +mod credentials; +mod offers; +mod subjects; + +use agent_issuance::{model::aggregate::IssuanceData, queries::IssuanceDataView, state::ApplicationState}; use axum::{ - extract::{Json, State}, - http::{header, StatusCode}, - response::IntoResponse, - routing::post, + routing::{get, post}, Router, }; -use serde_json::{json, Value}; - -pub fn app(state: ApplicationState) -> Router { - Router::new() - .route("/v1/credentials", post(create_credential_data)) - .with_state(state) -} +use credential_issuer::{ + credential::credential, + token::token, + well_known::{ + oauth_authorization_server::oauth_authorization_server, openid_credential_issuer::openid_credential_issuer, + }, +}; +use credentials::credentials; +use offers::offers; +use subjects::subjects; -#[axum_macros::debug_handler] -async fn create_credential_data( - State(state): State>, - Json(payload): Json, -) -> impl IntoResponse { - let command = IssuanceCommand::CreateCredentialData { - credential_subject: payload, - }; +// TODO: What to do with aggregate_id's? +pub const AGGREGATE_ID: &str = "agg-id-F39A0C"; - match create_credential(state, command).await { - Ok(_) => ( - StatusCode::CREATED, - [(header::LOCATION, format!("/v1/credentials/{}", "agg-id-F39A0C"))], - Json(json!({})), +pub fn app(state: ApplicationState) -> Router { + Router::new() + .route("/v1/subjects", post(subjects)) + .route("/v1/credentials", post(credentials)) + .route("/v1/offers", post(offers)) + .route( + "/.well-known/oauth-authorization-server", + get(oauth_authorization_server), ) - .into_response(), - Err(err) => { - println!("Error: {:#?}\n", err); - (StatusCode::BAD_REQUEST, err.to_string()).into_response() - } - } + .route("/.well-known/openid-credential-issuer", get(openid_credential_issuer)) + .route("/v1/oauth/token", post(token)) + .route("/v1/openid4vci/credential", post(credential)) + .with_state(state) } #[cfg(test)] mod tests { - use std::{fs::File, path::Path}; - use super::*; - use agent_issuance::state::new_application_state; - use axum::{ - body::Body, - http::{self, Request}, + use agent_issuance::command::IssuanceCommand; + use oid4vci::credential_issuer::{ + authorization_server_metadata::AuthorizationServerMetadata, + credential_issuer_metadata::CredentialIssuerMetadata, }; - use serde_json::json; - use tower::ServiceExt; - #[tokio::test] - async fn location_header_is_set_on_successful_creation() { - let state = new_application_state().await; - let app = app(state); + pub const PRE_AUTHORIZED_CODE: &str = "pre-authorized_code"; + pub const SUBJECT_ID: &str = "00000000-0000-0000-0000-000000000000"; + lazy_static::lazy_static! { + pub static ref BASE_URL: url::Url = url::Url::parse("https://example.com").unwrap(); + } - let response = app - .oneshot( - Request::builder() - .method(http::Method::POST) - .uri("/v1/credentials") - .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) - .body(Body::from( - serde_json::to_vec(&json!({ - "first_name": "Ferris", - "last_name": "Rustacean", - })) - .unwrap(), + pub async fn load_credential_format_template(state: ApplicationState) { + state + .execute_with_metadata( + AGGREGATE_ID, + IssuanceCommand::LoadCredentialFormatTemplate { + credential_format_template: serde_json::from_str(include_str!( + "../../agent_issuance/res/credential_format_templates/openbadges_v3.json" )) .unwrap(), + }, + Default::default(), ) .await .unwrap(); + } - assert_eq!(response.status(), StatusCode::CREATED); - - assert_eq!( - response.headers().get(http::header::LOCATION).unwrap(), - "/v1/credentials/agg-id-F39A0C" - ); + // TODO: actually use this for .well-known tests. + pub async fn _load_authorization_server_metadata(state: ApplicationState) { + state + .execute_with_metadata( + AGGREGATE_ID, + IssuanceCommand::LoadAuthorizationServerMetadata { + authorization_server_metadata: Box::new(AuthorizationServerMetadata { + issuer: BASE_URL.clone(), + token_endpoint: Some(BASE_URL.join("v1/oauth/token").unwrap()), + ..Default::default() + }), + }, + Default::default(), + ) + .await + .unwrap(); + } - let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let body: Value = serde_json::from_slice(&body).unwrap(); - assert_eq!( - body, - // serde_json::from_reader::<_, Value>( - // File::open(Path::new("../tests/response/create-open-badge.json")).unwrap() - // ) - // .unwrap() - serde_json::from_str::( - r#" - { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1", - "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" - ], - "id": "http://example.edu/credentials/3732", - "type": ["VerifiableCredential", "OpenBadgeCredential"], - "issuer": { - "id": "https://example.edu/issuers/565049", - "type": ["IssuerProfile"], - "name": "Example University" + // TODO: actually use this for .well-known tests. + pub async fn load_credential_issuer_metadata(state: ApplicationState) { + state + .execute_with_metadata( + AGGREGATE_ID, + IssuanceCommand::LoadCredentialIssuerMetadata { + credential_issuer_metadata: CredentialIssuerMetadata { + credential_issuer: BASE_URL.clone(), + authorization_server: None, + credential_endpoint: BASE_URL.join("v1/openid4vci/credential").unwrap(), + deferred_credential_endpoint: None, + batch_credential_endpoint: None, + credentials_supported: vec![], + display: None, }, - "issuanceDate": "2010-01-01T00:00:00Z", - "name": "Teamwork Badge", - "credentialSubject": { - "first_name": "Ferris", - "last_name": "Rustacean" - } - } - "# + }, + Default::default(), ) + .await + .unwrap(); + } + + pub async fn create_subject(state: ApplicationState) -> uuid::Uuid { + state + .execute_with_metadata( + AGGREGATE_ID, + IssuanceCommand::CreateSubject { + pre_authorized_code: PRE_AUTHORIZED_CODE.to_string(), + }, + Default::default(), + ) + .await + .unwrap(); + + let view = state.load(AGGREGATE_ID).await.unwrap().unwrap(); + view.subjects + .iter() + .find(|subject| subject.pre_authorized_code == PRE_AUTHORIZED_CODE.to_string()) .unwrap() - ); + .clone() + .id } } diff --git a/agent_api_rest/src/offers.rs b/agent_api_rest/src/offers.rs new file mode 100644 index 00000000..b832c0aa --- /dev/null +++ b/agent_api_rest/src/offers.rs @@ -0,0 +1,114 @@ +use agent_issuance::{ + command::IssuanceCommand, handlers::query_handler, model::aggregate::IssuanceData, + model::command_handler_without_id, queries::IssuanceDataView, state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, +}; +use serde_json::Value; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn offers( + State(state): State>, + Json(payload): Json, +) -> impl IntoResponse { + let subject_id: uuid::Uuid = payload["subjectId"].as_str().unwrap().parse().unwrap(); + let command = IssuanceCommand::CreateCredentialOffer { + subject_id: subject_id.clone(), + }; + + match command_handler_without_id(&state, command).await { + Ok(_) => {} + Err(err) => { + println!("Error: {:#?}\n", err); + return (StatusCode::BAD_REQUEST, err.to_string()).into_response(); + } + }; + + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) => { + let credential_offer = view + .subjects + .iter() + .find_map(|subject| { + (subject.id == subject_id).then(|| { + subject + .credential_offer + .as_ref() + .map(|credential_offer| credential_offer.form_urlencoded.clone()) + }) + }) + .flatten(); + if let Some(credential_offer) = credential_offer { + (StatusCode::OK, Json(credential_offer)).into_response() + } else { + StatusCode::NOT_FOUND.into_response() + } + .into_response() + } + Ok(None) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + app, + tests::{create_subject, load_credential_issuer_metadata}, + }; + + use super::*; + use agent_issuance::services::IssuanceServices; + use agent_store::in_memory; + use axum::{ + body::Body, + http::{self, Request}, + }; + use serde_json::json; + use std::sync::Arc; + use tower::ServiceExt; + + #[tokio::test] + async fn test_offers_endpoint() { + let state = Arc::new(in_memory::ApplicationState::new(vec![], IssuanceServices {}).await) + as ApplicationState; + + load_credential_issuer_metadata(state.clone()).await; + let subject_id = create_subject(state.clone()).await; + + let app = app(state); + + let response = app + .oneshot( + Request::builder() + .method(http::Method::POST) + .uri("/v1/offers") + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) + .body(Body::from( + serde_json::to_vec(&json!({ + "subjectId": subject_id + })) + .unwrap(), + )) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::OK); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + + let value: Value = serde_json::from_slice(&body).unwrap(); + let credential_offer = value.as_str().unwrap(); + assert_eq!(credential_offer, "openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fexample.com%2F%22%2C%22credentials%22%3A%5B%5D%2C%22grants%22%3A%7B%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22unsafe_pre_authorized_code%22%2C%22user_pin_required%22%3Afalse%7D%7D%7D"); + } +} diff --git a/agent_api_rest/src/subjects.rs b/agent_api_rest/src/subjects.rs new file mode 100644 index 00000000..4d121fd9 --- /dev/null +++ b/agent_api_rest/src/subjects.rs @@ -0,0 +1,116 @@ +use agent_issuance::{ + command::IssuanceCommand, handlers::query_handler, model::aggregate::IssuanceData, + model::command_handler_without_id, queries::IssuanceDataView, state::ApplicationState, +}; +use axum::{ + extract::{Json, State}, + http::StatusCode, + response::IntoResponse, +}; +use hyper::header; +use serde_json::Value; + +use crate::AGGREGATE_ID; + +#[axum_macros::debug_handler] +pub(crate) async fn subjects( + State(state): State>, + Json(payload): Json, +) -> impl IntoResponse { + let pre_authorized_code = payload["value"].as_str().unwrap().to_string(); + let command = IssuanceCommand::CreateSubject { + pre_authorized_code: pre_authorized_code.clone(), + }; + + match command_handler_without_id(&state, command).await { + Ok(_) => {} + Err(err) => { + println!("Error: {:#?}\n", err); + return (StatusCode::BAD_REQUEST, err.to_string()).into_response(); + } + }; + + match query_handler(AGGREGATE_ID.to_string(), &state).await { + Ok(Some(view)) => ( + StatusCode::CREATED, + [(header::LOCATION, format!("/v1/subjects/{}", AGGREGATE_ID))], + Json( + view.subjects + .iter() + .find(|subject| subject.pre_authorized_code == pre_authorized_code) + .unwrap() + .clone(), + ), + ) + .into_response(), + Ok(None) => StatusCode::NOT_FOUND.into_response(), + Err(err) => { + println!("Error: {:#?}\n", err); + (StatusCode::BAD_REQUEST, err.to_string()).into_response() + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + app, + tests::{create_subject, PRE_AUTHORIZED_CODE, SUBJECT_ID}, + }; + + use super::*; + use agent_issuance::{model::aggregate::IssuanceSubject, services::IssuanceServices}; + use agent_store::in_memory; + use axum::{ + body::Body, + http::{self, Request}, + }; + use serde_json::json; + use std::sync::Arc; + use tower::ServiceExt; + + #[tokio::test] + async fn test_subjects_endpoint() { + let state = Arc::new(in_memory::ApplicationState::new(vec![], IssuanceServices {}).await) + as ApplicationState; + + create_subject(state.clone()).await; + + let app = app(state); + + let response = app + .oneshot( + Request::builder() + .method(http::Method::POST) + .uri("/v1/subjects") + .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref()) + .body(Body::from( + serde_json::to_vec(&json!({ + "value": PRE_AUTHORIZED_CODE, + })) + .unwrap(), + )) + .unwrap(), + ) + .await + .unwrap(); + + assert_eq!(response.status(), StatusCode::CREATED); + + assert_eq!( + response.headers().get(http::header::LOCATION).unwrap(), + "/v1/subjects/agg-id-F39A0C" + ); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let body: Value = serde_json::from_slice(&body).unwrap(); + assert_eq!( + serde_json::from_value::(body).unwrap(), + IssuanceSubject { + id: SUBJECT_ID.parse().unwrap(), + pre_authorized_code: PRE_AUTHORIZED_CODE.to_string(), + ..Default::default() + } + ); + } +} diff --git a/agent_application/Cargo.toml b/agent_application/Cargo.toml index 8c78fca4..45ab9335 100644 --- a/agent_application/Cargo.toml +++ b/agent_application/Cargo.toml @@ -7,6 +7,11 @@ edition = "2021" agent_issuance = { path = "../agent_issuance" } agent_api_rest = { path = "../agent_api_rest" } agent_store = { path = "../agent_store" } + +oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } + axum = "0.6" +lazy_static.workspace = true serde_json.workspace = true tokio = { version = "1.34", features = ["full"] } +url.workspace = true diff --git a/agent_application/docker/db/init.sql b/agent_application/docker/db/init.sql index 59a87362..4ae2a446 100644 --- a/agent_application/docker/db/init.sql +++ b/agent_application/docker/db/init.sql @@ -10,7 +10,7 @@ CREATE TABLE events PRIMARY KEY (aggregate_type, aggregate_id, sequence) ); -CREATE TABLE credential_query +CREATE TABLE issuance_data_query ( view_id text NOT NULL, version bigint CHECK (version >= 0) NOT NULL, diff --git a/agent_application/src/main.rs b/agent_application/src/main.rs index d150356e..089e78d4 100644 --- a/agent_application/src/main.rs +++ b/agent_application/src/main.rs @@ -1,13 +1,19 @@ use agent_api_rest::app; use agent_issuance::{ - command::IssuanceCommand, handlers::command_handler, model::aggregate::Credential, queries::CredentialView, - state::new_application_state, + command::IssuanceCommand, handlers::command_handler, model::aggregate::IssuanceData, queries::IssuanceDataView, + services::IssuanceServices, state::ApplicationState, }; -use agent_store::state::ApplicationState; +use agent_store::in_memory; +use oid4vci::credential_issuer::{ + authorization_server_metadata::AuthorizationServerMetadata, credential_issuer_metadata::CredentialIssuerMetadata, +}; +use serde_json::json; +use std::sync::Arc; #[tokio::main] async fn main() { - let state = new_application_state().await; + let state = Arc::new(in_memory::ApplicationState::new(vec![], IssuanceServices {}).await) + as ApplicationState; tokio::spawn(startup_events(state.clone())); @@ -17,17 +23,92 @@ async fn main() { .unwrap(); } -async fn startup_events(state: ApplicationState) { +async fn startup_events(state: ApplicationState) { + let base_url: url::Url = "http://0.0.0.0:3033/".parse().unwrap(); + + match command_handler( + "agg-id-F39A0C".to_string(), + &state, + IssuanceCommand::LoadCredentialFormatTemplate { + credential_format_template: serde_json::from_str(include_str!( + "../../agent_issuance/res/credential_format_templates/openbadges_v3.json" + )) + .unwrap(), + }, + ) + .await + { + Ok(_) => println!("Startup task completed: `LoadCredentialFormatTemplate`"), + Err(err) => println!("Startup task failed: {:#?}", err), + }; + + match command_handler( + "agg-id-F39A0C".to_string(), + &state, + IssuanceCommand::LoadAuthorizationServerMetadata { + authorization_server_metadata: Box::new(AuthorizationServerMetadata { + issuer: base_url.clone(), + token_endpoint: Some(base_url.join("v1/oauth/token").unwrap()), + ..Default::default() + }), + }, + ) + .await + { + Ok(_) => println!("Startup task completed: `LoadAuthorizationServerMetadata`"), + Err(err) => println!("Startup task failed: {:#?}", err), + }; + + match command_handler( + "agg-id-F39A0C".to_string(), + &state, + IssuanceCommand::LoadCredentialIssuerMetadata { + credential_issuer_metadata: CredentialIssuerMetadata { + credential_issuer: base_url.clone(), + authorization_server: None, + credential_endpoint: base_url.join("v1/openid4vci/credential").unwrap(), + deferred_credential_endpoint: None, + batch_credential_endpoint: None, + credentials_supported: vec![], + display: None, + }, + }, + ) + .await + { + Ok(_) => println!("Startup task completed: `LoadCredentialIssuerMetadata`"), + Err(err) => println!("Startup task failed: {:#?}", err), + }; + match command_handler( "agg-id-F39A0C".to_string(), - state, - IssuanceCommand::LoadCredentialTemplate { - credential_template: serde_json::from_str(r#"{"foo":"bar"}"#).unwrap(), + &state, + IssuanceCommand::CreateCredentialsSupported { + credentials_supported: vec![serde_json::from_value(json!({ + "format": "jwt_vc_json", + "cryptographic_binding_methods_supported": [ + "did:key", + ], + "cryptographic_suites_supported": [ + "EdDSA" + ], + "credential_definition":{ + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ] + }, + "proof_types_supported": [ + "jwt" + ] + } + )) + .unwrap()], }, ) .await { - Ok(_) => println!("Startup task completed."), + Ok(_) => println!("Startup task completed: `CreateCredentialsSupported`"), Err(err) => println!("Startup task failed: {:#?}", err), }; } diff --git a/agent_issuance/Cargo.toml b/agent_issuance/Cargo.toml index a37c4006..1342453d 100644 --- a/agent_issuance/Cargo.toml +++ b/agent_issuance/Cargo.toml @@ -4,23 +4,23 @@ version = "0.1.0" edition = "2021" [dependencies] -agent_store = { path = "../agent_store" } +oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } +oid4vc-core = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } +oid4vc-manager = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } cqrs-es = "0.4.2" -sqlx = { version = "0.7", features = [ - "postgres", - "runtime-tokio-rustls", - "json", -] } -async-trait = "0.1.52" -serde.workspace = true -serde_json = "1.0" -jsonschema = "0.17" -identity_credential = { git = "https://github.com/iotaledger/identity.rs.git", default-features = false, features = [ - "credential", -] } +async-trait = "0.1" +derivative = "2.2" +did-key = "0.2" +jsonschema = "0.17" +jsonwebtoken = "8.2" +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true +url.workspace = true +uuid.workspace = true [dev-dependencies] tokio = { version = "1", features = ["full"] } -serde.workspace = true +lazy_static.workspace = true diff --git a/agent_issuance/res/credential_format_templates/openbadges_v3.json b/agent_issuance/res/credential_format_templates/openbadges_v3.json new file mode 100644 index 00000000..be8716f6 --- /dev/null +++ b/agent_issuance/res/credential_format_templates/openbadges_v3.json @@ -0,0 +1,16 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" + ], + "id": "http://example.com/credentials/3527", + "type": ["VerifiableCredential", "OpenBadgeCredential"], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": "Profile", + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": {} +} diff --git a/agent_issuance/res/format_templates/openbadges_v3.json b/agent_issuance/res/format_templates/openbadges_v3.json deleted file mode 100644 index a133d1fe..00000000 --- a/agent_issuance/res/format_templates/openbadges_v3.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json", - "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json" - ], - "id": "http://example.edu/credentials/3732", - "type": ["VerifiableCredential", "OpenBadgeCredential"], - "issuer": { - "id": "https://example.edu/issuers/565049", - "type": ["Profile"], - "name": "Example University" - }, - "issuanceDate": "2010-01-01T00:00:00Z", - "name": "Example University Degree", - "credentialSubject": {}, - "credentialSchema": [ - { - "id": "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json", - "type": "1EdTechJsonSchemaValidator2019" - } - ] -} diff --git a/agent_issuance/src/command.rs b/agent_issuance/src/command.rs index b026e787..b77c5767 100644 --- a/agent_issuance/src/command.rs +++ b/agent_issuance/src/command.rs @@ -1,13 +1,50 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Deserialize, Serialize)] -pub struct Metadata { - pub metadata: serde_json::Value, -} +use oid4vci::{ + credential_issuer::{ + authorization_server_metadata::AuthorizationServerMetadata, + credential_issuer_metadata::CredentialIssuerMetadata, credentials_supported::CredentialsSupportedObject, + }, + credential_request::CredentialRequest, + token_request::TokenRequest, +}; +use serde::Deserialize; +use uuid::Uuid; #[derive(Debug, Deserialize)] +#[serde(untagged)] pub enum IssuanceCommand { - LoadCredentialTemplate { credential_template: serde_json::Value }, - CreateCredentialData { credential_subject: serde_json::Value }, - SignCredential, + // Initialize Agent + LoadCredentialFormatTemplate { + credential_format_template: serde_json::Value, + }, + LoadAuthorizationServerMetadata { + authorization_server_metadata: Box, + }, + LoadCredentialIssuerMetadata { + credential_issuer_metadata: CredentialIssuerMetadata, + }, + + // Subject Management + CreateSubject { + pre_authorized_code: String, + }, + CreateCredentialsSupported { + credentials_supported: Vec, + }, + CreateUnsignedCredential { + subject_id: Uuid, + credential: serde_json::Value, + }, + // TODO: add option for credential_offer_uri (by reference) + CreateCredentialOffer { + subject_id: Uuid, + }, + + // OpenID4VCI Pre-Authorized Code Flow + CreateTokenResponse { + token_request: TokenRequest, + }, + CreateCredentialResponse { + access_token: String, + credential_request: CredentialRequest, + }, } diff --git a/agent_issuance/src/error.rs b/agent_issuance/src/error.rs index 59d06b40..f003c167 100644 --- a/agent_issuance/src/error.rs +++ b/agent_issuance/src/error.rs @@ -1,19 +1,25 @@ -use std::fmt::{Display, Formatter}; +use thiserror::Error; -// TODO: Use thiserror crate. -#[derive(Debug)] -pub struct IssuanceError(String); - -impl Display for IssuanceError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl std::error::Error for IssuanceError {} - -impl From<&str> for IssuanceError { - fn from(message: &str) -> Self { - IssuanceError(message.to_string()) - } +#[derive(Error, Debug)] +pub enum IssuanceError { + #[error("Missing Credential Issuer Metadata")] + MissingCredentialIssuerMetadataError, + #[error("Missing OAuth Authorization Server Metadata")] + MissingAuthorizationServerMetadataError, + #[error("Invalid Pre-Authorized Code")] + InvalidPreAuthorizedCodeError, + #[error("Invalid Access Token")] + InvalidAccessTokenError, + #[error("Credential must be an object")] + InvalidCredentialError, + #[error("Credential is missing")] + MissingCredentialError, + #[error("Missing `Proof` in Credential Request")] + MissingProofError, + #[error("Invalid `Proof` in Credential Request")] + InvalidProofError, + #[error("Missing `iss` claim in `Proof`")] + MissingProofIssuerError, + #[error("Cannot find Issuance Subject with `subject_id`: {0}")] + MissingIssuanceSubjectError(uuid::Uuid), } diff --git a/agent_issuance/src/event.rs b/agent_issuance/src/event.rs index eaadc77c..e7b8486f 100644 --- a/agent_issuance/src/event.rs +++ b/agent_issuance/src/event.rs @@ -1,17 +1,51 @@ -// use crate::model::aggregate::CredentialTemplate; use cqrs_es::DomainEvent; +use oid4vci::{ + credential_issuer::{ + authorization_server_metadata::AuthorizationServerMetadata, + credential_issuer_metadata::CredentialIssuerMetadata, credentials_supported::CredentialsSupportedObject, + }, + credential_response::CredentialResponse, + token_response::TokenResponse, +}; use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::model::aggregate::{Credential, CredentialOffer, IssuanceSubject}; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(untagged)] pub enum IssuanceEvent { - CredentialTemplateLoaded { - credential_template: serde_json::Value, + CredentialFormatTemplateLoaded { + credential_format_template: serde_json::Value, + }, + AuthorizationServerMetadataLoaded { + authorization_server_metadata: Box, + }, + CredentialIssuerMetadataLoaded { + credential_issuer_metadata: CredentialIssuerMetadata, + }, + SubjectCreated { + subject: IssuanceSubject, + }, + CredentialsSupportedCreated { + credentials_supported: Vec, + }, + UnsignedCredentialCreated { + subject_id: Uuid, + credential: Credential, + }, + CredentialOfferCreated { + subject_id: Uuid, + credential_offer: CredentialOffer, + }, + TokenResponseCreated { + subject_id: Uuid, + token_response: TokenResponse, }, - CredentialDataCreated { - credential_template: serde_json::Value, - credential_data: serde_json::Value, + CredentialResponseCreated { + subject_id: Uuid, + credential_response: CredentialResponse, }, - CredentialSigned, } impl DomainEvent for IssuanceEvent { @@ -19,9 +53,15 @@ impl DomainEvent for IssuanceEvent { use IssuanceEvent::*; let event_type: &str = match self { - CredentialTemplateLoaded { .. } => "CredentialTemplateCreated", - CredentialDataCreated { .. } => "CredentialDataCreated", - CredentialSigned { .. } => "CredentialSigned", + CredentialFormatTemplateLoaded { .. } => "CredentialFormatTemplateLoaded", + AuthorizationServerMetadataLoaded { .. } => "AuthorizationServerMetadataLoaded", + CredentialIssuerMetadataLoaded { .. } => "CredentialIssuerMetadataLoaded", + CredentialsSupportedCreated { .. } => "CredentialsSupportedCreated", + SubjectCreated { .. } => "SubjectCreated", + CredentialOfferCreated { .. } => "CredentialOfferCreated", + UnsignedCredentialCreated { .. } => "UnsignedCredentialCreated", + TokenResponseCreated { .. } => "TokenResponseCreated", + CredentialResponseCreated { .. } => "CredentialResponseCreated", }; event_type.to_string() } diff --git a/agent_issuance/src/handlers.rs b/agent_issuance/src/handlers.rs index ae5c6943..da520ff1 100644 --- a/agent_issuance/src/handlers.rs +++ b/agent_issuance/src/handlers.rs @@ -1,17 +1,14 @@ -use agent_store::state::ApplicationState; -use cqrs_es::{ - persist::{PersistenceError, ViewRepository}, - Aggregate, AggregateError, View, -}; +use crate::state::ApplicationState; +use cqrs_es::{persist::PersistenceError, Aggregate, AggregateError, View}; pub async fn query_handler>( credential_id: String, - state: ApplicationState, -) -> Result<(), PersistenceError> { - match state.credential_query.load(&credential_id).await { + state: &ApplicationState, +) -> Result, PersistenceError> { + match state.load(&credential_id).await { Ok(view) => { println!("View: {:#?}\n", view); - Ok(()) + Ok(view) } Err(err) => { println!("Error: {:#?}\n", err); @@ -22,11 +19,13 @@ pub async fn query_handler>( pub async fn command_handler>( aggregate_id: String, - state: ApplicationState, + state: &ApplicationState, command: A::Command, -) -> Result<(), AggregateError<::Error>> { +) -> Result<(), AggregateError<::Error>> +where + A::Command: Send + Sync, +{ state - .cqrs .execute_with_metadata(&aggregate_id, command, Default::default()) .await } diff --git a/agent_issuance/src/model/aggregate.rs b/agent_issuance/src/model/aggregate.rs index d8c32cd8..47f7e10f 100644 --- a/agent_issuance/src/model/aggregate.rs +++ b/agent_issuance/src/model/aggregate.rs @@ -1,28 +1,83 @@ +use crate::{command::IssuanceCommand, error::IssuanceError, event::IssuanceEvent, services::IssuanceServices}; use async_trait::async_trait; use cqrs_es::Aggregate; -use jsonschema::JSONSchema; +use derivative::Derivative; +use did_key::{from_existing_key, Ed25519KeyPair}; +use jsonwebtoken::{Algorithm, Header}; +use oid4vc_core::{jwt, Decoder, Subjects}; +use oid4vc_manager::methods::key_method::KeySubject; +use oid4vci::{ + credential_format_profiles::{self, w3c_verifiable_credentials::jwt_vc_json::JwtVcJson, CredentialFormats}, + credential_issuer::{ + authorization_server_metadata::AuthorizationServerMetadata, + credential_issuer_metadata::CredentialIssuerMetadata, CredentialIssuer, + }, + credential_offer::{ + CredentialOffer as OID4VCICredentialOffer, CredentialOfferQuery, CredentialsObject, Grants, PreAuthorizedCode, + }, + credential_response::{CredentialResponse, CredentialResponseType}, + token_request::TokenRequest, + token_response::TokenResponse, + VerifiableCredentialJwt, +}; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::json; +use std::sync::Arc; -use crate::{command::IssuanceCommand, error::IssuanceError, event::IssuanceEvent, services::IssuanceServices}; +// TODO: remove this. +const UNSAFE_PRE_AUTHORIZED_CODE: &str = "unsafe_pre_authorized_code"; +const UNSAFE_ACCESS_TOKEN: &str = "unsafe_access_token"; +const UNSAFE_C_NONCE: &str = "unsafe_c_nonce"; +const UNSAFE_ISSUER_KEY: &str = "this-is-a-very-UNSAFE-issuer-key"; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct OID4VCIData { + pub authorization_server_metadata: Option, + pub credential_issuer_metadata: Option, +} -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] +#[derive(Debug, Clone, Serialize, Deserialize, Default, Derivative)] +#[derivative(PartialEq)] pub struct Credential { - credential_template: serde_json::Value, - credential_data: serde_json::Value, - // TODO: add proof? - // proof: Option + #[derivative(PartialEq = "ignore")] + id: uuid::Uuid, + pub unsigned_credential: serde_json::Value, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CredentialOffer { + value: CredentialOfferQuery, + pub form_urlencoded: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, Derivative)] +#[derivative(PartialEq)] +pub struct IssuanceSubject { + #[derivative(PartialEq = "ignore")] + pub id: uuid::Uuid, + pub credential_offer: Option, + pub credentials: Option, + pub pre_authorized_code: String, + pub token_response: Option, + pub credential_response: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct IssuanceData { + credential_format_template: serde_json::Value, + oid4vci_data: OID4VCIData, + subjects: Vec, } #[async_trait] -impl Aggregate for Credential { +impl Aggregate for IssuanceData { type Command = IssuanceCommand; type Event = IssuanceEvent; type Error = IssuanceError; type Services = IssuanceServices; fn aggregate_type() -> String { - "Credential".to_string() + "IssuanceData".to_string() } async fn handle( @@ -30,116 +85,677 @@ impl Aggregate for Credential { command: Self::Command, _services: &Self::Services, ) -> Result, Self::Error> { + use IssuanceError::*; + match command { - IssuanceCommand::LoadCredentialTemplate { credential_template } => { - JSONSchema::compile(&credential_template).map_err(|e| IssuanceError::from(e.to_string().as_str()))?; + IssuanceCommand::LoadCredentialFormatTemplate { + credential_format_template, + } => Ok(vec![IssuanceEvent::CredentialFormatTemplateLoaded { + credential_format_template, + }]), + IssuanceCommand::LoadAuthorizationServerMetadata { + authorization_server_metadata, + } => Ok(vec![IssuanceEvent::AuthorizationServerMetadataLoaded { + authorization_server_metadata, + }]), + IssuanceCommand::LoadCredentialIssuerMetadata { + credential_issuer_metadata, + } => Ok(vec![IssuanceEvent::CredentialIssuerMetadataLoaded { + credential_issuer_metadata, + }]), + IssuanceCommand::CreateCredentialsSupported { credentials_supported } => { + self.oid4vci_data + .credential_issuer_metadata + .as_ref() + .ok_or(MissingCredentialIssuerMetadataError)?; + Ok(vec![IssuanceEvent::CredentialsSupportedCreated { + credentials_supported, + }]) + } + IssuanceCommand::CreateSubject { pre_authorized_code } => { + let subject = IssuanceSubject { + id: uuid::Uuid::new_v4(), + pre_authorized_code, + ..Default::default() + }; + Ok(vec![IssuanceEvent::SubjectCreated { subject }]) + } + IssuanceCommand::CreateCredentialOffer { subject_id } => { + let credential_issuer_metadata = self + .oid4vci_data + .credential_issuer_metadata + .as_ref() + .ok_or(MissingCredentialIssuerMetadataError)?; + let credentials_supported = credential_issuer_metadata.credentials_supported.clone(); + let credential_offer = CredentialOfferQuery::CredentialOffer(OID4VCICredentialOffer { + credential_issuer: credential_issuer_metadata.credential_issuer.clone(), + credentials: credentials_supported + .iter() + .map(|cso| CredentialsObject::ByValue(cso.credential_format.clone())) + .collect(), + grants: Some(Grants { + authorization_code: None, + pre_authorized_code: Some(PreAuthorizedCode { + pre_authorized_code: UNSAFE_PRE_AUTHORIZED_CODE.to_string(), + ..Default::default() + }), + }), + }); - Ok(vec![IssuanceEvent::CredentialTemplateLoaded { credential_template }]) + Ok(vec![IssuanceEvent::CredentialOfferCreated { + subject_id, + credential_offer: CredentialOffer { + value: credential_offer.clone(), + form_urlencoded: credential_offer.to_string(), + }, + }]) } - IssuanceCommand::CreateCredentialData { credential_subject } => { - let credential_template = self.credential_template.clone(); - dbg!(&credential_template); - // let json_schema = JSONSchema::compile(&credential_template) - // .map_err(|e| IssuanceError::from(e.to_string().as_str()))?; - let mut openbadges_v3_format_template = - serde_json::from_str::(include_str!("../../res/format_templates/openbadges_v3.json")) - .map_err(|e| IssuanceError::from(e.to_string().as_str()))?; + IssuanceCommand::CreateUnsignedCredential { subject_id, credential } => { + let mut unsigned_credential = self.credential_format_template.clone(); - openbadges_v3_format_template + unsigned_credential .as_object_mut() - .unwrap() - .insert("credentialSubject".to_string(), credential_subject.clone()); + .ok_or(InvalidCredentialError)? + .insert("credentialSubject".to_string(), credential["credentialSubject"].clone()); + + Ok(vec![IssuanceEvent::UnsignedCredentialCreated { + subject_id, + credential: Credential { + id: uuid::Uuid::new_v4(), + unsigned_credential, + }, + }]) + } + IssuanceCommand::CreateTokenResponse { token_request } => match token_request { + TokenRequest::PreAuthorizedCode { + pre_authorized_code, .. + } => { + let subject_id = self + .subjects + .iter() + .find(|subject| subject.pre_authorized_code == pre_authorized_code) + .map(|subject| subject.id) + .ok_or(InvalidPreAuthorizedCodeError)?; + + if self.subjects.iter().find(|subject| subject.id == subject_id).is_some() { + Ok(vec![IssuanceEvent::TokenResponseCreated { + subject_id: subject_id.clone(), + token_response: TokenResponse { + access_token: UNSAFE_ACCESS_TOKEN.to_string(), + token_type: "bearer".to_string(), + expires_in: None, + refresh_token: None, + scope: None, + c_nonce: Some(UNSAFE_C_NONCE.to_string()), + c_nonce_expires_in: None, + }, + }]) + } else { + Err(InvalidPreAuthorizedCodeError) + } + } + _ => Err(InvalidPreAuthorizedCodeError), + }, + IssuanceCommand::CreateCredentialResponse { + access_token, + credential_request, + } => { + use oid4vc_core::Subject; + + let subject_id = self + .subjects + .iter() + .find(|subject| { + subject + .token_response + .as_ref() + .map_or(false, |res| res.access_token == access_token) + }) + .map(|subject| subject.id) + .ok_or(InvalidAccessTokenError)?; + + // TODO: utilize `agent_kms`. + let issuer = Arc::new(KeySubject::from_keypair( + from_existing_key::(b"", Some(UNSAFE_ISSUER_KEY.as_bytes())), + None, + )); + let issuer_did = issuer.identifier().unwrap(); - dbg!(&openbadges_v3_format_template); + let credential_issuer = CredentialIssuer { + subject: issuer.clone(), + metadata: self + .oid4vci_data + .credential_issuer_metadata + .as_ref() + .ok_or(MissingCredentialIssuerMetadataError)? + .clone(), + authorization_server_metadata: self + .oid4vci_data + .authorization_server_metadata + .as_ref() + .ok_or(MissingAuthorizationServerMetadataError)? + .clone(), + }; - // json_schema.validate(&openbadges_v3_format_template).map_err(|e| { - // // TODO: remove ugly solution. - // let e: Vec<_> = e.map(|e| e.to_string()).collect(); - // IssuanceError::from(e.join(", ").as_str()) - // })?; + let proof = credential_issuer + .validate_proof( + credential_request.proof.ok_or(MissingProofError)?, + Decoder::from(&Subjects::try_from([issuer.clone() as Arc]).unwrap()), + ) + .await + .map_err(|_| InvalidProofError)?; - Ok(vec![IssuanceEvent::CredentialDataCreated { - credential_template, - credential_data: credential_subject, + let subject_did = proof + .rfc7519_claims + .iss() + .as_ref() + .ok_or(MissingProofIssuerError)? + .clone(); + + let mut credential = self + .subjects + .iter() + .find(|subject| subject.id == subject_id) + .ok_or(MissingIssuanceSubjectError(subject_id))? + .credentials + .as_ref() + .ok_or(MissingCredentialError)? + .unsigned_credential + .clone(); + + credential["issuer"] = json!(issuer_did); + credential["credentialSubject"]["id"] = json!(subject_did); + let credential_response = CredentialResponse { + credential: CredentialResponseType::Immediate(CredentialFormats::JwtVcJson( + credential_format_profiles::Credential { + format: JwtVcJson, + credential: json!(jwt::encode( + issuer.clone(), + Header::new(Algorithm::EdDSA), + VerifiableCredentialJwt::builder() + .sub(subject_did) + .iss(issuer_did) + .iat(0) + .exp(9999999999i64) + .verifiable_credential(credential) + .build() + .ok(), + ) + .ok()), + }, + )), + c_nonce: None, + c_nonce_expires_in: None, + }; + + Ok(vec![IssuanceEvent::CredentialResponseCreated { + subject_id, + credential_response, }]) } - _ => unimplemented!(), } } fn apply(&mut self, event: Self::Event) { use IssuanceEvent::*; match event { - CredentialTemplateLoaded { credential_template } => self.credential_template = credential_template, - CredentialDataCreated { - credential_template, - credential_data, + AuthorizationServerMetadataLoaded { + authorization_server_metadata, + } => { + self.oid4vci_data + .authorization_server_metadata + .replace(*authorization_server_metadata); + } + CredentialIssuerMetadataLoaded { + credential_issuer_metadata, + } => { + self.oid4vci_data + .credential_issuer_metadata + .replace(credential_issuer_metadata); + } + CredentialsSupportedCreated { credentials_supported } => { + self.oid4vci_data + .credential_issuer_metadata + .as_mut() + .unwrap() + .credentials_supported = credentials_supported + } + SubjectCreated { subject } => self.subjects.push(subject), + CredentialOfferCreated { + subject_id, + credential_offer, + } => { + self.subjects + .iter_mut() + .find(|subject| subject.id == subject_id) + .map(|subject| { + subject.credential_offer.replace(credential_offer); + }); + } + CredentialFormatTemplateLoaded { + credential_format_template, + } => self.credential_format_template = credential_format_template, + UnsignedCredentialCreated { subject_id, credential } => { + self.subjects + .iter_mut() + .find(|subject| subject.id == subject_id) + .map(|subject| { + subject.credentials.replace(credential); + }); + } + TokenResponseCreated { + subject_id, + token_response, + } => { + self.subjects + .iter_mut() + .find(|subject| subject.id == subject_id) + .map(|subject| { + subject.token_response.replace(token_response); + }); + } + CredentialResponseCreated { + subject_id, + credential_response, } => { - self.credential_template = credential_template; - self.credential_data = credential_data; + self.subjects + .iter_mut() + .find(|subject| subject.id == subject_id) + .map(|subject| { + subject.credential_response.replace(credential_response); + }); } - CredentialSigned { .. } => todo!(), } } } #[cfg(test)] mod tests { + use std::sync::Arc; + use super::*; use cqrs_es::test::TestFramework; + use did_key::Ed25519KeyPair; + use lazy_static::lazy_static; + use oid4vc_manager::methods::key_method::KeySubject; + use oid4vci::{ + credential_format_profiles::{ + self, + w3c_verifiable_credentials::jwt_vc_json::{CredentialDefinition, JwtVcJson}, + CredentialFormats, Parameters, + }, + credential_issuer::credentials_supported::CredentialsSupportedObject, + credential_offer::{CredentialsObject, Grants, PreAuthorizedCode}, + credential_request::CredentialRequest, + credential_response::{CredentialResponse, CredentialResponseType}, + token_request::TokenRequest, + token_response::TokenResponse, + Proof, ProofType, + }; use serde_json::json; - type CredentialTestFramework = TestFramework; + type CredentialTestFramework = TestFramework; - pub fn credential_template() -> serde_json::Value { - serde_json::from_str(include_str!("../../res/json_schema/openbadges_v3.json")).unwrap() + #[test] + fn test_load_credential_format_template() { + CredentialTestFramework::with(IssuanceServices) + .given_no_previous_events() + .when(IssuanceCommand::LoadCredentialFormatTemplate { + credential_format_template: CREDENTIAL_FORMAT_TEMPLATE.clone(), + }) + .then_expect_events(vec![IssuanceEvent::credential_format_template_loaded()]); } #[test] - fn test_credential_template_loaded() { - let expected = IssuanceEvent::CredentialTemplateLoaded { - credential_template: credential_template(), - }; + fn test_load_authorization_server_metadata() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![IssuanceEvent::credential_format_template_loaded()]) + .when(IssuanceCommand::LoadAuthorizationServerMetadata { + authorization_server_metadata: AUTHORIZATION_SERVER_METADATA.clone(), + }) + .then_expect_events(vec![IssuanceEvent::authorization_server_metadata_loaded()]); + } + #[test] + fn test_load_credential_issuer_metadata() { CredentialTestFramework::with(IssuanceServices) - .given_no_previous_events() - .when(IssuanceCommand::LoadCredentialTemplate { - credential_template: credential_template(), + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + ]) + .when(IssuanceCommand::LoadCredentialIssuerMetadata { + credential_issuer_metadata: CREDENTIAL_ISSUER_METADATA.clone(), + }) + .then_expect_events(vec![IssuanceEvent::credential_issuer_metadata_loaded()]); + } + + #[test] + fn test_create_subject() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + IssuanceEvent::credential_issuer_metadata_loaded(), + ]) + .when(IssuanceCommand::CreateSubject { + pre_authorized_code: UNSAFE_PRE_AUTHORIZED_CODE.to_string(), }) - .then_expect_events(vec![expected]); + .then_expect_events(vec![IssuanceEvent::subject_created()]); } #[test] - fn test_create_data_created() { - let credential = json!({ - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "type": "AchievementSubject", - "achievement": { - "id": "https://example.com/achievements/21st-century-skills/teamwork", - "type": "Achievement", - "criteria": { - "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." - }, - "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", - "name": "Teamwork" + fn test_create_credentials_supported() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + IssuanceEvent::credential_issuer_metadata_loaded(), + IssuanceEvent::subject_created(), + ]) + .when(IssuanceCommand::CreateCredentialsSupported { + credentials_supported: CREDENTIALS_SUPPORTED.clone(), + }) + .then_expect_events(vec![IssuanceEvent::credentials_supported_created()]); + } + + #[test] + fn test_create_credential_offer() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + IssuanceEvent::credential_issuer_metadata_loaded(), + IssuanceEvent::subject_created(), + IssuanceEvent::credentials_supported_created(), + ]) + .when(IssuanceCommand::CreateCredentialOffer { + subject_id: ISSUANCE_SUBJECT.id, + }) + .then_expect_events(vec![IssuanceEvent::credential_offer_created()]); + } + + #[test] + fn test_create_unsigned_credential() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + IssuanceEvent::credential_issuer_metadata_loaded(), + IssuanceEvent::subject_created(), + IssuanceEvent::credentials_supported_created(), + IssuanceEvent::credential_offer_created(), + ]) + .when(IssuanceCommand::CreateUnsignedCredential { + subject_id: ISSUANCE_SUBJECT.id, + credential: CREDENTIAL_SUBJECT.clone(), + }) + .then_expect_events(vec![IssuanceEvent::unsigned_credential_created()]); + } + + #[test] + fn test_create_token_response() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + IssuanceEvent::credential_issuer_metadata_loaded(), + IssuanceEvent::subject_created(), + IssuanceEvent::credentials_supported_created(), + IssuanceEvent::credential_offer_created(), + IssuanceEvent::unsigned_credential_created(), + ]) + .when(IssuanceCommand::CreateTokenResponse { + token_request: TOKEN_REQUEST.clone(), + }) + .then_expect_events(vec![IssuanceEvent::token_response_created()]); + } + + #[test] + fn test_create_credential_response() { + CredentialTestFramework::with(IssuanceServices) + .given(vec![ + IssuanceEvent::credential_format_template_loaded(), + IssuanceEvent::authorization_server_metadata_loaded(), + IssuanceEvent::credential_issuer_metadata_loaded(), + IssuanceEvent::subject_created(), + IssuanceEvent::credentials_supported_created(), + IssuanceEvent::credential_offer_created(), + IssuanceEvent::unsigned_credential_created(), + IssuanceEvent::token_response_created(), + ]) + .when(IssuanceCommand::CreateCredentialResponse { + access_token: UNSAFE_ACCESS_TOKEN.to_string(), + credential_request: CREDENTIAL_REQUEST.clone(), + }) + .then_expect_events(vec![IssuanceEvent::credential_response_created()]); + } + + lazy_static! { + static ref CREDENTIAL_FORMAT_TEMPLATE: serde_json::Value = + serde_json::from_str(include_str!("../../res/credential_format_templates/openbadges_v3.json")).unwrap(); + static ref BASE_URL: url::Url = "https://example.com/".parse().unwrap(); + static ref AUTHORIZATION_SERVER_METADATA: Box = + Box::new(AuthorizationServerMetadata { + issuer: BASE_URL.clone(), + token_endpoint: Some(BASE_URL.join("token").unwrap()), + ..Default::default() + }); + static ref CREDENTIAL_ISSUER_METADATA: CredentialIssuerMetadata = CredentialIssuerMetadata { + credential_issuer: BASE_URL.clone(), + authorization_server: None, + credential_endpoint: BASE_URL.join("credential").unwrap(), + deferred_credential_endpoint: None, + batch_credential_endpoint: Some(BASE_URL.join("batch_credential").unwrap()), + credentials_supported: vec![], + display: None, + }; + static ref CREDENTIALS_SUPPORTED: Vec = vec![serde_json::from_value(json!({ + "format": "jwt_vc_json", + "cryptographic_binding_methods_supported": [ + "did:key", + ], + "cryptographic_suites_supported": [ + "EdDSA" + ], + "credential_definition":{ + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ] + }, + "proof_types_supported": [ + "jwt" + ] + } + )) + .unwrap()]; + static ref ISSUANCE_SUBJECT_ID: uuid::Uuid = uuid::Uuid::new_v4(); + static ref ISSUANCE_SUBJECT: IssuanceSubject = IssuanceSubject { + id: ISSUANCE_SUBJECT_ID.clone(), + pre_authorized_code: UNSAFE_PRE_AUTHORIZED_CODE.to_string(), + ..Default::default() + }; + static ref CREDENTIALS_OBJECTS: Vec = CREDENTIALS_SUPPORTED + .iter() + .map(|cso| CredentialsObject::ByValue(cso.credential_format.clone())) + .collect(); + static ref PRE_AUTHORIZED_CODE: PreAuthorizedCode = PreAuthorizedCode { + pre_authorized_code: UNSAFE_PRE_AUTHORIZED_CODE.to_string(), + ..Default::default() + }; + static ref CREDENTIAL_OFFER: CredentialOffer = { + let credential_offer = CredentialOfferQuery::CredentialOffer(OID4VCICredentialOffer { + credential_issuer: CREDENTIAL_ISSUER_METADATA.credential_issuer.clone(), + credentials: CREDENTIALS_OBJECTS.clone(), + grants: Some(Grants { + authorization_code: None, + pre_authorized_code: Some(PRE_AUTHORIZED_CODE.clone()), + }), + }); + CredentialOffer { + value: credential_offer.clone(), + form_urlencoded: credential_offer.to_string(), + } + }; + static ref CREDENTIAL_SUBJECT: serde_json::Value = json!( + { + "credentialSubject": { + "id": {}, + "type": "AchievementSubject", + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": "Achievement", + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } } - } + } + ); + static ref UNSIGNED_CREDENTIAL: serde_json::Value = json!({ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" + ], + "id": "http://example.com/credentials/3527", + "type": ["VerifiableCredential", "OpenBadgeCredential"], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": "Profile", + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": CREDENTIAL_SUBJECT["credentialSubject"].clone(), }); + static ref CREDENTIAL: Credential = Credential { + id: uuid::Uuid::new_v4(), + unsigned_credential: UNSIGNED_CREDENTIAL.clone(), + }; + static ref TOKEN_REQUEST: TokenRequest = TokenRequest::PreAuthorizedCode { + pre_authorized_code: UNSAFE_PRE_AUTHORIZED_CODE.to_string(), + user_pin: None, + }; + static ref TOKEN_RESPONSE: TokenResponse = TokenResponse { + access_token: UNSAFE_ACCESS_TOKEN.to_string(), + token_type: "bearer".to_string(), + expires_in: None, + refresh_token: None, + scope: None, + c_nonce: Some(UNSAFE_C_NONCE.to_string()), + c_nonce_expires_in: None, + }; + static ref SUBJECT_KEY_DID: Arc = Arc::new(KeySubject::from_keypair( + from_existing_key::( + b"", + Some("this-is-a-very-UNSAFE-subjec-key".as_bytes().try_into().unwrap(),), + ), + None, + )); + static ref CREDENTIAL_REQUEST: CredentialRequest = { + use oid4vc_core::Subject; - let expected = IssuanceEvent::CredentialDataCreated { - credential_template: credential_template(), - credential_data: credential.clone(), + CredentialRequest { + credential_format: CredentialFormats::JwtVcJson(Parameters { + format: JwtVcJson, + parameters: ( + CredentialDefinition { + type_: vec!["VerifiableCredential".to_string(), "OpenBadgeCredential".to_string()], + credential_subject: None, + }, + None, + ) + .into(), + }), + proof: Some( + Proof::builder() + .proof_type(ProofType::Jwt) + .signer(SUBJECT_KEY_DID.clone()) + .iss(SUBJECT_KEY_DID.identifier().unwrap()) + .aud(CREDENTIAL_ISSUER_METADATA.credential_issuer.clone()) + .iat(1571324800) + .exp(9999999999i64) + .nonce(UNSAFE_C_NONCE.to_string()) + .build() + .unwrap(), + ), + } + }; + static ref VERIFIABLE_CREDENTIAL_JWT: String = { + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFZERTQSIsImtpZCI6ImRpZDprZXk6ejZNa3F5WmpEZmhzeVo1YzZOdUpoYm9zV2tTajg2Mmp5V2lDQ0tIRHpOTkttOGtoI3o2TWtxeVpqRGZoc3laNWM2TnVKaGJvc1drU2o4NjJqeVdpQ0NLSER6Tk5LbThraCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtxeVpqRGZoc3laNWM2TnVKaGJvc1drU2o4NjJqeVdpQ0NLSER6Tk5LbThraCIsInN1YiI6ImRpZDprZXk6ejZNa3RRcEJuTDY5MVdmdGhRV0xTNk0zQjZhczh2akx1Z29kZVFhWGtKVHdyclNxIiwiZXhwIjo5OTk5OTk5OTk5LCJpYXQiOjAsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIiwiaHR0cHM6Ly9wdXJsLmltc2dsb2JhbC5vcmcvc3BlYy9vYi92M3AwL2NvbnRleHQtMy4wLjIuanNvbiJdLCJpZCI6Imh0dHA6Ly9leGFtcGxlLmNvbS9jcmVkZW50aWFscy8zNTI3IiwidHlwZSI6WyJWZXJpZmlhYmxlQ3JlZGVudGlhbCIsIk9wZW5CYWRnZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmtleTp6Nk1rcXlaakRmaHN5WjVjNk51Smhib3NXa1NqODYyanlXaUNDS0hEek5OS204a2giLCJpc3N1YW5jZURhdGUiOiIyMDEwLTAxLTAxVDAwOjAwOjAwWiIsIm5hbWUiOiJUZWFtd29yayBCYWRnZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmtleTp6Nk1rdFFwQm5MNjkxV2Z0aFFXTFM2TTNCNmFzOHZqTHVnb2RlUWFYa0pUd3JyU3EiLCJ0eXBlIjoiQWNoaWV2ZW1lbnRTdWJqZWN0IiwiYWNoaWV2ZW1lbnQiOnsiaWQiOiJodHRwczovL2V4YW1wbGUuY29tL2FjaGlldmVtZW50cy8yMXN0LWNlbnR1cnktc2tpbGxzL3RlYW13b3JrIiwidHlwZSI6IkFjaGlldmVtZW50IiwiY3JpdGVyaWEiOnsibmFycmF0aXZlIjoiVGVhbSBtZW1iZXJzIGFyZSBub21pbmF0ZWQgZm9yIHRoaXMgYmFkZ2UgYnkgdGhlaXIgcGVlcnMgYW5kIHJlY29nbml6ZWQgdXBvbiByZXZpZXcgYnkgRXhhbXBsZSBDb3JwIG1hbmFnZW1lbnQuIn0sImRlc2NyaXB0aW9uIjoiVGhpcyBiYWRnZSByZWNvZ25pemVzIHRoZSBkZXZlbG9wbWVudCBvZiB0aGUgY2FwYWNpdHkgdG8gY29sbGFib3JhdGUgd2l0aGluIGEgZ3JvdXAgZW52aXJvbm1lbnQuIiwibmFtZSI6IlRlYW13b3JrIn19fX0.Klwqycvwq86HbwaRxO4kwNkgrComMTbQhAHrU5j9dNKC0IQg3-KB8SDVxbbOSCnta7yDGSkq45M931XmIPYuCg".to_string() + }; + static ref CREDENTIAL_RESPONSE: CredentialResponse = CredentialResponse { + credential: CredentialResponseType::Immediate(CredentialFormats::JwtVcJson( + credential_format_profiles::Credential { + format: JwtVcJson, + credential: json!(VERIFIABLE_CREDENTIAL_JWT.clone()), + } + )), + c_nonce: None, + c_nonce_expires_in: None, }; + } - CredentialTestFramework::with(IssuanceServices) - .given(vec![IssuanceEvent::CredentialTemplateLoaded { - credential_template: credential_template(), - }]) - .when(IssuanceCommand::CreateCredentialData { - credential_subject: credential, - }) - .then_expect_events(vec![expected]); + impl IssuanceEvent { + pub fn credential_format_template_loaded() -> IssuanceEvent { + IssuanceEvent::CredentialFormatTemplateLoaded { + credential_format_template: CREDENTIAL_FORMAT_TEMPLATE.clone(), + } + } + + pub fn authorization_server_metadata_loaded() -> IssuanceEvent { + IssuanceEvent::AuthorizationServerMetadataLoaded { + authorization_server_metadata: AUTHORIZATION_SERVER_METADATA.clone(), + } + } + + pub fn credential_issuer_metadata_loaded() -> IssuanceEvent { + IssuanceEvent::CredentialIssuerMetadataLoaded { + credential_issuer_metadata: CREDENTIAL_ISSUER_METADATA.clone(), + } + } + + pub fn subject_created() -> IssuanceEvent { + IssuanceEvent::SubjectCreated { + subject: ISSUANCE_SUBJECT.clone(), + } + } + + pub fn credentials_supported_created() -> IssuanceEvent { + IssuanceEvent::CredentialsSupportedCreated { + credentials_supported: CREDENTIALS_SUPPORTED.clone(), + } + } + + pub fn credential_offer_created() -> IssuanceEvent { + IssuanceEvent::CredentialOfferCreated { + subject_id: ISSUANCE_SUBJECT.id, + credential_offer: CREDENTIAL_OFFER.clone(), + } + } + + pub fn unsigned_credential_created() -> IssuanceEvent { + IssuanceEvent::UnsignedCredentialCreated { + subject_id: ISSUANCE_SUBJECT.id, + credential: CREDENTIAL.clone(), + } + } + + pub fn token_response_created() -> IssuanceEvent { + IssuanceEvent::TokenResponseCreated { + subject_id: ISSUANCE_SUBJECT.id, + token_response: TOKEN_RESPONSE.clone(), + } + } + + pub fn credential_response_created() -> IssuanceEvent { + IssuanceEvent::CredentialResponseCreated { + subject_id: ISSUANCE_SUBJECT.id, + credential_response: CREDENTIAL_RESPONSE.clone(), + } + } } } diff --git a/agent_issuance/src/model/mod.rs b/agent_issuance/src/model/mod.rs index 63b57440..a4aa5e0a 100644 --- a/agent_issuance/src/model/mod.rs +++ b/agent_issuance/src/model/mod.rs @@ -1,13 +1,16 @@ pub mod aggregate; -use agent_store::state::ApplicationState; +use crate::state::ApplicationState; use cqrs_es::{Aggregate, AggregateError, View}; use crate::handlers::command_handler; -pub async fn create_credential>( - state: ApplicationState, +pub async fn command_handler_without_id>( + state: &ApplicationState, command: A::Command, -) -> Result<(), AggregateError<::Error>> { +) -> Result<(), AggregateError<::Error>> +where + A::Command: Send + Sync, +{ command_handler("agg-id-F39A0C".to_string(), state, command).await } diff --git a/agent_issuance/src/queries.rs b/agent_issuance/src/queries.rs index 3bc36af2..d9d3b957 100644 --- a/agent_issuance/src/queries.rs +++ b/agent_issuance/src/queries.rs @@ -2,15 +2,15 @@ use async_trait::async_trait; use cqrs_es::{persist::GenericQuery, EventEnvelope, Query, View}; use serde::{Deserialize, Serialize}; -use crate::model::aggregate::Credential; +use crate::model::aggregate::{IssuanceData, IssuanceSubject, OID4VCIData}; pub struct SimpleLoggingQuery {} // Our simplest query, this is great for debugging but absolutely useless in production. // This query just pretty prints the events as they are processed. #[async_trait] -impl Query for SimpleLoggingQuery { - async fn dispatch(&self, aggregate_id: &str, events: &[EventEnvelope]) { +impl Query for SimpleLoggingQuery { + async fn dispatch(&self, aggregate_id: &str, events: &[EventEnvelope]) { for event in events { let payload = serde_json::to_string_pretty(&event.payload).unwrap(); println!("{}-{}\n{}", aggregate_id, event.sequence, payload); @@ -18,30 +18,88 @@ impl Query for SimpleLoggingQuery { } } -pub type CredentialQuery = GenericQuery; +pub type CredentialQuery = GenericQuery; #[derive(Debug, Default, Serialize, Deserialize, Clone)] -pub struct CredentialView { - credential_template: serde_json::Value, - credential_data: serde_json::Value, +pub struct IssuanceDataView { + pub credential_format_template: serde_json::Value, + pub oid4vci_data: OID4VCIData, + pub subjects: Vec, } -impl View for CredentialView { - fn update(&mut self, event: &EventEnvelope) { +impl View for IssuanceDataView { + fn update(&mut self, event: &EventEnvelope) { use crate::event::IssuanceEvent::*; match &event.payload { - CredentialTemplateLoaded { credential_template } => { - self.credential_template = credential_template.clone(); + CredentialFormatTemplateLoaded { + credential_format_template, + } => { + self.credential_format_template = credential_format_template.clone(); + } + AuthorizationServerMetadataLoaded { + authorization_server_metadata, + } => { + self.oid4vci_data + .authorization_server_metadata + .replace(*authorization_server_metadata.clone()); + } + CredentialIssuerMetadataLoaded { + credential_issuer_metadata, + } => { + self.oid4vci_data.credential_issuer_metadata = Some(credential_issuer_metadata.clone()); + } + CredentialsSupportedCreated { credentials_supported } => { + self.oid4vci_data + .credential_issuer_metadata + .as_mut() + .unwrap() + .credentials_supported = credentials_supported.clone(); + } + SubjectCreated { subject } => { + self.subjects.push(subject.clone()); + } + CredentialOfferCreated { + subject_id, + credential_offer, + } => { + self.subjects + .iter_mut() + .find(|s| s.id == *subject_id) + .unwrap() + .credential_offer + .replace(credential_offer.clone()); + } + UnsignedCredentialCreated { subject_id, credential } => { + self.subjects + .iter_mut() + .find(|subject| subject.id == *subject_id) + .map(|subject| { + subject.credentials.replace(credential.clone()); + }); + } + TokenResponseCreated { + subject_id, + token_response, + } => { + self.subjects + .iter_mut() + .find(|subject| subject.id == *subject_id) + .map(|subject| { + subject.token_response.replace(token_response.clone()); + }); } - CredentialDataCreated { - credential_template, - credential_data, + CredentialResponseCreated { + subject_id, + credential_response, } => { - self.credential_template = credential_template.clone(); - self.credential_data = credential_data.clone(); + self.subjects + .iter_mut() + .find(|subject| subject.id == *subject_id) + .map(|subject| { + subject.credential_response.replace(credential_response.clone()); + }); } - CredentialSigned => todo!(), } } } diff --git a/agent_issuance/src/services.rs b/agent_issuance/src/services.rs index a69f7474..24aab619 100644 --- a/agent_issuance/src/services.rs +++ b/agent_issuance/src/services.rs @@ -1,11 +1,11 @@ pub struct IssuanceServices; impl IssuanceServices { - async fn sign_credential(&self) -> Result<(), String> { + async fn _sign_credential(&self) -> Result<(), String> { todo!() } - async fn retrieve_template(&self) -> Result<(), String> { + async fn _retrieve_template(&self) -> Result<(), String> { todo!() } } diff --git a/agent_issuance/src/state.rs b/agent_issuance/src/state.rs index 170aef3f..25a10349 100644 --- a/agent_issuance/src/state.rs +++ b/agent_issuance/src/state.rs @@ -1,13 +1,20 @@ -use crate::model::aggregate::Credential; -use crate::queries::CredentialView; -use crate::services::IssuanceServices; -use agent_store::state::ApplicationState; +use async_trait::async_trait; +use cqrs_es::persist::PersistenceError; +use cqrs_es::{Aggregate, View}; +use std::collections::HashMap; +use std::sync::Arc; -pub async fn new_application_state() -> ApplicationState { - agent_store::state::application_state( - // vec![Box::new(SimpleLoggingQuery {})], - vec![], - IssuanceServices {}, - ) - .await +#[async_trait] +pub trait CQRS> { + async fn execute_with_metadata( + &self, + aggregate_id: &str, + command: A::Command, + metadata: HashMap, + ) -> Result<(), cqrs_es::AggregateError> + where + A::Command: Send + Sync; + + async fn load(&self, view_id: &str) -> Result, PersistenceError>; } +pub type ApplicationState = Arc + Send + Sync>; diff --git a/agent_issuance/tests/mod.rs b/agent_issuance/tests/mod.rs deleted file mode 100644 index 31897763..00000000 --- a/agent_issuance/tests/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use agent_issuance::{command::IssuanceCommand, state::new_application_state}; - -#[tokio::test] -async fn test() { - pub fn credential_template() -> serde_json::Value { - serde_json::from_str(include_str!("../res/json_schema/openbadges_v3.json")).unwrap() - } - - let application_state = new_application_state().await; - - let command = IssuanceCommand::LoadCredentialTemplate { - credential_template: credential_template(), - }; - - application_state.cqrs.execute("agg-id-0007", command).await.unwrap(); - - let command = IssuanceCommand::CreateCredentialData { - credential_subject: serde_json::json!({ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.2.json" - ], - "id": "http://example.com/credentials/3527", - "type": ["VerifiableCredential", "OpenBadgeCredential"], - "issuer": { - "id": "https://example.com/issuers/876543", - "type": "Profile", - "name": "Example Corp" - }, - "issuanceDate": "2010-01-01T00:00:00Z", - "name": "Teamwork Badge", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "type": "AchievementSubject", - "achievement": { - "id": "https://example.com/achievements/21st-century-skills/teamwork", - "type": "Achievement", - "criteria": { - "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." - }, - "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", - "name": "Teamwork" - } - } - }), - }; - application_state.cqrs.execute("agg-id-0007", command).await.unwrap(); -} diff --git a/agent_store/Cargo.toml b/agent_store/Cargo.toml index 4f09c50e..ae5e59c2 100644 --- a/agent_store/Cargo.toml +++ b/agent_store/Cargo.toml @@ -4,10 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -config = "0.13" +agent_issuance = { path = "../agent_issuance" } + cqrs-es = "0.4.2" -dotenvy = "0.15" postgres-es = "0.4.10" + +async-trait = "0.1" +config = "0.13" +dotenvy = "0.15" +serde_json.workspace = true sqlx = { version = "0.7", features = [ "postgres", "runtime-tokio-rustls", diff --git a/agent_store/src/config.rs b/agent_store/src/config.rs index abd85bac..d6772af9 100644 --- a/agent_store/src/config.rs +++ b/agent_store/src/config.rs @@ -1,29 +1,3 @@ -use cqrs_es::{persist::GenericQuery, Aggregate, Query, View}; -use postgres_es::{PostgresCqrs, PostgresViewRepository}; -use sqlx::{Pool, Postgres}; -use std::sync::Arc; - -pub fn cqrs_framework( - pool: Pool, - mut queries: Vec>>, - services: A::Services, -) -> (Arc>, Arc>) -where - A: Aggregate + 'static, - V: View + 'static, -{ - // A query that stores the current state of an individual aggregate. - let credential_view_repo = Arc::new(PostgresViewRepository::new("credential_query", pool.clone())); - let mut credential_query = GenericQuery::new(credential_view_repo.clone()); - credential_query.use_error_handler(Box::new(|e| println!("{}", e))); - queries.push(Box::new(credential_query)); - - ( - Arc::new(postgres_es::postgres_cqrs(pool, queries, services)), - credential_view_repo, - ) -} - /// Read environment variables pub fn config() -> config::Config { // Load .env file diff --git a/agent_store/src/in_memory.rs b/agent_store/src/in_memory.rs new file mode 100644 index 00000000..675415e4 --- /dev/null +++ b/agent_store/src/in_memory.rs @@ -0,0 +1,95 @@ +use agent_issuance::state::CQRS; +use async_trait::async_trait; +use cqrs_es::mem_store::MemStore; +use cqrs_es::persist::{GenericQuery, PersistenceError, ViewContext, ViewRepository}; +use cqrs_es::CqrsFramework; +use cqrs_es::{Aggregate, Query, View}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +#[derive(Default)] +pub struct MemRepository, A: Aggregate> { + pub map: Mutex>, + _phantom: std::marker::PhantomData<(V, A)>, +} + +impl, A: Aggregate> MemRepository { + pub fn new() -> Self { + Self::default() + } +} + +#[async_trait] +impl ViewRepository for MemRepository +where + V: View, + A: Aggregate, +{ + async fn load(&self, view_id: &str) -> Result, PersistenceError> { + Ok(self + .map + .lock() + .unwrap() + .get(view_id) + .map(|view| serde_json::from_value(view.clone()).unwrap())) + } + + async fn load_with_context(&self, view_id: &str) -> Result, PersistenceError> { + Ok(self.map.lock().unwrap().get(view_id).map(|view| { + let view = serde_json::from_value(view.clone()).unwrap(); + let view_context = ViewContext::new(view_id.to_string(), 0); + (view, view_context) + })) + } + + async fn update_view(&self, view: V, context: ViewContext) -> Result<(), PersistenceError> { + let payload = serde_json::to_value(&view).unwrap(); + self.map.lock().unwrap().insert(context.view_instance_id, payload); + Ok(()) + } +} + +#[derive(Clone)] +pub struct ApplicationState> { + pub cqrs: Arc>>, + pub issuance_data_query: Arc>, +} + +impl ApplicationState +where + A: Aggregate + 'static, + V: View + 'static, +{ + pub async fn new(queries: Vec>>, services: A::Services) -> ApplicationState { + let credential_view_repo = Arc::new(MemRepository::::new()); + let mut issuance_data_query = GenericQuery::new(credential_view_repo.clone()); + issuance_data_query.use_error_handler(Box::new(|e| println!("{}", e))); + + let mut queries = queries; + queries.push(Box::new(issuance_data_query)); + + ApplicationState { + cqrs: Arc::new(CqrsFramework::new(MemStore::default(), queries, services)), + issuance_data_query: credential_view_repo, + } + } +} + +#[async_trait] +impl> CQRS for ApplicationState { + async fn execute_with_metadata( + &self, + aggregate_id: &str, + command: A::Command, + metadata: HashMap, + ) -> Result<(), cqrs_es::AggregateError> + where + A::Command: Send + Sync, + { + self.cqrs.execute_with_metadata(aggregate_id, command, metadata).await + } + + async fn load(&self, view_id: &str) -> Result, PersistenceError> { + self.issuance_data_query.load(view_id).await + } +} diff --git a/agent_store/src/lib.rs b/agent_store/src/lib.rs index 0b0fd7eb..6804a56d 100644 --- a/agent_store/src/lib.rs +++ b/agent_store/src/lib.rs @@ -1,2 +1,3 @@ -pub mod config; -pub mod state; +mod config; +pub mod in_memory; +pub mod postgres; diff --git a/agent_store/src/postgres.rs b/agent_store/src/postgres.rs new file mode 100644 index 00000000..efd2434b --- /dev/null +++ b/agent_store/src/postgres.rs @@ -0,0 +1,75 @@ +use crate::config::config; +use agent_issuance::state::CQRS; +use async_trait::async_trait; +use cqrs_es::persist::{GenericQuery, PersistenceError, ViewRepository}; +use cqrs_es::{Aggregate, Query, View}; +use postgres_es::{default_postgress_pool, PostgresCqrs, PostgresViewRepository}; +use sqlx::{Pool, Postgres}; +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Clone)] +pub struct ApplicationState> { + pub cqrs: Arc>, + pub issuance_data_query: Arc>, +} + +impl ApplicationState +where + A: Aggregate, + V: View, +{ + pub async fn new(queries: Vec>>, services: A::Services) -> ApplicationState + where + A: Aggregate + 'static, + V: View + 'static, + { + let pool = default_postgress_pool(&config().get_string("db_connection_string").unwrap()).await; + let (cqrs, issuance_data_query) = cqrs_framework(pool, queries, services); + ApplicationState { + cqrs, + issuance_data_query, + } + } +} + +#[async_trait] +impl> CQRS for ApplicationState { + async fn execute_with_metadata( + &self, + aggregate_id: &str, + command: A::Command, + metadata: HashMap, + ) -> Result<(), cqrs_es::AggregateError> + where + A::Command: Send + Sync, + { + self.cqrs.execute_with_metadata(aggregate_id, command, metadata).await + } + + async fn load(&self, view_id: &str) -> Result, PersistenceError> { + self.issuance_data_query.load(view_id).await + } +} + +pub fn cqrs_framework( + pool: Pool, + mut queries: Vec>>, + services: A::Services, +) -> (Arc>, Arc>) +where + A: Aggregate + 'static, + V: View + 'static, +{ + // A query that stores the current state of an individual account. + let issuance_data_repo = Arc::new(PostgresViewRepository::new("issuance_data_query", pool.clone())); + let mut issuance_data_query = GenericQuery::new(issuance_data_repo.clone()); + issuance_data_query.use_error_handler(Box::new(|e| println!("{}", e))); + + // Create and return an event-sourced `CqrsFramework`. + queries.push(Box::new(issuance_data_query)); + ( + Arc::new(postgres_es::postgres_cqrs(pool, queries, services)), + issuance_data_repo, + ) +} diff --git a/agent_store/src/state.rs b/agent_store/src/state.rs deleted file mode 100644 index b52a84e5..00000000 --- a/agent_store/src/state.rs +++ /dev/null @@ -1,22 +0,0 @@ -use cqrs_es::{Aggregate, Query, View}; -use postgres_es::{default_postgress_pool, PostgresCqrs, PostgresViewRepository}; -use std::sync::Arc; - -use crate::config::config; -use crate::config::cqrs_framework; - -#[derive(Clone)] -pub struct ApplicationState> { - pub cqrs: Arc>, - pub credential_query: Arc>, -} - -pub async fn application_state(queries: Vec>>, services: A::Services) -> ApplicationState -where - A: Aggregate + 'static, - V: View + 'static, -{ - let pool = default_postgress_pool(&config().get_string("db_connection_string").unwrap()).await; - let (cqrs, credential_query) = cqrs_framework(pool, queries, services); - ApplicationState { cqrs, credential_query } -}