diff --git a/scripts/devenv/mock_relying_party.toml.template b/scripts/devenv/mock_relying_party.toml.template index dedd74a07..9b881c4f7 100644 --- a/scripts/devenv/mock_relying_party.toml.template +++ b/scripts/devenv/mock_relying_party.toml.template @@ -15,19 +15,19 @@ sha256 = "${WALLET_WEB_SHA256}" docType = "com.example.pid" nameSpaces = { "com.example.pid" = { bsn = true } } -[[usecases.xyz_bank.items_requests]] +[[usecases.online_marketplace.items_requests]] docType = "com.example.pid" -nameSpaces = { "com.example.pid" = { given_name = true, family_name = true, birth_date = true, bsn = true } } +nameSpaces = { "com.example.pid" = { given_name = true, family_name = true, birth_date = true } } -[[usecases.xyz_bank.items_requests]] +[[usecases.online_marketplace.items_requests]] docType = "com.example.address" nameSpaces = { "com.example.address" = { resident_street = true, resident_house_number = true, resident_postal_code = true } } -[[usecases.online_marketplace.items_requests]] +[[usecases.xyz_bank.items_requests]] docType = "com.example.pid" -nameSpaces = { "com.example.pid" = { given_name = true, family_name = true, birth_date = true } } +nameSpaces = { "com.example.pid" = { given_name = true, family_name = true, birth_date = true, bsn = true } } -[[usecases.online_marketplace.items_requests]] +[[usecases.xyz_bank.items_requests]] docType = "com.example.address" nameSpaces = { "com.example.address" = { resident_street = true, resident_house_number = true, resident_postal_code = true } } diff --git a/scripts/devenv/mrp_verification_server.toml.template b/scripts/devenv/mrp_verification_server.toml.template index 6b3c34010..9fe1a252e 100644 --- a/scripts/devenv/mrp_verification_server.toml.template +++ b/scripts/devenv/mrp_verification_server.toml.template @@ -22,14 +22,14 @@ ephemeral_id_secret = "${MRP_VERIFICATION_SERVER_EPHEMERAL_ID_SECRET}" certificate = "${MOCK_RELYING_PARTY_CRT_MIJN_AMSTERDAM}" private_key = "${MOCK_RELYING_PARTY_KEY_MIJN_AMSTERDAM}" -[verifier.usecases.xyz_bank] -certificate = "${MOCK_RELYING_PARTY_CRT_XYZ_BANK}" -private_key = "${MOCK_RELYING_PARTY_KEY_XYZ_BANK}" - [verifier.usecases.online_marketplace] certificate = "${MOCK_RELYING_PARTY_CRT_ONLINE_MARKETPLACE}" private_key = "${MOCK_RELYING_PARTY_KEY_ONLINE_MARKETPLACE}" +[verifier.usecases.xyz_bank] +certificate = "${MOCK_RELYING_PARTY_CRT_XYZ_BANK}" +private_key = "${MOCK_RELYING_PARTY_KEY_XYZ_BANK}" + [verifier.usecases.monkey_bike] certificate = "${MOCK_RELYING_PARTY_CRT_MONKEY_BIKE}" private_key = "${MOCK_RELYING_PARTY_KEY_MONKEY_BIKE}" diff --git a/scripts/setup-devenv.sh b/scripts/setup-devenv.sh index ec47a2c0d..965c3e1e6 100755 --- a/scripts/setup-devenv.sh +++ b/scripts/setup-devenv.sh @@ -221,13 +221,6 @@ export MOCK_RELYING_PARTY_KEY_MIJN_AMSTERDAM MOCK_RELYING_PARTY_CRT_MIJN_AMSTERDAM=$(< "${TARGET_DIR}/mock_relying_party/mijn_amsterdam.crt.der" ${BASE64}) export MOCK_RELYING_PARTY_CRT_MIJN_AMSTERDAM -# Generate relying party key and cert -generate_mock_relying_party_key_pair xyz_bank -MOCK_RELYING_PARTY_KEY_XYZ_BANK=$(< "${TARGET_DIR}/mock_relying_party/xyz_bank.key.der" ${BASE64}) -export MOCK_RELYING_PARTY_KEY_XYZ_BANK -MOCK_RELYING_PARTY_CRT_XYZ_BANK=$(< "${TARGET_DIR}/mock_relying_party/xyz_bank.crt.der" ${BASE64}) -export MOCK_RELYING_PARTY_CRT_XYZ_BANK - # Generate relying party key and cert generate_mock_relying_party_key_pair online_marketplace MOCK_RELYING_PARTY_KEY_ONLINE_MARKETPLACE=$(< "${TARGET_DIR}/mock_relying_party/online_marketplace.key.der" ${BASE64}) @@ -235,6 +228,13 @@ export MOCK_RELYING_PARTY_KEY_ONLINE_MARKETPLACE MOCK_RELYING_PARTY_CRT_ONLINE_MARKETPLACE=$(< "${TARGET_DIR}/mock_relying_party/online_marketplace.crt.der" ${BASE64}) export MOCK_RELYING_PARTY_CRT_ONLINE_MARKETPLACE +# Generate relying party key and cert +generate_mock_relying_party_key_pair xyz_bank +MOCK_RELYING_PARTY_KEY_XYZ_BANK=$(< "${TARGET_DIR}/mock_relying_party/xyz_bank.key.der" ${BASE64}) +export MOCK_RELYING_PARTY_KEY_XYZ_BANK +MOCK_RELYING_PARTY_CRT_XYZ_BANK=$(< "${TARGET_DIR}/mock_relying_party/xyz_bank.crt.der" ${BASE64}) +export MOCK_RELYING_PARTY_CRT_XYZ_BANK + # Generate relying party key and cert generate_mock_relying_party_key_pair monkey_bike MOCK_RELYING_PARTY_KEY_MONKEY_BIKE=$(< "${TARGET_DIR}/mock_relying_party/monkey_bike.key.der" ${BASE64}) diff --git a/wallet_core/Cargo.lock b/wallet_core/Cargo.lock index c054de6ba..d67f61b3d 100644 --- a/wallet_core/Cargo.lock +++ b/wallet_core/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "accept-language" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f27d075294830fcab6f66e320dab524bc6d048f4a151698e153205559113772" + [[package]] name = "addr2line" version = "0.22.0" @@ -798,10 +804,13 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" dependencies = [ + "indexmap 2.2.6", "lazy_static", "nom", "pathdiff", + "ron", "serde", + "serde_json", "toml 0.8.14", ] @@ -2415,6 +2424,7 @@ dependencies = [ name = "mock_relying_party" version = "0.1.0" dependencies = [ + "accept-language", "anyhow", "askama", "axum", @@ -2422,9 +2432,11 @@ dependencies = [ "config", "futures", "http", + "indexmap 2.2.6", "nl_wallet_mdoc", "nutype", "reqwest", + "rstest", "sentry", "serde", "serde_json", @@ -3352,6 +3364,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.6.0", + "indexmap 2.2.6", + "serde", + "serde_derive", +] + [[package]] name = "rsa" version = "0.9.6" @@ -4799,6 +4824,7 @@ version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", diff --git a/wallet_core/Cargo.toml b/wallet_core/Cargo.toml index 3fce73339..5118b5a98 100644 --- a/wallet_core/Cargo.toml +++ b/wallet_core/Cargo.toml @@ -37,6 +37,7 @@ rust-version = "1.80" async_fn_in_trait = "allow" [workspace.dependencies] +accept-language = "3.1.0" aes-gcm = "0.10.3" android_logger = { version = "0.14.1", default-features = false } anyhow = "1.0.66" diff --git a/wallet_core/mock_relying_party/Cargo.toml b/wallet_core/mock_relying_party/Cargo.toml index 9106154dd..c2e32d56b 100644 --- a/wallet_core/mock_relying_party/Cargo.toml +++ b/wallet_core/mock_relying_party/Cargo.toml @@ -15,13 +15,15 @@ doctest = false allow_http_return_url = ["wallet_server/allow_http_return_url"] [dependencies] +accept-language.workspace = true anyhow.workspace = true askama.workspace = true axum = { workspace = true, features = ["http1", "query", "tokio", "tower-log", "tracing"] } base64.workspace = true -config = { workspace = true, features = ["toml"] } +config = { workspace = true, features = ["preserve_order", "toml"] } futures = { workspace = true, features = ["std"] } http.workspace = true +indexmap.workspace = true nutype = { workspace = true, features = ["serde"] } reqwest = { workspace = true, features = ["rustls-tls-webpki-roots"] } sentry = { workspace = true, features = [ @@ -58,3 +60,6 @@ url = { workspace = true, features = ["serde"] } nl_wallet_mdoc.path = "../mdoc" wallet_common = { path = "../wallet_common", features = ["sentry"] } wallet_server = { path = "../wallet_server", features = ["disclosure"] } + +[dev-dependencies] +rstest.workspace = true diff --git a/wallet_core/mock_relying_party/assets/css/demo_bar.css b/wallet_core/mock_relying_party/assets/css/demo_bar.css index 7adcce5ee..6e8c5d5a8 100644 --- a/wallet_core/mock_relying_party/assets/css/demo_bar.css +++ b/wallet_core/mock_relying_party/assets/css/demo_bar.css @@ -1,53 +1,157 @@ aside { - order: -1; + order: -1; - width: 100vw; - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 16px; - padding: 16px; - background: #f2f1fe; - color: #152a62; + width: 100vw; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 16px; + gap: 16px; + color: #152a62; +} + +aside:has(.text) { + background: #f2f1fe; } @media screen and (min-width: 500px) { - aside { - padding: 8px 24px; - } + aside { + padding: 8px 24px 8px 94px; /* 24 + 16 + 54 */ + } } aside b { - font-weight: 700; + font-weight: 700; } aside a { - color: #383ede; + color: #383ede; } aside a:hover { - color: #3237c4; - text-decoration: none; + color: #3237c4; + text-decoration: none; +} + +aside .demo-bar { + flex-grow: 0; + display: flex; + justify-content: center; + align-items: center; + gap: 16px; } -aside::before { - content: " "; - background: url("../non-free/images/nl-wallet.svg") no-repeat center center / cover; - width: 40px; - height: 40px; +aside .demo-bar::before { + content: " "; + background: url("../non-free/images/nl-wallet.svg") no-repeat center center / cover; + width: 40px; + height: 40px; } aside .text { - display: flex; - flex-direction: column; - justify-content: center; + display: flex; + flex-direction: column; + justify-content: center; } @media screen and (min-width: 500px) { - aside .text { - flex-direction: row; - align-items: center; - gap: 8px; - } + aside { + justify-content: right; + } + + aside .demo-bar { + flex-grow: 1; + } + + aside .text { + flex-direction: row; + align-items: center; + gap: 8px; + } +} + +.lang-selector { + position: relative; +} + +.lang-selector label[for="lang_toggle"] { + display: flex; + align-items: center; + + padding: 4px 8px; + gap: 4px; + border-radius: 2px; + + background: #fcfcfc; + color: #383ede; + font-weight: 700; + text-transform: uppercase; + line-height: 1.25; + + user-select: none; +} + +.lang-selector label[for="lang_toggle"]:hover { + background-color: #f1f1f1; + cursor: pointer; +} + +.lang-selector label[for="lang_toggle"]::after { + content: " "; + background: url("../non-free/images/down.svg") no-repeat center center / contain; + + width: 16px; + height: 16px; +} + +#lang_toggle { + display: none; +} + +#lang_toggle:checked + .lang-modal { + display: block; +} + +.lang-selector .lang-modal { + position: absolute; + right: 0; + z-index: 1; + + margin-top: 2px; + + background: #fcfcfc; + box-shadow: 0px 4px 40px 0px #00000029; + border-radius: 2px; + + overflow: hidden; + + display: none; +} + +.lang-selector .lang-modal button { + display: flex; + padding: 12px 24px 12px 12px; + gap: 12px; + color: #152a62; + width: 100%; +} + +.lang-selector .lang-modal button:not(:disabled):hover { + background-color: #f1f1f1; + cursor: pointer; +} + +.lang-selector .lang-modal button::before { + content: " "; + width: 24px; + height: 24px; +} + +.lang-selector .lang-modal button:disabled::before { + content: " "; + background-color: #152a62; + mask: url("../non-free/images/checkmark.svg") no-repeat center center / contain; + width: 24px; + height: 24px; } diff --git a/wallet_core/mock_relying_party/assets/css/nav.css b/wallet_core/mock_relying_party/assets/css/nav.css index 6b049ce23..cdc3031e9 100644 --- a/wallet_core/mock_relying_party/assets/css/nav.css +++ b/wallet_core/mock_relying_party/assets/css/nav.css @@ -1,126 +1,132 @@ :root { - font-family: "RO Sans", sans-serif; - font-feature-settings: - "clig" off, - "liga" off; - font-size: 16px; - line-height: 1.5; + font-family: "RO Sans", sans-serif; + font-feature-settings: + "clig" off, + "liga" off; + font-size: 16px; + line-height: 1.5; } body { - background: #faf9fb; - display: flex; - flex-direction: column; - align-items: center; + background: #faf9fb; + display: flex; + flex-direction: column; + align-items: center; } main { - display: flex; - flex-direction: column; - align-items: center; - margin: 0 16px; + display: flex; + flex-direction: column; + align-items: center; + margin: 0 16px; } main > * { - max-width: 500px; + max-width: 500px; } aside { - /* TODO this should become the translations bar */ - height: 60px; + justify-content: right; + padding: 24px 16px 8px 16px; +} + +@media only screen and (min-width: 577px) { + aside { + padding: 24px; + } } header { - content: " "; - background: url("../non-free/images/nl-wallet.svg") no-repeat center center / cover; - width: 64px; - height: 64px; + content: " "; + background: url("../non-free/images/nl-wallet.svg") no-repeat center center / cover; + width: 64px; + height: 64px; } main section { - display: flex; - flex-direction: column; - text-align: center; - gap: 8px; - margin: 40px 0; + display: flex; + flex-direction: column; + text-align: center; + gap: 8px; + margin: 40px 0; } main section h1 { - color: #152a62; - font-size: 30px; - font-weight: 700; - line-height: 44px; + color: #152a62; + font-size: 30px; + font-weight: 700; + line-height: 44px; } main section p { - color: #30293d; - letter-spacing: 0.5px; + color: #30293d; + letter-spacing: 0.5px; } main section a { - color: #383ede; + color: #383ede; } main section a:hover { - color: #3237c4; - text-decoration: none; + color: #3237c4; + text-decoration: none; } nav { - display: flex; - flex-direction: column; - gap: 32px; - width: 100%; + display: flex; + flex-direction: column; + gap: 32px; + width: 100%; } nav a { - display: flex; - text-align: center; - gap: 16px; - height: 100%; - text-decoration: none; + display: flex; + text-align: center; + gap: 16px; + height: 100%; + text-decoration: none; } nav a span { - flex-grow: 1; - display: flex; - align-items: center; - justify-content: center; - background: #383ede; - color: white; - font-weight: 700; - letter-spacing: 1px; - border-radius: 12px; - padding: 16px 24px; - line-height: 20px; + flex-grow: 1; + display: flex; + align-items: center; + justify-content: center; + background: #383ede; + color: white; + font-weight: 700; + letter-spacing: 1px; + border-radius: 12px; + padding: 16px 24px; + line-height: 20px; } nav a:hover span { - color: white; - background: #3237c4; + color: white; + background: #3237c4; } nav a::before { - content: " "; - background-color: black; - border-radius: 9.75px; - width: 52px; - height: 52px; + content: " "; + background-color: black; + border-radius: 9.75px; + width: 52px; + height: 52px; } /* use case icons */ #mijn_amsterdam::before { - background: url("../non-free/images/mijn_amsterdam.svg") no-repeat center center / cover; + background: url("../non-free/images/mijn_amsterdam.svg") no-repeat center center / cover; } #online_marketplace::before { - background: url("../images/online_marketplace.svg") no-repeat center center / cover; + background: url("../images/online_marketplace.svg") no-repeat center center / cover; } #xyz_bank::before { - background: url("../images/xyz_bank.svg") no-repeat center center / cover; + background: url("../images/xyz_bank.svg") no-repeat center center / cover; } #monkey_bike::before { - background: url("../images/monkey_bike.svg") no-repeat center center / cover; + background: url("../images/monkey_bike.svg") no-repeat center center / cover; } diff --git a/wallet_core/mock_relying_party/assets/index.html b/wallet_core/mock_relying_party/assets/index.html deleted file mode 100644 index 62cf6ffc3..000000000 --- a/wallet_core/mock_relying_party/assets/index.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - NL Wallet demo - - - - -
-
-
-

NL Wallet demo

-

- Deze voorbeelden zijn fictief en dienen alleen ter illustratie. Volg de ontwikkelingen op - edi.pleio.nl. -

-
- -
- - diff --git a/wallet_core/mock_relying_party/assets/non-free/images/down.svg b/wallet_core/mock_relying_party/assets/non-free/images/down.svg new file mode 100644 index 000000000..2b79d995a --- /dev/null +++ b/wallet_core/mock_relying_party/assets/non-free/images/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wallet_core/mock_relying_party/assets/non-free/images/nl-wallet-border.svg.bak b/wallet_core/mock_relying_party/assets/non-free/images/nl-wallet-border.svg.bak new file mode 100644 index 000000000..b0d3c4e3b --- /dev/null +++ b/wallet_core/mock_relying_party/assets/non-free/images/nl-wallet-border.svg.bak @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wallet_core/mock_relying_party/assets/usecase.js b/wallet_core/mock_relying_party/assets/usecase.js index 5f678c5f0..1c7c2b0b5 100644 --- a/wallet_core/mock_relying_party/assets/usecase.js +++ b/wallet_core/mock_relying_party/assets/usecase.js @@ -5,10 +5,15 @@ for (const button of wallet_buttons) { const session_token = e.detail[0] const session_type = e.detail[1] const usecase = button.attributes.getNamedItem("usecase").value + const lang = button.attributes.getNamedItem("lang") + ? button.attributes.getNamedItem("lang").value + : "nl" // this only works for cross_device without a configured return URL if (session_type === "cross_device") { - window.location.assign("../" + usecase + "/return?session_token=" + session_token) + window.location.assign( + "../" + usecase + "/return?session_token=" + session_token + "&lang=" + lang, + ) } } } diff --git a/wallet_core/mock_relying_party/src/app.rs b/wallet_core/mock_relying_party/src/app.rs index 6f6a07df1..5f3d337dc 100644 --- a/wallet_core/mock_relying_party/src/app.rs +++ b/wallet_core/mock_relying_party/src/app.rs @@ -1,5 +1,4 @@ use std::{ - collections::HashMap, env, path::PathBuf, result::Result as StdResult, @@ -8,7 +7,8 @@ use std::{ use askama::Template; use axum::{ - extract::{Path, Query, Request, State}, + async_trait, + extract::{FromRequestParts, Path, Query, Request, State}, handler::HandlerWithoutStateExt, http::{Method, StatusCode}, middleware::{self, Next}, @@ -17,8 +17,14 @@ use axum::{ Json, Router, }; use base64::prelude::*; -use http::{header::CACHE_CONTROL, HeaderValue}; +use http::{ + header::{ACCEPT_LANGUAGE, CACHE_CONTROL}, + request::Parts, + HeaderMap, HeaderValue, +}; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator; use tower::ServiceBuilder; use tower_http::{ cors::{Any, CorsLayer}, @@ -35,6 +41,7 @@ use crate::{ askama_axum, client::WalletServerClient, settings::{Origin, ReturnUrlMode, Settings, Usecase, WalletWeb}, + translations::{Words, TRANSLATIONS}, }; #[derive(Debug)] @@ -61,7 +68,7 @@ struct ApplicationState { client: WalletServerClient, public_wallet_server_url: BaseUrl, public_url: BaseUrl, - usecases: HashMap, + usecases: IndexMap, wallet_web: WalletWeb, } @@ -111,6 +118,7 @@ pub fn create_router(settings: Settings) -> Router { let root_dir = env::var("CARGO_MANIFEST_DIR").map(PathBuf::from).unwrap_or_default(); let mut app = Router::new() + .route("/", get(index)) .route("/sessions", post(create_session)) .route("/:usecase/", get(usecase)) .route(&format!("/:usecase/{}", RETURN_URL_SEGMENT), get(disclosed_attributes)) @@ -131,7 +139,7 @@ pub fn create_router(settings: Settings) -> Router { app } -#[derive(Deserialize, Serialize)] +#[derive(Serialize, Deserialize)] struct SessionOptions { usecase: String, } @@ -142,31 +150,59 @@ struct SessionResponse { session_token: SessionToken, } -#[derive(Template, Serialize)] -#[template(path = "disclosed/attributes.askama", escape = "html", ext = "html")] -struct DisclosureTemplate<'a> { - usecase: &'a str, - attributes: DisclosedAttributes, +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, strum::Display, strum::EnumIter)] +pub enum Language { + #[default] + #[serde(rename = "nl")] + #[strum(to_string = "nl")] + Nl, + #[serde(rename = "en")] + #[strum(to_string = "en")] + En, } -#[derive(Template, Serialize)] -#[template(path = "usecase/usecase.askama", escape = "html", ext = "html")] -struct UsecaseTemplate<'a> { - usecase: &'a str, - usecase_js_sha256: &'a str, - wallet_web_filename: &'a str, - wallet_web_sha256: &'a str, - error: Option<&'a str>, +impl Language { + fn parse(s: &str) -> Option { + match s.split('-').next() { + Some("en") => Some(Language::En), + Some("nl") => Some(Language::Nl), + _ => None, + } + } + + fn match_accept_language(headers: &HeaderMap) -> Option { + let accept_language = headers.get(ACCEPT_LANGUAGE)?; + let languages = accept_language::parse(accept_language.to_str().ok()?); + + // applies function to the elements of iterator and returns the first non-None result + languages.into_iter().find_map(|l| Language::parse(&l)) + } } #[derive(Debug, Serialize, Deserialize)] -pub struct DisclosedAttributesParams { - pub nonce: Option, - pub session_token: SessionToken, +pub struct LanguageParam { + pub lang: Language, +} + +#[async_trait] +impl FromRequestParts for Language +where + S: Send + Sync, +{ + type Rejection = std::convert::Infallible; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> std::result::Result { + let lang = Query::::from_request_parts(parts, state) + .await + .map(|l| l.lang) + .unwrap_or(Language::match_accept_language(&parts.headers).unwrap_or_default()); + Ok(lang) + } } async fn create_session( State(state): State>, + language: Language, Json(options): Json, ) -> Result> { let usecase = state @@ -174,24 +210,26 @@ async fn create_session( .get(&options.usecase) .ok_or(anyhow::Error::msg("usecase not found"))?; + let return_url_template = match usecase.return_url { + ReturnUrlMode::None => None, + _ => Some( + format!( + "{}/{}?session_token={{session_token}}&lang={}", + state.public_url.join(&options.usecase), + RETURN_URL_SEGMENT, + language, + ) + .parse() + .expect("should always be a valid ReturnUrlTemplate"), + ), + }; + let session_token = state .client .start( options.usecase.clone(), usecase.items_requests.clone(), - if usecase.return_url == ReturnUrlMode::None { - None - } else { - Some( - format!( - "{}/{}?session_token={{session_token}}", - state.public_url.join(&options.usecase), - RETURN_URL_SEGMENT - ) - .parse() - .expect("should always be a valid ReturnUrlTemplate"), - ) - }, + return_url_template, ) .await?; @@ -204,29 +242,105 @@ async fn create_session( Ok(result.into()) } +struct BaseTemplate<'a> { + session_token: Option, + nonce: Option, + selected_lang: Language, + trans: &'a Words<'a>, + available_languages: &'a Vec, +} + +#[derive(Template)] +#[template(path = "index.askama", escape = "html", ext = "html")] +struct IndexTemplate<'a> { + usecases: &'a [&'a str], + base: BaseTemplate<'a>, +} + +async fn index(State(state): State>, language: Language) -> Result { + let result = IndexTemplate { + usecases: &state.usecases.keys().map(|s| s.as_str()).collect::>(), + base: BaseTemplate { + session_token: None, + nonce: None, + selected_lang: language, + trans: &TRANSLATIONS[language], + available_languages: &Language::iter().collect(), + }, + }; + + Ok(askama_axum::into_response(&result)) +} + +#[derive(Template)] +#[template(path = "usecase/usecase.askama", escape = "html", ext = "html")] +struct UsecaseTemplate<'a> { + usecase: &'a str, + start_url: Url, + usecase_js_sha256: &'a str, + wallet_web_filename: &'a str, + wallet_web_sha256: &'a str, + base: BaseTemplate<'a>, +} + static USECASE_JS_SHA256: LazyLock = LazyLock::new(|| BASE64_STANDARD.encode(sha256(include_bytes!("../assets/usecase.js")))); -async fn usecase(State(state): State>, Path(usecase): Path) -> Result { +fn format_start_url(public_url: &BaseUrl, lang: Language) -> Url { + let mut start_url = public_url.join("/sessions"); + start_url.set_query(Some( + serde_urlencoded::to_string(LanguageParam { lang }).unwrap().as_str(), + )); + start_url +} + +async fn usecase( + State(state): State>, + Path(usecase): Path, + language: Language, +) -> Result { if !state.usecases.contains_key(&usecase) { return Ok(StatusCode::NOT_FOUND.into_response()); } + let start_url = format_start_url(&state.public_url, language); let result = UsecaseTemplate { usecase: &usecase, + start_url, usecase_js_sha256: &USECASE_JS_SHA256, wallet_web_filename: &state.wallet_web.filename.to_string_lossy(), wallet_web_sha256: &state.wallet_web.sha256, - error: None, + base: BaseTemplate { + session_token: None, + nonce: None, + selected_lang: language, + trans: &TRANSLATIONS[language], + available_languages: &Language::iter().collect(), + }, }; Ok(askama_axum::into_response(&result)) } +#[derive(Debug, Serialize, Deserialize)] +pub struct DisclosedAttributesParams { + pub nonce: Option, + pub session_token: SessionToken, +} + +#[derive(Template)] +#[template(path = "disclosed/attributes.askama", escape = "html", ext = "html")] +struct DisclosedAttributesTemplate<'a> { + usecase: &'a str, + attributes: DisclosedAttributes, + base: BaseTemplate<'a>, +} + async fn disclosed_attributes( State(state): State>, Path(usecase): Path, Query(params): Query, + language: Language, ) -> Result { if !state.usecases.contains_key(&usecase) { return Ok(StatusCode::NOT_FOUND.into_response()); @@ -234,25 +348,36 @@ async fn disclosed_attributes( let attributes = state .client - .disclosed_attributes(params.session_token, params.nonce) + .disclosed_attributes(params.session_token.clone(), params.nonce.clone()) .await; + let start_url = format_start_url(&state.public_url, language); + let base = BaseTemplate { + session_token: Some(params.session_token), + nonce: params.nonce, + selected_lang: language, + trans: &TRANSLATIONS[language], + available_languages: &Language::iter().collect(), + }; + match attributes { Ok(attributes) => { - let result = DisclosureTemplate { + let result = DisclosedAttributesTemplate { usecase: &usecase, attributes, + base, }; Ok(askama_axum::into_response(&result)) } Err(err) => { - let err = err.to_string(); + warn!("Error getting disclosed attributes: {err}"); let result = UsecaseTemplate { usecase: &usecase, + start_url, usecase_js_sha256: &USECASE_JS_SHA256, wallet_web_filename: &state.wallet_web.filename.to_string_lossy(), wallet_web_sha256: &state.wallet_web.sha256, - error: Some(&err), + base, }; Ok(askama_axum::into_response(&result)) } @@ -276,3 +401,20 @@ mod filters { Ok(format!("attribute '{name}' cannot be found")) } } + +#[cfg(test)] +mod test { + use rstest::rstest; + + use super::*; + + #[rstest] + #[case("en", Some(Language::En))] + #[case("nl", Some(Language::Nl))] + #[case("123", None)] + #[case("en-GB", Some(Language::En))] + #[case("nl-NL", Some(Language::Nl))] + fn test_parse_language(#[case] s: &str, #[case] expected: Option) { + assert_eq!(Language::parse(s), expected); + } +} diff --git a/wallet_core/mock_relying_party/src/lib.rs b/wallet_core/mock_relying_party/src/lib.rs index 3334a2c61..1a16bb231 100644 --- a/wallet_core/mock_relying_party/src/lib.rs +++ b/wallet_core/mock_relying_party/src/lib.rs @@ -2,6 +2,7 @@ pub mod app; pub mod client; pub mod server; pub mod settings; +pub mod translations; // workaround for: https://github.com/djc/askama/issues/810#issuecomment-1494522435 pub mod askama_axum; diff --git a/wallet_core/mock_relying_party/src/settings.rs b/wallet_core/mock_relying_party/src/settings.rs index f7262cb8a..04a92b3f9 100644 --- a/wallet_core/mock_relying_party/src/settings.rs +++ b/wallet_core/mock_relying_party/src/settings.rs @@ -1,7 +1,8 @@ -use std::{collections::HashMap, env, net::IpAddr, path::PathBuf}; +use std::{env, net::IpAddr, path::PathBuf}; use config::{Config, ConfigError, Environment, File}; use http::{header::InvalidHeaderValue, HeaderValue}; +use indexmap::IndexMap; use nutype::nutype; use serde::{Deserialize, Serialize}; use url::Url; @@ -19,7 +20,7 @@ pub struct Settings { #[serde(default)] pub allow_origins: Vec, pub wallet_web: WalletWeb, - pub usecases: HashMap, + pub usecases: IndexMap, pub sentry: Option, } diff --git a/wallet_core/mock_relying_party/src/translations.rs b/wallet_core/mock_relying_party/src/translations.rs new file mode 100644 index 000000000..7c3ebe7e1 --- /dev/null +++ b/wallet_core/mock_relying_party/src/translations.rs @@ -0,0 +1,187 @@ +use std::ops::Index; + +use crate::app::Language; + +pub struct Translations<'a> { + en: Words<'a>, + nl: Words<'a>, +} + +impl<'a> Index for Translations<'a> { + type Output = Words<'a>; + + fn index(&self, lang: Language) -> &Self::Output { + match lang { + Language::Nl => &self.nl, + Language::En => &self.en, + } + } +} + +pub const TRANSLATIONS: Translations = Translations { + en: Words { + en: "English", + nl: "Nederlands", + index_title: "NL Wallet demo", + index_intro: "These examples are fictional and for illustration purposes only. Follow the developments at", + index_intro_link: "edi.pleio.nl", + amsterdam_index: "Log in municipality", + marketplace_index: "Log in webshop", + xyz_index: "Open bank account", + monkeybike_index: "Create account", + demo_bar_text: "NL Wallet demo", + demo_see_other: "View other", + demo_see_examples: "examples", + demo_follow_development: "Follow the developments at", + continue_with_nl_wallet: "Continue with NL Wallet", + continue_with_google: "Continue with Google", + continue_with_email: "Continue with email", + login_with_nl_wallet: "Login with NL Wallet", + login_with_digid: "Login with DigiD", + use_nl_wallet: "Use NL Wallet", + choose_another_method: "Choose another method", + amsterdam_title: "Municipality of Amsterdam", + amsterdam_failed: "Login failed", + amsterdam_try_again: "Try again", + amsterdam_login: "Login to Mijn Amsterdam", + amsterdam_subtitle: "For individuals and sole proprietors", + amsterdam_nl_wallet_digid: "You need either the NL Wallet app or DigiD.", + amsterdam_profile_name: "Account", + amsterdam_success: "Success", + amsterdam_logged_in: "You are logged in.", + amsterdam_welcome: "Welcome to Mijn Amsterdam", + amsterdam_subtitle_disclosed: "Personal online services for Amsterdam residents", + monkeybike_title: "MonkeyBike", + monkeybike_login: "Log in", + marketplace_title: "Marktplek", + marketplace_login: "Sign up or log in", + login_failed_try_again: "Login failed. Try again.", + click_continue: "By clicking \"Continue\", you agree to the", + terms_and_conditions: "Terms and Conditions", + and_the: "and the", + privacy_policy: "Privacy Policy", + xyz_title: "XYZ Bank", + xyz_open_account: "Open bank account", + xyz_identify_yourself: "Step 1. Identify yourself", + xyz_failed_try_again: "Identification failed. Try again.", + xyz_success: "Identification successful", + welcome: "Welcome", + search_product: "Search product...", + search_by_topic: "Search by topic...", + next: "Next", + }, + nl: Words { + en: "English", + nl: "Nederlands", + index_title: "NL Wallet demo", + index_intro: "Deze voorbeelden zijn fictief en dienen alleen ter illustratie. Volg de ontwikkelingen op", + index_intro_link: "edi.pleio.nl", + amsterdam_index: "Inloggen gemeente", + marketplace_index: "Inloggen webshop", + xyz_index: "Bankrekening openen", + monkeybike_index: "Account aanmaken", + demo_bar_text: "NL Wallet demo", + demo_see_other: "Bekijk andere", + demo_see_examples: "voorbeelden", + demo_follow_development: "Volg de ontwikkelingen op", + continue_with_nl_wallet: "Verder met NL Wallet", + continue_with_google: "Verder met Google", + continue_with_email: "Verder met email", + login_with_nl_wallet: "Inloggen met NL Wallet", + login_with_digid: "Inloggen met DigiD", + use_nl_wallet: "Gebruik NL Wallet", + choose_another_method: "Kies een ander middel", + amsterdam_title: "Gemeente Amsterdam", + amsterdam_failed: "Inloggen mislukt", + amsterdam_try_again: "Probeer het opnieuw", + amsterdam_login: "Inloggen op Mijn Amsterdam", + amsterdam_subtitle: "Voor particulieren en eenmanszaken", + amsterdam_nl_wallet_digid: "U heeft de NL Wallet app of DigiD nodig.", + amsterdam_profile_name: "Account", + amsterdam_success: "Gelukt", + amsterdam_logged_in: "Je bent ingelogd.", + amsterdam_welcome: "Welkom in Mijn Amsterdam", + amsterdam_subtitle_disclosed: "Persoonlijke online dienstverlening voor de Amsterdammer", + monkeybike_title: "MonkeyBike", + monkeybike_login: "Meld je aan", + marketplace_title: "Marktplek", + marketplace_login: "Meld je aan of log in", + login_failed_try_again: "Inloggen mislukt. Probeer het opnieuw.", + click_continue: "Door op \"Verder\" te klikken, ga je akkoord met de", + terms_and_conditions: "Algemene Voorwaarden", + and_the: "en het", + privacy_policy: "Privacybeleid", + xyz_title: "XYZ Bank", + xyz_open_account: "Bankrekening openen", + xyz_identify_yourself: "Stap 1. Identificeer uzelf", + xyz_failed_try_again: "Identificatie mislukt. Probeer het opnieuw.", + xyz_success: "Identificatie gelukt", + welcome: "Welkom", + search_product: "Zoek product...", + search_by_topic: "Zoek op onderwerp...", + next: "Volgende", + }, +}; + +pub struct Words<'a> { + en: &'a str, + nl: &'a str, + pub index_title: &'a str, + pub index_intro: &'a str, + pub index_intro_link: &'a str, + pub amsterdam_index: &'a str, + pub monkeybike_index: &'a str, + pub marketplace_index: &'a str, + pub xyz_index: &'a str, + pub demo_bar_text: &'a str, + pub demo_see_other: &'a str, + pub demo_see_examples: &'a str, + pub demo_follow_development: &'a str, + pub continue_with_nl_wallet: &'a str, + pub continue_with_google: &'a str, + pub continue_with_email: &'a str, + pub login_with_nl_wallet: &'a str, + pub login_with_digid: &'a str, + pub use_nl_wallet: &'a str, + pub choose_another_method: &'a str, + pub amsterdam_title: &'a str, + pub amsterdam_failed: &'a str, + pub amsterdam_try_again: &'a str, + pub amsterdam_login: &'a str, + pub amsterdam_subtitle: &'a str, + pub amsterdam_nl_wallet_digid: &'a str, + pub amsterdam_profile_name: &'a str, + pub amsterdam_success: &'a str, + pub amsterdam_logged_in: &'a str, + pub amsterdam_welcome: &'a str, + pub amsterdam_subtitle_disclosed: &'a str, + pub monkeybike_title: &'a str, + pub monkeybike_login: &'a str, + pub marketplace_title: &'a str, + pub marketplace_login: &'a str, + pub login_failed_try_again: &'a str, + pub click_continue: &'a str, + pub terms_and_conditions: &'a str, + pub and_the: &'a str, + pub privacy_policy: &'a str, + pub xyz_title: &'a str, + pub xyz_open_account: &'a str, + pub xyz_identify_yourself: &'a str, + pub xyz_failed_try_again: &'a str, + pub xyz_success: &'a str, + pub welcome: &'a str, + pub search_product: &'a str, + pub search_by_topic: &'a str, + pub next: &'a str, +} + +impl<'a> Index for Words<'a> { + type Output = &'a str; + + fn index(&self, lang: Language) -> &Self::Output { + match lang { + Language::Nl => &self.nl, + Language::En => &self.en, + } + } +} diff --git a/wallet_core/mock_relying_party/templates/components/demo_bar.askama b/wallet_core/mock_relying_party/templates/components/demo_bar.askama index 30be1b18c..b0036e5f4 100644 --- a/wallet_core/mock_relying_party/templates/components/demo_bar.askama +++ b/wallet_core/mock_relying_party/templates/components/demo_bar.askama @@ -1,11 +1,36 @@ -{% macro demo_bar(text, url, url_text) %} +{% macro demo_bar(text, url, url_text, selected_lang, trans, available_languages, session_token, nonce) %} {% endmacro %} + +{% macro lang_selector(selected_lang, trans, available_languages, session_token, nonce) %} +
+ + +
+ {% match session_token %} + {% when Some with (session_token) %} + + {% else %} + {% endmatch %} + {% match nonce %} + {% when Some with (nonce) %} + + {% else %} + {% endmatch %} + {% for lang in available_languages.to_owned() %} + + {% endfor %} +
+
+{% endmacro %} diff --git a/wallet_core/mock_relying_party/templates/components/header.askama b/wallet_core/mock_relying_party/templates/components/header.askama index 84c5e0618..48ad29b0a 100644 --- a/wallet_core/mock_relying_party/templates/components/header.askama +++ b/wallet_core/mock_relying_party/templates/components/header.askama @@ -1,7 +1,7 @@ {% macro header(title, username) %}
-

{{ title }}

+

{{ title }}

{% if username.len() > 0 %} {{ username }} {% endif %} diff --git a/wallet_core/mock_relying_party/templates/components/notification.askama b/wallet_core/mock_relying_party/templates/components/notification.askama index ddaa5f0d2..5d1430695 100644 --- a/wallet_core/mock_relying_party/templates/components/notification.askama +++ b/wallet_core/mock_relying_party/templates/components/notification.askama @@ -1,3 +1,3 @@ {% macro notification(span_text, text, error) %} - + {% endmacro %} diff --git a/wallet_core/mock_relying_party/templates/disclosed/attributes.askama b/wallet_core/mock_relying_party/templates/disclosed/attributes.askama index e9e94d7ca..62a30190d 100644 --- a/wallet_core/mock_relying_party/templates/disclosed/attributes.askama +++ b/wallet_core/mock_relying_party/templates/disclosed/attributes.askama @@ -24,7 +24,8 @@ {% else %} {% call attributes::attributes(attributes) %} {% endmatch %} - +{# using it in the call directly didn't work #} +{% let lang = base.selected_lang %} {# should be last for accessibility purposes #} -{% call demo_bar::demo_bar("Bekijk andere", "../", "voorbeelden") %} +{% call demo_bar::demo_bar(base.trans.demo_see_other, format!("../?lang={}", lang), base.trans.demo_see_examples, lang, base.trans, base.available_languages, base.session_token, base.nonce) %} {% endblock %} diff --git a/wallet_core/mock_relying_party/templates/disclosed/mijn_amsterdam.askama b/wallet_core/mock_relying_party/templates/disclosed/mijn_amsterdam.askama index 1f0faab91..544e64bb0 100644 --- a/wallet_core/mock_relying_party/templates/disclosed/mijn_amsterdam.askama +++ b/wallet_core/mock_relying_party/templates/disclosed/mijn_amsterdam.askama @@ -1,10 +1,8 @@ -{% let title = "Gemeente Amsterdam" %} - -{% call header::header(title, "Account") %} +{% call header::header(base.trans.amsterdam_title, base.trans.amsterdam_profile_name) %}
- {% call notification::notification("Gelukt", "Je bent ingelogd.", "") %} + {% call notification::notification(base.trans.amsterdam_success, base.trans.amsterdam_logged_in, "") %}
-

Welkom in Mijn Amsterdam

-

Persoonlijke online dienstverlening voor de Amsterdammer

+

{{ base.trans.amsterdam_welcome }}

+

{{ base.trans.amsterdam_subtitle }}

diff --git a/wallet_core/mock_relying_party/templates/disclosed/monkey_bike.askama b/wallet_core/mock_relying_party/templates/disclosed/monkey_bike.askama index d6f9fff2d..aa6024717 100644 --- a/wallet_core/mock_relying_party/templates/disclosed/monkey_bike.askama +++ b/wallet_core/mock_relying_party/templates/disclosed/monkey_bike.askama @@ -1,8 +1,8 @@ {% let username = attributes|attribute("given_name") %} -{% call header::header("MonkeyBike", username) %} +{% call header::header(base.trans.monkeybike_title, username) %}
- {% call notification::notification(format!("Welkom {username}"), "", "") %} - {% call search::search("Zoek product...") %} + {% call notification::notification(format!("{{ base.trans.welcome }} {username}"), "", "") %} + {% call search::search(base.trans.search_product) %} {% call products::products(6) %}
diff --git a/wallet_core/mock_relying_party/templates/disclosed/online_marketplace.askama b/wallet_core/mock_relying_party/templates/disclosed/online_marketplace.askama index 81200def9..17fba225c 100644 --- a/wallet_core/mock_relying_party/templates/disclosed/online_marketplace.askama +++ b/wallet_core/mock_relying_party/templates/disclosed/online_marketplace.askama @@ -1,8 +1,8 @@ {% let username = attributes|attribute("given_name") %} -{% call header::header("Marktplek", username) %} +{% call header::header(base.trans.marketplace_title, username) %}
- {% call notification::notification(format!("Welkom {username}"), "", "") %} - {% call search::search("Zoek product...") %} + {% call notification::notification(format!("{{ base.trans.welcome }} {username}"), "", "") %} + {% call search::search(base.trans.search_by_topic) %} {% call products::products(6) %}
diff --git a/wallet_core/mock_relying_party/templates/disclosed/xyz_bank.askama b/wallet_core/mock_relying_party/templates/disclosed/xyz_bank.askama index 5ba9a5746..4f6567af4 100644 --- a/wallet_core/mock_relying_party/templates/disclosed/xyz_bank.askama +++ b/wallet_core/mock_relying_party/templates/disclosed/xyz_bank.askama @@ -1,14 +1,12 @@ -{% let title = "XYZ Bank" %} - -{% call header::header(title, "") %} +{% call header::header(base.trans.xyz_title, "") %}
-

Bankrekening openen

-

Stap 1. Identificeer uzelf

- {% call notification::notification("Identificatie gelukt", "", "") %} +

{{ base.trans.xyz_open_account }}

+

{{ base.trans.xyz_identify_yourself }}

+ {% call notification::notification(base.trans.xyz_success, "", "") %}
{% call attributes::attributes(attributes) %}
- +
diff --git a/wallet_core/mock_relying_party/templates/index.askama b/wallet_core/mock_relying_party/templates/index.askama new file mode 100644 index 000000000..a64c92efd --- /dev/null +++ b/wallet_core/mock_relying_party/templates/index.askama @@ -0,0 +1,44 @@ +{% extends "base.askama" %} +{%- import "components/demo_bar.askama" as demo_bar -%} + +{% block styles %} + + + + + +{% endblock %} + +{% block content %} + +
+
+
+

{{ base.trans.index_title }}

+

+ {{ base.trans.index_intro }} + {{ base.trans.index_intro_link }}. +

+
+ +
+{% endblock %} diff --git a/wallet_core/mock_relying_party/templates/usecase/mijn_amsterdam.askama b/wallet_core/mock_relying_party/templates/usecase/mijn_amsterdam.askama index 500b3b915..67cd7a919 100644 --- a/wallet_core/mock_relying_party/templates/usecase/mijn_amsterdam.askama +++ b/wallet_core/mock_relying_party/templates/usecase/mijn_amsterdam.askama @@ -1,25 +1,24 @@ -{% let title = "Gemeente Amsterdam" %} - -{% call header::header(title, "") %} +{% call header::header(base.trans.amsterdam_title, "") %}
- {% match error %} - {% when Some with (error) %} - {% call notification::notification("Het is niet gelukt", "Probeer het opnieuw.", error) %} + {% match base.session_token %} + {% when Some with (session_token) %} + {% call notification::notification(base.trans.amsterdam_failed, base.trans.amsterdam_try_again, session_token) %} {% when None %} {% endmatch %}
-

Inloggen op Mijn Amsterdam

+

{{ base.trans.amsterdam_login }}

-

Voor particulieren en eenmanszaken

-

U heeft de NL Wallet app of DigiD nodig.

+

{{ base.trans.amsterdam_subtitle }}

+

{{ base.trans.amsterdam_nl_wallet_digid }}

- + start-url="{{ start_url }}" + lang="{{ base.selected_lang }}"> + +
diff --git a/wallet_core/mock_relying_party/templates/usecase/monkey_bike.askama b/wallet_core/mock_relying_party/templates/usecase/monkey_bike.askama index aaa3dd4aa..11496d2d1 100644 --- a/wallet_core/mock_relying_party/templates/usecase/monkey_bike.askama +++ b/wallet_core/mock_relying_party/templates/usecase/monkey_bike.askama @@ -1,27 +1,26 @@ -
-

MonkeyBike

-
-
-
Meld je aan +{% call header::header(base.trans.monkeybike_title, "") %} +
+
+

{{ base.trans.monkeybike_login }}

- {% match error %} - {% when Some with (error) %} - {% call notification::notification("Inloggen mislukt. Probeer het opnieuw.", "", error) %} + {% match base.session_token %} + {% when Some with (session_token) %} + {% call notification::notification(base.trans.login_failed_try_again, "", session_token) %} {% when None %} {% endmatch %}
- - + start-url="{{ start_url }}" + lang="{{ base.selected_lang }}"> + + +

- Door op "Verder" te klikken, ga je akkoord met de - Algemene Voorwaarden en het - Privacybeleid. + {{ base.trans.click_continue }} + {{ base.trans.terms_and_conditions }} {{ base.trans.and_the }} + {{ base.trans.privacy_policy }}.

diff --git a/wallet_core/mock_relying_party/templates/usecase/online_marketplace.askama b/wallet_core/mock_relying_party/templates/usecase/online_marketplace.askama index 392f829e9..677224012 100644 --- a/wallet_core/mock_relying_party/templates/usecase/online_marketplace.askama +++ b/wallet_core/mock_relying_party/templates/usecase/online_marketplace.askama @@ -1,27 +1,26 @@ -
-

Marktplek

-
+{% call header::header(base.trans.marketplace_title, "") %}
-

Meld je aan of log in

+

{{ base.trans.marketplace_login }}

- {% match error %} - {% when Some with (error) %} - {% call notification::notification("Inloggen mislukt. Probeer het opnieuw.", "", error) %} + {% match base.session_token %} + {% when Some with (session_token) %} + {% call notification::notification(base.trans.login_failed_try_again, "", session_token) %} {% when None %} {% endmatch %}
- - + start-url="{{ start_url }}" + lang="{{ base.selected_lang }}"> + + +

- Door op "Verder" te klikken, ga je akkoord met de - Algemene Voorwaarden en het - Privacybeleid. + {{ base.trans.click_continue }} + {{ base.trans.terms_and_conditions }} {{ base.trans.and_the }} + {{ base.trans.privacy_policy }}.

diff --git a/wallet_core/mock_relying_party/templates/usecase/usecase.askama b/wallet_core/mock_relying_party/templates/usecase/usecase.askama index 6e5598096..f7757cc66 100644 --- a/wallet_core/mock_relying_party/templates/usecase/usecase.askama +++ b/wallet_core/mock_relying_party/templates/usecase/usecase.askama @@ -24,9 +24,14 @@ {% when "xyz_bank" %} {% include "xyz_bank.askama" %} {% else %} - + + {% endmatch %} {# should be last for accessibility purposes #} -{% call demo_bar::demo_bar("Volg de ontwikkelingen op", "https://edi.pleio.nl", "edi.pleio.nl") %} +{% call demo_bar::demo_bar(base.trans.demo_follow_development, "https://edi.pleio.nl", "edi.pleio.nl", base.selected_lang, base.trans, base.available_languages, base.session_token, base.nonce) %} {% endblock %} diff --git a/wallet_core/mock_relying_party/templates/usecase/xyz_bank.askama b/wallet_core/mock_relying_party/templates/usecase/xyz_bank.askama index 000ebe2e3..e55a9f3a8 100644 --- a/wallet_core/mock_relying_party/templates/usecase/xyz_bank.askama +++ b/wallet_core/mock_relying_party/templates/usecase/xyz_bank.askama @@ -1,22 +1,21 @@ -{% let title = "XYZ Bank" %} - -{% call header::header(title, "") %} +{% call header::header(base.trans.xyz_title, "") %}
-

Bankrekening openen

-

Stap 1. Identificeer uzelf

+

{{ base.trans.xyz_open_account }}

+

{{ base.trans.xyz_identify_yourself }}

- {% match error %} - {% when Some with (error) %} - {% call notification::notification("Identificatie mislukt. Probeer het opnieuw.", "", error) %} + {% match base.session_token %} + {% when Some with (session_token) %} + {% call notification::notification(base.trans.xyz_failed_try_again, "", session_token) %} {% when None %} {% endmatch %}
- Kies een ander middel + start-url="{{ start_url }}" + lang="{{ base.selected_lang }}"> + + {{ base.trans.choose_another_method }}
diff --git a/wallet_web/index.html b/wallet_web/index.html index e323cbf51..f2b9aab8d 100644 --- a/wallet_web/index.html +++ b/wallet_web/index.html @@ -52,14 +52,14 @@ id="mijn_amsterdam_button" text="18+ met NL Wallet" usecase="mijn_amsterdam" - base-url="http://localhost:3004" + start-url="http://localhost:3004/sessions" >

@@ -78,30 +78,30 @@ diff --git a/wallet_web/lib/__tests__/components/WalletModal.spec.ts b/wallet_web/lib/__tests__/components/WalletModal.spec.ts index 1c54f3452..114e142b0 100644 --- a/wallet_web/lib/__tests__/components/WalletModal.spec.ts +++ b/wallet_web/lib/__tests__/components/WalletModal.spec.ts @@ -24,7 +24,7 @@ describe("WalletModal", () => { it("should show loading screen", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) @@ -35,7 +35,7 @@ describe("WalletModal", () => { it("should show qr code directly for desktop mode", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [isMobileKey as symbol]: false, @@ -49,7 +49,7 @@ describe("WalletModal", () => { it("should show loading screen after choosing", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [isMobileKey as symbol]: true, [translationsKey as symbol]: translations("nl") }, }, @@ -82,7 +82,7 @@ describe("WalletModal", () => { it("should show qr code for mobile after choosing", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [isMobileKey as symbol]: true, [translationsKey as symbol]: translations("nl") }, }, @@ -96,7 +96,7 @@ describe("WalletModal", () => { it("should show loading when closing model", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl"), @@ -115,7 +115,7 @@ describe("WalletModal", () => { const status = vi.mocked(getStatus) const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -131,7 +131,7 @@ describe("WalletModal", () => { it("should show in progress when qr code is scanned", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -153,7 +153,7 @@ describe("WalletModal", () => { it("should show confirm stop when clicking stop on in-progress screen", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -182,7 +182,7 @@ describe("WalletModal", () => { it("should ask where the wallet is for mobile mode", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [isMobileKey as symbol]: true, [translationsKey as symbol]: translations("nl") }, }, @@ -195,7 +195,7 @@ describe("WalletModal", () => { it("should have anchor for same device flow", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [isMobileKey as symbol]: true, [translationsKey as symbol]: translations("nl") }, }, @@ -211,7 +211,7 @@ describe("WalletModal", () => { beforeEach(async () => { wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -263,7 +263,7 @@ describe("WalletModal", () => { vi.mocked(createSession).mockRejectedValueOnce("failed" as ErrorType) const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -275,7 +275,7 @@ describe("WalletModal", () => { vi.mocked(createSession).mockRejectedValueOnce("network" as ErrorType) const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -285,7 +285,7 @@ describe("WalletModal", () => { it("should show qr code again after retrying for desktop mode", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [translationsKey as symbol]: translations("nl") } }, }) await flushPromises() @@ -309,7 +309,7 @@ describe("WalletModal", () => { it("should show device choice again after retrying for mobile mode", async () => { const wrapper = mount(WalletModal, { - props: { baseUrl: "http://localhost", usecase: "test123" }, + props: { startUrl: new URL("http://localhost/sessions"), usecase: "test123" }, global: { provide: { [isMobileKey as symbol]: true, [translationsKey as symbol]: translations("nl") }, }, diff --git a/wallet_web/lib/__tests__/util/base_url.test.ts b/wallet_web/lib/__tests__/util/base_url.test.ts deleted file mode 100644 index 74eaba108..000000000 --- a/wallet_web/lib/__tests__/util/base_url.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createAbsoluteUrl } from "@/util/base_url" -import { describe, expect, test } from "vitest" - -describe("createAbsoluteUrl", () => { - test.each([ - ["", "http://localhost:3004/", "/", "http://localhost:3004/"], - [".", "http://localhost:3004/", "/", "http://localhost:3004/"], - ["./", "http://localhost:3004/", "/", "http://localhost:3004/"], - ["..", "http://localhost:3004/path1/", "/path1/", "http://localhost:3004/"], - ["../", "http://localhost:3004/path1/", "/path1/", "http://localhost:3004/"], - ["../path2", "http://localhost:3004/path1/", "/path1/", "http://localhost:3004/path2"], - ["", "https://localhost/abcd123/index.hml", "/abcd123/index.hml", "https://localhost/abcd123/"], - ["", "https://localhost/abcd123/", "/abcd123/", "https://localhost/abcd123/"], - ["../", "https://localhost/abcd123/foo/", "/abcd123/foo/", "https://localhost/abcd123/"], - [ - "../path2", - "http://localhost:3004/path1/index.html", - "/path1/index.html", - "http://localhost:3004/path2", - ], - [ - "./path2", - "http://localhost:3004/path1/path1a/path1b/index.html", - "/path1/path1a/path1b/index.html", - "http://localhost:3004/path1/path1a/path1b/path2", - ], - [ - "/path2", - "http://localhost:3004/path1/path1a/path1b/index.html", - "/path1/path1a/path1b/index.html", - "http://localhost:3004/path2", - ], - [ - "/path2", - "http://localhost:3004/path1/path1a/path1b/", - "/path1/path1a/path1b/", - "http://localhost:3004/path2", - ], - [ - "http://192.168.1.1:3003/path2", - "http://localhost:3004/path1/", - "/path1/", - "http://192.168.1.1:3003/path2", - ], - [ - "http://192.168.1.1:3003/path2/", - "http://localhost:3004/path1/", - "/path1/", - "http://192.168.1.1:3003/path2/", - ], - ])("should detect desktop for useragent: %s", (base_url, href, path, expected) => { - expect(createAbsoluteUrl(base_url, href, path).toString()).toEqual(expected) - }) -}) diff --git a/wallet_web/lib/api/session.ts b/wallet_web/lib/api/session.ts index 16bf8a113..82adac918 100644 --- a/wallet_web/lib/api/session.ts +++ b/wallet_web/lib/api/session.ts @@ -3,11 +3,11 @@ import axios, { AxiosError } from "axios" import { catch_axios_error, REQUEST_TIMEOUT } from "./base" export const createSession = async ( - baseUrl: string, + startUrl: URL, sessionOptions: SessionOptions, ): Promise => { try { - const response = await axios.post(new URL("sessions", baseUrl).toString(), sessionOptions, { + const response = await axios.post(startUrl.toString(), sessionOptions, { timeout: REQUEST_TIMEOUT, }) return await response.data diff --git a/wallet_web/lib/components/WalletButton.ce.vue b/wallet_web/lib/components/WalletButton.ce.vue index 724f64e1b..0fd895123 100644 --- a/wallet_web/lib/components/WalletButton.ce.vue +++ b/wallet_web/lib/components/WalletButton.ce.vue @@ -1,14 +1,13 @@