From 2540ebe95396b814cf99703a3c014633c97dbdd5 Mon Sep 17 00:00:00 2001 From: Daniel Mader Date: Thu, 30 Nov 2023 17:41:44 +0100 Subject: [PATCH] feat: update OpenAPI spec, add tracing, enhance Docker setup, update docs (#4) * feat: add issuance structure * feat: implement Credential aggregate handle * test: dummy handle implementation * feat: add CredentialTemplateCreated event * feat: add agent_store * test: add integration test * feat: add jsonschema dep * feat: add JSONschema's * feat: add queries agent_api and agent_application * refactor: move handlers to agent_issuance * refactor: rename resources folder * chore: add workspace * refactor: move api code to crate * ci: add docker files * chore: clean up deps, define workspace deps, add basic crate descriptions * style: set lines max_width to 120 * feat: add IssuanceData and openid4vci * WIP * feat: add axum_auth * test: fix tests * feat: add in memory application state * fix: lil' fix * style: fix clippy * style: clean code * fix: rename credential_query to issuance_data_query * fix: replace snakecase * docs: update OpenAPI spec * fix: resolve review comments * style: move fonfig() to config.rs * fix: fix messages * feat: add endpoints * feat: prepare multisubject * feat: implement thiserror for agent_issuance * feat: add tracing, add timestamps to events * refactor: extract init function * ci: enhance docker setup for local development * feat: prepare startup_events * test: add postman collection * fix: undo openapi changes * fix: newline * fix: temporary fix for unsafe indexing * fix: clean * refactor: use tracing * fix: use workspace * fix: apply review changes * chore: add license * fix: remove unsafe pre-authorized_code * fix: remove unsafe pre-authorized_code * WIP * feat: add config options for log format and event store * docs: update documentation * chore: revert postman collection changes --------- Co-authored-by: nanderstabel --- .env.example | 3 + Cargo.lock | 377 +++++++++++++----- Cargo.toml | 6 +- LICENSE | 176 ++++++++ README.md | 9 + agent_api_rest/Cargo.toml | 3 +- agent_api_rest/README.md | 10 +- agent_api_rest/openapi.yaml | 82 +++- agent_api_rest/res/open-badge-request.json | 25 +- agent_api_rest/src/offers.rs | 2 +- agent_application/Cargo.toml | 4 + agent_application/README.md | 4 +- agent_application/docker/.dockerignore | 3 + agent_application/docker/Dockerfile | 6 +- agent_application/docker/README.md | 20 +- .../docker/db/pgadmin4/servers.json | 13 + agent_application/docker/docker-compose.yml | 16 +- agent_application/src/main.rs | 72 ++-- agent_issuance/Cargo.toml | 2 + agent_issuance/README.md | 4 + agent_issuance/src/handlers.rs | 17 +- agent_issuance/src/init.rs | 23 ++ agent_issuance/src/lib.rs | 1 + agent_issuance/src/model/aggregate.rs | 12 +- agent_issuance/src/queries.rs | 3 +- agent_store/Cargo.toml | 4 +- agent_store/README.md | 4 +- agent_store/src/config.rs | 2 +- 28 files changed, 735 insertions(+), 168 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 agent_application/docker/.dockerignore create mode 100644 agent_application/docker/db/pgadmin4/servers.json create mode 100644 agent_issuance/README.md create mode 100644 agent_issuance/src/init.rs diff --git a/.env.example b/.env.example index 631f0bd7..34eebb93 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,4 @@ +AGENT_CONFIG_LOG_FORMAT=json +AGENT_CONFIG_EVENT_STORE=postgres +AGENT_APPLICATION_HOST=my-domain.example.org AGENT_STORE_DB_CONNECTION_STRING=postgresql://demo_user:demo_pass@localhost:5432/demo diff --git a/Cargo.lock b/Cargo.lock index e47f6e39..ec1adf03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,7 @@ dependencies = [ "axum", "axum-auth", "axum-macros", + "http-api-problem", "hyper", "lazy_static", "mime", @@ -35,6 +36,7 @@ dependencies = [ "serde_json", "tokio", "tower", + "tracing", "url", "uuid", ] @@ -47,10 +49,14 @@ dependencies = [ "agent_issuance", "agent_store", "axum", + "config", + "dotenvy", "lazy_static", "oid4vci", "serde_json", "tokio", + "tracing", + "tracing-subscriber", "url", ] @@ -71,7 +77,9 @@ dependencies = [ "serde", "serde_json", "thiserror", + "time", "tokio", + "tracing", "url", "uuid", ] @@ -180,7 +188,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -190,7 +198,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -234,6 +242,16 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atomic-write-file" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae364a6c1301604bbc6dfbf8c385c47ff82301dd01eef506195a029196d8d04" +dependencies = [ + "nix", + "rand 0.8.5", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -360,9 +378,9 @@ checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "base64-url" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5b0a88aa36e9f095ee2e2b13fb8c5e4313e022783aedacc123328c0084916d" +checksum = "ed5efc028d778cd6fb4d1779ca001b3282717e34127b726593002506aa77ca08" dependencies = [ "base64 0.21.5", ] @@ -507,14 +525,14 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] name = "clap" -version = "4.4.8" +version = "4.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" dependencies = [ "clap_builder", "clap_derive", @@ -522,9 +540,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.8" +version = "4.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" dependencies = [ "anstream", "anstyle", @@ -986,7 +1004,7 @@ dependencies = [ "elliptic-curve 0.13.8", "rfc6979 0.4.0", "signature 2.2.0", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] @@ -1029,7 +1047,7 @@ checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ "curve25519-dalek 4.1.1", "ed25519 2.2.3", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "hex", "rand_core 0.6.4", "sha2 0.10.8", @@ -1102,12 +1120,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1118,7 +1136,7 @@ checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1367,9 +1385,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "group" @@ -1432,9 +1450,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash 0.8.6", "allocator-api2", @@ -1446,7 +1464,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.2", + "hashbrown 0.14.3", ] [[package]] @@ -1548,7 +1566,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1562,6 +1580,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-api-problem" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce650a2c63171c9d92f95887c8d81154b77385301c8a581b9af96491f52d765" +dependencies = [ + "http", + "serde", + "serde_json", +] + [[package]] name = "http-body" version = "0.4.5" @@ -1783,7 +1812,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.2", + "hashbrown 0.14.3", "serde", ] @@ -1870,9 +1899,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" dependencies = [ "wasm-bindgen", ] @@ -2038,9 +2067,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", @@ -2136,7 +2165,7 @@ checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2150,6 +2179,17 @@ dependencies = [ "data-encoding-macro", ] +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -2160,6 +2200,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num" version = "0.4.1" @@ -2402,6 +2452,12 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "p256" version = "0.11.1" @@ -2467,7 +2523,7 @@ dependencies = [ "libc", "redox_syscall 0.4.1", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2591,7 +2647,7 @@ checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der 0.7.8", "pkcs8 0.10.2", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] @@ -2611,7 +2667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der 0.7.8", - "spki 0.7.2", + "spki 0.7.3", ] [[package]] @@ -2679,9 +2735,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -2856,7 +2912,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.25.3", + "webpki-roots", "winreg", ] @@ -2947,16 +3003,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.5" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +checksum = "684d5e6e18f669ccebf64a92236bb7db9a34f07be010e3627368182027180866" dependencies = [ "cc", "getrandom 0.2.11", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2972,9 +3028,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3211b01eea83d80687da9eef70e39d65144a3894866a5153a2723e425a157f" +checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" dependencies = [ "const-oid", "digest 0.10.7", @@ -2985,7 +3041,7 @@ dependencies = [ "pkcs8 0.10.2", "rand_core 0.6.4", "signature 2.2.0", - "spki 0.7.2", + "spki 0.7.3", "subtle", "zeroize", ] @@ -3025,7 +3081,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3035,7 +3091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", - "ring 0.17.5", + "ring 0.17.6", "rustls-webpki", "sct", ] @@ -3055,7 +3111,7 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.5", + "ring 0.17.6", "untrusted 0.9.0", ] @@ -3083,7 +3139,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.5", + "ring 0.17.6", "untrusted 0.9.0", ] @@ -3278,6 +3334,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3380,7 +3445,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3410,9 +3475,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der 0.7.8", @@ -3431,9 +3496,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e50c216e3624ec8e7ecd14c6a6a6370aad6ee5d8cfc3ab30b5162eeeef2ed33" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3444,9 +3509,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" dependencies = [ "ahash 0.8.6", "atoi", @@ -3482,14 +3547,14 @@ dependencies = [ "tokio-stream", "tracing", "url", - "webpki-roots 0.24.0", + "webpki-roots", ] [[package]] name = "sqlx-macros" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a793bb3ba331ec8359c1853bd39eed32cdd7baaf22c35ccf5c92a7e8d1189ec" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" dependencies = [ "proc-macro2", "quote", @@ -3500,10 +3565,11 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4ee1e104e00dedb6aa5ffdd1343107b0a4702e862a84320ee7cc74782d96fc" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" dependencies = [ + "atomic-write-file", "dotenvy", "either", "heck", @@ -3526,9 +3592,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" dependencies = [ "atoi", "base64 0.21.5", @@ -3568,9 +3634,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" dependencies = [ "atoi", "base64 0.21.5", @@ -3607,9 +3673,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" dependencies = [ "atoi", "flume", @@ -3625,6 +3691,7 @@ dependencies = [ "sqlx-core", "tracing", "url", + "urlencoding", ] [[package]] @@ -3752,7 +3819,7 @@ dependencies = [ "fastrand", "redox_syscall 0.4.1", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3775,6 +3842,16 @@ dependencies = [ "syn 2.0.39", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.3.30" @@ -3835,7 +3912,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.5.5", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -3969,6 +4046,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", + "tracing-serde", ] [[package]] @@ -4064,6 +4180,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8parse" version = "0.2.1" @@ -4081,6 +4203,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -4116,9 +4244,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -4126,9 +4254,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" dependencies = [ "bumpalo", "log", @@ -4141,9 +4269,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" dependencies = [ "cfg-if", "js-sys", @@ -4153,9 +4281,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4163,9 +4291,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", @@ -4176,9 +4304,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-timer" @@ -4197,23 +4325,14 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "webpki-roots" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b291546d5d9d1eab74f069c77749f2cb8504a12caa20f0f2de93ddbf6f411888" -dependencies = [ - "rustls-webpki", -] - [[package]] name = "webpki-roots" version = "0.25.3" @@ -4254,7 +4373,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -4263,7 +4382,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", ] [[package]] @@ -4272,13 +4400,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -4287,42 +4430,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.50.0" @@ -4330,7 +4515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d8a8d3da..8766477a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] - +resolver = "2" members = [ "agent_api_rest", "agent_application", @@ -8,9 +8,13 @@ members = [ ] [workspace.dependencies] +config = { version = "0.13" } +dotenvy = { version = "0.15" } 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" +tracing = { version = "0.1" } +tracing-subscriber = { version = "0.3", features = ["json"] } url = "2.5" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2bb9ad24 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..43d3bf86 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# SSI Agent + +## API specification + +[Follow these instructions](./agent_api_rest/README.md) to inspect the REST API. + +## Build & Run + +Build and run the **SSI Agent** in a local Docker environment following [these steps](./agent_application/docker/README.md). diff --git a/agent_api_rest/Cargo.toml b/agent_api_rest/Cargo.toml index 4344310f..12408d39 100644 --- a/agent_api_rest/Cargo.toml +++ b/agent_api_rest/Cargo.toml @@ -12,9 +12,11 @@ oid4vc-core = { git = "https://git@github.com/impierce/openid4vc.git", branch = axum = "0.6" axum-auth = "0.4" axum-macros = "0.3" +http-api-problem = "0.57" hyper = { version = "0.14", features = ["full"] } serde.workspace = true serde_json.workspace = true +tracing.workspace = true uuid.workspace = true [dev-dependencies] @@ -25,4 +27,3 @@ mime = { version = "0.3" } tokio = { version = "1.34", features = ["full"] } tower = { version = "0.4" } url.workspace = true - diff --git a/agent_api_rest/README.md b/agent_api_rest/README.md index b36ccaaa..558a593b 100644 --- a/agent_api_rest/README.md +++ b/agent_api_rest/README.md @@ -1,7 +1,11 @@ -# agent-api +# agent_api_rest -## Swagger UI +A lightweight REST API for the SSI Agent. + +### OpenAPI specification (Swagger UI) ```bash -docker run --rm -p 9090:8080 -e SWAGGER_JSON=/mnt/openapi.yaml -v $(pwd):/mnt swaggerapi/swagger-ui +docker run --rm -p 9090:8080 -e SWAGGER_JSON=/tmp/openapi.yaml -v $(pwd):/tmp swaggerapi/swagger-ui ``` + +Browse to http://localhost:9090 diff --git a/agent_api_rest/openapi.yaml b/agent_api_rest/openapi.yaml index c7c8f63b..988ee140 100644 --- a/agent_api_rest/openapi.yaml +++ b/agent_api_rest/openapi.yaml @@ -9,6 +9,26 @@ servers: description: Development paths: + /v1/subjects: + post: + summary: Create a new Subject for a given unique user-bound identifier + description: Works as a "get or create", will be used as the "pre-authorized code" + tags: + - Creation + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + value: + type: string + description: arbitrary value unique to a user + required: + - value + example: + value: "c86289fa-b105-4ec3-9326-a02436788f11" /v1/credentials: post: summary: Create a new Credential for a given Subject @@ -16,7 +36,7 @@ paths: tags: - Creation requestBody: - description: Any JSON object to be used as the Subject of the new Credential + # description: n/a required: true content: application/json: @@ -29,10 +49,16 @@ paths: # credentialTemplate: # type: string # description: The template to be used to create the credential - credentialSubject: + subjectId: + type: string + credential: type: object + properties: + credentialSubject: + type: object required: - - credentialSubject + - subjectId + - credential examples: open-badges-3: summary: Open Badges 3.0 @@ -54,3 +80,53 @@ paths: open-badges-3: summary: Open Badges 3.0 externalValue: res/open-badge-response.json + /v1/offers: + post: + summary: Create a new Offer for one or more Credentials + tags: + - Distribution + requestBody: + description: The id of the Subject + required: true + content: + application/json: + schema: + type: object + properties: + subjectId: + type: string + required: + - subjectId + example: + subjectId: "c86289fa-b105-4ec3-9326-a02436788f11" + responses: + "200": + description: Offer created successfully. Response value should be displayed to the user in the form of a QR code. + content: + text/plain: + schema: + type: string + example: openid-credential-offer://?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fcredential-issuer.example.com%2F%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22ldp_vc%22%2C%22credential_definition%22%3A%7B%22%40context%22%3A%5B%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fv1%22%2C%22https%3A%2F%2Fwww.w3.org%2F2018%2Fcredentials%2Fexamples%2Fv1%22%5D%2C%22type%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%7D%5D%7D + + # (proxied) + /.well-known/oauth-authorization-server: + get: + summary: Standard OpenID Connect discovery endpoint for authorization metadata + description: Standard OpenID Connect discovery endpoint for authorization metadata + tags: + - (proxied) + /.well-known/openid-credential-issuer: + get: + summary: Standard OpenID Connect discovery endpoint for issuer metadata + tags: + - (proxied) + /auth/token: + post: + summary: Standard OAuth 2.0 endpoint for fetching a token + tags: + - (proxied) + /openid4vci/credential: + post: + summary: Standard OpenID Connect endpoint for redeeming a token for a credential + tags: + - (proxied) diff --git a/agent_api_rest/res/open-badge-request.json b/agent_api_rest/res/open-badge-request.json index 0c6b0159..fa8a2391 100644 --- a/agent_api_rest/res/open-badge-request.json +++ b/agent_api_rest/res/open-badge-request.json @@ -1,15 +1,18 @@ { - "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" + "subjectId": "c86289fa-b105-4ec3-9326-a02436788f11", + "credential": { + "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" + } } } } diff --git a/agent_api_rest/src/offers.rs b/agent_api_rest/src/offers.rs index b832c0aa..3ceb9695 100644 --- a/agent_api_rest/src/offers.rs +++ b/agent_api_rest/src/offers.rs @@ -109,6 +109,6 @@ mod tests { 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"); + 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%22pre-authorized_code%22%2C%22user_pin_required%22%3Afalse%7D%7D%7D"); } } diff --git a/agent_application/Cargo.toml b/agent_application/Cargo.toml index 45ab9335..b2286ebc 100644 --- a/agent_application/Cargo.toml +++ b/agent_application/Cargo.toml @@ -11,7 +11,11 @@ agent_store = { path = "../agent_store" } oid4vci = { git = "https://git@github.com/impierce/openid4vc.git", branch = "feat/refactor-request" } axum = "0.6" +config.workspace = true +dotenvy.workspace = true lazy_static.workspace = true serde_json.workspace = true tokio = { version = "1.34", features = ["full"] } +tracing.workspace = true +tracing-subscriber.workspace = true url.workspace = true diff --git a/agent_application/README.md b/agent_application/README.md index 113097df..87804fc1 100644 --- a/agent_application/README.md +++ b/agent_application/README.md @@ -1,4 +1,4 @@ # agent_application -The application crate combines individual modules together as an executable. -In most cases, it will spin up a webserver and serve the API. +The application crate combines individual modules and makes them executable. +In most cases, it will simply spin up a webserver and serve the API. diff --git a/agent_application/docker/.dockerignore b/agent_application/docker/.dockerignore new file mode 100644 index 00000000..bc7a410f --- /dev/null +++ b/agent_application/docker/.dockerignore @@ -0,0 +1,3 @@ +Dockerfile +**/target +**/.git diff --git a/agent_application/docker/Dockerfile b/agent_application/docker/Dockerfile index 2c742d3b..15755deb 100644 --- a/agent_application/docker/Dockerfile +++ b/agent_application/docker/Dockerfile @@ -12,9 +12,9 @@ FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json RUN cargo chef cook --release --recipe-path recipe.json COPY . . -RUN cargo build --release --bin ssi-agent +RUN cargo build --release --bin agent_application FROM debian:bookworm-slim AS runtime WORKDIR /app -COPY --from=builder /app/target/release/ssi-agent /usr/local/bin -ENTRYPOINT ["/usr/local/bin/ssi-agent"] +COPY --from=builder /app/target/release/agent_application /usr/local/bin +ENTRYPOINT ["/usr/local/bin/agent_application"] diff --git a/agent_application/docker/README.md b/agent_application/docker/README.md index 3f969a2d..eaf45e63 100644 --- a/agent_application/docker/README.md +++ b/agent_application/docker/README.md @@ -1,23 +1,23 @@ # Docker -## Local development +## Build the image -SSI Agent + Postgres + Admin UI +From within the directory `/agent_application` run: ```bash -docker compose up -d +docker build -f docker/Dockerfile -t ssi-agent .. ``` -Go to `http://localhost:5433` to access the event store database. +## Local development -## Deployment +Inside the folder `/agent_application/docker`: -```bash -docker build -t ssi-agent . -``` +1. _Inside `docker-compose.yml` replace the value `` for the environment variable `AGENT_APPLICATION_HOST` with your actual local ip address (such as 192.168.1.234)_ -### Run +2. To start the **SSI Agent**, a **Postgres** database along with **pgadmin** (Postgres Admin Interface) simply run: ```bash -docker run --rm -p 3033:3033 ssi-agent +docker compose up -d ``` + +3. The REST API will be served at `http://0.0.0.0:3033` diff --git a/agent_application/docker/db/pgadmin4/servers.json b/agent_application/docker/db/pgadmin4/servers.json new file mode 100644 index 00000000..f278b4a0 --- /dev/null +++ b/agent_application/docker/db/pgadmin4/servers.json @@ -0,0 +1,13 @@ +{ + "Servers": { + "1": { + "Name": "Local Development", + "Group": "Servers", + "Host": "ssi-agent-cqrs-postgres-db-1", + "Port": 5432, + "MaintenanceDB": "demo", + "Username": "demo_user", + "SSLMode": "prefer" + } + } +} diff --git a/agent_application/docker/docker-compose.yml b/agent_application/docker/docker-compose.yml index b02b7f0b..c276acee 100644 --- a/agent_application/docker/docker-compose.yml +++ b/agent_application/docker/docker-compose.yml @@ -1,6 +1,6 @@ version: "3.1" -name: cqrs-postgres +name: ssi-agent services: cqrs-postgres-db: @@ -22,3 +22,17 @@ services: environment: PGADMIN_DEFAULT_EMAIL: admin@example.org PGADMIN_DEFAULT_PASSWORD: admin + volumes: + - "./db/pgadmin4/servers.json:/pgadmin4/servers.json" + depends_on: + - cqrs-postgres-db + ssi-agent: + image: ssi-agent + restart: always + ports: + - 3033:3033 + environment: + AGENT_CONFIG_LOG_FORMAT: json + AGENT_CONFIG_EVENT_STORE: postgres + AGENT_APPLICATION_HOST: + AGENT_STORE_DB_CONNECTION_STRING: postgresql://demo_user:demo_pass@cqrs-postgres-db:5432/demo diff --git a/agent_application/src/main.rs b/agent_application/src/main.rs index 089e78d4..d74aa745 100644 --- a/agent_application/src/main.rs +++ b/agent_application/src/main.rs @@ -1,19 +1,37 @@ use agent_api_rest::app; use agent_issuance::{ - command::IssuanceCommand, handlers::command_handler, model::aggregate::IssuanceData, queries::IssuanceDataView, - services::IssuanceServices, state::ApplicationState, + command::IssuanceCommand, + handlers::command_handler, + init::load_templates, + model::aggregate::IssuanceData, + queries::{IssuanceDataView, SimpleLoggingQuery}, + services::IssuanceServices, + state::ApplicationState, }; -use agent_store::in_memory; +use agent_store::{in_memory, postgres}; use oid4vci::credential_issuer::{ authorization_server_metadata::AuthorizationServerMetadata, credential_issuer_metadata::CredentialIssuerMetadata, }; use serde_json::json; use std::sync::Arc; +use tracing::info; #[tokio::main] async fn main() { - let state = Arc::new(in_memory::ApplicationState::new(vec![], IssuanceServices {}).await) - as ApplicationState; + let state = + match config().get_string("event_store").unwrap().as_str() { + "postgres" => Arc::new( + postgres::ApplicationState::new(vec![Box::new(SimpleLoggingQuery {})], IssuanceServices {}).await, + ) as ApplicationState, + _ => Arc::new( + in_memory::ApplicationState::new(vec![Box::new(SimpleLoggingQuery {})], IssuanceServices {}).await, + ) as ApplicationState, + }; + + match config().get_string("log_format").unwrap().as_str() { + "json" => tracing_subscriber::fmt().json().init(), + _ => tracing_subscriber::fmt::init(), + } tokio::spawn(startup_events(state.clone())); @@ -24,23 +42,11 @@ async fn main() { } async fn startup_events(state: ApplicationState) { - let base_url: url::Url = "http://0.0.0.0:3033/".parse().unwrap(); + info!("Starting up ..."); - 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), - }; + let host = config().get_string("host").unwrap(); + + let base_url: url::Url = format!("http://{}:3033/", host).parse().unwrap(); match command_handler( "agg-id-F39A0C".to_string(), @@ -55,7 +61,7 @@ async fn startup_events(state: ApplicationState) ) .await { - Ok(_) => println!("Startup task completed: `LoadAuthorizationServerMetadata`"), + Ok(_) => info!("Startup task completed: `LoadAuthorizationServerMetadata`"), Err(err) => println!("Startup task failed: {:#?}", err), }; @@ -76,10 +82,13 @@ async fn startup_events(state: ApplicationState) ) .await { - Ok(_) => println!("Startup task completed: `LoadCredentialIssuerMetadata`"), + Ok(_) => info!("Startup task completed: `LoadCredentialIssuerMetadata`"), Err(err) => println!("Startup task failed: {:#?}", err), }; + // Load templates + load_templates(&state).await; + match command_handler( "agg-id-F39A0C".to_string(), &state, @@ -112,3 +121,20 @@ async fn startup_events(state: ApplicationState) Err(err) => println!("Startup task failed: {:#?}", err), }; } + +/// Read environment variables +pub fn config() -> config::Config { + // Load global .env file + dotenvy::dotenv().ok(); + + // Build configuration + let config = config::Config::builder() + .add_source(config::Environment::with_prefix("AGENT_APPLICATION")) + .add_source(config::Environment::with_prefix("AGENT_CONFIG")) + .build() + .unwrap(); + + info!("{:?}", config); + + config +} diff --git a/agent_issuance/Cargo.toml b/agent_issuance/Cargo.toml index 1342453d..cd99630c 100644 --- a/agent_issuance/Cargo.toml +++ b/agent_issuance/Cargo.toml @@ -18,6 +18,8 @@ jsonwebtoken = "8.2" serde.workspace = true serde_json.workspace = true thiserror.workspace = true +time = { version = "0.3" } +tracing.workspace = true url.workspace = true uuid.workspace = true diff --git a/agent_issuance/README.md b/agent_issuance/README.md new file mode 100644 index 00000000..b2caac77 --- /dev/null +++ b/agent_issuance/README.md @@ -0,0 +1,4 @@ +# agent_issuance + +This module contains business logic for issuing credentials. This ranges from using a credential template, +applying user-specific subject data to it and offering the credential to a user wallet via the [OpenID4VCI](https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html) standard protocol. diff --git a/agent_issuance/src/handlers.rs b/agent_issuance/src/handlers.rs index da520ff1..f2c2d5d6 100644 --- a/agent_issuance/src/handlers.rs +++ b/agent_issuance/src/handlers.rs @@ -1,5 +1,8 @@ use crate::state::ApplicationState; use cqrs_es::{persist::PersistenceError, Aggregate, AggregateError, View}; +use std::collections::HashMap; +use time::format_description::well_known::Rfc3339; +use tracing::{debug, error}; pub async fn query_handler>( credential_id: String, @@ -7,11 +10,11 @@ pub async fn query_handler>( ) -> Result, PersistenceError> { match state.load(&credential_id).await { Ok(view) => { - println!("View: {:#?}\n", view); + debug!("View: {:#?}\n", view); Ok(view) } Err(err) => { - println!("Error: {:#?}\n", err); + error!("Error: {:#?}\n", err); Err(err) } } @@ -25,7 +28,11 @@ pub async fn command_handler>( where A::Command: Send + Sync, { - state - .execute_with_metadata(&aggregate_id, command, Default::default()) - .await + let mut metadata = HashMap::new(); + metadata.insert( + "timestamp".to_string(), + time::OffsetDateTime::now_utc().format(&Rfc3339).unwrap(), + ); + + state.execute_with_metadata(&aggregate_id, command, metadata).await } diff --git a/agent_issuance/src/init.rs b/agent_issuance/src/init.rs new file mode 100644 index 00000000..a16c2bef --- /dev/null +++ b/agent_issuance/src/init.rs @@ -0,0 +1,23 @@ +use crate::{ + command::IssuanceCommand, handlers::command_handler, model::aggregate::IssuanceData, queries::IssuanceDataView, + state::ApplicationState, +}; +use tracing::{info, warn}; + +pub async fn load_templates(state: &ApplicationState) { + match command_handler( + "agg-id-F39A0C".to_string(), + state, + IssuanceCommand::LoadCredentialFormatTemplate { + credential_format_template: serde_json::from_str(include_str!( + "../res/credential_format_templates/openbadges_v3.json" + )) + .unwrap(), + }, + ) + .await + { + Ok(_) => info!("Template loaded: \"Open Badges 3.0\""), + Err(err) => warn!("Template failed to load: {:#?}", err), + }; +} diff --git a/agent_issuance/src/lib.rs b/agent_issuance/src/lib.rs index 2484cf7f..9585fafe 100644 --- a/agent_issuance/src/lib.rs +++ b/agent_issuance/src/lib.rs @@ -2,6 +2,7 @@ pub mod command; pub mod error; pub mod event; pub mod handlers; +pub mod init; pub mod model; pub mod queries; pub mod services; diff --git a/agent_issuance/src/model/aggregate.rs b/agent_issuance/src/model/aggregate.rs index 47f7e10f..021acfa2 100644 --- a/agent_issuance/src/model/aggregate.rs +++ b/agent_issuance/src/model/aggregate.rs @@ -23,9 +23,9 @@ use oid4vci::{ use serde::{Deserialize, Serialize}; use serde_json::json; use std::sync::Arc; +use tracing::info; // 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"; @@ -121,6 +121,12 @@ impl Aggregate for IssuanceData { Ok(vec![IssuanceEvent::SubjectCreated { subject }]) } IssuanceCommand::CreateCredentialOffer { subject_id } => { + let subject = self + .subjects + .iter() + .find(|subject| subject.id == subject_id) + .ok_or(MissingIssuanceSubjectError(subject_id))?; + let credential_issuer_metadata = self .oid4vci_data .credential_issuer_metadata @@ -136,7 +142,7 @@ impl Aggregate for IssuanceData { grants: Some(Grants { authorization_code: None, pre_authorized_code: Some(PreAuthorizedCode { - pre_authorized_code: UNSAFE_PRE_AUTHORIZED_CODE.to_string(), + pre_authorized_code: subject.pre_authorized_code.clone(), ..Default::default() }), }), @@ -531,6 +537,8 @@ mod tests { .then_expect_events(vec![IssuanceEvent::credential_response_created()]); } + const UNSAFE_PRE_AUTHORIZED_CODE: &str = "unsafe_pre_authorized_code"; + lazy_static! { static ref CREDENTIAL_FORMAT_TEMPLATE: serde_json::Value = serde_json::from_str(include_str!("../../res/credential_format_templates/openbadges_v3.json")).unwrap(); diff --git a/agent_issuance/src/queries.rs b/agent_issuance/src/queries.rs index d9d3b957..7f12b67b 100644 --- a/agent_issuance/src/queries.rs +++ b/agent_issuance/src/queries.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use cqrs_es::{persist::GenericQuery, EventEnvelope, Query, View}; use serde::{Deserialize, Serialize}; +use tracing::info; use crate::model::aggregate::{IssuanceData, IssuanceSubject, OID4VCIData}; @@ -13,7 +14,7 @@ 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); + info!("{}-{} - {}", aggregate_id, event.sequence, payload); } } } diff --git a/agent_store/Cargo.toml b/agent_store/Cargo.toml index ae5e59c2..d00f87fe 100644 --- a/agent_store/Cargo.toml +++ b/agent_store/Cargo.toml @@ -10,8 +10,8 @@ cqrs-es = "0.4.2" postgres-es = "0.4.10" async-trait = "0.1" -config = "0.13" -dotenvy = "0.15" +config.workspace = true +dotenvy.workspace = true serde_json.workspace = true sqlx = { version = "0.7", features = [ "postgres", diff --git a/agent_store/README.md b/agent_store/README.md index 57b6b794..de2f4647 100644 --- a/agent_store/README.md +++ b/agent_store/README.md @@ -1,4 +1,4 @@ # agent_store -Persistence layer. Sets up the CQRS framework and creates the application state. -Sets up a connection to some database to store the events. +The persistence layer sets up the CQRS framework and manages the application state. +It will bind to some event store database to persist domain events. diff --git a/agent_store/src/config.rs b/agent_store/src/config.rs index d6772af9..a104dc0d 100644 --- a/agent_store/src/config.rs +++ b/agent_store/src/config.rs @@ -1,6 +1,6 @@ /// Read environment variables pub fn config() -> config::Config { - // Load .env file + // Load global .env file dotenvy::dotenv().ok(); // Build configuration