Skip to content

feat(api): accept Yivi condiscon in IrmaAuthRequest#198

Merged
rubenhensen merged 1 commit into
mainfrom
feat/condiscon-auth-request
Jun 2, 2026
Merged

feat(api): accept Yivi condiscon in IrmaAuthRequest#198
rubenhensen merged 1 commit into
mainfrom
feat/condiscon-auth-request

Conversation

@rubenhensen
Copy link
Copy Markdown
Contributor

Summary

  • pg-core::api: add untagged ConItem enum (Single | Discon); widen IrmaAuthRequest.con to Vec<ConItem>.
  • pg-pkg::handlers::start: extract con_item_to_discon; map Single as today, Discon straight through to the Yivi DisclosureRequest.
  • pg-cli: callers wrap into ConItem::Single (no behaviour change).

Backwards compatible at the HTTP boundary — legacy {"con":[{t,v?,optional?}, ...]} bodies still parse, because ConItem is #[serde(untagged)].

Why

postguard-website needs to accept a sender name from one of four Yivi credentials — gemeente personalData.fullname or firstName+lastName from pbdf.pbdf.passport / idcard / drivinglicence. The current flat con shape can only express a conjunction with per-attribute optional flags; it cannot express OR across credentials. The PostGuard hand-off doc §2.4 already treats Yivi condiscon as the canonical input that Algorithm 3 normalises into a sorted conjunction — this PR just exposes that shape at the HTTP boundary. No protocol-layer changes (header, wire format, IBE/IBS identities).

Test plan

  • cargo test -p pg-core --features test — 43/43.
  • cargo test -p pg-pkg — 34/34 (4 new tests on con_item_to_discon + 30 prior).
  • Manual smoke against a running dev compose stack: POST a body with one Single and one nested Discon entry to /v2/request/start; check the disclosure request: log line (start.rs:77) shows the expected nested discon.

Companion PRs

  • postguard-js — exposes the discon shape through pg.sign.yivi({ attributes }).
  • cryptify — extends sender_display to render firstName + lastName from the three ID credentials.
  • postguard-website — uses the new condiscon to make the name requirement satisfiable from any of the four credentials (supersedes #239 in that repo).

Add `ConItem` to `pg-core::api`, an `#[serde(untagged)]` enum whose two
variants — `Single(DisclosureAttribute)` and `Discon(Vec<Vec<...>>)` —
let a top-level `con` entry be either one attribute (legacy) or a
disjunction-of-conjunctions (new). `IrmaAuthRequest.con` widens to
`Vec<ConItem>`. The legacy flat `{"con":[{t,v?,optional?}, ...]}` JSON
body still parses, because untagged enums fall through to the matching
variant.

In `pg-pkg::handlers::start`, the request → Yivi `DisclosureRequest`
mapping moves into `con_item_to_discon`. `Single` keeps today's
behaviour (`[[ar]]` or `[[],[ar]]` when `optional`); `Discon` passes
each inner conjunction through verbatim. The auth middleware already
flattens whatever the user disclosed, so verification is unchanged.

Motivation: postguard-website needs to accept a name from one of four
credentials (gemeente fullname OR firstName+lastName from passport /
idcard / drivinglicence). Expressing that requires a Yivi
disjunction-of-conjunctions, which the previous flat shape could not
encode. The condiscon-to-conjunction normalisation already happens
inside `Derive` (Algorithm 3 in the hand-off doc), so this only widens
the HTTP boundary, not the protocol.

Backwards compatible: legacy callers (pg-cli, postguard-js < this PR)
keep working without changes; new callers opt in to discons.

Tests:
- `pg-core/src/api.rs`: round-trip a JSON body containing a discon
  entry; round-trip a legacy flat body. Both pass.
- `pg-pkg/src/handlers/start.rs`: unit tests on
  `con_item_to_discon` for Single (required + optional) and Discon
  (basic + with empty alternative).

`cargo test -p pg-core --features test`: 43/43.
`cargo test -p pg-pkg`: 34/34 (4 new + 30 prior).
@dobby-coder
Copy link
Copy Markdown
Contributor

dobby-coder Bot commented Jun 1, 2026

Dobby has received the request! Routing to the right specialist now...

@rubenhensen
Copy link
Copy Markdown
Contributor Author

/dobby review

@dobby-coder
Copy link
Copy Markdown
Contributor

dobby-coder Bot commented Jun 2, 2026

Dobby has received the request! Routing to the right specialist now...

Copy link
Copy Markdown
Contributor

@dobby-coder dobby-coder Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review

Widens IrmaAuthRequest.con to Vec<ConItem> (Single | Discon via #[serde(untagged)]). Legacy flat-con clients still parse because the two variant shapes are disjoint, mapping logic in con_item_to_discon is faithful, and tests cover both parse paths plus all four mapping branches. Two minor inline notes on edge cases worth documenting or rejecting.

}
ConItem::Discon(disjuncts) => disjuncts
.iter()
.map(|conj| conj.iter().map(attr_to_request).collect())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Code review] Inside the Discon arm, the per-attribute optional: true flag is silently dropped — only Single honors it (via the empty-alternative prepend). A caller setting optional: true on a DisclosureAttribute nested in a Discon will get required-disclosure behavior with no warning. Document that discon-level optionality must use an empty inner conjunction, or reject optional: true here.

Comment thread pg-core/src/api.rs
/// A single attribute, optionally marked `optional: true`.
Single(DisclosureAttribute),
/// A disjunction of conjunctions of attributes.
Discon(Vec<Vec<DisclosureAttribute>>),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Code review] ConItem::Discon(vec![]) (empty outer disjunction) deserializes successfully and is forwarded to Yivi as an empty discon, which is semantically unsatisfiable. No test covers this; consider rejecting it at parse or mapping time.

rubenhensen added a commit to encryption4all/postguard-website that referenced this pull request Jun 2, 2026
…crypt reactivity (#240)

* feat(filesharing): accept signer name from passport/idcard/drivinglicence

Replace the previous (PR #239) mandatory pbdf.gemeente.personalData.fullname
disclosure with a Yivi disjunction-of-conjunctions: the signer must
disclose a name, but they may satisfy that from any one of four
credentials -- gemeente fullname, OR firstName+lastName from
pbdf.pbdf.{passport,idcard,drivinglicence}.

This addresses dobby's review of #239: requiring only the gemeente
credential silently locked out everyone without a Dutch municipality
attestation. The disjunction is mandatory -- disclosure refuses to
complete unless at least one option is satisfied. Optional
mobilenumber and dateofbirth remain unchanged.

The disjunction lives in a new signAttributes.ts module exporting a
typed AttrConItem[] consumed by SendButton.svelte. Splitting it out
keeps the component file focused and the attribute list reviewable
in isolation.

Locale copy updates flagged by dobby on en.json/nl.json:
- emailSenderSubHeading describes the four-credential rule.
- yiviTip no longer frames the name as optional.

The $derived.by + (!canEncrypt) call-site fixes from #239 are preserved.

Depends on encryption4all/postguard#198 (PKG), postguard-js#78
(sign.yivi attrs), and cryptify#170 (render firstName+lastName).
Supersedes #239 in this repo.

npm run check: 0 errors, 0 warnings.

* feat(filesharing): only require email on sign step; name is optional

Remove the mandatory name disjunction from SIGN_ATTRIBUTES so senders
only need to prove their email address (the pre-#239 UX). The condiscon
infrastructure in postguard/pg-js remains available for future use.
Update locale strings to no longer mention the name requirement.

* fix: remove AttrConItem import (not yet in published pg-js)

* feat(filesharing): optional name disjunction via pg-js 1.11.0 condiscon

Bump @e4a/pg-js to 1.11.0 which adds AttrDiscon support. Use it to
request the sender's name optionally from any of four credentials
(gemeente fullname, passport, ID card, or driving licence). The leading
empty alternative keeps the group skippable for senders without any of
these credentials.
@rubenhensen rubenhensen merged commit cc47b60 into main Jun 2, 2026
21 checks passed
@rubenhensen rubenhensen deleted the feat/condiscon-auth-request branch June 2, 2026 08:28
@github-actions github-actions Bot mentioned this pull request Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant