From 87269052e65e668c33f382718580359635002f7b Mon Sep 17 00:00:00 2001 From: Nander Stabel Date: Thu, 28 Nov 2024 10:36:29 +0100 Subject: [PATCH 01/15] feat: validate linked VPs in SIOP flow (#256) * WIP * Add Thuiswinkel verification checkmark * fix: fix logo_url binding, renameto thuiswinkel_validation * Rename `thuiswinkel_waarborg_verification` to `thuiswinkel_verification` in frontend * feat: add `issuance_date` to `ValidationResult` * Change label for domain verification * fix: fix `issuance_date` * Add issuance date and change logo position * chore(deps-dev): bump prettier-plugin-tailwindcss from 0.6.1 to 0.6.5 (#259) Bumps [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) from 0.6.1 to 0.6.5. - [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.6.1...v0.6.5) --- updated-dependencies: - dependency-name: prettier-plugin-tailwindcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @ianvs/prettier-plugin-sort-imports (#260) Bumps [@ianvs/prettier-plugin-sort-imports](https://github.com/ianvs/prettier-plugin-sort-imports) from 4.2.1 to 4.3.0. - [Release notes](https://github.com/ianvs/prettier-plugin-sort-imports/releases) - [Changelog](https://github.com/IanVS/prettier-plugin-sort-imports/blob/main/CHANGELOG.md) - [Commits](https://github.com/ianvs/prettier-plugin-sort-imports/compare/v4.2.1...v4.3.0) --- updated-dependencies: - dependency-name: "@ianvs/prettier-plugin-sort-imports" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump prettier-plugin-svelte from 3.2.3 to 3.2.5 (#262) Bumps [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte) from 3.2.3 to 3.2.5. - [Changelog](https://github.com/sveltejs/prettier-plugin-svelte/blob/master/CHANGELOG.md) - [Commits](https://github.com/sveltejs/prettier-plugin-svelte/compare/v3.2.3...v3.2.5) --- updated-dependencies: - dependency-name: prettier-plugin-svelte dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nander Stabel * chore(deps-dev): bump prettier from 3.3.0 to 3.3.2 (#261) Bumps [prettier](https://github.com/prettier/prettier) from 3.3.0 to 3.3.2. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.3.0...3.3.2) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Make `imageId` reactive * fix: fix lint errors * feat: add `validate_linked_verifiable_presentations` * refactor: apply clippy suggestions * feat: use `ServiceEndpoint::from` * refactor: remove unused variables * refactor: replace icon imports * fix: return `Err` when `verify` returns `Ok(false)` * fix: remove unused dependency * test: fix breaking tests --------- Signed-off-by: dependabot[bot] Co-authored-by: Thilo Maier Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 44 +- Cargo.toml | 4 +- identity-wallet/Cargo.toml | 6 +- .../bindings/user_prompt/CurrentUserPrompt.ts | 3 +- .../LinkedVerifiableCredentialData.ts | 3 + .../bindings/user_prompt/ValidationResult.ts | 2 +- identity-wallet/src/state/did/mod.rs | 1 + .../src/state/did/validate_domain_linkage.rs | 101 +- ...alidate_linked_verifiable_presentations.rs | 873 ++++++++++++++++++ .../reducers/read_authorization_request.rs | 14 +- identity-wallet/src/state/user_prompt.rs | 8 +- identity-wallet/src/subject.rs | 37 +- unime/src-tauri/Cargo.toml | 2 +- unime/src-tauri/tauri.conf.json | 2 +- unime/src-tauri/tests/common/mod.rs | 21 +- .../fixtures/states/accept_connection.json | 3 +- unime/src/i18n/de-DE/index.ts | 2 +- unime/src/i18n/en/index.ts | 2 +- unime/src/i18n/nl-NL/index.ts | 2 +- .../src/lib/components/StatusIndicator.svelte | 60 ++ unime/src/lib/components/index.ts | 1 + .../prompt/accept-connection/+page.svelte | 121 ++- unime/vite.config.ts | 2 +- 23 files changed, 1146 insertions(+), 168 deletions(-) create mode 100644 identity-wallet/bindings/user_prompt/LinkedVerifiableCredentialData.ts create mode 100644 identity-wallet/src/state/did/validate_linked_verifiable_presentations.rs create mode 100644 unime/src/lib/components/StatusIndicator.svelte diff --git a/Cargo.lock b/Cargo.lock index e346bb590..f632e41e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,7 +1316,7 @@ checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" [[package]] name = "consumer" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "did_iota", "did_jwk", @@ -1929,7 +1929,7 @@ dependencies = [ [[package]] name = "did_iota" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "bls12_381_plus 0.8.15", "identity_iota", @@ -1943,7 +1943,7 @@ dependencies = [ [[package]] name = "did_jwk" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "did-jwk", "identity_iota", @@ -1960,7 +1960,7 @@ dependencies = [ [[package]] name = "did_key" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "did-method-key", "identity_iota", @@ -1978,7 +1978,7 @@ dependencies = [ [[package]] name = "did_manager" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "consumer", "producer", @@ -2006,7 +2006,7 @@ dependencies = [ [[package]] name = "did_web" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "did-web", "identity_iota", @@ -4051,9 +4051,11 @@ dependencies = [ "dyn-clone", "futures", "icu", + "identity_core", "identity_credential", "identity_eddsa_verifier", "identity_iota", + "identity_jose", "iota_stronghold", "itertools 0.10.5", "jsonwebtoken", @@ -4062,13 +4064,13 @@ dependencies = [ "oid4vc", "p256 0.13.2", "reqwest 0.11.27", - "ring", "serde", "serde_json", + "serde_with 3.9.0", "serial_test", "sha256", "stronghold_engine", - "stronghold_ext", + "stronghold_ext 0.1.0 (git+https://github.com/tensor-programming/stronghold_ext)", "strum", "tauri", "tempfile", @@ -4281,7 +4283,7 @@ dependencies = [ [[package]] name = "identity_stronghold_ext" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "async-trait", "elliptic-curve 0.13.8", @@ -4293,7 +4295,7 @@ dependencies = [ "log", "p256 0.13.2", "serde_json", - "stronghold_ext", + "stronghold_ext 0.1.0 (git+https://github.com/impierce/stronghold_ext.git)", "tokio", ] @@ -6800,7 +6802,7 @@ dependencies = [ [[package]] name = "producer" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "did_iota", "did_jwk", @@ -8097,7 +8099,7 @@ dependencies = [ [[package]] name = "shared" version = "0.1.0" -source = "git+https://github.com/impierce/did-manager.git?rev=2b88f55#2b88f55b8f5e42aa794a701c5fe11bc962250139" +source = "git+https://git@github.com/impierce/did-manager.git?tag=v1.0.0-beta.3#3ad5e3dba7bc76df8d6cb4a4fd2df2238d88710b" dependencies = [ "identity_iota", "identity_storage", @@ -8687,6 +8689,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "stronghold_ext" +version = "0.1.0" +source = "git+https://github.com/impierce/stronghold_ext.git#cad0e5ac4d9011a38c88303b668e790e2f2f3a5e" +dependencies = [ + "ecdsa 0.16.9", + "iota_stronghold", + "k256", + "p256 0.13.2", + "rand 0.8.5", + "serde", + "sha2 0.10.8", + "stronghold-utils", + "stronghold_engine", + "thiserror", + "zeroize", +] + [[package]] name = "stronghold_ext" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0e4abb261..ddf1dae70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ tauri-runtime-wry = { version = "=2.0.0-beta.20" } tauri-utils = { version = "=2.0.0-beta.19", features = [ "resources" ] } tauri-winres = "=0.1" -did_manager = { git = "https://github.com/impierce/did-manager.git", rev = "2b88f55" } +did_manager = { git = "https://git@github.com/impierce/did-manager.git", tag = "v1.0.0-beta.3" } jsonwebtoken = "9.3" log = "^0.4" oid4vc = { git = "https://git@github.com/impierce/openid4vc.git", rev = "d095db0" } @@ -45,4 +45,4 @@ keywords = ["identity", "did", "ssi", "wallet", "siopv2"] license = "Apache-2.0" repository = "https://github.com/impierce/identity-wallet" edition = "2021" -rust-version = "1.75.0" +rust-version = "1.76.0" diff --git a/identity-wallet/Cargo.toml b/identity-wallet/Cargo.toml index f24eabfa0..6ccd95db0 100644 --- a/identity-wallet/Cargo.toml +++ b/identity-wallet/Cargo.toml @@ -2,7 +2,7 @@ name = "identity-wallet" version = "0.6.12" edition = "2021" -rust-version = "1.75.0" +rust-version.workspace = true [dependencies] tauri.workspace = true @@ -23,8 +23,10 @@ identity_credential = { version = "1.3", default-features = false, features = [ "presentation", "validator", ] } +identity_core = { version = "1.3" } identity_eddsa_verifier = { version = "1.3" } identity_iota = { version = "1.3" } +identity_jose = { version = "1.3" } iota_stronghold = { version = "2.1" } itertools = "0.10.5" jsonwebtoken.workspace = true @@ -37,6 +39,7 @@ reqwest = { version = "0.11", default-features = false, features = [ "rustls-tls", ] } serde = { version = "1.0", features = ["derive"] } +serde_with = "3.8" serde_json.workspace = true sha256 = "1.4" stronghold_engine = { version = "2.0.1" } @@ -51,7 +54,6 @@ url = "2.5" uuid = { version = "1.4", features = ["v4", "fast-rng", "serde"] } [dev-dependencies] -ring = "0.17" serial_test.workspace = true tempfile.workspace = true wiremock.workspace = true diff --git a/identity-wallet/bindings/user_prompt/CurrentUserPrompt.ts b/identity-wallet/bindings/user_prompt/CurrentUserPrompt.ts index fa82e0198..f7e7b9976 100644 --- a/identity-wallet/bindings/user_prompt/CurrentUserPrompt.ts +++ b/identity-wallet/bindings/user_prompt/CurrentUserPrompt.ts @@ -1,4 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { LinkedVerifiableCredentialData } from "./LinkedVerifiableCredentialData"; import type { ValidationResult } from "./ValidationResult"; -export type CurrentUserPrompt = { "type": "redirect", target: string, } | { "type": "password-required" } | { "type": "accept-connection", client_name: string, logo_uri?: string, redirect_uri: string, previously_connected: boolean, domain_validation: ValidationResult, } | { "type": "credential-offer", issuer_name: string, logo_uri?: string, credential_configurations: Record, } | { "type": "share-credentials", client_name: string, logo_uri?: string, options: Array, }; \ No newline at end of file +export type CurrentUserPrompt = { "type": "redirect", target: string, } | { "type": "password-required" } | { "type": "accept-connection", client_name: string, logo_uri?: string, redirect_uri: string, previously_connected: boolean, domain_validation: ValidationResult, linked_verifiable_presentations: Array, } | { "type": "credential-offer", issuer_name: string, logo_uri?: string, credential_configurations: Record, } | { "type": "share-credentials", client_name: string, logo_uri?: string, options: Array, }; \ No newline at end of file diff --git a/identity-wallet/bindings/user_prompt/LinkedVerifiableCredentialData.ts b/identity-wallet/bindings/user_prompt/LinkedVerifiableCredentialData.ts new file mode 100644 index 000000000..9203f2df2 --- /dev/null +++ b/identity-wallet/bindings/user_prompt/LinkedVerifiableCredentialData.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface LinkedVerifiableCredentialData { name: string | null, logo_uri: string | null, issuance_date: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/user_prompt/ValidationResult.ts b/identity-wallet/bindings/user_prompt/ValidationResult.ts index fcc211ac3..9a5206b0d 100644 --- a/identity-wallet/bindings/user_prompt/ValidationResult.ts +++ b/identity-wallet/bindings/user_prompt/ValidationResult.ts @@ -1,4 +1,4 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ValidationStatus } from "./ValidationStatus"; -export interface ValidationResult { status: ValidationStatus, message: string | null, } \ No newline at end of file +export interface ValidationResult { status: ValidationStatus, name?: string, logo_uri?: string, issuance_date?: string, message?: string, } \ No newline at end of file diff --git a/identity-wallet/src/state/did/mod.rs b/identity-wallet/src/state/did/mod.rs index 6d99f468f..60bd8a5ee 100644 --- a/identity-wallet/src/state/did/mod.rs +++ b/identity-wallet/src/state/did/mod.rs @@ -1,3 +1,4 @@ pub mod actions; pub mod reducers; pub mod validate_domain_linkage; +pub mod validate_linked_verifiable_presentations; diff --git a/identity-wallet/src/state/did/validate_domain_linkage.rs b/identity-wallet/src/state/did/validate_domain_linkage.rs index 41d7ef0d3..aea20441f 100644 --- a/identity-wallet/src/state/did/validate_domain_linkage.rs +++ b/identity-wallet/src/state/did/validate_domain_linkage.rs @@ -14,12 +14,18 @@ use identity_iota::{ use jsonwebtoken::{crypto::verify, jwk::Jwk as JsonWebTokenJwk, Algorithm, DecodingKey, Validation}; use log::info; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; use ts_rs::TS; +#[skip_serializing_none] #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, TS, Default)] #[ts(export, export_to = "bindings/user_prompt/ValidationResult.ts")] pub struct ValidationResult { pub(crate) status: ValidationStatus, + pub(crate) name: Option, + #[ts(type = "string", optional)] + pub(crate) logo_uri: Option, + pub(crate) issuance_date: Option, pub(crate) message: Option, } @@ -33,7 +39,7 @@ pub enum ValidationStatus { } /// This `Verifier` uses `jsonwebtoken` under the hood to verify verification input. -struct Verifier; +pub struct Verifier; impl JwsVerifier for Verifier { fn verify(&self, input: VerificationInput, public_key: &IotaIdentityJwk) -> Result<(), SignatureVerificationError> { use SignatureVerificationErrorKind::*; @@ -59,8 +65,8 @@ impl JwsVerifier for Verifier { &decoding_key, algorithm, ) { - Ok(_) => Ok(()), - Err(_) => Err(SignatureVerificationError::new( + Ok(true) => Ok(()), + Err(_) | Ok(false) => Err(SignatureVerificationError::new( // TODO: more fine-grained error handling? InvalidSignature, )), @@ -78,6 +84,7 @@ pub async fn validate_domain_linkage(url: url::Url, did: &str) -> ValidationResu return ValidationResult { status: ValidationStatus::Unknown, message: Some(e.to_string()), + ..Default::default() }; } }; @@ -92,10 +99,13 @@ pub async fn validate_domain_linkage(url: url::Url, did: &str) -> ValidationResu return ValidationResult { status: ValidationStatus::Unknown, message: Some(e.to_string()), + ..Default::default() }; } }; + info!("Resolved document: {:?}", document); + let url = identity_iota::core::Url::from(url); let res = validator.validate_linkage( @@ -108,12 +118,13 @@ pub async fn validate_domain_linkage(url: url::Url, did: &str) -> ValidationResu if res.is_ok() { ValidationResult { status: ValidationStatus::Success, - message: None, + ..Default::default() } } else { ValidationResult { status: ValidationStatus::Failure, message: res.err().map(|e| e.to_string()), + ..Default::default() } } } @@ -151,15 +162,17 @@ async fn fetch_configuration(mut url: url::Url) -> Result, + pub logo_uri: Option, + pub issuance_date: String, + #[ts(skip)] + pub issuer_linked_domains: Vec, +} + +// Skip the partial equality check for `issuance_date` during testing. +#[cfg(test)] +impl PartialEq for LinkedVerifiableCredentialData { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + && self.logo_uri == other.logo_uri + && self.issuer_linked_domains == other.issuer_linked_domains + } +} + +/// Validate the linked verifiable presentations for the given holder DID. Returns a list of linked verifiable +/// credential data. It starts by resolving the holder DID and then iterates over the linked verifiable presentation +/// URLs. For each linked verifiable presentation, it validates the presentation and then validates the linked +/// verifiable credentials. It only considers linked verifiable credentials with successful domain linkage validation. +pub async fn validate_linked_verifiable_presentations(holder_did: &str) -> Vec> { + info!("Validating linked verifiable presentations for holder DID: {holder_did}"); + + let resolver = Resolver::new().await; + + let holder_document = match resolver.resolve(holder_did).await { + Ok(holder_document) => holder_document, + _ => { + warn!("Failed to resolve holder DID: {holder_did}"); + return vec![]; + } + }; + + info!("Holder document: {holder_document:#?}"); + + iter( + // Get all linked verifiable presentation URLs from the holder document + holder_document + .service() + .iter() + .filter_map(get_linked_verifiable_presentation_urls) + .flatten(), + ) + .filter_map(|linked_verifiable_presentation_url| { + // Validate the linked verifiable presentation and get the linked verifiable credential data + get_validated_linked_presentation_data(&resolver, &holder_document, linked_verifiable_presentation_url) + }) + .collect::>() + .await +} + +/// Get the linked verifiable presentation URLs from the service. It returns a list of URLs if the service type is a +/// `LinkedVerifiablePresentation`. +fn get_linked_verifiable_presentation_urls(service: &Service) -> Option> { + service + .type_() + .contains("LinkedVerifiablePresentation") + .then(|| { + info!("Found LinkedVerifiablePresentation service: {service:#?}"); + service.service_endpoint() + }) + .and_then(|service_endpoint| service_endpoint.to_json_value().ok()) + .and_then( + // Parse the linked verifiable presentation URLs from the service endpoint. The service endpoint must be + // either a string or an array of strings: https://identity.foundation/linked-vp/#linked-verifiable-presentation + |linked_verifiable_presentation_urls| match linked_verifiable_presentation_urls { + Value::String(url) => url + .parse() + .inspect_err(|err| warn!("Failed to parse linked verifiable presentation URL: {}", err)) + .ok() + .map(|url| vec![url]), + Value::Array(array) => Some( + array + .into_iter() + .filter_map(|url| { + url.as_str().and_then(|url| { + url.parse() + .inspect_err(|err| { + warn!("Failed to parse linked verifiable presentation URL: {}", err) + }) + .ok() + }) + }) + .collect(), + ), + _ => None, + }, + ) +} + +/// Validate the linked verifiable presentations for the given holder document and linked verifiable presentation URL. +/// It returns a list of linked verifiable credential data. +async fn get_validated_linked_presentation_data( + resolver: &Resolver, + holder_document: &CoreDocument, + linked_verifiable_presentation_url: Url, +) -> Option> { + OptionFuture::from( + validate_linked_verifiable_presentation(holder_document, linked_verifiable_presentation_url) + .await + .map(|linked_verifiable_presentation| { + get_validated_linked_credential_data(resolver, linked_verifiable_presentation) + }), + ) + .await +} + +/// Retrieves the linked verifiable presentation from the given URL and validates it against the holder document. +/// Returns the decoded linked verifiable presentation if successful. +async fn validate_linked_verifiable_presentation( + holder_document: &CoreDocument, + linked_verifiable_presentation_url: Url, +) -> Option> { + let response = reqwest::get(linked_verifiable_presentation_url) + .await + .inspect_err(|err| { + warn!("Failed to retrieve linked verifiable presentation: {}", err); + }) + .ok()?; + let status = response.status(); + + response + .text() + .await + .inspect_err(|err| { + warn!("Failed to read linked verifiable presentation response: {}", err); + }) + .ok() + .and_then(|presentation_jwt| { + status.is_success().then(|| { + let validator = JwtPresentationValidator::with_signature_verifier(Verifier); + validator + .validate(&presentation_jwt.into(), &holder_document, &Default::default()) + .inspect_err(|err| { + warn!("Failed to validate linked verifiable presentation: {:#?}", err); + }) + .ok() + })? + }) +} + +/// Validate the linked verifiable credentials in the linked verifiable presentation. Skips invalid credentials or credentials with invalid domain linkage. +/// Since anyone can host a linked verifiable presentation, it is important to validate the linked verifiable +/// credentials. The `issuer` field in the linked verifiable credential is used to resolve the issuer document and which +/// is then used to retrieve the linked domains. The linked domains then are used to validate the domain linkage. +async fn get_validated_linked_credential_data( + resolver: &Resolver, + linked_verifiable_presentation: DecodedJwtPresentation, +) -> Vec { + iter(linked_verifiable_presentation.presentation.verifiable_credential) + .filter_map(|linked_verifiable_credential| async move { + // Resolve the issuer document and issuer DID + let issuer_document = get_issuer_document(resolver, &linked_verifiable_credential).await?; + let issuer_did = issuer_document.id().to_string(); + + info!("Issuer document: {issuer_document:#?}"); + + // Resolve the issuer linked domains from the issuer document + let issuer_linked_domains = get_issuer_linked_domains(&issuer_document).await; + + info!("Issuer linked domains: {issuer_linked_domains:#?}"); + + // Only linked verifiable credentials with at least one successful domain linkage validation are considered + let validated_linked_domains = get_validated_linked_domains(&issuer_linked_domains, &issuer_did).await; + if !validated_linked_domains.is_empty() { + let validator = JwtCredentialValidator::with_signature_verifier(Verifier); + + // Decode the linked verifiable credential and validate it + if let Ok(linked_verifiable_credential) = validator.validate::<_, Value>( + &linked_verifiable_credential, + &issuer_document, + &Default::default(), + FailFast::FirstError, + ) { + info!("Validated linked verifiable credential: {linked_verifiable_credential:#?}"); + + let credential_subject = match &linked_verifiable_credential.credential.credential_subject { + OneOrMany::One(subject) => Some(subject), + // TODO: how to handle multiple credential subjects? + OneOrMany::Many(subjects) => subjects.first(), + }; + + OptionFuture::from(credential_subject.map(|credential_subject| async { + let name = get_name(credential_subject); + let logo_uri = get_logo_uri(credential_subject).await; + let issuance_date = linked_verifiable_credential.credential.issuance_date.to_rfc3339(); + + LinkedVerifiableCredentialData { + name, + logo_uri, + issuance_date, + issuer_linked_domains: validated_linked_domains, + } + })) + .await + } else { + warn!("Failed to validate linked verifiable credential: {linked_verifiable_credential:#?}"); + // TODO: Should we add more fine-grained error handling? `None` here means that the linked verifiable credential is invalid. + None + } + } else { + warn!("No validated linked domains for issuer DID: {issuer_did}"); + // TODO: Should we add more fine-grained error handling? `None` here means that the domain linkage + // validation failed or is unknown. + None + } + }) + .collect::>() + .await +} + +/// Returns a Vec of successfully validated issuer linked domains. +async fn get_validated_linked_domains(issuer_linked_domains: &[Url], issuer_did: &str) -> Vec { + FuturesUnordered::from_iter(issuer_linked_domains.iter().map(|issuer_linked_domain| async move { + let validation_status = validate_domain_linkage(issuer_linked_domain.clone(), issuer_did) + .await + .status; + + if validation_status == ValidationStatus::Success { + info!("Successfully validated domain linkage for issuer linked domain: {issuer_linked_domain}"); + Some(issuer_linked_domain.clone()) + } else { + warn!("Failed to validate domain linkage for issuer linked domain: {issuer_linked_domain}"); + None + } + })) + .filter_map(|result| async move { result }) + .collect() + .await +} + +/// This function uses the linked verifiable credential to resolve the issuer document. +async fn get_issuer_document(resolver: &Resolver, linked_verifiable_credential: &Jwt) -> Option { + let decoder = Decoder::new(); + + // Decode the linked verifiable credential. + let decoded_linked_verifiable_credential = decoder + .decode_compact_serialization(linked_verifiable_credential.as_str().as_bytes(), None) + .inspect_err(|err| warn!("Failed to decode linked verifiable credential: {:#?}", err)) + .ok()?; + + let claims: JwtClaims = serde_json::from_slice(decoded_linked_verifiable_credential.claims()) + .inspect_err(|err| warn!("Failed to parse linked verifiable credential claims: {:#?}", err)) + .ok()?; + + info!("Linked verifiable credential claims: {:#?}", claims); + + // Resolve the DID + resolver + .resolve(claims.iss()?) + .await + .inspect_err(|err| warn!("Failed to resolve issuer DID.: {:#?}", err)) + .ok() +} + +/// Get the linked domains from the issuer document. It returns a list of URLs if the service type is `LinkedDomains`. +async fn get_issuer_linked_domains(issuer_document: &CoreDocument) -> Vec { + issuer_document + .service() + .iter() + .filter_map(|service| { + service + .type_() + .contains("LinkedDomains") + .then(|| service.service_endpoint()) + .and_then(|service_endpoint| service_endpoint.to_json_value().ok()) + .and_then(|linked_domain| { + linked_domain.get("origins").and_then(|origins| { + origins.as_array().and_then(|origins| { + origins + .iter() + .map(|origin| { + origin.as_str().and_then(|origin| { + origin + .parse() + .inspect_err(|err| warn!("Failed to parse linked domain: {:#?}", err)) + .ok() + }) + }) + .collect::>>() + }) + }) + }) + }) + .flatten() + .collect() +} + +fn get_name(credential_subject: &Subject) -> Option { + credential_subject + .properties + .get("name") + .and_then(Value::as_str) + .map(ToString::to_string) +} + +async fn get_logo_uri(credential_subject: &Subject) -> Option { + OptionFuture::from( + credential_subject + .properties + .get("image") + .and_then(Value::as_str) + .map(|image| async { + let _ = download_asset( + image + .parse() + .inspect_err(|err| warn!("Failed to parse logo URI: {:#?}", err)) + .ok()?, + &hash(image), + ) + .await; + Some(image.to_string()) + }), + ) + .await + .flatten() +} + +#[cfg(test)] +mod tests { + use super::*; + use base64::engine::general_purpose::URL_SAFE_NO_PAD; + use base64::Engine; + use did_manager::SecretManager; + use identity_credential::domain_linkage::{DomainLinkageConfiguration, DomainLinkageCredentialBuilder}; + use identity_iota::{ + core::{Duration, FromJson as _, Object, OrderedSet, Timestamp, Url}, + credential::{Credential, CredentialBuilder, Presentation}, + document::{CoreDocument, Service, ServiceEndpoint}, + verification::jws::JwsAlgorithm, + }; + use jsonwebtoken::{Algorithm, Header}; + use serde_json::json; + use std::sync::Arc; + use tempfile::TempDir; + use tokio::sync::Mutex; + use wiremock::{ + matchers::{method, path}, + Mock, MockServer, ResponseTemplate, + }; + + // 'Entity' struct that represents a digital identity including a DID Document, a domain, and a secret manager. + struct TestEntity { + pub mock_server: MockServer, + pub domain: url::Url, + pub did_document: CoreDocument, + pub secret_manager: Arc>, + } + + impl TestEntity { + // Create a new 'Entity' with a DID Document, mock server, a domain, and a secret manager. + async fn new() -> Self { + engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + + let mock_server = MockServer::start().await; + + let uri = mock_server.uri(); + let port = uri.split(':').last().unwrap(); + let domain: url::Url = format!("http://localhost:{port}").parse().unwrap(); + + let temp_dir = TempDir::new().unwrap(); + let path = temp_dir.path().join("stronghold.stronghold"); + let snapshot_path = path.as_os_str().to_str().unwrap(); + + let mut secret_manager = SecretManager::builder() + .snapshot_path(snapshot_path) + .password("sup3rSecr3t") + .build() + .await + .unwrap(); + + let did_document = secret_manager + .produce_document( + did_manager::DidMethod::Web, + Some(did_manager::MethodSpecificParameters::Web { + origin: domain.origin(), + }), + identity_iota::verification::jws::JwsAlgorithm::ES256, + ) + .await + .unwrap(); + + TestEntity { + mock_server, + domain, + did_document, + secret_manager: Arc::new(Mutex::new(secret_manager)), + } + } + + // Add the `.well-known/did.json` endpoint to the mock server. + async fn add_well_known_did_json(&self) { + Mock::given(method("GET")) + .and(path(".well-known/did.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!(self.did_document))) + .mount(&self.mock_server) + .await; + } + + // Add the `.well-known/did-configuration.json` endpoint to the mock server. + async fn add_well_known_did_configuration_json(&mut self, service_id: &str, origins: &[Url]) { + let service = Service::builder(Default::default()) + .id(format!("{}#{service_id}", self.did_document.id()).parse().unwrap()) + .type_("LinkedDomains") + .service_endpoint( + serde_json::from_value::(serde_json::json!( + { + "origins": origins + } + )) + .unwrap(), + ) + .build() + .expect("Failed to create DID Configuration Resource"); + self.did_document + .insert_service(service) + .expect("Service already exists in DID Document"); + + let domain_linkage_configuration = { + let origin = Url::parse(self.domain.origin().ascii_serialization()).unwrap(); + let payload = DomainLinkageCredentialBuilder::new() + .issuer(self.did_document.id().clone()) + .origin(origin) + .issuance_date(Timestamp::now_utc()) + .expiration_date(Timestamp::now_utc().checked_add(Duration::seconds(60)).unwrap()) + .build() + .and_then(|credential| credential.serialize_jwt(Default::default())) + .unwrap(); + + DomainLinkageConfiguration::new(vec![self.generate_jwt(payload).await]) + }; + + Mock::given(method("GET")) + .and(path(".well-known/did-configuration.json")) + .respond_with(ResponseTemplate::new(200).set_body_json(json!(domain_linkage_configuration))) + .mount(&self.mock_server) + .await; + } + + // Add a linked verifiable presentation to the DID Document and the mock server. + async fn add_linked_verifiable_presentation( + &mut self, + service_id: &str, + linked_verifiable_presentation_data: Vec<(String, Vec)>, + ) { + let mut urls: Vec = vec![]; + + for (linked_verifiable_presentation_endpoint, linked_verifiable_credential_jwts) in + linked_verifiable_presentation_data + { + let url = format!( + "{}/{linked_verifiable_presentation_endpoint}", + self.domain.origin().ascii_serialization() + ) + .parse() + .unwrap(); + urls.push(url); + + let linked_verifiable_presentation = { + let presentation = { + let mut builder = + Presentation::builder(self.did_document.id().to_string().parse().unwrap(), Object::new()); + for linked_verifiable_credential_jwt in linked_verifiable_credential_jwts { + builder = builder.credential(linked_verifiable_credential_jwt); + } + builder.build().unwrap() + }; + + self.generate_jwt(presentation.serialize_jwt(&Default::default()).unwrap()) + .await + }; + + Mock::given(method("GET")) + .and(path(format!("/{linked_verifiable_presentation_endpoint}"))) + .respond_with(ResponseTemplate::new(200).set_body_string(linked_verifiable_presentation.as_str())) + .mount(&self.mock_server) + .await; + } + + let service_endpoint = match urls.len() { + // Value::String + 1 => ServiceEndpoint::from(urls[0].clone()), + // Value::Array + _ => ServiceEndpoint::from(OrderedSet::from_iter(urls)), + }; + let service = Service::builder(Default::default()) + .id(format!("{}#{service_id}", self.did_document.id()).parse().unwrap()) + .type_("LinkedVerifiablePresentation") + .service_endpoint(service_endpoint) + .build() + .unwrap(); + + self.did_document + .insert_service(service) + .expect("Service already exists in DID Document"); + } + + // 'Issues' a Credential Jwt to a subject. + async fn issue_credential(&mut self, subject_id: &str, subject_name: &str, subject_image: Url) -> Jwt { + let subject = identity_credential::credential::Subject::from_json_value(json!({ + "id": subject_id, + "name": subject_name, + "image": subject_image + })) + .unwrap(); + + let issuer = identity_iota::credential::Issuer::Url(self.did_document.id().to_string().parse().unwrap()); + + let credential: Credential = CredentialBuilder::default() + .issuer(issuer) + .subject(subject) + .build() + .unwrap(); + + self.generate_jwt(credential.serialize_jwt(Default::default()).unwrap()) + .await + } + + // Generates a JWT with the given payload. + async fn generate_jwt(&mut self, payload: String) -> Jwt { + let subject_did = self.did_document.id().to_string(); + + // Compose JWT + let header = Header { + alg: Algorithm::ES256, + typ: Some("JWT".to_string()), + kid: Some(format!("{subject_did}#key-0")), + ..Default::default() + }; + + let message = [ + URL_SAFE_NO_PAD.encode(serde_json::to_vec(&header).unwrap().as_slice()), + URL_SAFE_NO_PAD.encode(payload.as_bytes()), + ] + .join("."); + + let secret_manager = self.secret_manager.lock().await; + + let proof_value = secret_manager + .sign(message.as_bytes(), JwsAlgorithm::ES256) + .await + .unwrap(); + let signature = URL_SAFE_NO_PAD.encode(proof_value.as_slice()); + let message = [message, signature].join("."); + + Jwt::from(message) + } + } + + #[tokio::test] + async fn validate_linked_verifiable_presentations_successfully_validates_multiple_presentations() { + let mut holder = TestEntity::new().await; + + let mut issuer_a = TestEntity::new().await; + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer A mock server. + issuer_a + .add_well_known_did_configuration_json("linked-domain", &[issuer_a.domain.clone().into()]) + .await; + issuer_a.add_well_known_did_json().await; + + let mut issuer_b = TestEntity::new().await; + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer B mock server. + issuer_b + .add_well_known_did_configuration_json("linked-domain", &[issuer_b.domain.clone().into()]) + .await; + issuer_b.add_well_known_did_json().await; + + let verifiable_credential_jwt = issuer_a + .issue_credential( + holder.did_document.id().to_string().as_str(), + "Webshop", + "https://webshop.com/logo.jpg".parse().unwrap(), + ) + .await; + + let service_id = "linked-verifiable-presentation"; + let linked_verifiable_presentation_endpoint = "linked-verifiable-presentation.jwt"; + + // Add the first linked verifiable presentation endpoint and the service to the holder DID Document. + holder + .add_linked_verifiable_presentation( + service_id, + vec![( + linked_verifiable_presentation_endpoint.to_string(), + vec![verifiable_credential_jwt], + )], + ) + .await; + + let verifiable_credential_jwt_2 = issuer_b + .issue_credential( + holder.did_document.id().to_string().as_str(), + "Webshop", + "https://webshop.com/logo.jpg".parse().unwrap(), + ) + .await; + + let service_id2 = "linked-verifiable-presentation-2"; + + // Add the second linked verifiable presentation endpoint and the service to the holder DID Document. + let linked_verifiable_presentation_endpoint2 = "linked-verifiable-presentation2.jwt"; + holder + .add_linked_verifiable_presentation( + service_id2, + vec![( + linked_verifiable_presentation_endpoint2.to_string(), + vec![verifiable_credential_jwt_2], + )], + ) + .await; + + holder.add_well_known_did_json().await; + + assert_eq!( + validate_linked_verifiable_presentations(holder.did_document.id().to_string().as_ref()).await, + vec![ + vec![LinkedVerifiableCredentialData { + name: Some("Webshop".to_string()), + logo_uri: Some("https://webshop.com/logo.jpg".to_string()), + issuer_linked_domains: vec![issuer_a.domain.clone()], + ..Default::default() + }], + vec![LinkedVerifiableCredentialData { + name: Some("Webshop".to_string()), + logo_uri: Some("https://webshop.com/logo.jpg".to_string()), + issuer_linked_domains: vec![issuer_b.domain.clone()], + ..Default::default() + }] + ] + ); + } + + #[tokio::test] + async fn validate_linked_verifiable_presentations_successfully_considers_missing_issuer_domain_linkage() { + let mut holder = TestEntity::new().await; + + let mut issuer = TestEntity::new().await; + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer mock server. + issuer + .add_well_known_did_configuration_json("linked-domain", &[issuer.domain.clone().into()]) + .await; + + // This time we do not add the `/did.json` endpoint to the issuer mock server, which makes it impossible to + // validate the domain linkage of the issuer. + // issuer.add_well_known_did_json().await; + + let verifiable_credential_jwt = issuer + .issue_credential( + holder.did_document.id().to_string().as_str(), + "Webshop", + "https://webshop.com/logo.jpg".parse().unwrap(), + ) + .await; + + let service_id = "linked-verifiable-presentation"; + let linked_verifiable_presentation_endpoint = "linked-verifiable-presentation.jwt"; + + // Add the linked verifiable presentation endpoint and the service to the holder DID Document. + holder + .add_linked_verifiable_presentation( + service_id, + vec![( + linked_verifiable_presentation_endpoint.to_string(), + vec![verifiable_credential_jwt], + )], + ) + .await; + + holder.add_well_known_did_json().await; + + assert_eq!( + validate_linked_verifiable_presentations(holder.did_document.id().to_string().as_ref()).await, + // The domain linkage validation of the issuer failed, so the linked verifiable credential is not considered. + vec![vec![]] + ); + } + + #[tokio::test] + async fn get_linked_verifiable_presentation_urls_successfully_retrieves_urls() { + let mut holder = TestEntity::new().await; + + let service_id = "linked-verifiable-presentation"; + let linked_verifiable_presentation_endpoint = "linked-verifiable-presentation.jwt"; + let linked_verifiable_presentation_endpoint2 = "linked-verifiable-presentation2.jwt"; + holder + .add_linked_verifiable_presentation( + service_id, + vec![ + ( + linked_verifiable_presentation_endpoint.to_string(), + // Linked verifiable presentation can include multiple linked verifiable credentials. + vec![Jwt::from("test1".to_string()), Jwt::from("test2".to_string())], + ), + ( + linked_verifiable_presentation_endpoint2.to_string(), + vec![Jwt::from("test3".to_string())], + ), + ], + ) + .await; + + // Assert that the URLs of both linked verifiable presentations are retrieved. + assert!( + get_linked_verifiable_presentation_urls(&holder.did_document.service()[0]) + .unwrap() + .iter() + .all(|item| [ + format!("{}{}", holder.domain, linked_verifiable_presentation_endpoint) + .parse() + .unwrap(), + format!("{}{}", holder.domain, linked_verifiable_presentation_endpoint2) + .parse() + .unwrap() + ] + .contains(item)) + ); + } + + #[tokio::test] + async fn get_validated_linked_credential_data_succesfully_returns_linked_verifiable_credential_data() { + let mut issuer = TestEntity::new().await; + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer mock server. + issuer + .add_well_known_did_configuration_json("linked-domain", &[issuer.domain.clone().into()]) + .await; + issuer.add_well_known_did_json().await; + + let mut holder = TestEntity::new().await; + + let verifiable_credential_jwt = issuer + .issue_credential( + holder.did_document.id().to_string().as_str(), + "Webshop", + "https://webshop.com/logo.jpg".parse().unwrap(), + ) + .await; + + let service_id = "linked-verifiable-presentation"; + let linked_verifiable_presentation_endpoint = "linked-verifiable-presentation.jwt"; + holder + .add_linked_verifiable_presentation( + service_id, + vec![( + linked_verifiable_presentation_endpoint.to_string(), + vec![verifiable_credential_jwt], + )], + ) + .await; + + let resolver = Resolver::new().await; + + let linked_verifiable_presentation_url: url::Url = + format!("{}{linked_verifiable_presentation_endpoint}", holder.domain) + .parse() + .unwrap(); + + let validated_linked_presentation_data = + get_validated_linked_presentation_data(&resolver, &holder.did_document, linked_verifiable_presentation_url) + .await; + + assert_eq!( + validated_linked_presentation_data, + Some(vec![LinkedVerifiableCredentialData { + name: Some("Webshop".to_string()), + logo_uri: Some("https://webshop.com/logo.jpg".to_string()), + issuer_linked_domains: vec![issuer.domain.clone()], + ..Default::default() + }]) + ); + } + + #[tokio::test] + async fn get_validated_linked_domains_returns_only_succesfully_validated_linked_domains() { + let mut issuer1 = TestEntity::new().await; + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer mock server. + issuer1 + .add_well_known_did_configuration_json("linked-domain", &[issuer1.domain.clone().into()]) + .await; + issuer1.add_well_known_did_json().await; + + // Succesfully validate the linked domain. + assert_eq!( + get_validated_linked_domains( + &[issuer1.domain.clone()], + issuer1.did_document.id().to_string().as_ref() + ) + .await, + vec![issuer1.domain.clone()] + ); + + // Assert that only one domain was validated. + assert_eq!( + get_validated_linked_domains( + &[issuer1.domain.clone(), "http://invalid-domain.org".parse().unwrap()], + issuer1.did_document.id().to_string().as_ref() + ) + .await, + vec![issuer1.domain.clone()] + ); + + let mut issuer2 = TestEntity::new().await; + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer mock server. + issuer2 + .add_well_known_did_configuration_json("linked-domain-2", &[issuer2.domain.clone().into()]) + .await; + issuer2.add_well_known_did_json().await; + + // Assert that only one domain was validated. The second domain cannot be validated because the issuer DID is different. + assert_eq!( + get_validated_linked_domains( + &[issuer1.domain.clone(), issuer2.domain.clone()], + issuer1.did_document.id().to_string().as_ref() + ) + .await, + vec![issuer1.domain.clone()] + ); + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer mock server. Use the same issuer DID as + // issuer1, but a different domain. + let mut issuer2 = TestEntity::new().await; + issuer2.did_document = issuer1.did_document.clone(); + issuer2.secret_manager = issuer1.secret_manager.clone(); + + // Add the `/did_configuration.json` and `/did.json` endpoints to the issuer mock server. + issuer2 + .add_well_known_did_configuration_json("linked-domain-2", &[issuer2.domain.clone().into()]) + .await; + issuer2.add_well_known_did_json().await; + + // Assert that both domains were validated (regardless of the order). + assert!(get_validated_linked_domains( + &[issuer1.domain.clone(), issuer2.domain.clone()], + issuer1.did_document.id().to_string().as_ref() + ) + .await + .iter() + .all(|item| [issuer1.domain.clone(), issuer2.domain.clone()].contains(item))); + } +} diff --git a/identity-wallet/src/state/qr_code/reducers/read_authorization_request.rs b/identity-wallet/src/state/qr_code/reducers/read_authorization_request.rs index a4a0e4c30..3b3451413 100644 --- a/identity-wallet/src/state/qr_code/reducers/read_authorization_request.rs +++ b/identity-wallet/src/state/qr_code/reducers/read_authorization_request.rs @@ -8,7 +8,10 @@ use crate::{ credentials::reducers::handle_oid4vp_authorization_request::{ get_oid4vp_client_name_and_logo_uri, OID4VPClientMetadata, }, - did::validate_domain_linkage::validate_domain_linkage, + did::{ + validate_domain_linkage::validate_domain_linkage, + validate_linked_verifiable_presentations::validate_linked_verifiable_presentations, + }, qr_code::actions::qrcode_scanned::QrCodeScanned, user_prompt::CurrentUserPrompt, AppState, @@ -82,7 +85,13 @@ pub async fn read_authorization_request(state: AppState, action: Action) -> Resu let did = siopv2_authorization_request.body.client_id.as_str(); - let domain_validation = validate_domain_linkage(url, did).await; + let domain_validation = Box::new(validate_domain_linkage(url, did).await); + + let linked_verifiable_presentations = validate_linked_verifiable_presentations(did) + .await + .into_iter() + .flatten() + .collect(); drop(state_guard); @@ -97,6 +106,7 @@ pub async fn read_authorization_request(state: AppState, action: Action) -> Resu redirect_uri, previously_connected, domain_validation, + linked_verifiable_presentations, }), ..state }); diff --git a/identity-wallet/src/state/user_prompt.rs b/identity-wallet/src/state/user_prompt.rs index 2afe33670..ba5a59613 100644 --- a/identity-wallet/src/state/user_prompt.rs +++ b/identity-wallet/src/state/user_prompt.rs @@ -6,6 +6,8 @@ use ts_rs::TS; use crate::state::did::validate_domain_linkage::ValidationResult; +use super::did::validate_linked_verifiable_presentations::LinkedVerifiableCredentialData; + /// "User prompts" are a way for the backend to communicate a desired/required user interaction to the frontend. /// This application design leaves it up to the frontend how it wants to handle such "user prompts". /// Having too much frontend logic in the backend would pollute the loose coupling and make it a lot harder to maintain. @@ -28,7 +30,8 @@ pub enum CurrentUserPrompt { logo_uri: Option, redirect_uri: String, previously_connected: bool, - domain_validation: ValidationResult, + domain_validation: Box, + linked_verifiable_presentations: Vec, }, #[serde(rename = "credential-offer")] CredentialOffer { @@ -72,10 +75,11 @@ mod tests { redirect_uri: "https://example.com".to_string(), previously_connected: false, domain_validation: Default::default(), + linked_verifiable_presentations: Default::default(), }; assert_eq!( serde_json::to_string(&prompt).unwrap(), - r#"{"type":"accept-connection","client_name":"Test Client","logo_uri":null,"redirect_uri":"https://example.com","previously_connected":false,"domain_validation":{"status":"Unknown","message":null}}"# + r#"{"type":"accept-connection","client_name":"Test Client","logo_uri":null,"redirect_uri":"https://example.com","previously_connected":false,"domain_validation":{"status":"Unknown"},"linked_verifiable_presentations":[]}"# ); } } diff --git a/identity-wallet/src/subject.rs b/identity-wallet/src/subject.rs index 04becca2d..fcb59c59c 100644 --- a/identity-wallet/src/subject.rs +++ b/identity-wallet/src/subject.rs @@ -11,13 +11,14 @@ use identity_iota::{ use jsonwebtoken::Algorithm; use oid4vc::oid4vc_core::{authentication::sign::ExternalSign, Sign, Verify}; use std::sync::Arc; +use tokio::sync::Mutex; /// A `Subject` implements functions required for signatures and verification. /// In UniMe, it serves as the "binding link" between the protocol libraries (OID4VC) and the secret management (DID Manager). #[derive(Debug)] pub struct Subject { pub stronghold_manager: Arc, - pub secret_manager: SecretManager, + pub secret_manager: Arc>, } #[async_trait] @@ -25,7 +26,9 @@ impl Sign for Subject { async fn key_id(&self, subject_syntax_type: &str, algorithm: Algorithm) -> Option { let method: DidMethod = serde_json::from_str(&format!("{subject_syntax_type:?}")).ok()?; - self.secret_manager + let mut secret_manager = self.secret_manager.lock().await; + + secret_manager .produce_document(method, None, algorithm.into_jws_algorithm()) .await .ok() @@ -34,8 +37,9 @@ impl Sign for Subject { } async fn sign(&self, message: &str, _subject_syntax_type: &str, algorithm: Algorithm) -> anyhow::Result> { - Ok(self - .secret_manager + let secret_manager = self.secret_manager.lock().await; + + Ok(secret_manager .sign(message.as_bytes(), algorithm.into_jws_algorithm()) .await?) } @@ -49,9 +53,9 @@ impl Sign for Subject { impl oid4vc::oid4vc_core::Subject for Subject { async fn identifier(&self, subject_syntax_type: &str, algorithm: Algorithm) -> anyhow::Result { let method: DidMethod = serde_json::from_str(&format!("{subject_syntax_type:?}"))?; + let mut secret_manager = self.secret_manager.lock().await; - Ok(self - .secret_manager + Ok(secret_manager .produce_document(method, None, algorithm.into_jws_algorithm()) .await .map(|document| document.id().to_string())?) @@ -114,17 +118,16 @@ pub async fn subject(stronghold_manager: Arc, password: Strin Arc::new(Subject { stronghold_manager: stronghold_manager.clone(), - secret_manager: SecretManager::load( - client_path, - password, - Some("ed25519-0".to_owned()), - Some("es256-0".to_owned()), - Some("es256k-0".to_owned()), - None, - None, - ) - .await - .unwrap(), + secret_manager: Arc::new(Mutex::new( + SecretManager::builder() + .snapshot_path(&client_path) + .with_ed25519_key("ed25519-0") + .with_es256_key("es256-0") + .password(&password) + .build() + .await + .unwrap(), + )), }) } diff --git a/unime/src-tauri/Cargo.toml b/unime/src-tauri/Cargo.toml index e303367f1..9189c3c55 100644 --- a/unime/src-tauri/Cargo.toml +++ b/unime/src-tauri/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["identity", "did", "ssi", "wallet", "siopv2"] license = "Apache-2.0" repository = "https://github.com/impierce/identity-wallet" edition = "2021" -rust-version = "1.75.0" +rust-version.workspace = true [lib] name = "unime" diff --git a/unime/src-tauri/tauri.conf.json b/unime/src-tauri/tauri.conf.json index ce1c48488..cc33e1681 100644 --- a/unime/src-tauri/tauri.conf.json +++ b/unime/src-tauri/tauri.conf.json @@ -27,7 +27,7 @@ "build": { "beforeDevCommand": "pnpm dev", "beforeBuildCommand": "pnpm build", - "devUrl": "http://localhost:5173", + "devUrl": "http://localhost:4173", "frontendDist": "../build" }, "bundle": { diff --git a/unime/src-tauri/tests/common/mod.rs b/unime/src-tauri/tests/common/mod.rs index c1852cb1f..b4054eeee 100644 --- a/unime/src-tauri/tests/common/mod.rs +++ b/unime/src-tauri/tests/common/mod.rs @@ -12,6 +12,7 @@ use identity_wallet::{ state::core_utils::{IdentityManager, Managers}, stronghold::StrongholdManager, }; +use tokio::sync::Mutex; use self::assert_state_update::setup_stronghold; use serde::de::DeserializeOwned; @@ -54,17 +55,15 @@ pub async fn test_managers( let subject: Arc = Arc::new(Subject { stronghold_manager: stronghold_manager.clone(), - secret_manager: SecretManager::load( - stronghold_snapshot_path, - TEST_PASSWORD.to_string(), - Some(KEY_ID.to_string()), - None, - None, - None, - None, - ) - .await - .unwrap(), + secret_manager: Arc::new(Mutex::new( + SecretManager::builder() + .snapshot_path(&stronghold_snapshot_path) + .with_ed25519_key(KEY_ID) + .password(TEST_PASSWORD) + .build() + .await + .unwrap(), + )), }); let provider_manager = ProviderManager::new( diff --git a/unime/src-tauri/tests/fixtures/states/accept_connection.json b/unime/src-tauri/tests/fixtures/states/accept_connection.json index 7c2c4aac9..ad93e2cca 100644 --- a/unime/src-tauri/tests/fixtures/states/accept_connection.json +++ b/unime/src-tauri/tests/fixtures/states/accept_connection.json @@ -15,6 +15,7 @@ "domain_validation": { "status": "Unknown", "message": "error decoding response body: expected value at line 1 column 1" - } + }, + "linked_verifiable_presentations": [] } } diff --git a/unime/src/i18n/de-DE/index.ts b/unime/src/i18n/de-DE/index.ts index 149ed0482..a69c0e0a3 100644 --- a/unime/src/i18n/de-DE/index.ts +++ b/unime/src/i18n/de-DE/index.ts @@ -298,7 +298,7 @@ const de = { }, }, DOMAIN_LINKAGE: { - TITLE: 'Verifiziert', + TITLE: 'Verifizierte Website', SUCCESS: 'UniMe konnte die Identität erfolgreich verifizieren, um dir einen sicheren Login zu ermöglichen.', FAILURE: 'UniMe konnte die Verknüpfung der Identität mit der Domain nicht überprüfen.', UNKNOWN: 'UniMe konnte keinen Nachweis über die verbundene Identität der Domain finden.', diff --git a/unime/src/i18n/en/index.ts b/unime/src/i18n/en/index.ts index c0882a7a4..f664c8f26 100644 --- a/unime/src/i18n/en/index.ts +++ b/unime/src/i18n/en/index.ts @@ -296,7 +296,7 @@ const en = { }, }, DOMAIN_LINKAGE: { - TITLE: 'Verified', + TITLE: 'Verified website', SUCCESS: 'UniMe successfully verified the identity to provide you with a secure login.', FAILURE: 'UniMe could not verify the linkage of the identity to the domain.', UNKNOWN: "UniMe could not find any proof of the domain's associated identity.", diff --git a/unime/src/i18n/nl-NL/index.ts b/unime/src/i18n/nl-NL/index.ts index fe14fc369..ee49897b9 100644 --- a/unime/src/i18n/nl-NL/index.ts +++ b/unime/src/i18n/nl-NL/index.ts @@ -297,7 +297,7 @@ const nl = { }, }, DOMAIN_LINKAGE: { - TITLE: 'Geverifieerd', + TITLE: 'Geverifieerde website', SUCCESS: 'UniMe heeft de identiteit met succes geverifieerd om u een veilige login te geven.', FAILURE: 'UniMe kon de koppeling van de identiteit aan het domein niet verifiëren.', UNKNOWN: 'UniMe kon geen bewijs vinden van de bijbehorende identiteit van het domein.', diff --git a/unime/src/lib/components/StatusIndicator.svelte b/unime/src/lib/components/StatusIndicator.svelte new file mode 100644 index 000000000..40a418294 --- /dev/null +++ b/unime/src/lib/components/StatusIndicator.svelte @@ -0,0 +1,60 @@ + + + + +
+
+

+ {title} +

+ {#if description} +

{description}

+ {/if} +
+ + {#if logoUrl} + + {/if} + + {#if status === 'Success'} + + {:else if status === 'Failure'} + + {:else} + + {/if} +
+ + +{#if $$slots.popover && $open} +
+
+ +
+{/if} diff --git a/unime/src/lib/components/index.ts b/unime/src/lib/components/index.ts index 11b5048d6..439e024b3 100644 --- a/unime/src/lib/components/index.ts +++ b/unime/src/lib/components/index.ts @@ -14,4 +14,5 @@ export { default as TopNavBar } from './navigation/TopNavBar.svelte'; export { default as PaddedIcon } from './PaddedIcon.svelte'; export { default as ProgressBar } from './ProgressBar.svelte'; export { default as SettingsEntry } from './SettingsEntry.svelte'; +export { default as StatusIndicator } from './StatusIndicator.svelte'; export { default as Switch } from './Switch.svelte'; diff --git a/unime/src/routes/prompt/accept-connection/+page.svelte b/unime/src/routes/prompt/accept-connection/+page.svelte index dd5b5eedb..b403ca041 100644 --- a/unime/src/routes/prompt/accept-connection/+page.svelte +++ b/unime/src/routes/prompt/accept-connection/+page.svelte @@ -3,27 +3,16 @@ import { goto } from '$app/navigation'; import LL from '$i18n/i18n-svelte'; - import { fade } from 'svelte/transition'; import type { CurrentUserPrompt } from '@bindings/user_prompt/CurrentUserPrompt'; - import { createPopover, melt } from '@melt-ui/svelte'; - import { Button, Image, PaddedIcon, TopNavBar } from '$lib/components'; + import { Button, Image, PaddedIcon, StatusIndicator, TopNavBar } from '$lib/components'; import { dispatch } from '$lib/dispatcher'; - import { - CheckBoldIcon, - PlugsConnectedFillIcon, - QuestionMarkBoldIcon, - WarningCircleFillIcon, - XRegularIcon, - } from '$lib/icons'; + import { PlugsConnectedFillIcon, WarningCircleFillIcon } from '$lib/icons'; import { error, state } from '$lib/stores'; - import { hash } from '$lib/utils'; + import { formatDate, hash } from '$lib/utils'; - const { - elements: { trigger, content, arrow }, - states: { open }, - } = createPopover(); + const profile_settings = $state.profile_settings; let loading = false; @@ -32,8 +21,14 @@ type IsAcceptConnectionPrompt = T extends { type: 'accept-connection' } ? T : never; type AcceptConnectionPrompt = IsAcceptConnectionPrompt; - const { client_name, domain_validation, logo_uri, previously_connected, redirect_uri } = - $state.current_user_prompt as AcceptConnectionPrompt; + const { + client_name, + domain_validation, + logo_uri, + previously_connected, + redirect_uri, + linked_verifiable_presentations, + } = $state.current_user_prompt as AcceptConnectionPrompt; $: ({ hostname } = new URL(redirect_uri)); $: imageId = logo_uri ? hash(logo_uri) : '_'; @@ -92,63 +87,51 @@
{/if} + -
-

- {$LL.SCAN.CONNECTION_REQUEST.CONNECTED_PREVIOUSLY()} -

- {#if previously_connected} - - {:else} - - {/if} -
+ + -
-
-

{$LL.DOMAIN_LINKAGE.TITLE()}

- {#if $open} -
-
-
- {#if domain_validation.status === 'Success'} - -

{$LL.DOMAIN_LINKAGE.SUCCESS()}

- {:else if domain_validation.status === 'Failure'} -

{$LL.DOMAIN_LINKAGE.FAILURE()}

- -

{$LL.DOMAIN_LINKAGE.CAUTION()}

- {:else} -

{$LL.DOMAIN_LINKAGE.UNKNOWN()}

- -

{$LL.DOMAIN_LINKAGE.CAUTION()}

- {/if} - - {#if $state.dev_mode !== 'Off' && domain_validation.message} - -

{domain_validation.message}

- {/if} -
-
+ +
+ {#if domain_validation.status === 'Success'} + +

{$LL.DOMAIN_LINKAGE.SUCCESS()}

+ {:else if domain_validation.status === 'Failure'} +

{$LL.DOMAIN_LINKAGE.FAILURE()}

+ +

{$LL.DOMAIN_LINKAGE.CAUTION()}

+ {:else} +

{$LL.DOMAIN_LINKAGE.UNKNOWN()}

+ +

{$LL.DOMAIN_LINKAGE.CAUTION()}

+ {/if} + + {#if $state.dev_mode !== 'Off' && domain_validation.message} + +

{domain_validation.message}

{/if}
- {#if domain_validation.status === 'Success'} - - {:else if domain_validation.status === 'Failure'} - - {:else} - +
+ + + {#each linked_verifiable_presentations as presentation} + {#if presentation.name} + {@const issuanceDate = + presentation.issuance_date && profile_settings.locale + ? formatDate(presentation.issuance_date, profile_settings.locale) + : undefined} + {/if} -
+ {/each}
diff --git a/unime/vite.config.ts b/unime/vite.config.ts index 2230c5af8..53e01e49d 100644 --- a/unime/vite.config.ts +++ b/unime/vite.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ clearScreen: false, server: { host: '0.0.0.0', - port: 5173, + port: 4173, strictPort: true, hmr: { protocol: 'ws', From 443ad9195756185b6aeaa587f4c66fb2ca2463a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:08:24 +0100 Subject: [PATCH 02/15] chore(deps-dev): bump tailwind-merge from 2.5.2 to 2.5.5 (#429) Bumps [tailwind-merge](https://github.com/dcastil/tailwind-merge) from 2.5.2 to 2.5.5. - [Release notes](https://github.com/dcastil/tailwind-merge/releases) - [Commits](https://github.com/dcastil/tailwind-merge/compare/v2.5.2...v2.5.5) --- updated-dependencies: - dependency-name: tailwind-merge dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 10 +++++----- unime/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff596e1cd..7566f4bd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,8 +129,8 @@ importers: specifier: ^2.0.2 version: 2.0.2 tailwind-merge: - specifier: ^2.4.0 - version: 2.5.2 + specifier: ^2.5.5 + version: 2.5.5 tailwindcss: specifier: ^3.4.14 version: 3.4.14 @@ -2527,8 +2527,8 @@ packages: tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - tailwind-merge@2.5.2: - resolution: {integrity: sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==} + tailwind-merge@2.5.5: + resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==} tailwindcss@3.4.14: resolution: {integrity: sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==} @@ -5229,7 +5229,7 @@ snapshots: tabbable@6.2.0: {} - tailwind-merge@2.5.2: {} + tailwind-merge@2.5.5: {} tailwindcss@3.4.14: dependencies: diff --git a/unime/package.json b/unime/package.json index 7793f00ff..264873b89 100644 --- a/unime/package.json +++ b/unime/package.json @@ -51,7 +51,7 @@ "svelte": "^4.2.19", "svelte-check": "^3.8.5", "svelte-sequential-preprocessor": "^2.0.2", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.5", "tailwindcss": "^3.4.14", "tslib": "^2.7.0", "typesafe-i18n": "^5.26.2", From 842fe2ab69539480f1be9ca44020ddf7b74d9754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:14:08 +0100 Subject: [PATCH 03/15] chore(deps-dev): bump @lottiefiles/lottie-player from 2.0.11 to 2.0.12 (#425) Bumps [@lottiefiles/lottie-player](https://github.com/LottieFiles/lottie-player) from 2.0.11 to 2.0.12. - [Release notes](https://github.com/LottieFiles/lottie-player/releases) - [Changelog](https://github.com/LottieFiles/lottie-player/blob/master/CHANGELOG.md) - [Commits](https://github.com/LottieFiles/lottie-player/commits) --- updated-dependencies: - dependency-name: "@lottiefiles/lottie-player" dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 10 +++++----- unime/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7566f4bd5..bbbcd193e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: ^1.2.0 version: 1.2.0 '@lottiefiles/lottie-player': - specifier: ^2.0.11 - version: 2.0.11 + specifier: ^2.0.12 + version: 2.0.12 '@melt-ui/pp': specifier: ^0.3.0 version: 0.3.2(@melt-ui/svelte@0.83.0(svelte@4.2.19))(svelte@4.2.19) @@ -508,8 +508,8 @@ packages: '@lit/reactive-element@1.6.3': resolution: {integrity: sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==} - '@lottiefiles/lottie-player@2.0.11': - resolution: {integrity: sha512-KPKPycp/dDxpuNBV9Pu5h9GdiFaA/cTh0YhkmV/KFdoTrg4NfeOowS8VKbSqqSfmVqQV6sb/uPtqeO4UaqBE7w==} + '@lottiefiles/lottie-player@2.0.12': + resolution: {integrity: sha512-VuQnB+IFaY4ijrUTByth7jOLz9p7xK6goeYr/MtyGOVIggSl/TDCcSp6qztltdflFhyZrFpfbHEZNxeK5AiVgg==} '@melt-ui/pp@0.3.2': resolution: {integrity: sha512-xKkPvaIAFinklLXcQOpwZ8YSpqAFxykjWf8Y/fSJQwsixV/0rcFs07hJ49hJjPy5vItvw5Qa0uOjzFUbXzBypQ==} @@ -3220,7 +3220,7 @@ snapshots: dependencies: '@lit-labs/ssr-dom-shim': 1.2.1 - '@lottiefiles/lottie-player@2.0.11': + '@lottiefiles/lottie-player@2.0.12': dependencies: '@types/pako': 1.0.7 lit: 2.8.0 diff --git a/unime/package.json b/unime/package.json index 264873b89..7dbd19267 100644 --- a/unime/package.json +++ b/unime/package.json @@ -19,7 +19,7 @@ "@aws-crypto/sha256-js": "^5.2.0", "@iconify-json/circle-flags": "^1.2.0", "@iconify-json/ph": "^1.2.0", - "@lottiefiles/lottie-player": "^2.0.11", + "@lottiefiles/lottie-player": "^2.0.12", "@melt-ui/pp": "^0.3.0", "@melt-ui/svelte": "^0.83.0", "@sveltejs/adapter-static": "^3.0.4", From 7c60a282af0f1002eceda6ca01cad88ed9fafc2e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:47:00 +0100 Subject: [PATCH 04/15] chore(deps-dev): bump vitest from 2.0.5 to 2.1.8 (#431) Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 2.0.5 to 2.1.8. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v2.1.8/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 283 ++++++++++++++++++++++----------------------- unime/package.json | 2 +- 2 files changed, 138 insertions(+), 147 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bbbcd193e..4c2ba5de5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,7 +88,7 @@ importers: version: 1.5.5 '@vitest/coverage-v8': specifier: ^2.0.3 - version: 2.0.5(vitest@2.0.5(@types/node@22.5.0)(jsdom@22.1.0)) + version: 2.0.5(vitest@2.1.8(@types/node@22.5.0)(jsdom@22.1.0)) autoprefixer: specifier: ^10.4.14 version: 10.4.20(postcss@8.4.47) @@ -153,8 +153,8 @@ importers: specifier: ^5.4.10 version: 5.4.10(@types/node@22.5.0) vitest: - specifier: ^2.0.3 - version: 2.0.5(@types/node@22.5.0)(jsdom@22.1.0) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.5.0)(jsdom@22.1.0) packages: @@ -874,23 +874,34 @@ packages: peerDependencies: vitest: 2.0.5 - '@vitest/expect@2.0.5': - resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/pretty-format@2.0.5': - resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} - '@vitest/runner@2.0.5': - resolution: {integrity: sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig==} + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} - '@vitest/snapshot@2.0.5': - resolution: {integrity: sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==} + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} - '@vitest/spy@2.0.5': - resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} - '@vitest/utils@2.0.5': - resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} @@ -1034,8 +1045,8 @@ packages: caniuse-lite@1.0.30001651: resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==} - chai@5.1.1: - resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} engines: {node: '>=12'} chalk@2.4.2: @@ -1131,6 +1142,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize@1.2.0: resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} engines: {node: '>=0.10.0'} @@ -1227,6 +1247,9 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} @@ -1329,9 +1352,9 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1411,9 +1434,6 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -1422,10 +1442,6 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1518,10 +1534,6 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -1653,10 +1665,6 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -1807,8 +1815,8 @@ packages: lottie-web@5.12.2: resolution: {integrity: sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==} - loupe@3.1.1: - resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1823,6 +1831,9 @@ packages: magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} + magic-string@0.30.14: + resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==} + magicast@0.3.4: resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==} @@ -1863,10 +1874,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1903,6 +1910,9 @@ packages: ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -1934,10 +1944,6 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - nwsapi@2.2.12: resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} @@ -1972,10 +1978,6 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2040,10 +2042,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -2398,6 +2396,9 @@ packages: std-env@3.7.0: resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} @@ -2426,10 +2427,6 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -2558,6 +2555,9 @@ packages: tinyexec@0.2.0: resolution: {integrity: sha512-au8dwv4xKSDR+Fw52csDo3wcDztPdne2oM1o/7LFro4h6bdFmvyUAeAfX40pwDtzHgRFqz1XWaUqgKS2G83/ig==} + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + tinypool@1.0.1: resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -2566,8 +2566,8 @@ packages: resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@3.0.0: - resolution: {integrity: sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} to-fast-properties@2.0.0: @@ -2682,8 +2682,8 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite-node@2.0.5: - resolution: {integrity: sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -2726,15 +2726,15 @@ packages: vite: optional: true - vitest@2.0.5: - resolution: {integrity: sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA==} + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 2.0.5 - '@vitest/ui': 2.0.5 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2926,7 +2926,7 @@ snapshots: '@babel/traverse': 7.25.4 '@babel/types': 7.25.4 convert-source-map: 2.0.0 - debug: 4.3.6 + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -3011,7 +3011,7 @@ snapshots: '@babel/parser': 7.25.4 '@babel/template': 7.25.0 '@babel/types': 7.25.4 - debug: 4.3.6 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3535,7 +3535,7 @@ snapshots: '@typescript-eslint/types': 8.12.0 '@typescript-eslint/typescript-estree': 8.12.0(typescript@5.5.4) '@typescript-eslint/visitor-keys': 8.12.0 - debug: 4.3.6 + debug: 4.3.7 eslint: 9.9.0(jiti@1.21.6) optionalDependencies: typescript: 5.5.4 @@ -3551,7 +3551,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.12.0(typescript@5.5.4) '@typescript-eslint/utils': 8.12.0(eslint@9.9.0(jiti@1.21.6))(typescript@5.5.4) - debug: 4.3.6 + debug: 4.3.7 ts-api-utils: 1.3.0(typescript@5.5.4) optionalDependencies: typescript: 5.5.4 @@ -3565,7 +3565,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.12.0 '@typescript-eslint/visitor-keys': 8.12.0 - debug: 4.3.6 + debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -3592,7 +3592,7 @@ snapshots: '@typescript-eslint/types': 8.12.0 eslint-visitor-keys: 3.4.3 - '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@22.5.0)(jsdom@22.1.0))': + '@vitest/coverage-v8@2.0.5(vitest@2.1.8(@types/node@22.5.0)(jsdom@22.1.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 0.2.3 @@ -3606,41 +3606,48 @@ snapshots: std-env: 3.7.0 test-exclude: 7.0.1 tinyrainbow: 1.2.0 - vitest: 2.0.5(@types/node@22.5.0)(jsdom@22.1.0) + vitest: 2.1.8(@types/node@22.5.0)(jsdom@22.1.0) transitivePeerDependencies: - supports-color - '@vitest/expect@2.0.5': + '@vitest/expect@2.1.8': dependencies: - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 - chai: 5.1.1 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 tinyrainbow: 1.2.0 - '@vitest/pretty-format@2.0.5': + '@vitest/mocker@2.1.8(vite@5.4.10(@types/node@22.5.0))': + dependencies: + '@vitest/spy': 2.1.8 + estree-walker: 3.0.3 + magic-string: 0.30.14 + optionalDependencies: + vite: 5.4.10(@types/node@22.5.0) + + '@vitest/pretty-format@2.1.8': dependencies: tinyrainbow: 1.2.0 - '@vitest/runner@2.0.5': + '@vitest/runner@2.1.8': dependencies: - '@vitest/utils': 2.0.5 + '@vitest/utils': 2.1.8 pathe: 1.1.2 - '@vitest/snapshot@2.0.5': + '@vitest/snapshot@2.1.8': dependencies: - '@vitest/pretty-format': 2.0.5 - magic-string: 0.30.11 + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.14 pathe: 1.1.2 - '@vitest/spy@2.0.5': + '@vitest/spy@2.1.8': dependencies: - tinyspy: 3.0.0 + tinyspy: 3.0.2 - '@vitest/utils@2.0.5': + '@vitest/utils@2.1.8': dependencies: - '@vitest/pretty-format': 2.0.5 - estree-walker: 3.0.3 - loupe: 3.1.1 + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 tinyrainbow: 1.2.0 abab@2.0.6: {} @@ -3653,7 +3660,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.6 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -3770,12 +3777,12 @@ snapshots: caniuse-lite@1.0.30001651: {} - chai@5.1.1: + chai@5.1.2: dependencies: assertion-error: 2.0.1 check-error: 2.1.1 deep-eql: 5.0.2 - loupe: 3.1.1 + loupe: 3.1.2 pathval: 2.0.0 chalk@2.4.2: @@ -3877,6 +3884,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decamelize@1.2.0: {} decimal.js@10.4.3: {} @@ -3974,6 +3985,8 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 + es-module-lexer@1.5.4: {} + es6-promise@3.3.1: {} esbuild@0.21.5: @@ -4133,17 +4146,7 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + expect-type@1.1.0: {} fast-deep-equal@3.1.3: {} @@ -4222,8 +4225,6 @@ snapshots: get-caller-file@2.0.5: {} - get-func-name@2.0.2: {} - get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -4234,8 +4235,6 @@ snapshots: get-stream@6.0.1: {} - get-stream@8.0.1: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4325,8 +4324,6 @@ snapshots: human-signals@2.1.0: {} - human-signals@5.0.0: {} - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -4442,8 +4439,6 @@ snapshots: is-stream@2.0.1: {} - is-stream@3.0.0: {} - is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -4604,9 +4599,7 @@ snapshots: lottie-web@5.12.2: {} - loupe@3.1.1: - dependencies: - get-func-name: 2.0.2 + loupe@3.1.2: {} lru-cache@10.4.3: {} @@ -4620,6 +4613,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + magic-string@0.30.14: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + magicast@0.3.4: dependencies: '@babel/parser': 7.25.4 @@ -4660,8 +4657,6 @@ snapshots: mimic-fn@2.1.0: {} - mimic-fn@4.0.0: {} - min-indent@1.0.1: {} minimatch@3.1.2: @@ -4693,6 +4688,8 @@ snapshots: ms@2.1.2: {} + ms@2.1.3: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -4715,10 +4712,6 @@ snapshots: dependencies: path-key: 3.1.1 - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 - nwsapi@2.2.12: {} object-assign@4.1.1: {} @@ -4749,10 +4742,6 @@ snapshots: dependencies: mimic-fn: 2.1.0 - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4810,8 +4799,6 @@ snapshots: path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: @@ -5099,6 +5086,8 @@ snapshots: std-env@3.7.0: {} + std-env@3.8.0: {} + stop-iteration-iterator@1.0.0: dependencies: internal-slot: 1.0.7 @@ -5127,8 +5116,6 @@ snapshots: strip-final-newline@2.0.0: {} - strip-final-newline@3.0.0: {} - strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -5283,11 +5270,13 @@ snapshots: tinyexec@0.2.0: {} + tinyexec@0.3.1: {} + tinypool@1.0.1: {} tinyrainbow@1.2.0: {} - tinyspy@3.0.0: {} + tinyspy@3.0.2: {} to-fast-properties@2.0.0: {} @@ -5383,12 +5372,12 @@ snapshots: util-deprecate@1.0.2: {} - vite-node@2.0.5(@types/node@22.5.0): + vite-node@2.1.8(@types/node@22.5.0): dependencies: cac: 6.7.14 - debug: 4.3.6 + debug: 4.3.7 + es-module-lexer: 1.5.4 pathe: 1.1.2 - tinyrainbow: 1.2.0 vite: 5.4.10(@types/node@22.5.0) transitivePeerDependencies: - '@types/node' @@ -5414,26 +5403,27 @@ snapshots: optionalDependencies: vite: 5.4.10(@types/node@22.5.0) - vitest@2.0.5(@types/node@22.5.0)(jsdom@22.1.0): - dependencies: - '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.5 - '@vitest/pretty-format': 2.0.5 - '@vitest/runner': 2.0.5 - '@vitest/snapshot': 2.0.5 - '@vitest/spy': 2.0.5 - '@vitest/utils': 2.0.5 - chai: 5.1.1 - debug: 4.3.6 - execa: 8.0.1 - magic-string: 0.30.11 + vitest@2.1.8(@types/node@22.5.0)(jsdom@22.1.0): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.10(@types/node@22.5.0)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.3.7 + expect-type: 1.1.0 + magic-string: 0.30.14 pathe: 1.1.2 - std-env: 3.7.0 + std-env: 3.8.0 tinybench: 2.9.0 + tinyexec: 0.3.1 tinypool: 1.0.1 tinyrainbow: 1.2.0 vite: 5.4.10(@types/node@22.5.0) - vite-node: 2.0.5(@types/node@22.5.0) + vite-node: 2.1.8(@types/node@22.5.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 22.5.0 @@ -5441,6 +5431,7 @@ snapshots: transitivePeerDependencies: - less - lightningcss + - msw - sass - sass-embedded - stylus diff --git a/unime/package.json b/unime/package.json index 7dbd19267..de5d45797 100644 --- a/unime/package.json +++ b/unime/package.json @@ -59,7 +59,7 @@ "typescript-eslint": "^8.12.0", "unplugin-icons": "^0.19.1", "vite": "^5.4.10", - "vitest": "^2.0.3" + "vitest": "^2.1.8" }, "engines": { "node": ">=20", From 04e8319491358423e6030cbcb8b57886e5a716bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 23:47:45 +0100 Subject: [PATCH 05/15] chore(deps-dev): bump @ianvs/prettier-plugin-sort-imports (#422) Bumps [@ianvs/prettier-plugin-sort-imports](https://github.com/ianvs/prettier-plugin-sort-imports) from 4.3.1 to 4.4.0. - [Release notes](https://github.com/ianvs/prettier-plugin-sort-imports/releases) - [Changelog](https://github.com/IanVS/prettier-plugin-sort-imports/blob/main/CHANGELOG.md) - [Commits](https://github.com/ianvs/prettier-plugin-sort-imports/compare/v4.3.1...v4.4.0) --- updated-dependencies: - dependency-name: "@ianvs/prettier-plugin-sort-imports" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 144 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 124 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 45ca3ddbd..bf6d8e75a 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "test": "pnpm --filter=unime test" }, "devDependencies": { - "@ianvs/prettier-plugin-sort-imports": "^4.3.0", + "@ianvs/prettier-plugin-sort-imports": "^4.4.0", "prettier": "^3.3.3", "prettier-plugin-svelte": "^3.2.7", "prettier-plugin-tailwindcss": "^0.6.5" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c2ba5de5..b9d7e1bc3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@ianvs/prettier-plugin-sort-imports': - specifier: ^4.3.0 - version: 4.3.1(prettier@3.3.3) + specifier: ^4.4.0 + version: 4.4.0(prettier@3.3.3) prettier: specifier: ^3.3.3 version: 3.3.3 @@ -19,7 +19,7 @@ importers: version: 3.2.7(prettier@3.3.3)(svelte@4.2.19) prettier-plugin-tailwindcss: specifier: ^0.6.5 - version: 0.6.6(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier-plugin-svelte@3.2.7(prettier@3.3.3)(svelte@4.2.19))(prettier@3.3.3) + version: 0.6.6(@ianvs/prettier-plugin-sort-imports@4.4.0(prettier@3.3.3))(prettier-plugin-svelte@3.2.7(prettier@3.3.3)(svelte@4.2.19))(prettier@3.3.3) unime: devDependencies: @@ -193,6 +193,10 @@ packages: resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.25.4': resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} engines: {node: '>=6.9.0'} @@ -205,6 +209,10 @@ packages: resolution: {integrity: sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==} engines: {node: '>=6.9.0'} + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.25.2': resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} engines: {node: '>=6.9.0'} @@ -227,10 +235,18 @@ packages: resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.24.7': resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.8': resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} engines: {node: '>=6.9.0'} @@ -248,6 +264,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.25.4': resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==} engines: {node: '>=6.9.0'} @@ -256,14 +277,26 @@ packages: resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.4': resolution: {integrity: sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.25.4': resolution: {integrity: sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -448,8 +481,8 @@ packages: resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} engines: {node: '>=18.18'} - '@ianvs/prettier-plugin-sort-imports@4.3.1': - resolution: {integrity: sha512-ZHwbyjkANZOjaBm3ZosADD2OUYGFzQGxfy67HmGZU94mHqe7g1LCMA7YYKB1Cq+UTPCBqlAYapY0KXAjKEw8Sg==} + '@ianvs/prettier-plugin-sort-imports@4.4.0': + resolution: {integrity: sha512-f4/e+/ANGk3tHuwRW0uh2YuBR50I4h1ZjGQ+5uD8sWfinHTivQsnieR5cz24t8M6Vx4rYvZ5v/IEKZhYpzQm9Q==} peerDependencies: '@vue/compiler-sfc': 2.7.x || 3.x prettier: 2 || 3 @@ -1731,6 +1764,11 @@ packages: engines: {node: '>=4'} hasBin: true + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -2911,7 +2949,14 @@ snapshots: '@babel/highlight': 7.24.7 picocolors: 1.1.1 - '@babel/compat-data@7.25.4': {} + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.25.4': + optional: true '@babel/core@7.25.2': dependencies: @@ -2932,6 +2977,7 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color + optional: true '@babel/generator@7.25.5': dependencies: @@ -2939,6 +2985,15 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + optional: true + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 '@babel/helper-compilation-targets@7.25.2': dependencies: @@ -2947,6 +3002,7 @@ snapshots: browserslist: 4.23.3 lru-cache: 5.1.1 semver: 6.3.1 + optional: true '@babel/helper-module-imports@7.24.7': dependencies: @@ -2954,6 +3010,7 @@ snapshots: '@babel/types': 7.25.4 transitivePeerDependencies: - supports-color + optional: true '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': dependencies: @@ -2964,6 +3021,7 @@ snapshots: '@babel/traverse': 7.25.4 transitivePeerDependencies: - supports-color + optional: true '@babel/helper-simple-access@7.24.7': dependencies: @@ -2971,17 +3029,24 @@ snapshots: '@babel/types': 7.25.4 transitivePeerDependencies: - supports-color + optional: true '@babel/helper-string-parser@7.24.8': {} + '@babel/helper-string-parser@7.25.9': {} + '@babel/helper-validator-identifier@7.24.7': {} - '@babel/helper-validator-option@7.24.8': {} + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.24.8': + optional: true '@babel/helpers@7.25.0': dependencies: '@babel/template': 7.25.0 '@babel/types': 7.25.4 + optional: true '@babel/highlight@7.24.7': dependencies: @@ -2994,6 +3059,10 @@ snapshots: dependencies: '@babel/types': 7.25.4 + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + '@babel/runtime@7.25.4': dependencies: regenerator-runtime: 0.14.1 @@ -3003,6 +3072,13 @@ snapshots: '@babel/code-frame': 7.24.7 '@babel/parser': 7.25.4 '@babel/types': 7.25.4 + optional: true + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 '@babel/traverse@7.25.4': dependencies: @@ -3015,6 +3091,19 @@ snapshots: globals: 11.12.0 transitivePeerDependencies: - supports-color + optional: true + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.6 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color '@babel/types@7.25.4': dependencies: @@ -3022,6 +3111,11 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} '@esbuild/aix-ppc64@0.21.5': @@ -3141,13 +3235,12 @@ snapshots: '@humanwhocodes/retry@0.3.0': {} - '@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3)': + '@ianvs/prettier-plugin-sort-imports@4.4.0(prettier@3.3.3)': dependencies: - '@babel/core': 7.25.2 - '@babel/generator': 7.25.5 - '@babel/parser': 7.25.4 - '@babel/traverse': 7.25.4 - '@babel/types': 7.25.4 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 prettier: 3.3.3 semver: 7.6.3 transitivePeerDependencies: @@ -3851,7 +3944,8 @@ snapshots: confbox@0.1.7: {} - convert-source-map@2.0.0: {} + convert-source-map@2.0.0: + optional: true cookie@0.6.0: {} @@ -4221,7 +4315,8 @@ snapshots: functions-have-names@1.2.3: {} - gensync@1.0.0-beta.2: {} + gensync@1.0.0-beta.2: + optional: true get-caller-file@2.0.5: {} @@ -4523,7 +4618,10 @@ snapshots: - supports-color - utf-8-validate - jsesc@2.5.2: {} + jsesc@2.5.2: + optional: true + + jsesc@3.0.2: {} json-buffer@3.0.1: {} @@ -4531,7 +4629,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json5@2.2.3: {} + json5@2.2.3: + optional: true keyv@4.5.4: dependencies: @@ -4606,6 +4705,7 @@ snapshots: lru-cache@5.1.1: dependencies: yallist: 3.1.1 + optional: true lz-string@1.5.0: {} @@ -4900,11 +5000,11 @@ snapshots: prettier: 3.3.3 svelte: 4.2.19 - prettier-plugin-tailwindcss@0.6.6(@ianvs/prettier-plugin-sort-imports@4.3.1(prettier@3.3.3))(prettier-plugin-svelte@3.2.7(prettier@3.3.3)(svelte@4.2.19))(prettier@3.3.3): + prettier-plugin-tailwindcss@0.6.6(@ianvs/prettier-plugin-sort-imports@4.4.0(prettier@3.3.3))(prettier-plugin-svelte@3.2.7(prettier@3.3.3)(svelte@4.2.19))(prettier@3.3.3): dependencies: prettier: 3.3.3 optionalDependencies: - '@ianvs/prettier-plugin-sort-imports': 4.3.1(prettier@3.3.3) + '@ianvs/prettier-plugin-sort-imports': 4.4.0(prettier@3.3.3) prettier-plugin-svelte: 3.2.7(prettier@3.3.3)(svelte@4.2.19) prettier@3.3.3: {} @@ -5022,7 +5122,8 @@ snapshots: dependencies: xmlchars: 2.2.0 - semver@6.3.1: {} + semver@6.3.1: + optional: true semver@7.6.3: {} @@ -5524,7 +5625,8 @@ snapshots: y18n@4.0.3: {} - yallist@3.1.1: {} + yallist@3.1.1: + optional: true yaml@1.10.2: {} From f2ead849297b27b96a4408d9c620cf79e40c8078 Mon Sep 17 00:00:00 2001 From: Oran Dan <53859531+Oran-Dan@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:28:52 +0100 Subject: [PATCH 06/15] feat: trust linked VPs based on domain trust lists (#353) * WIP * Add Thuiswinkel verification checkmark * fix: fix logo_url binding, renameto thuiswinkel_validation * Rename `thuiswinkel_waarborg_verification` to `thuiswinkel_verification` in frontend * feat: add `issuance_date` to `ValidationResult` * Change label for domain verification * fix: fix `issuance_date` * Add issuance date and change logo position * chore(deps-dev): bump prettier-plugin-tailwindcss from 0.6.1 to 0.6.5 (#259) Bumps [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) from 0.6.1 to 0.6.5. - [Release notes](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/prettier-plugin-tailwindcss/compare/v0.6.1...v0.6.5) --- updated-dependencies: - dependency-name: prettier-plugin-tailwindcss dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump @ianvs/prettier-plugin-sort-imports (#260) Bumps [@ianvs/prettier-plugin-sort-imports](https://github.com/ianvs/prettier-plugin-sort-imports) from 4.2.1 to 4.3.0. - [Release notes](https://github.com/ianvs/prettier-plugin-sort-imports/releases) - [Changelog](https://github.com/IanVS/prettier-plugin-sort-imports/blob/main/CHANGELOG.md) - [Commits](https://github.com/ianvs/prettier-plugin-sort-imports/compare/v4.2.1...v4.3.0) --- updated-dependencies: - dependency-name: "@ianvs/prettier-plugin-sort-imports" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump prettier-plugin-svelte from 3.2.3 to 3.2.5 (#262) Bumps [prettier-plugin-svelte](https://github.com/sveltejs/prettier-plugin-svelte) from 3.2.3 to 3.2.5. - [Changelog](https://github.com/sveltejs/prettier-plugin-svelte/blob/master/CHANGELOG.md) - [Commits](https://github.com/sveltejs/prettier-plugin-svelte/compare/v3.2.3...v3.2.5) --- updated-dependencies: - dependency-name: prettier-plugin-svelte dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Nander Stabel * chore(deps-dev): bump prettier from 3.3.0 to 3.3.2 (#261) Bumps [prettier](https://github.com/prettier/prettier) from 3.3.0 to 3.3.2. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.3.0...3.3.2) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Make `imageId` reactive * fix: fix lint errors * add: backend actions-reducers for editable trust list * cargo fmt * add: testing * feat: trusted domains list (first version) (#352) * ci: release version 0.6.8 (#346) * fix: bindings * feat: add default_trust_list.json * test: add testing, not final * feat: enable multiple trust lists * chore: remove comment * refactore: rename actions and reducers * feat: add TrustList level actions * refactor: trustlist owned bool, name to id, reset to default * chore: fix domain name * feat: add trust list management frontend * refactor: implement lists view & domain list * refactor: Wire up Tauri listeners in `onMount` of root layout (#322) * Wire up Tauri state-changed listener in `onMount` of root layout * Wire up Tauri error listener in root layout * Update comments * build: Replace `npm` with `pnpm` (#348) * Swap out `npm` for `pnpm` Fixes #347 * Address review comments * chore: cargo fmt and fix binding naming * feat: prevent edits on imported lists * refactor: remove unused reset functionality * feat: add placeholder when no lists * refactor: trustlist actions * refactor: remove trustlist default impl * refactor: use updated Action names * fix: rename broken bindings * refactor: remove `Default` trait * refactor: show `display_name` instead of `id` * feat: update default trusted domains --------- Co-authored-by: Daniel Mader Co-authored-by: Thilo Maier * feat: add `validate_linked_verifiable_presentations` * refactor: apply clippy suggestions * feat: use `ServiceEndpoint::from` * refactor: remove unused variables * refactor: replace icon imports * feat: filter linked VPs based on trust list * feat: display trusted domains as trusted issuers * chore: add 'naam' to fn get_name * chore: make default trustlist for all accounts upon creation, cargo clippy * chore: add documentation to the TrustLists field in the appstate * chore: fix prerendering, set prerender of trust-list/[id] to false * fix: use explicit name for variable * feat: use different random value source for custom list names * chore: fix comments PR * chore: fix PR comments * chore: add https:// to test strings and default trust list * chore: fix PR comments * chore: pnpm format * chore: fix PR comments * chore: fix PR comment * chore: fix PR comments * chore: updated bindings * chore: fix default trust list empty id * chore: fix action name * chore: edit action names * chore: remove actual company name * chore: use consistent id * chore: remove renamed actions * chore: make log messages consistent * feat: improve log messages * feat: update custom trust list name * chore: remove unused code * style: fix formatting --------- Signed-off-by: dependabot[bot] Co-authored-by: Nander Stabel Co-authored-by: Thilo Maier Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Daniel Mader --- identity-wallet/bindings/AppState.ts | 3 +- identity-wallet/bindings/actions/Action.ts | 9 +- .../bindings/actions/AddTrustList.ts | 3 + .../bindings/actions/AddTrustListEntry.ts | 3 + .../bindings/actions/DeleteTrustList.ts | 3 + .../bindings/actions/DeleteTrustListEntry.ts | 3 + .../bindings/actions/EditTrustList.ts | 3 + .../bindings/actions/EditTrustListEntry.ts | 3 + .../bindings/actions/ToggleTrustListEntry.ts | 3 + .../bindings/trust_list/TrustList.ts | 3 + .../bindings/trust_list/TrustLists.ts | 4 + .../resources/default_trust_list.json | 5 + identity-wallet/src/error.rs | 4 + identity-wallet/src/state/actions.rs | 19 ++ .../reducers/ferris_static_profile.rs | 7 + ...alidate_linked_verifiable_presentations.rs | 1 + identity-wallet/src/state/mod.rs | 3 + .../profile_settings/reducers/create_new.rs | 8 + .../reducers/read_authorization_request.rs | 24 +++ .../trust_list/actions/add_trust_list.rs | 19 ++ .../actions/add_trust_list_entry.rs | 20 +++ .../trust_list/actions/delete_trust_list.rs | 19 ++ .../actions/delete_trust_list_entry.rs | 22 +++ .../trust_list/actions/edit_trust_list.rs | 20 +++ .../actions/edit_trust_list_entry.rs | 24 +++ .../src/state/trust_list/actions/mod.rs | 7 + .../actions/toggle_trust_list_entry.rs | 22 +++ identity-wallet/src/state/trust_list/mod.rs | 119 ++++++++++++ .../state/trust_list/reducers/add_entry.rs | 74 ++++++++ .../trust_list/reducers/add_trust_list.rs | 65 +++++++ .../state/trust_list/reducers/delete_entry.rs | 72 ++++++++ .../trust_list/reducers/delete_trust_list.rs | 59 ++++++ .../state/trust_list/reducers/edit_entry.rs | 78 ++++++++ .../trust_list/reducers/edit_trust_list.rs | 73 ++++++++ .../src/state/trust_list/reducers/mod.rs | 7 + .../state/trust_list/reducers/toggle_entry.rs | 73 ++++++++ unime/src/lib/icons/index.ts | 4 + unime/src/lib/stores.ts | 1 + .../routes/(app)/me/settings/app/+page.svelte | 11 +- .../(app)/me/settings/app/keys/+page.svelte | 5 +- .../me/settings/app/trust-list/+page.svelte | 89 +++++++++ .../settings/app/trust-list/[id]/+page.svelte | 169 ++++++++++++++++++ .../me/settings/app/trust-list/[id]/+page.ts | 2 + 43 files changed, 1159 insertions(+), 6 deletions(-) create mode 100644 identity-wallet/bindings/actions/AddTrustList.ts create mode 100644 identity-wallet/bindings/actions/AddTrustListEntry.ts create mode 100644 identity-wallet/bindings/actions/DeleteTrustList.ts create mode 100644 identity-wallet/bindings/actions/DeleteTrustListEntry.ts create mode 100644 identity-wallet/bindings/actions/EditTrustList.ts create mode 100644 identity-wallet/bindings/actions/EditTrustListEntry.ts create mode 100644 identity-wallet/bindings/actions/ToggleTrustListEntry.ts create mode 100644 identity-wallet/bindings/trust_list/TrustList.ts create mode 100644 identity-wallet/bindings/trust_list/TrustLists.ts create mode 100644 identity-wallet/resources/default_trust_list.json create mode 100644 identity-wallet/src/state/trust_list/actions/add_trust_list.rs create mode 100644 identity-wallet/src/state/trust_list/actions/add_trust_list_entry.rs create mode 100644 identity-wallet/src/state/trust_list/actions/delete_trust_list.rs create mode 100644 identity-wallet/src/state/trust_list/actions/delete_trust_list_entry.rs create mode 100644 identity-wallet/src/state/trust_list/actions/edit_trust_list.rs create mode 100644 identity-wallet/src/state/trust_list/actions/edit_trust_list_entry.rs create mode 100644 identity-wallet/src/state/trust_list/actions/mod.rs create mode 100644 identity-wallet/src/state/trust_list/actions/toggle_trust_list_entry.rs create mode 100644 identity-wallet/src/state/trust_list/mod.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/add_entry.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/add_trust_list.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/delete_entry.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/delete_trust_list.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/edit_entry.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/edit_trust_list.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/mod.rs create mode 100644 identity-wallet/src/state/trust_list/reducers/toggle_entry.rs create mode 100644 unime/src/routes/(app)/me/settings/app/trust-list/+page.svelte create mode 100644 unime/src/routes/(app)/me/settings/app/trust-list/[id]/+page.svelte create mode 100644 unime/src/routes/(app)/me/settings/app/trust-list/[id]/+page.ts diff --git a/identity-wallet/bindings/AppState.ts b/identity-wallet/bindings/AppState.ts index 27baaa945..4fda4f475 100644 --- a/identity-wallet/bindings/AppState.ts +++ b/identity-wallet/bindings/AppState.ts @@ -6,5 +6,6 @@ import type { DisplayCredential } from "./credentials/DisplayCredential"; import type { HistoryEvent } from "./history/HistoryEvent"; import type { ProfileSettings } from "./profile_settings/ProfileSettings"; import type { SearchResults } from "./search/SearchResults"; +import type { TrustLists } from "./trust_list/TrustLists"; -export interface AppState { dids: Record, connections: Connections, credentials: Array, search_results: SearchResults, profile_settings: ProfileSettings, current_user_prompt: CurrentUserPrompt | null, user_journey: any | null, debug_messages: Array, history: Array, dev_mode: DevMode, } \ No newline at end of file +export interface AppState { dids: Record, connections: Connections, credentials: Array, trust_lists: TrustLists, search_results: SearchResults, profile_settings: ProfileSettings, current_user_prompt: CurrentUserPrompt | null, user_journey: any | null, debug_messages: Array, history: Array, dev_mode: DevMode, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/Action.ts b/identity-wallet/bindings/actions/Action.ts index eb8637a88..1fc14bbb6 100644 --- a/identity-wallet/bindings/actions/Action.ts +++ b/identity-wallet/bindings/actions/Action.ts @@ -1,20 +1,27 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { AddRecentSearch } from "./AddRecentSearch"; +import type { AddTrustList } from "./AddTrustList"; +import type { AddTrustListEntry } from "./AddTrustListEntry"; import type { CancelUserFlow } from "./CancelUserFlow"; import type { CreateNew } from "./CreateNew"; import type { CredentialOffersSelected } from "./CredentialOffersSelected"; import type { CredentialsSelected } from "./CredentialsSelected"; import type { DeleteCredential } from "./DeleteCredential"; import type { DeleteRecentSearch } from "./DeleteRecentSearch"; +import type { DeleteTrustList } from "./DeleteTrustList"; +import type { DeleteTrustListEntry } from "./DeleteTrustListEntry"; import type { DevProfile } from "./DevProfile"; +import type { EditTrustList } from "./EditTrustList"; +import type { EditTrustListEntry } from "./EditTrustListEntry"; import type { QrCodeScanned } from "./QrCodeScanned"; import type { SearchQuery } from "./SearchQuery"; import type { SetLocale } from "./SetLocale"; import type { SetPreferredDidMethod } from "./SetPreferredDidMethod"; import type { SetPreferredKeyType } from "./SetPreferredKeyType"; +import type { ToggleTrustListEntry } from "./ToggleTrustListEntry"; import type { UnlockStorage } from "./UnlockStorage"; import type { UpdateCredentialMetadata } from "./UpdateCredentialMetadata"; import type { UpdateProfileSettings } from "./UpdateProfileSettings"; import type { UpdateSortingPreference } from "./UpdateSortingPreference"; -export type Action = { "type": "[App] Get state" } | { "type": "[Storage] Unlock", payload: UnlockStorage, } | { "type": "[App] Reset" } | { "type": "[DID] Create new", payload: CreateNew, } | { "type": "[Settings] Set locale", payload: SetLocale, } | { "type": "[Settings] Update profile", payload: UpdateProfileSettings, } | { "type": "[QR Code] Scanned", payload: QrCodeScanned, } | { "type": "[Authenticate] Connection accepted" } | { "type": "[User Flow] Cancel", payload?: CancelUserFlow, } | { "type": "[DEV] Load DEV profile", payload: DevProfile, } | { "type": "[DEV] Toggle DEV mode" } | { "type": "[Authenticate] Credentials selected", payload: CredentialsSelected, } | { "type": "[Credential Offer] Selected", payload: CredentialOffersSelected, } | { "type": "[Credential Metadata] Update", payload: UpdateCredentialMetadata, } | { "type": "[Credential] Delete", payload: DeleteCredential, } | { "type": "[User Journey] Cancel" } | { "type": "[Settings] Update sorting preference", payload: UpdateSortingPreference, } | { "type": "[Search] Query", payload: SearchQuery, } | { "type": "[Search] Add recent", payload: AddRecentSearch, } | { "type": "[Search] Delete recent", payload: DeleteRecentSearch, } | { "type": "[DID] Set preferred method", payload: SetPreferredDidMethod, } | { "type": "[Keys] Set preferred key type", payload: SetPreferredKeyType, }; \ No newline at end of file +export type Action = { "type": "[App] Get state" } | { "type": "[Storage] Unlock", payload: UnlockStorage, } | { "type": "[App] Reset" } | { "type": "[DID] Create new", payload: CreateNew, } | { "type": "[Settings] Set locale", payload: SetLocale, } | { "type": "[Settings] Update profile", payload: UpdateProfileSettings, } | { "type": "[QR Code] Scanned", payload: QrCodeScanned, } | { "type": "[Authenticate] Connection accepted" } | { "type": "[User Flow] Cancel", payload?: CancelUserFlow, } | { "type": "[DEV] Load DEV profile", payload: DevProfile, } | { "type": "[DEV] Toggle DEV mode" } | { "type": "[Authenticate] Credentials selected", payload: CredentialsSelected, } | { "type": "[Credential Offer] Selected", payload: CredentialOffersSelected, } | { "type": "[Credential Metadata] Update", payload: UpdateCredentialMetadata, } | { "type": "[Credential] Delete", payload: DeleteCredential, } | { "type": "[User Journey] Cancel" } | { "type": "[Settings] Update sorting preference", payload: UpdateSortingPreference, } | { "type": "[Search] Query", payload: SearchQuery, } | { "type": "[Search] Add recent", payload: AddRecentSearch, } | { "type": "[Search] Delete recent", payload: DeleteRecentSearch, } | { "type": "[DID] Set preferred method", payload: SetPreferredDidMethod, } | { "type": "[Keys] Set preferred key type", payload: SetPreferredKeyType, } | { "type": "[Trust List] Add entry", payload: AddTrustListEntry, } | { "type": "[Trust List] Edit entry", payload: EditTrustListEntry, } | { "type": "[Trust List] Delete entry", payload: DeleteTrustListEntry, } | { "type": "[Trust List] Toggle entry", payload: ToggleTrustListEntry, } | { "type": "[Trust Lists] Add", payload: AddTrustList, } | { "type": "[Trust Lists] Edit", payload: EditTrustList, } | { "type": "[Trust Lists] Delete", payload: DeleteTrustList, }; \ No newline at end of file diff --git a/identity-wallet/bindings/actions/AddTrustList.ts b/identity-wallet/bindings/actions/AddTrustList.ts new file mode 100644 index 000000000..433f6a207 --- /dev/null +++ b/identity-wallet/bindings/actions/AddTrustList.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface AddTrustList { display_name: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/AddTrustListEntry.ts b/identity-wallet/bindings/actions/AddTrustListEntry.ts new file mode 100644 index 000000000..d7b4c0de0 --- /dev/null +++ b/identity-wallet/bindings/actions/AddTrustListEntry.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface AddTrustListEntry { trust_list_id: string, domain: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/DeleteTrustList.ts b/identity-wallet/bindings/actions/DeleteTrustList.ts new file mode 100644 index 000000000..4bfe805bf --- /dev/null +++ b/identity-wallet/bindings/actions/DeleteTrustList.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface DeleteTrustList { trust_list_id: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/DeleteTrustListEntry.ts b/identity-wallet/bindings/actions/DeleteTrustListEntry.ts new file mode 100644 index 000000000..1b587d659 --- /dev/null +++ b/identity-wallet/bindings/actions/DeleteTrustListEntry.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface DeleteTrustListEntry { trust_list_id: string, domain: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/EditTrustList.ts b/identity-wallet/bindings/actions/EditTrustList.ts new file mode 100644 index 000000000..33f772965 --- /dev/null +++ b/identity-wallet/bindings/actions/EditTrustList.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface EditTrustList { trust_list_id: string, new_display_name: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/EditTrustListEntry.ts b/identity-wallet/bindings/actions/EditTrustListEntry.ts new file mode 100644 index 000000000..891d2e9b8 --- /dev/null +++ b/identity-wallet/bindings/actions/EditTrustListEntry.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface EditTrustListEntry { trust_list_id: string, old_domain: string, new_domain: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/actions/ToggleTrustListEntry.ts b/identity-wallet/bindings/actions/ToggleTrustListEntry.ts new file mode 100644 index 000000000..ac1619c37 --- /dev/null +++ b/identity-wallet/bindings/actions/ToggleTrustListEntry.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface ToggleTrustListEntry { trust_list_id: string, domain: string, } \ No newline at end of file diff --git a/identity-wallet/bindings/trust_list/TrustList.ts b/identity-wallet/bindings/trust_list/TrustList.ts new file mode 100644 index 000000000..797cde87c --- /dev/null +++ b/identity-wallet/bindings/trust_list/TrustList.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export interface TrustList { id: string, display_name: string, custom: boolean, entries: Record, } \ No newline at end of file diff --git a/identity-wallet/bindings/trust_list/TrustLists.ts b/identity-wallet/bindings/trust_list/TrustLists.ts new file mode 100644 index 000000000..849f90c00 --- /dev/null +++ b/identity-wallet/bindings/trust_list/TrustLists.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { TrustList } from "./TrustList"; + +export type TrustLists = Array; \ No newline at end of file diff --git a/identity-wallet/resources/default_trust_list.json b/identity-wallet/resources/default_trust_list.json new file mode 100644 index 000000000..c841c80fe --- /dev/null +++ b/identity-wallet/resources/default_trust_list.json @@ -0,0 +1,5 @@ +{ + "id": "b01f4a74-3005-4749-a030-c5444bc4dab5", + "display_name": "Impierce Demos", + "entries": ["https://ngdil.com", "https://selv.iota.org", "https://thuiswinkel-agent.dev.impierce.com"] +} diff --git a/identity-wallet/src/error.rs b/identity-wallet/src/error.rs index 9ff9ddb53..c88ef5f6f 100644 --- a/identity-wallet/src/error.rs +++ b/identity-wallet/src/error.rs @@ -30,6 +30,8 @@ pub enum AppError { DownloadAborted(&'static str), #[error("Failed to write to the file: {0}")] WriteFailed(#[from] std::io::Error), + #[error("Failed to deserialize: {0}")] + DeserializeFailed(#[from] serde_json::Error), #[error("Error while initializing OID4VC provider manager")] OID4VCProviderManagerError(#[source] anyhow::Error), #[error("Error while initializing OID4VC wallet")] @@ -92,6 +94,8 @@ pub enum AppError { StrongholdPublicKeyError(#[source] anyhow::Error), #[error("Failed to delete state file")] StateFileDeletionError(#[source] anyhow::Error), + #[error("Failed to find TrustList with ID `{0}`")] + TrustListNotFoundError(String), } impl std::fmt::Debug for AppError { diff --git a/identity-wallet/src/state/actions.rs b/identity-wallet/src/state/actions.rs index 5ab6dba64..e8517458b 100644 --- a/identity-wallet/src/state/actions.rs +++ b/identity-wallet/src/state/actions.rs @@ -80,6 +80,11 @@ mod bindings { search::actions::{ add_recent_search::AddRecentSearch, delete_recent_search::DeleteRecentSearch, search_query::SearchQuery, }, + trust_list::actions::{ + add_trust_list::AddTrustList, add_trust_list_entry::AddTrustListEntry, delete_trust_list::DeleteTrustList, + delete_trust_list_entry::DeleteTrustListEntry, edit_trust_list::EditTrustList, + edit_trust_list_entry::EditTrustListEntry, toggle_trust_list_entry::ToggleTrustListEntry, + }, }; #[derive(Serialize, Deserialize, TS)] @@ -133,5 +138,19 @@ mod bindings { SetPreferredDidMethod { payload: SetPreferredDidMethod }, #[serde(rename = "[Keys] Set preferred key type")] SetPreferredKeyType { payload: SetPreferredKeyType }, + #[serde(rename = "[Trust List] Add entry")] + AddTrustListEntry { payload: AddTrustListEntry }, + #[serde(rename = "[Trust List] Edit entry")] + EditTrustListEntry { payload: EditTrustListEntry }, + #[serde(rename = "[Trust List] Delete entry")] + DeleteTrustListEntry { payload: DeleteTrustListEntry }, + #[serde(rename = "[Trust List] Toggle entry")] + ToggleTrustListEntry { payload: ToggleTrustListEntry }, + #[serde(rename = "[Trust Lists] Add")] + TrustListsAdd { payload: AddTrustList }, + #[serde(rename = "[Trust Lists] Edit")] + TrustListsEdit { payload: EditTrustList }, + #[serde(rename = "[Trust Lists] Delete")] + TrustListsDelete { payload: DeleteTrustList }, } } diff --git a/identity-wallet/src/state/dev_mode/reducers/ferris_static_profile.rs b/identity-wallet/src/state/dev_mode/reducers/ferris_static_profile.rs index 1a23d60ec..a47857024 100644 --- a/identity-wallet/src/state/dev_mode/reducers/ferris_static_profile.rs +++ b/identity-wallet/src/state/dev_mode/reducers/ferris_static_profile.rs @@ -10,6 +10,7 @@ use crate::{ credentials::VerifiableCredentialRecord, dev_mode::DevMode, profile_settings::{AppTheme, Profile}, + trust_list::TrustList, user_prompt::CurrentUserPrompt, AppState, SUPPORTED_DID_METHODS, SUPPORTED_SIGNING_ALGORITHMS, }, @@ -287,6 +288,12 @@ pub async fn load_ferris_profile() -> Result { OPEN_BADGE.display_credential.id.clone(), ]; + // Import trusted domains + let default_trust_list: TrustList = + serde_json::from_str(include_str!("../../../../resources/default_trust_list.json")).unwrap(); + + state.trust_lists.insert(default_trust_list); + state.current_user_prompt = Some(CurrentUserPrompt::Redirect { target: "me".to_string(), }); diff --git a/identity-wallet/src/state/did/validate_linked_verifiable_presentations.rs b/identity-wallet/src/state/did/validate_linked_verifiable_presentations.rs index 223d311ac..b18dba3e5 100644 --- a/identity-wallet/src/state/did/validate_linked_verifiable_presentations.rs +++ b/identity-wallet/src/state/did/validate_linked_verifiable_presentations.rs @@ -318,6 +318,7 @@ fn get_name(credential_subject: &Subject) -> Option { credential_subject .properties .get("name") + .or_else(|| credential_subject.properties.get("naam")) // TODO: "naam" is expected to be used in Dutch credentials .and_then(Value::as_str) .map(ToString::to_string) } diff --git a/identity-wallet/src/state/mod.rs b/identity-wallet/src/state/mod.rs index 9625800af..06b46973c 100644 --- a/identity-wallet/src/state/mod.rs +++ b/identity-wallet/src/state/mod.rs @@ -8,6 +8,7 @@ pub mod did; pub mod profile_settings; pub mod qr_code; pub mod search; +pub mod trust_list; pub mod user_journey; pub mod user_prompt; @@ -28,6 +29,7 @@ use jsonwebtoken::Algorithm; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::{collections::VecDeque, pin::Pin}; +use trust_list::TrustLists; use ts_rs::TS; /// The AppState is the main state of the application shared between the backend and the frontend. @@ -87,6 +89,7 @@ pub struct AppState { pub dids: HashMap, pub connections: Connections, pub credentials: Vec, + pub trust_lists: TrustLists, pub search_results: SearchResults, /// This field contains utils needed for the backend to perform its tasks. #[serde(skip)] diff --git a/identity-wallet/src/state/profile_settings/reducers/create_new.rs b/identity-wallet/src/state/profile_settings/reducers/create_new.rs index 4a8881317..7e6aec92a 100644 --- a/identity-wallet/src/state/profile_settings/reducers/create_new.rs +++ b/identity-wallet/src/state/profile_settings/reducers/create_new.rs @@ -4,6 +4,7 @@ use crate::{ actions::{listen, Action}, core_utils::IdentityManager, profile_settings::{actions::create_new::CreateNew, Profile, ProfileSettings}, + trust_list::{TrustList, TrustLists}, user_prompt::CurrentUserPrompt, AppState, SUPPORTED_DID_METHODS, SUPPORTED_SIGNING_ALGORITHMS, }, @@ -80,6 +81,12 @@ pub async fn create_identity(state: AppState, action: Action) -> Result Result Resu let domain_validation = Box::new(validate_domain_linkage(url, did).await); + let trusted_domains: Vec = state + .trust_lists + .0 + .iter() + .map(|trust_list| { + trust_list + .entries + .iter() + .filter_map(|(domain, trusted)| trusted.then_some(domain.clone().to_string())) + .collect::() + }) + .collect(); + + info!("Trusted Domains: {:?}", trusted_domains); + let linked_verifiable_presentations = validate_linked_verifiable_presentations(did) .await .into_iter() .flatten() + .filter(|linked_verifiable_credential| { + linked_verifiable_credential.issuer_linked_domains.iter().any(|domain| { + info!("domain: {:?}", domain.to_string()); + + trusted_domains.contains(&domain.to_string()) + }) + }) .collect(); + info!("linked_verifiable_presentations: {:?}", linked_verifiable_presentations); + drop(state_guard); return Ok(AppState { diff --git a/identity-wallet/src/state/trust_list/actions/add_trust_list.rs b/identity-wallet/src/state/trust_list/actions/add_trust_list.rs new file mode 100644 index 000000000..8b2252ba7 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/add_trust_list.rs @@ -0,0 +1,19 @@ +use crate::reducer; +use crate::state::trust_list::reducers::add_trust_list::trust_list_add; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/AddTrustList.ts")] +pub struct AddTrustList { + pub display_name: String, +} + +#[typetag::serde(name = "[Trust Lists] Add")] +impl ActionTrait for AddTrustList { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(trust_list_add)] + } +} diff --git a/identity-wallet/src/state/trust_list/actions/add_trust_list_entry.rs b/identity-wallet/src/state/trust_list/actions/add_trust_list_entry.rs new file mode 100644 index 000000000..bd7d27cb9 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/add_trust_list_entry.rs @@ -0,0 +1,20 @@ +use crate::reducer; +use crate::state::trust_list::reducers::add_entry::add_trust_list_entry; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/AddTrustListEntry.ts")] +pub struct AddTrustListEntry { + pub trust_list_id: String, + pub domain: String, +} + +#[typetag::serde(name = "[Trust List] Add entry")] +impl ActionTrait for AddTrustListEntry { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(add_trust_list_entry)] + } +} diff --git a/identity-wallet/src/state/trust_list/actions/delete_trust_list.rs b/identity-wallet/src/state/trust_list/actions/delete_trust_list.rs new file mode 100644 index 000000000..80ea234b4 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/delete_trust_list.rs @@ -0,0 +1,19 @@ +use crate::reducer; +use crate::state::trust_list::reducers::delete_trust_list::trust_list_delete; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/DeleteTrustList.ts")] +pub struct DeleteTrustList { + pub trust_list_id: String, +} + +#[typetag::serde(name = "[Trust Lists] Delete")] +impl ActionTrait for DeleteTrustList { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(trust_list_delete)] + } +} diff --git a/identity-wallet/src/state/trust_list/actions/delete_trust_list_entry.rs b/identity-wallet/src/state/trust_list/actions/delete_trust_list_entry.rs new file mode 100644 index 000000000..50cb9961b --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/delete_trust_list_entry.rs @@ -0,0 +1,22 @@ +use crate::reducer; +use crate::state::trust_list::reducers::delete_entry::delete_trust_list_entry; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use url::Url; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/DeleteTrustListEntry.ts")] +pub struct DeleteTrustListEntry { + pub trust_list_id: String, + #[ts(type = "string")] + pub domain: Url, +} + +#[typetag::serde(name = "[Trust List] Delete entry")] +impl ActionTrait for DeleteTrustListEntry { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(delete_trust_list_entry)] + } +} diff --git a/identity-wallet/src/state/trust_list/actions/edit_trust_list.rs b/identity-wallet/src/state/trust_list/actions/edit_trust_list.rs new file mode 100644 index 000000000..cca137ec4 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/edit_trust_list.rs @@ -0,0 +1,20 @@ +use crate::reducer; +use crate::state::trust_list::reducers::edit_trust_list::trust_list_edit; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/EditTrustList.ts")] +pub struct EditTrustList { + pub trust_list_id: String, + pub new_display_name: String, +} + +#[typetag::serde(name = "[Trust Lists] Edit")] +impl ActionTrait for EditTrustList { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(trust_list_edit)] + } +} diff --git a/identity-wallet/src/state/trust_list/actions/edit_trust_list_entry.rs b/identity-wallet/src/state/trust_list/actions/edit_trust_list_entry.rs new file mode 100644 index 000000000..5b5b670d8 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/edit_trust_list_entry.rs @@ -0,0 +1,24 @@ +use crate::reducer; +use crate::state::trust_list::reducers::edit_entry::edit_trust_list_entry; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use url::Url; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/EditTrustListEntry.ts")] +pub struct EditTrustListEntry { + pub trust_list_id: String, + #[ts(type = "string")] + pub old_domain: Url, + #[ts(type = "string")] + pub new_domain: Url, +} + +#[typetag::serde(name = "[Trust List] Edit entry")] +impl ActionTrait for EditTrustListEntry { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(edit_trust_list_entry)] + } +} diff --git a/identity-wallet/src/state/trust_list/actions/mod.rs b/identity-wallet/src/state/trust_list/actions/mod.rs new file mode 100644 index 000000000..46a354452 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/mod.rs @@ -0,0 +1,7 @@ +pub mod add_trust_list; +pub mod add_trust_list_entry; +pub mod delete_trust_list; +pub mod delete_trust_list_entry; +pub mod edit_trust_list; +pub mod edit_trust_list_entry; +pub mod toggle_trust_list_entry; diff --git a/identity-wallet/src/state/trust_list/actions/toggle_trust_list_entry.rs b/identity-wallet/src/state/trust_list/actions/toggle_trust_list_entry.rs new file mode 100644 index 000000000..4e7aa6559 --- /dev/null +++ b/identity-wallet/src/state/trust_list/actions/toggle_trust_list_entry.rs @@ -0,0 +1,22 @@ +use crate::reducer; +use crate::state::trust_list::reducers::toggle_entry::toggle_trust_list_entry; +use crate::state::{actions::ActionTrait, Reducer}; + +use serde::{Deserialize, Serialize}; +use ts_rs::TS; +use url::Url; + +#[derive(Serialize, Deserialize, Debug, TS, Clone)] +#[ts(export, export_to = "bindings/actions/ToggleTrustListEntry.ts")] +pub struct ToggleTrustListEntry { + pub trust_list_id: String, + #[ts(type = "string")] + pub domain: Url, +} + +#[typetag::serde(name = "[Trust List] Toggle entry")] +impl ActionTrait for ToggleTrustListEntry { + fn reducers<'a>(&self) -> Vec> { + vec![reducer!(toggle_trust_list_entry)] + } +} diff --git a/identity-wallet/src/state/trust_list/mod.rs b/identity-wallet/src/state/trust_list/mod.rs new file mode 100644 index 000000000..1229cd637 --- /dev/null +++ b/identity-wallet/src/state/trust_list/mod.rs @@ -0,0 +1,119 @@ +pub mod actions; +pub mod reducers; + +use super::FeatTrait; + +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, ops::Not}; +use ts_rs::TS; +use url::Url; +use uuid::Uuid; + +/// TrustLists is a Vec capable of holding TrustList's of 2 types, external and custom, differentiated by a bool. +/// insert, get_mut and remove methods are modelled after the std::collections::HashMap methods. +#[derive(Serialize, Deserialize, Clone, Debug, TS, PartialEq, Default)] +#[ts(export, export_to = "bindings/trust_list/TrustLists.ts")] +pub struct TrustLists(pub Vec); + +#[typetag::serde(name = "trust_lists")] +impl FeatTrait for TrustLists {} + +impl TrustLists { + pub fn new() -> Self { + Self(Vec::new()) + } + + pub fn contains(&self, id: &str) -> bool { + self.0.iter().any(|trust_list| trust_list.id == id) + } + + /// Modelled after the `std::collections::HashMap::insert` method. + pub fn insert(&mut self, trust_list: TrustList) -> Option<&TrustList> { + self.contains(&trust_list.id) + .not() + .then(|| { + self.0.push(trust_list); + self.0.last() + }) + .flatten() + } + + /// Modelled after the `std::collections::HashMap::get_mut` method. + fn get_mut(&mut self, id: &str) -> Option<&mut TrustList> { + self.0.iter_mut().find(|trust_list| trust_list.id == id) + } + + /// Modelled after the `std::collections::HashMap::remove` method. + fn remove(&mut self, id: &str) -> Option { + let index = self.0.iter().position(|trust_list| trust_list.id == id)?; + Some(self.0.remove(index)) + } +} + +/// A TrustList will contain trusted URL's. +/// Domains with the https:// scheme don't need to be added with the scheme, nor will they be displayed by it. +/// These URL's will determine whether a Linked VP is to be trusted and therefore displayed to the user or not. +/// A default trust list has been added as well, containing domains we use in our demos. +#[derive(Serialize, Deserialize, Clone, Debug, TS, PartialEq)] +#[ts(export, export_to = "bindings/trust_list/TrustList.ts")] +pub struct TrustList { + #[serde(default)] + pub id: String, + pub display_name: String, + /// Custom false: this currently would mean injected at profile creation similar to our default trust list. + /// Custom true: TrustList's can be created in dev mode at any time. + #[serde(default)] + pub custom: bool, + #[serde(deserialize_with = "deserialize_domains")] + #[ts(type = "Record")] + pub entries: HashMap, +} + +fn deserialize_domains<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let domains: Vec = Vec::deserialize(deserializer)?; + Ok(domains.into_iter().map(|domain| (domain, true)).collect()) +} + +impl Default for TrustList { + fn default() -> Self { + Self::new() + } +} + +impl TrustList { + pub fn new() -> Self { + Self { + id: Uuid::new_v4().to_string(), + display_name: String::new(), + custom: true, + entries: std::collections::HashMap::new(), + } + } + + pub fn insert(&mut self, domain: Url, trusted: bool) { + self.entries.insert(domain, trusted); + } + + pub fn remove(&mut self, domain: &Url) -> Option { + self.entries.remove(domain) + } + + pub fn contains(&self, domain: &Url) -> bool { + self.entries.contains_key(domain) + } + + pub fn get(&self, domain: &Url) -> Option<&bool> { + self.entries.get(domain) + } + + pub fn get_mut(&mut self, domain: &Url) -> Option<&mut bool> { + self.entries.get_mut(domain) + } + + pub fn iter(&self) -> std::collections::hash_map::Iter { + self.entries.iter() + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/add_entry.rs b/identity-wallet/src/state/trust_list/reducers/add_entry.rs new file mode 100644 index 000000000..aa514dbb1 --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/add_entry.rs @@ -0,0 +1,74 @@ +use log::info; +use url::Url; + +use crate::error::AppError; +use crate::state::trust_list::actions::add_trust_list_entry::AddTrustListEntry; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn add_trust_list_entry(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + + // Parse the domain value into a Url + let domain = Url::parse(&action.domain) + // If the domain value does not contain a scheme, then apply the `https` scheme as the default scheme. + .or_else(|_| Url::parse(&format!("https://{}", action.domain))) + .map_err(|_| AppError::Error(format!("Invalid domain value: `{}`", action.domain)))?; + + let trust_list = trust_lists + .get_mut(&action.trust_list_id) + .ok_or_else(|| AppError::TrustListNotFoundError(action.trust_list_id.clone()))?; + + trust_list.insert(domain, true); + + info!( + "Added trusted domain `{}` to list `{}`", + action.domain, trust_list.display_name + ); + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use url::Url; + use uuid::Uuid; + + use super::*; + use crate::state::trust_list::TrustList; + + use std::{collections::HashMap, sync::Arc}; + + #[tokio::test] + async fn test_add_trust_list_entry() { + let mut state = AppState::default(); + let default_trust_list = TrustList { + id: Uuid::new_v4().to_string(), + display_name: "impierce".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://impierce.com").unwrap(), true)]), + }; + state.trust_lists.insert(default_trust_list.clone()); + + let action = Arc::new(AddTrustListEntry { + trust_list_id: default_trust_list.id.clone(), + domain: "example.com".to_string(), + }); + + let result = add_trust_list_entry(state, action).await.unwrap(); + + let mut expected = default_trust_list.clone(); + expected.insert(Url::parse("https://example.com").unwrap(), true); + + assert_eq!(result.trust_lists.0.first().unwrap().clone(), expected); + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/add_trust_list.rs b/identity-wallet/src/state/trust_list/reducers/add_trust_list.rs new file mode 100644 index 000000000..61b9ee75c --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/add_trust_list.rs @@ -0,0 +1,65 @@ +use log::info; + +use crate::error::AppError; +use crate::state::trust_list::actions::add_trust_list::AddTrustList; +use crate::state::trust_list::TrustList; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn trust_list_add(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + trust_lists.insert(TrustList { + id: uuid::Uuid::new_v4().to_string(), + display_name: action.display_name.clone(), + custom: true, + entries: Default::default(), + }); + + info!("Added trust list `{}`", action.display_name); + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::state::trust_list::TrustLists; + + use std::sync::Arc; + + #[tokio::test] + async fn test_trust_list_add() { + let state = AppState::default(); + + let action = Arc::new(AddTrustList { + display_name: "example".to_string(), + }); + + let result = trust_list_add(state, action).await.unwrap(); + + let mut expected = TrustLists::default(); + expected.insert(TrustList { + id: "_".to_string(), + display_name: "example".to_string(), + custom: true, + entries: Default::default(), + }); + + let actual = result.trust_lists.0.first().unwrap(); + let expected = expected.0.first().unwrap(); + + // ID is not asserted as it is randomly generated upon creation. + assert_eq!(actual.display_name, expected.display_name); + assert_eq!(actual.custom, expected.custom); + assert_eq!(actual.entries, expected.entries); + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/delete_entry.rs b/identity-wallet/src/state/trust_list/reducers/delete_entry.rs new file mode 100644 index 000000000..28f3bf17c --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/delete_entry.rs @@ -0,0 +1,72 @@ +use log::info; + +use crate::error::AppError; +use crate::state::trust_list::actions::delete_trust_list_entry::DeleteTrustListEntry; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn delete_trust_list_entry(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + + let trust_list = trust_lists + .get_mut(&action.trust_list_id) + .ok_or_else(|| AppError::TrustListNotFoundError(action.trust_list_id.clone()))?; + + trust_list.remove(&action.domain); + + info!( + "Deleted trusted domain `{}` from list `{}`", + action.domain, trust_list.display_name + ); + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use url::Url; + use uuid::Uuid; + + use super::*; + use crate::state::trust_list::{TrustList, TrustLists}; + + use std::{collections::HashMap, sync::Arc}; + + #[tokio::test] + async fn test_delete_trust_list_entry() { + let mut state = AppState::default(); + let default_trust_list = TrustList { + id: Uuid::new_v4().to_string(), + display_name: "impierce".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), true)]), + }; + state.trust_lists.insert(default_trust_list.clone()); + + let action = Arc::new(DeleteTrustListEntry { + trust_list_id: default_trust_list.id.clone(), + domain: Url::parse("https://example.com").unwrap(), + }); + + let result = delete_trust_list_entry(state, action).await.unwrap(); + + let mut expected = TrustLists::new(); + expected.insert(TrustList { + id: default_trust_list.id.clone(), + display_name: default_trust_list.display_name.clone(), + custom: true, + entries: HashMap::new(), + }); + + assert_eq!(result.trust_lists, expected); + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/delete_trust_list.rs b/identity-wallet/src/state/trust_list/reducers/delete_trust_list.rs new file mode 100644 index 000000000..1f6ba8dd5 --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/delete_trust_list.rs @@ -0,0 +1,59 @@ +use log::{info, warn}; + +use crate::error::AppError; +use crate::state::trust_list::actions::delete_trust_list::DeleteTrustList; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn trust_list_delete(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + if let Some(trust_list) = trust_lists.remove(&action.trust_list_id) { + info!("Deleted trust list `{}`", trust_list.display_name); + } else { + warn!("Attempted to delete non-existent trust list `{}`", action.trust_list_id); + } + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use url::Url; + use uuid::Uuid; + + use crate::state::trust_list::{TrustList, TrustLists}; + + use super::*; + use std::{collections::HashMap, sync::Arc}; + + #[tokio::test] + async fn test_trust_list_delete() { + let mut state = AppState::default(); + let default_trust_list = TrustList { + id: Uuid::new_v4().to_string(), + display_name: "impierce".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), true)]), + }; + state.trust_lists.insert(default_trust_list.clone()); + + let action = Arc::new(DeleteTrustList { + trust_list_id: default_trust_list.id, + }); + + let result = trust_list_delete(state, action).await.unwrap(); + + let expected = TrustLists::new(); + + assert_eq!(result.trust_lists, expected); + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/edit_entry.rs b/identity-wallet/src/state/trust_list/reducers/edit_entry.rs new file mode 100644 index 000000000..9ffbdc21f --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/edit_entry.rs @@ -0,0 +1,78 @@ +use log::info; + +use crate::error::AppError; +use crate::state::trust_list::actions::edit_trust_list_entry::EditTrustListEntry; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn edit_trust_list_entry(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + + let trust_list = trust_lists + .get_mut(&action.trust_list_id) + .ok_or_else(|| AppError::TrustListNotFoundError(action.trust_list_id.clone()))?; + + let new_bool = *trust_list + .get(&action.old_domain) + .ok_or_else(|| AppError::Error(format!("Value does not exist: {}", action.old_domain.clone())))?; + + trust_list.remove(&action.old_domain); + trust_list.insert(action.new_domain.clone(), new_bool); + + info!( + "Updated trusted domain: `{}` --> `{}` on list `{}`", + action.old_domain, action.new_domain, trust_list.display_name + ); + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use url::Url; + use uuid::Uuid; + + use crate::state::trust_list::{TrustList, TrustLists}; + + use super::*; + use std::{collections::HashMap, sync::Arc}; + + #[tokio::test] + async fn test_edit_trust_list_entry() { + let mut state = AppState::default(); + let default_trust_list = TrustList { + id: Uuid::new_v4().to_string(), + display_name: "impierce".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), true)]), + }; + state.trust_lists.insert(default_trust_list.clone()); + + let action = Arc::new(EditTrustListEntry { + trust_list_id: default_trust_list.id.clone(), + old_domain: Url::parse("https://example.com").unwrap(), + new_domain: Url::parse("https://new.com").unwrap(), + }); + + let result = edit_trust_list_entry(state, action).await.unwrap(); + + let mut expected = TrustLists::new(); + expected.insert(TrustList { + id: default_trust_list.id.clone(), + display_name: default_trust_list.display_name.clone(), + custom: true, + entries: HashMap::from([(Url::parse("https://new.com").unwrap(), true)]), + }); + + assert_eq!(result.trust_lists, expected); + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/edit_trust_list.rs b/identity-wallet/src/state/trust_list/reducers/edit_trust_list.rs new file mode 100644 index 000000000..18327f647 --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/edit_trust_list.rs @@ -0,0 +1,73 @@ +use log::info; + +use crate::error::AppError; +use crate::state::trust_list::actions::edit_trust_list::EditTrustList; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn trust_list_edit(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + let trust_list = trust_lists + .get_mut(&action.trust_list_id) + .ok_or_else(|| AppError::TrustListNotFoundError(action.trust_list_id.clone()))?; + + let display_name_before = trust_list.display_name.clone(); + + trust_list.display_name = action.new_display_name.clone(); + + info!( + "Updated trust list name: `{}` --> `{}`", + display_name_before, action.new_display_name + ); + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use url::Url; + use uuid::Uuid; + + use super::*; + use crate::state::trust_list::{TrustList, TrustLists}; + + use std::{collections::HashMap, sync::Arc}; + + #[tokio::test] + async fn test_trust_list_edit() { + let mut state = AppState::default(); + let default_trust_list = TrustList { + id: Uuid::new_v4().to_string(), + display_name: "impierce".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), true)]), + }; + state.trust_lists.insert(default_trust_list.clone()); + + let action = Arc::new(EditTrustList { + trust_list_id: default_trust_list.id.clone(), + new_display_name: "example".to_string(), + }); + + let result = trust_list_edit(state, action).await.unwrap(); + + let mut expected = TrustLists::new(); + expected.insert(TrustList { + id: default_trust_list.id.clone(), + display_name: "example".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), true)]), + }); + + assert_eq!(result.trust_lists, expected); + } +} diff --git a/identity-wallet/src/state/trust_list/reducers/mod.rs b/identity-wallet/src/state/trust_list/reducers/mod.rs new file mode 100644 index 000000000..0b49f0b57 --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/mod.rs @@ -0,0 +1,7 @@ +pub mod add_entry; +pub mod add_trust_list; +pub mod delete_entry; +pub mod delete_trust_list; +pub mod edit_entry; +pub mod edit_trust_list; +pub mod toggle_entry; diff --git a/identity-wallet/src/state/trust_list/reducers/toggle_entry.rs b/identity-wallet/src/state/trust_list/reducers/toggle_entry.rs new file mode 100644 index 000000000..679d38b7c --- /dev/null +++ b/identity-wallet/src/state/trust_list/reducers/toggle_entry.rs @@ -0,0 +1,73 @@ +use log::info; + +use crate::error::AppError; +use crate::state::trust_list::actions::toggle_trust_list_entry::ToggleTrustListEntry; +use crate::state::{ + actions::{listen, Action}, + AppState, +}; + +pub async fn toggle_trust_list_entry(state: AppState, action: Action) -> Result { + if let Some(action) = listen::(action) { + let mut trust_lists = state.trust_lists; + + if let Some(trust_list) = trust_lists.get_mut(&action.trust_list_id) { + let display_name = trust_list.display_name.clone(); + if let Some(value) = trust_list.get_mut(&action.domain) { + *value = !*value; + + info!( + "Toggled domain `{}` to `{}` in trust list: `{}`", + action.domain, value, display_name + ); + } + } + + return Ok(AppState { + trust_lists, + current_user_prompt: None, + ..state + }); + } + Ok(state) +} + +#[cfg(test)] +mod tests { + use url::Url; + use uuid::Uuid; + + use crate::state::trust_list::{TrustList, TrustLists}; + + use super::*; + use std::{collections::HashMap, sync::Arc}; + + #[tokio::test] + async fn test_toggletrust_list_entry() { + let mut state = AppState::default(); + let default_trust_list = TrustList { + id: Uuid::new_v4().to_string(), + display_name: "impierce".to_string(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), true)]), + }; + state.trust_lists.insert(default_trust_list.clone()); + + let action = Arc::new(ToggleTrustListEntry { + trust_list_id: default_trust_list.id.clone(), + domain: Url::parse("https://example.com").unwrap(), + }); + + let result = toggle_trust_list_entry(state, action).await.unwrap(); + + let mut expected = TrustLists::new(); + expected.insert(TrustList { + id: default_trust_list.id.clone(), + display_name: default_trust_list.display_name.clone(), + custom: true, + entries: HashMap::from([(Url::parse("https://example.com").unwrap(), false)]), + }); + + assert_eq!(result.trust_lists, expected); + } +} diff --git a/unime/src/lib/icons/index.ts b/unime/src/lib/icons/index.ts index 44accefd5..89caaa343 100644 --- a/unime/src/lib/icons/index.ts +++ b/unime/src/lib/icons/index.ts @@ -5,6 +5,7 @@ export { default as NLFlagIcon } from '~icons/circle-flags/nl'; export { default as USFlagIcon } from '~icons/circle-flags/us'; // Phosphor icons: https://icon-sets.iconify.design/ph/ +export { default as ArrowCounterClockwiseBoldIcon } from '~icons/ph/arrow-counter-clockwise-bold'; export { default as ArrowLeftRegularIcon } from '~icons/ph/arrow-left'; export { default as ArrowSquareOutBoldIcon } from '~icons/ph/arrow-square-out-bold'; export { default as BankLightIcon } from '~icons/ph/bank-light'; @@ -37,9 +38,11 @@ export { default as GhostFillIcon } from '~icons/ph/ghost-fill'; export { default as HeartFillIcon } from '~icons/ph/heart-fill'; export { default as HeartStraightRegularIcon } from '~icons/ph/heart-straight'; export { default as HeartStraightFillIcon } from '~icons/ph/heart-straight-fill'; +export { default as InfoRegularIcon } from '~icons/ph/info'; export { default as InfoFillIcon } from '~icons/ph/info-fill'; export { default as KeyFillIcon } from '~icons/ph/key-fill'; export { default as KeyboardFillIcon } from '~icons/ph/keyboard-fill'; +export { default as ListStarFillIcon } from '~icons/ph/list-star-fill'; export { default as LockSimpleOpenFillIcon } from '~icons/ph/lock-simple-open-fill'; export { default as MagnifyingGlassIcon } from '~icons/ph/magnifying-glass'; export { default as MagnifyingGlassBoldIcon } from '~icons/ph/magnifying-glass-bold'; @@ -57,6 +60,7 @@ export { default as SignOutFillIcon } from '~icons/ph/sign-out-fill'; export { default as SlidersHorizontalRegularIcon } from '~icons/ph/sliders-horizontal'; export { default as SmileyRegularIcon } from '~icons/ph/smiley'; export { default as SmileySadRegularIcon } from '~icons/ph/smiley-sad'; +export { default as StarFillIcon } from '~icons/ph/star-fill'; export { default as SunFillIcon } from '~icons/ph/sun-fill'; export { default as TranslateFillIcon } from '~icons/ph/translate-fill'; export { default as TrashRegularIcon } from '~icons/ph/trash'; diff --git a/unime/src/lib/stores.ts b/unime/src/lib/stores.ts index 24321d5a8..ef1b5fa8d 100644 --- a/unime/src/lib/stores.ts +++ b/unime/src/lib/stores.ts @@ -36,6 +36,7 @@ const empty_state: AppState = { user_journey: null, debug_messages: [], history: [], + trust_lists: [], dev_mode: 'Off', }; diff --git a/unime/src/routes/(app)/me/settings/app/+page.svelte b/unime/src/routes/(app)/me/settings/app/+page.svelte index 0d7a1bed7..01e6977c2 100644 --- a/unime/src/routes/(app)/me/settings/app/+page.svelte +++ b/unime/src/routes/(app)/me/settings/app/+page.svelte @@ -11,6 +11,7 @@ ConfettiFillIcon, FilesFillIcon, KeyFillIcon, + ListStarFillIcon, PasswordFillIcon, SunFillIcon, TranslateFillIcon, @@ -68,7 +69,7 @@ on:click={() => goto('/me/settings/app/did')} /> -
+
goto('/me/settings/app/keys')} />
+
+ goto('/me/settings/app/trust-list')} + /> +
{/if}
diff --git a/unime/src/routes/(app)/me/settings/app/keys/+page.svelte b/unime/src/routes/(app)/me/settings/app/keys/+page.svelte index 991c43c55..fd3ddc893 100644 --- a/unime/src/routes/(app)/me/settings/app/keys/+page.svelte +++ b/unime/src/routes/(app)/me/settings/app/keys/+page.svelte @@ -1,10 +1,9 @@ + + history.back()} title={'Trusted issuers'} /> +
+
+ +
+ + + +
+

Developer info

+
    +
  • Verifiable Presentations are trusted based on domains found in trust lists.
  • +
  • You can add, remove and update custom entries.
  • +
  • All entries can be manually disabled.
  • + +
+
+
+ +
+

Trust lists

+ {#if trustLists.length === 0} +
+

No trust lists yet.

+
+ {/if} + {#each trustLists as trustList} + + {/each} +
+
+
diff --git a/unime/src/routes/(app)/me/settings/app/trust-list/[id]/+page.svelte b/unime/src/routes/(app)/me/settings/app/trust-list/[id]/+page.svelte new file mode 100644 index 000000000..bd0c23778 --- /dev/null +++ b/unime/src/routes/(app)/me/settings/app/trust-list/[id]/+page.svelte @@ -0,0 +1,169 @@ + + + history.back()} title={trustList?.display_name ?? ''}> + + +
+
+ + + + {:else} + + {/if} +
+ + {#key active} + + dispatch({ type: '[Trust List] Toggle entry', payload: { trust_list_id: $page.params.id, domain } })} + /> + {/key} +
+ + {/each} + {#if showNewEntry} +
+ +
+ + + + {/if} + + + +
-
From 2e1698eaffec1ed4a45c197b5dd1285081492b87 Mon Sep 17 00:00:00 2001 From: Thilo Maier Date: Mon, 16 Dec 2024 11:32:25 +0100 Subject: [PATCH 15/15] fix: content overflow & scrolling issues (#420) --- .../src/lib/components/StatusIndicator.svelte | 2 +- .../components/navigation/TopNavBar.svelte | 5 +- unime/src/routes/(app)/+layout.svelte | 8 +- unime/src/routes/(app)/me/+page.svelte | 2 +- .../src/routes/(app)/me/settings/+page.svelte | 92 ++++++-------- .../(app)/me/settings/about/+page.svelte | 13 +- .../routes/(app)/me/settings/app/+page.svelte | 12 +- .../(app)/me/settings/app/did/+page.svelte | 120 ++++++++---------- .../(app)/me/settings/app/keys/+page.svelte | 87 ++++++------- .../me/settings/app/language/+page.svelte | 54 ++++---- .../(app)/me/settings/app/theme/+page.svelte | 5 +- .../me/settings/app/trust-list/+page.svelte | 103 ++++++++------- .../(app)/me/settings/profile/+page.svelte | 64 ++++------ unime/src/routes/+layout.svelte | 2 +- .../prompt/accept-connection/+page.svelte | 17 ++- .../prompt/credential-offer/+page.svelte | 14 +- .../prompt/share-credentials/+page.svelte | 12 +- 17 files changed, 291 insertions(+), 321 deletions(-) diff --git a/unime/src/lib/components/StatusIndicator.svelte b/unime/src/lib/components/StatusIndicator.svelte index 40a418294..3ebe3b5fc 100644 --- a/unime/src/lib/components/StatusIndicator.svelte +++ b/unime/src/lib/components/StatusIndicator.svelte @@ -11,7 +11,7 @@ export let status: ValidationStatus; export let title: string; export let description: string | undefined = undefined; - export let logoUrl: string | undefined = undefined; + export let logoUrl: string | null = null; const { elements: { trigger, content, arrow }, diff --git a/unime/src/lib/components/navigation/TopNavBar.svelte b/unime/src/lib/components/navigation/TopNavBar.svelte index a195e7ece..2d57fbac7 100644 --- a/unime/src/lib/components/navigation/TopNavBar.svelte +++ b/unime/src/lib/components/navigation/TopNavBar.svelte @@ -9,13 +9,16 @@ export let title: string; export let disabled = false; + + let className: string = ''; + export { className as class };
+ history.back()} title={'Manage identities'} class="sticky top-0 z-10" /> + +
+ +
+

Produce

+
+ {#each methods as method (method.method)} + {/if} - - {/each} -
+
+ {#if method.did} +
+

+ {method.did} +

+
+ {/if} + + {/each}
+
- -
-

Verify

-
- {#each verifiableMethods as method} -
- -

- {method} -

-
- {/each} -
+ +
+

Verify

+
+ {#each verifiableMethods as method} +
+ +

+ {method} +

+
+ {/each}
- - diff --git a/unime/src/routes/(app)/me/settings/app/keys/+page.svelte b/unime/src/routes/(app)/me/settings/app/keys/+page.svelte index fd3ddc893..1927d29ed 100644 --- a/unime/src/routes/(app)/me/settings/app/keys/+page.svelte +++ b/unime/src/routes/(app)/me/settings/app/keys/+page.svelte @@ -36,55 +36,52 @@ ]; - history.back()} title={'Manage keys'} /> -
-
-
-

Available keys

- {#each keys as key} - - {/each} -
+
+ {#if key.key_id} +
+

+ {key.key_id} +

+
+ {/if} + + {/each} +
-
- - - -
-

Developer info

-
    -
  • All keys are generated once on profile creation.
  • -
  • Only one key per type is currently supported.
  • -
  • - UniMe will automatically select the key type based on the server capabilities, but respect your preference - if there's multiple matches. -
  • -
-
+
+ + + +
+

Developer info

+
    +
  • All keys are generated once on profile creation.
  • +
  • Only one key per type is currently supported.
  • +
  • + UniMe will automatically select the key type based on the server capabilities, but respect your preference if + there's multiple matches. +
  • +
diff --git a/unime/src/routes/(app)/me/settings/app/language/+page.svelte b/unime/src/routes/(app)/me/settings/app/language/+page.svelte index 153794f13..b5094a2ea 100644 --- a/unime/src/routes/(app)/me/settings/app/language/+page.svelte +++ b/unime/src/routes/(app)/me/settings/app/language/+page.svelte @@ -10,36 +10,28 @@ $: selected = locales.find((l) => l.locale === $state?.profile_settings.locale); - history.back()} title={$LL.SETTINGS.APP.LANGUAGE.NAVBAR_TITLE()} /> -
-
- {#each locales as l} - - {/each} -
+ on:click={() => dispatch({ type: '[Settings] Set locale', payload: { locale: l.locale } })} + disabled={incompleteLocales.includes(l.locale)} + > + +

+ {l.displayName} +

+ {#if incompleteLocales.includes(l.locale)} +
+ {$LL.SETTINGS.APP.LANGUAGE.COMING_SOON()} +
+ {/if} + {#if selected && l.locale === selected.locale} + + {/if} + + {/each}
- - diff --git a/unime/src/routes/(app)/me/settings/app/theme/+page.svelte b/unime/src/routes/(app)/me/settings/app/theme/+page.svelte index 10e1142ed..d882ad7a2 100644 --- a/unime/src/routes/(app)/me/settings/app/theme/+page.svelte +++ b/unime/src/routes/(app)/me/settings/app/theme/+page.svelte @@ -10,8 +10,9 @@ let currentValue = $state.profile_settings.profile?.theme ?? 'system'; - history.back()} title={$LL.SETTINGS.APP.THEME.NAVBAR_TITLE()} /> -
+ history.back()} title={$LL.SETTINGS.APP.THEME.NAVBAR_TITLE()} class="sticky top-0 z-10" /> + +

{$LL.SETTINGS.APP.THEME.TITLE_1()} diff --git a/unime/src/routes/(app)/me/settings/app/trust-list/+page.svelte b/unime/src/routes/(app)/me/settings/app/trust-list/+page.svelte index 7a0754787..d6a063964 100644 --- a/unime/src/routes/(app)/me/settings/app/trust-list/+page.svelte +++ b/unime/src/routes/(app)/me/settings/app/trust-list/+page.svelte @@ -30,60 +30,59 @@ }; - history.back()} title={'Trusted issuers'} /> -

-
- -
- - - -
-

Developer info

-
    -
  • Verifiable Presentations are trusted based on domains found in trust lists.
  • -
  • You can add, remove and update custom entries.
  • -
  • All entries can be manually disabled.
  • - -
-
+ history.back()} title={'Trusted issuers'} class="sticky top-0 z-10" /> + +
+ +
+ + + +
+

Developer info

+
    +
  • Verifiable Presentations are trusted based on domains found in trust lists.
  • +
  • You can add, remove and update custom entries.
  • +
  • All entries can be manually disabled.
  • + +
+
-
-

Trust lists

- {#if trustLists.length === 0} -
-

No trust lists yet.

-
- {/if} - {#each trustLists as trustList} - - {/each} -
-
+

+ {activeDomainsString(trustList)} +

+ + + {/each}
+
diff --git a/unime/src/routes/(app)/me/settings/profile/+page.svelte b/unime/src/routes/(app)/me/settings/profile/+page.svelte index 3a55d7bf2..fa3862384 100644 --- a/unime/src/routes/(app)/me/settings/profile/+page.svelte +++ b/unime/src/routes/(app)/me/settings/profile/+page.svelte @@ -9,43 +9,35 @@ import { KeyboardFillIcon, TrashFillIcon } from '$lib/icons'; - history.back()} title={$LL.SETTINGS.PROFILE.TITLE()} /> -
-
- goto('/me/settings/profile/name')} - /> + history.back()} title={$LL.SETTINGS.PROFILE.TITLE()} class="sticky top-0 z-10" /> - - +
+ goto('/me/settings/profile/name')} + /> + + + + + +
- -

- {$LL.SETTINGS.PROFILE.DELETE_PROFILE.TITLE()} -

- - -
- -
-
+
+
- - diff --git a/unime/src/routes/+layout.svelte b/unime/src/routes/+layout.svelte index 3456c1eed..320f00502 100644 --- a/unime/src/routes/+layout.svelte +++ b/unime/src/routes/+layout.svelte @@ -308,7 +308,7 @@ Stacking context: We have to deviate from the DOM-sequence. Add paddings to honor safe-area-insets. This is also the portal for the ActionSheet instead of default `body` to ensure it works with dark mode. --> -
+
{#if $appState?.dev_mode !== 'Off'} {#if expandedDevMenu} diff --git a/unime/src/routes/prompt/accept-connection/+page.svelte b/unime/src/routes/prompt/accept-connection/+page.svelte index b403ca041..be11987f5 100644 --- a/unime/src/routes/prompt/accept-connection/+page.svelte +++ b/unime/src/routes/prompt/accept-connection/+page.svelte @@ -47,8 +47,13 @@ }); -
- history.back()} disabled={loading} /> +
+ history.back()} + disabled={loading} + class="sticky top-0 z-10" + />
{#if logo_uri} @@ -135,9 +140,8 @@
- - -
+ +
diff --git a/unime/src/routes/prompt/credential-offer/+page.svelte b/unime/src/routes/prompt/credential-offer/+page.svelte index 21552d700..adfb5932a 100644 --- a/unime/src/routes/prompt/credential-offer/+page.svelte +++ b/unime/src/routes/prompt/credential-offer/+page.svelte @@ -40,8 +40,13 @@ }); -
- history.back()} disabled={loading} /> +
+ history.back()} + disabled={loading} + class="sticky top-0 z-10" + />
{#if $state.current_user_prompt.logo_uri} @@ -83,7 +88,7 @@
- +