Skip to content

Commit 3cee004

Browse files
feat: add host option to override session QR host
Add an optional `host` field to the base session request, mirroring irmago's `host` field (irmago v0.14.0). It overrides the host used in the QR (`Qr.u`) and is validated server-side against the requestor's configured `host_perms` allowlist. The field serializes as `host` only when set, and a `.host(...)` builder method is exposed on the disclosure, signature, and issuance builders. Closes #7 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aff8103 commit 3cee004

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

src/sessionrequest.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ pub struct BaseRequest {
138138
deserialize_with = "crate::util::de_int_key"
139139
)]
140140
pub labels: HashMap<usize, TranslatedString>,
141+
/// Host to use in the session QR (`Qr.u`), overriding the IRMA server's default.
142+
/// The server validates this against the requestor's configured `host_perms` allowlist.
143+
/// Requires irmago v0.14.0 or newer on the server side.
144+
#[serde(skip_serializing_if = "Option::is_none", default)]
145+
pub host: Option<String>,
141146
}
142147

143148
/// IRMA session requests
@@ -179,6 +184,7 @@ impl BaseRequestBuilder {
179184
return_url: None,
180185
augment_return: false,
181186
labels: HashMap::new(),
187+
host: None,
182188
},
183189
}
184190
}
@@ -215,6 +221,11 @@ impl BaseRequestBuilder {
215221
self.base.return_url = Some(return_url);
216222
self.base.augment_return = true;
217223
}
224+
225+
fn host(&mut self, host: String) {
226+
debug_assert!(self.base.host.is_none());
227+
self.base.host = Some(host);
228+
}
218229
}
219230

220231
impl Default for BaseRequestBuilder {
@@ -276,6 +287,14 @@ impl DisclosureRequestBuilder {
276287
self.base.augmented_return_url(return_url);
277288
self
278289
}
290+
291+
/// Set the host to use in the session QR, overriding the IRMA server's default.
292+
/// The server validates this against the requestor's configured `host_perms` allowlist
293+
/// (requires irmago v0.14.0 or newer).
294+
pub fn host(mut self, host: String) -> DisclosureRequestBuilder {
295+
self.base.host(host);
296+
self
297+
}
279298
}
280299

281300
/// Build a signature request
@@ -335,6 +354,14 @@ impl SignatureRequestBuilder {
335354
self.base.augmented_return_url(return_url);
336355
self
337356
}
357+
358+
/// Set the host to use in the session QR, overriding the IRMA server's default.
359+
/// The server validates this against the requestor's configured `host_perms` allowlist
360+
/// (requires irmago v0.14.0 or newer).
361+
pub fn host(mut self, host: String) -> SignatureRequestBuilder {
362+
self.base.host(host);
363+
self
364+
}
338365
}
339366

340367
/// Build a request to issue one or more credentials
@@ -401,6 +428,14 @@ impl IssuanceRequestBuilder {
401428
self.base.augmented_return_url(return_url);
402429
self
403430
}
431+
432+
/// Set the host to use in the session QR, overriding the IRMA server's default.
433+
/// The server validates this against the requestor's configured `host_perms` allowlist
434+
/// (requires irmago v0.14.0 or newer).
435+
pub fn host(mut self, host: String) -> IssuanceRequestBuilder {
436+
self.base.host(host);
437+
self
438+
}
404439
}
405440

406441
/// An IRMA request extended with extra information for the server on how to execute it.
@@ -543,6 +578,53 @@ mod tests {
543578
);
544579
}
545580

581+
#[test]
582+
fn test_host_request() {
583+
// host is omitted from the serialized request when not set
584+
let req1 = DisclosureRequestBuilder::new()
585+
.add_discon(vec![vec![AttributeRequest::Simple("a.b.c.d".into())]])
586+
.build();
587+
assert!(!serde_json::to_string(&req1).unwrap().contains("host"));
588+
589+
// host is serialized as "host" when set, and survives a round-trip
590+
let req2 = DisclosureRequestBuilder::new()
591+
.add_discon(vec![vec![AttributeRequest::Simple("a.b.c.d".into())]])
592+
.host("https://example.com".into())
593+
.build();
594+
assert_eq!("{\"@context\":\"https://irma.app/ld/request/disclosure/v2\",\"disclose\":[[[\"a.b.c.d\"]]],\"host\":\"https://example.com\"}", serde_json::to_string(&req2).unwrap());
595+
assert_eq!(
596+
req2,
597+
serde_json::from_str(&serde_json::to_string(&req2).unwrap()).unwrap()
598+
);
599+
600+
// the host setter is available on every public builder
601+
let req3 = SignatureRequestBuilder::new("testmessage".into())
602+
.add_discon(vec![vec![AttributeRequest::Simple("a.b.c.d".into())]])
603+
.host("https://signature.example.com".into())
604+
.build();
605+
assert_eq!("{\"@context\":\"https://irma.app/ld/request/signature/v2\",\"message\":\"testmessage\",\"disclose\":[[[\"a.b.c.d\"]]],\"host\":\"https://signature.example.com\"}", serde_json::to_string(&req3).unwrap());
606+
assert_eq!(
607+
req3,
608+
serde_json::from_str(&serde_json::to_string(&req3).unwrap()).unwrap()
609+
);
610+
611+
let req4 = IssuanceRequestBuilder::new()
612+
.add_credential(Credential {
613+
credential: "a.b.c".into(),
614+
validity: Some(123456789),
615+
attributes: hashmap![
616+
"d".into() => "e".into(),
617+
],
618+
})
619+
.host("https://issuance.example.com".into())
620+
.build();
621+
assert_eq!("{\"@context\":\"https://irma.app/ld/request/issuance/v2\",\"credentials\":[{\"credential\":\"a.b.c\",\"validity\":123456789,\"attributes\":{\"d\":\"e\"}}],\"host\":\"https://issuance.example.com\"}", serde_json::to_string(&req4).unwrap());
622+
assert_eq!(
623+
req4,
624+
serde_json::from_str(&serde_json::to_string(&req4).unwrap()).unwrap()
625+
);
626+
}
627+
546628
#[test]
547629
fn test_signature_request() {
548630
let req1 = SignatureRequestBuilder::new("testmessage".into())

0 commit comments

Comments
 (0)