From c377cc1e6e700ad8f526c8664a7eb371d7b29f4f Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Tue, 2 Jun 2026 09:49:23 +0200 Subject: [PATCH 1/2] docs(sdk): document attribute disjunctions for Yivi sign step --- docs/sdk/js-auth-methods.md | 83 ++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/docs/sdk/js-auth-methods.md b/docs/sdk/js-auth-methods.md index d31c480..d1f4e2a 100644 --- a/docs/sdk/js-auth-methods.md +++ b/docs/sdk/js-auth-methods.md @@ -75,9 +75,90 @@ When `includeSender` is `true`, the sender's identity is added to the encryption |-----------|------|----------|-------------| | `element` | `string` | Yes | CSS selector for the QR code container | | `senderEmail` | `string` | No | The sender's email address to prove | -| `attributes` | `Array<{ t, v?, optional? }>` | No | Extra attributes to request (e.g. name, phone). Email is always included automatically. | +| `attributes` | `AttrConItem[]` | No | Extra attributes to request. Each entry is either a single attribute (`AttrReq`) or a disjunction (`AttrDiscon`). Email is always included automatically. | | `includeSender` | `boolean` | No | Also encrypt for the sender so they can decrypt their own message (default: `false`) | +### Attribute disjunctions + +By default each entry in `attributes` is a single attribute object (`AttrReq`). When you need to accept the same piece of information from multiple credential types — for example, a name that can come from a municipality credential, a passport, an ID card, or a driving licence — you can pass an **`AttrDiscon`** instead: a nested array where each inner array is one acceptable conjunction of attributes (an AND), and the outer array is the list of alternatives (an OR). + +```ts +type AttrReq = { t: string; v?: string; optional?: boolean } +type AttrDiscon = AttrReq[][] // OR of ANDs +type AttrConItem = AttrReq | AttrDiscon +``` + +Narrow the union at runtime with `Array.isArray(item)`: `true` → `AttrDiscon`, `false` → `AttrReq`. + +**Making a disjunction optional** follows the Yivi convention: add an empty array `[]` as the first alternative. Yivi treats an empty conjunction as always-satisfiable, making the whole discon skippable. + +#### Example: optional name from any government ID + +This requests the sender's name, but accepts any of four credential types and makes the whole group optional. If the sender does not have any of the listed credentials loaded in Yivi, they can skip the disclosure entirely. + +```ts +const sign = pg.sign.yivi({ + element: '#crypt-irma-qr', + attributes: [ + // Optional name — sender can satisfy with any one of the four alternatives, + // or skip entirely (the empty [] alternative is always satisfiable). + [ + [], // skip (optional) + [{ t: 'pbdf.gemeente.personalData.fullname' }], // OR: municipality full name + [{ t: 'pbdf.pbdf.passport.firstName' }, + { t: 'pbdf.pbdf.passport.lastName' }], // OR: passport first + last + [{ t: 'pbdf.pbdf.idcard.firstName' }, + { t: 'pbdf.pbdf.idcard.lastName' }], // OR: ID card first + last + [{ t: 'pbdf.pbdf.drivinglicence.firstName' }, + { t: 'pbdf.pbdf.drivinglicence.lastName' }], // OR: driving licence first + last + ], + // Other optional attributes can still be flat AttrReq entries. + { t: 'pbdf.sidn-pbdf.mobilenumber.mobilenumber', optional: true }, + { t: 'pbdf.gemeente.personalData.dateofbirth', optional: true }, + ], + includeSender: true, +}); +``` + +The Yivi app presents the name group as a single step. The user picks whichever credential they have loaded; if they have none, they skip. The remainder of the `attributes` array (phone, date of birth) is presented as separate optional steps. + +#### Mandatory disjunction + +Remove the empty `[]` alternative to make the disclosure required. The Yivi session will not complete until the sender proves their name from one of the listed credentials: + +```ts +[ + [{ t: 'pbdf.gemeente.personalData.fullname' }], + [{ t: 'pbdf.pbdf.passport.firstName' }, { t: 'pbdf.pbdf.passport.lastName' }], + [{ t: 'pbdf.pbdf.idcard.firstName' }, { t: 'pbdf.pbdf.idcard.lastName' }], + [{ t: 'pbdf.pbdf.drivinglicence.firstName' }, { t: 'pbdf.pbdf.drivinglicence.lastName' }], +] +``` + +::: info PKG requirement +Attribute disjunctions require `@e4a/pg-js` ≥ 1.11.0 and a PKG running `postguard` with condiscon support. Earlier PKG versions only accept a flat `con` array. +::: + +#### How it maps to the PKG wire format + +The `attributes` array is forwarded verbatim as the `con` field in the `POST /v2/request/start` body. Single `AttrReq` objects serialise as `{"t":"..."}` objects; `AttrDiscon` entries serialise as nested arrays — exactly the shape the PKG's `ConItem` untagged enum expects: + +```json +{ + "con": [ + { "t": "pbdf.sidn-pbdf.email.email" }, + [ + [], + [{ "t": "pbdf.gemeente.personalData.fullname" }], + [{ "t": "pbdf.pbdf.passport.firstName" }, { "t": "pbdf.pbdf.passport.lastName" }], + [{ "t": "pbdf.pbdf.idcard.firstName" }, { "t": "pbdf.pbdf.idcard.lastName" }], + [{ "t": "pbdf.pbdf.drivinglicence.firstName" }, { "t": "pbdf.pbdf.drivinglicence.lastName" }] + ], + { "t": "pbdf.sidn-pbdf.mobilenumber.mobilenumber", "optional": true } + ] +} +``` + ## Session Callback The most flexible method. You provide a callback function that receives a session request and must return a JWT string. This lets you handle the Yivi session yourself: in a popup window, a separate process, or any custom flow. From ecab3b2f709b744cd70f76765bd7af46deb5ca8e Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Tue, 2 Jun 2026 10:15:51 +0200 Subject: [PATCH 2/2] fix(docs): address dobby style and consistency comments --- docs/sdk/js-auth-methods.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/sdk/js-auth-methods.md b/docs/sdk/js-auth-methods.md index d1f4e2a..166cd43 100644 --- a/docs/sdk/js-auth-methods.md +++ b/docs/sdk/js-auth-methods.md @@ -80,7 +80,7 @@ When `includeSender` is `true`, the sender's identity is added to the encryption ### Attribute disjunctions -By default each entry in `attributes` is a single attribute object (`AttrReq`). When you need to accept the same piece of information from multiple credential types — for example, a name that can come from a municipality credential, a passport, an ID card, or a driving licence — you can pass an **`AttrDiscon`** instead: a nested array where each inner array is one acceptable conjunction of attributes (an AND), and the outer array is the list of alternatives (an OR). +By default each entry in `attributes` is a single attribute object (`AttrReq`). When you need to accept the same piece of information from multiple credential types (for example, a name that can come from a municipality credential, a passport, an ID card, or a driving licence), you can pass an `AttrDiscon` instead: a nested array where each inner array is one acceptable conjunction of attributes (an AND), and the outer array is the list of alternatives (an OR). ```ts type AttrReq = { t: string; v?: string; optional?: boolean } @@ -90,7 +90,9 @@ type AttrConItem = AttrReq | AttrDiscon Narrow the union at runtime with `Array.isArray(item)`: `true` → `AttrDiscon`, `false` → `AttrReq`. -**Making a disjunction optional** follows the Yivi convention: add an empty array `[]` as the first alternative. Yivi treats an empty conjunction as always-satisfiable, making the whole discon skippable. +#### Optional disjunctions + +To make a disjunction optional, add an empty array `[]` as the first alternative. Yivi treats an empty conjunction as always-satisfiable, making the whole discon skippable. #### Example: optional name from any government ID @@ -141,7 +143,7 @@ Attribute disjunctions require `@e4a/pg-js` ≥ 1.11.0 and a PKG running `postgu #### How it maps to the PKG wire format -The `attributes` array is forwarded verbatim as the `con` field in the `POST /v2/request/start` body. Single `AttrReq` objects serialise as `{"t":"..."}` objects; `AttrDiscon` entries serialise as nested arrays — exactly the shape the PKG's `ConItem` untagged enum expects: +The `attributes` array is forwarded verbatim as the `con` field in the `POST /v2/request/start` body. Single `AttrReq` objects serialise as `{"t":"..."}` objects; `AttrDiscon` entries serialise as nested arrays, exactly the shape the PKG's `ConItem` untagged enum expects: ```json { @@ -154,7 +156,8 @@ The `attributes` array is forwarded verbatim as the `con` field in the `POST /v2 [{ "t": "pbdf.pbdf.idcard.firstName" }, { "t": "pbdf.pbdf.idcard.lastName" }], [{ "t": "pbdf.pbdf.drivinglicence.firstName" }, { "t": "pbdf.pbdf.drivinglicence.lastName" }] ], - { "t": "pbdf.sidn-pbdf.mobilenumber.mobilenumber", "optional": true } + { "t": "pbdf.sidn-pbdf.mobilenumber.mobilenumber", "optional": true }, + { "t": "pbdf.gemeente.personalData.dateofbirth", "optional": true } ] } ```