Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export default withMermaid(defineConfig({
collapsed: false,
items: [
{ text: 'pg-sveltekit', link: '/repos/pg-sveltekit' },
{ text: 'pg-node', link: '/repos/pg-node' },
{ text: 'pg-dotnet', link: '/repos/pg-dotnet' },
{ text: 'pg-manual', link: '/repos/pg-manual' },
],
Expand Down
1 change: 1 addition & 0 deletions docs/repos/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ From the [postguard-examples](https://github.com/encryption4all/postguard-exampl
| Project | Language | Description |
|---|---|---|
| [pg-sveltekit](/repos/pg-sveltekit) | TypeScript | SvelteKit web app using `@e4a/pg-js` |
| [pg-node](/repos/pg-node) | JavaScript | Node.js CLI using `@e4a/pg-js` from a server runtime |
| [pg-dotnet](/repos/pg-dotnet) | C# | .NET console app using `E4A.PostGuard` |
| [pg-manual](/repos/pg-manual) | JavaScript | Browser example using `@e4a/pg-wasm` directly (no SDK) |

Expand Down
2 changes: 1 addition & 1 deletion docs/repos/pg-dotnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ It shows two patterns:

## Prerequisites

- .NET 8.0+ SDK
- .NET 10.0+ SDK
- Rust toolchain via [rustup](https://rustup.rs/) (for building the native crypto library)
- A PostGuard API key

Expand Down
79 changes: 79 additions & 0 deletions docs/repos/pg-node.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# pg-node

[GitHub](https://github.com/encryption4all/postguard-examples/tree/main/pg-node) · JavaScript · Node.js Example

A plain Node.js CLI example showing how to use [`@e4a/pg-js`](/repos/postguard-js) from a server runtime. Part of the [postguard-examples](https://github.com/encryption4all/postguard-examples) repository.

Mirrors the [pg-sveltekit](/repos/pg-sveltekit) "Informatierijk notificeren" flow (citizen exact-email recipient + organisation email-domain recipient) as a CLI script.

It shows two modes:

1. **Encrypt and Send**: Encrypts the input files for both recipients, uploads to Cryptify, and asks Cryptify to email each recipient a download link.
2. **Encrypt and Upload**: Same upload, but silent. Cryptify returns a UUID you can distribute through some other channel.

## Prerequisites

- **Node.js 22+**, matching the example's `engines.node`. The SDK itself supports Node 20.3+, Bun, and Deno (see [postguard-js > Server-side usage](/repos/postguard-js#server-side-usage)).
- A PostGuard for Business API key.

## Setup

```bash
cd pg-node
npm install
cp .env.example .env
# edit .env: set at minimum PG_API_KEY
```

Run one of the two modes:

```bash
npm run send # encrypt + upload + ask Cryptify to send mails
npm run upload # encrypt + upload silently, no mails
```

The script prints the resulting `uuid` and the corresponding `/download?uuid=...` URL.

## Configuration

| Variable | Description | Default |
| ----------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `PG_API_KEY` | PostGuard for Business API key (`PG-...`) | *(required)* |
| `PG_PKG_URL` | PostGuard PKG server URL | `https://pkg.staging.postguard.eu` |
| `PG_CRYPTIFY_URL` | Cryptify file-sharing URL | `https://storage.staging.postguard.eu` |
| `PG_DOWNLOAD_URL` | PostGuard website used in `/download` URLs | `https://staging.postguard.eu` on staging Cryptify, else `https://postguard.eu` |
| `PG_CITIZEN_EMAIL` | Citizen recipient (exact email match) | `citizen@example.com` |
| `PG_ORGANISATION_EMAIL` | Organisation recipient (matches by domain) | `noreply@example.org` |
| `PG_MESSAGE` | Optional unencrypted body for Cryptify's notify mail | *(empty)* |
| `PG_INPUT_FILES` | Comma-separated file paths to encrypt | two in-memory demo files |

The default `PG_CRYPTIFY_URL` is the staging deployment. Staging Cryptify does not actually deliver notification emails, so `npm run send` succeeds without spamming real inboxes while you integrate. The upload still returns a real UUID and the download URL is usable.

## How it maps to the SDK

The encryption code lives in `src/encryption.mjs`. The send mode passes a `notify` object to opt into Cryptify-sent emails:

```js
const sealed = pg.encrypt({
files,
recipients: [
pg.recipient.email(citizen.email),
pg.recipient.emailDomain(organisation.email),
],
sign: pg.sign.apiKey(apiKey),
onProgress,
signal,
});

const result = await sealed.upload({
notify: {
recipients: true,
message: message || undefined,
language: 'EN',
},
});
```

<small>[Source: encryption.mjs#L13-L40](https://github.com/encryption4all/postguard-examples/blob/0fb7789560595d29d28fcf4222e67dc1ab887c2e/pg-node/src/encryption.mjs#L13-L40)</small>

The upload mode calls `sealed.upload()` with no options. The upload is silent by default; pass `notify` only when you want Cryptify to send the recipient mail. See [JS SDK > Notify options](/sdk/js-encryption#notify-options) for the full shape.
10 changes: 9 additions & 1 deletion docs/repos/postguard-dotnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ var result = await sealed.UploadAsync(new UploadOptions
byte[] bytes = await sealed.ToBytesAsync();
```

### Client version header

The SDK sends an `X-POSTGUARD-CLIENT-VERSION` header on every PKG and Cryptify request so the servers can attribute traffic by SDK and version. The value has four comma-separated parts: `dotnet,<framework>,pg-dotnet,<version>`, where `<framework>` comes from `RuntimeInformation.FrameworkDescription` and `<version>` from the assembly version.

The header is injected once on the SDK-owned `HttpClient`. A caller-supplied header (any casing) wins. If you bring your own `HttpClient`, the SDK does not mutate it, so you own its headers in that case.

Source: [encryption4all/postguard-dotnet#33](https://github.com/encryption4all/postguard-dotnet/pull/33).

## Architecture

```
Expand All @@ -71,7 +79,7 @@ The SDK calls into the Rust `pg-ffi` native library for all cryptographic operat

### Prerequisites

- .NET 8.0+ SDK
- .NET 10.0+ SDK
- Rust toolchain (for building the native library)

### Build the native library
Expand Down
18 changes: 17 additions & 1 deletion docs/repos/postguard-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ const pg = new PostGuard({
});
```

### Client version header

The SDK stamps an `X-POSTGUARD-CLIENT-VERSION` header onto every PKG and Cryptify request so the servers can attribute traffic by SDK and version. The value has four comma-separated parts: `host,host_version,pg-js,<version>`, where `host` is the detected runtime (`browser`, `node`, `bun`, or `deno`).

You do not need to set it. If you pass your own `X-POSTGUARD-CLIENT-VERSION` in `headers` (any casing), it wins. Embedding hosts use this to report their own identity, for example the Outlook add-in reports `pg4ol`.

Source: [encryption4all/postguard-js#90](https://github.com/encryption4all/postguard-js/pull/90).

## Architecture

The SDK uses a lazy builder pattern. `pg.encrypt()` and `pg.open()` return builder objects that capture parameters but do no work. The actual operation runs when you call a terminal method.
Expand Down Expand Up @@ -262,11 +270,19 @@ try {
}
```

## Server-side usage

The SDK runs on browsers, Node.js (20.3+), Bun, and Deno. The lower bound is set by `AbortSignal.any`, listed in `engines.node` of the package.

Encrypt and upload calls work identically across all four runtimes. Decryption with `pg.sign.yivi(...)` and `opened.decrypt({ element })` is browser-only — both render the Yivi QR widget into a DOM element. Calling `pg.sign.yivi(...)` from a server runtime throws `YiviSessionError` upfront before any session starts; pick `pg.sign.apiKey(...)` or `pg.sign.session(...)` instead.

See the [pg-node example](/repos/pg-node) for a runnable Node.js script using `pg.sign.apiKey`.

## Development

### Prerequisites

- Node.js 20+
- Node.js 20.3+ (or Bun / Deno)

### Building

Expand Down
5 changes: 3 additions & 2 deletions docs/repos/postguard-outlook-addon.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ The add-in runs inside Outlook's web add-in framework. It uses the Office JavaSc
The Outlook add-in uses Office JS APIs instead of WebExtension APIs:

- Manifest: XML-based (`manifest.xml`) instead of `manifest.json`.
- Taskpane: read-mode decryption UI (`src/taskpane/read-view.ts`) and compose-mode policy editor (`src/taskpane/compose-view.ts`, `src/taskpane/policy-editor.ts`). The taskpane shell (`src/taskpane/taskpane.ts`) routes between views.
- Taskpane: read-mode decryption UI (`src/taskpane/read-view.ts`) and compose-mode policy editor (`src/taskpane/compose-view.ts`, `src/taskpane/policy-editor.ts`). The taskpane shell (`src/taskpane/taskpane.ts`) routes between views. After a successful decrypt, the read view opens the decrypted message in the read dialog (below); it only renders the message inline in the taskpane as a fallback when the dialog cannot open.
- Yivi dialog: a separate page (`src/yivi-dialog/yivi-dialog.{ts,html}`) hosted at `yivi-dialog.html`. It runs pg-js plus the Yivi QR widget in its own WebView2 window so encryption can happen during the Send pipeline, where the taskpane is not available.
- Read dialog: a separate page (`src/read-dialog/read-dialog.{ts,html}`, manifest entry `ReadDialog.Url`) that shows the decrypted email in a larger popup window with a normal email layout (header fields, badges, body, attachments). The taskpane opens it with `displayDialogAsync` after decryption and passes the already-decrypted content in memory over the same `messageChild` / `messageParent` chunking protocol as the Yivi dialog — nothing is written to disk. The body is rendered through the shared `wrapHtml()` helper into the same sandboxed (`allow-same-origin`, no `allow-scripts`) iframe the taskpane uses. The taskpane keeps the plaintext on in-memory state, so a "Show decrypted message" button can re-open the window without a second Yivi/decrypt round-trip.
- Launchevent runtime: `src/launchevent/launchevent.ts` registers two events. `OnNewMessageCompose` fires when a new compose, reply, or forward opens — it seeds the per-draft `x-pg-encrypt-on-send` header from the mailbox-wide default and paints the persistent in-message banner. `OnMessageSend` reads only that header to decide whether to open the Yivi dialog with `displayDialogAsync`, write the encrypted result back into the outgoing item, and release Send. On `Office.context.platform === Office.PlatformType.Mac` the send handler exits early with a Smart Alert pointing the user at the taskpane "Encrypt & Send" button; `displayDialogAsync` from a launchevent runtime is broken on Outlook for Mac native (OfficeDev/office-js #3138, #3085, #5681).
- Settings view: `src/taskpane/settings-view.ts` (taskpane gear icon, top-right). Exposes the mailbox-wide encryption default, the optimistic-dialog opt-in, and Yivi sign-attribute prefills. All values are written to `roamingSettings` so the launchevent runtime can read them too.
- Shared helpers under `src/lib/`: `office-helpers.ts` (Office.js wrappers including the notification banner helpers), `settings.ts` (typed roaming-settings keys shared by taskpane and launchevent), `mime.ts` (MIME assembly and parsing), `graph-client.ts` (Graph API for fetching the full sent item), `pkg-client.ts` (PKG endpoints and host config), `auth.ts` (PKG bearer JWT exchange), `i18n.ts`, `encoding.ts`, `attributes.ts`, `storage.ts`, `types.ts`, and `dialog-chunk.ts` (chunked `messageChild` / `messageParent` protocol).
Expand Down Expand Up @@ -108,7 +109,7 @@ The dispatch loop drives the message protocol:

<small>[Source: launchevent.ts#L340-L356](https://github.com/encryption4all/postguard-outlook-addon/blob/2fcc56ec4fc7ec34bb557a4d5de2b3d317d636fa/src/launchevent/launchevent.ts#L340-L356)</small>

The taskpane Yivi flow (compose-mode policy signing, read-mode decryption) is different: there the Yivi QR widget runs inline in the taskpane DOM at `#yivi-web-form` rather than in a popup, because the taskpane is already a long-lived web context.
The taskpane Yivi flow (compose-mode policy signing, read-mode decryption) is different: there the Yivi QR widget runs inline in the taskpane DOM at `#yivi-web-form` rather than in a popup, because the taskpane is already a long-lived web context. In read mode, once that inline Yivi step and the decrypt finish, the plaintext message itself is handed off to the read dialog (above) rather than shown in the taskpane.

The pg-js SDK inlines its WASM as base64 at build time, so no separate WASM loader is needed.

Expand Down
5 changes: 4 additions & 1 deletion docs/sdk/dotnet-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,17 @@ Thrown when an HTTP request to the PKG or Cryptify fails.
|---|---|---|
| `StatusCode` | `int` | HTTP status code |
| `Body` | `string` | Response body |
| `Url` | `string` | Request URL that failed (PKG or Cryptify endpoint) |

```csharp
catch (NetworkException ex)
{
Console.WriteLine($"HTTP {ex.StatusCode}: {ex.Body}");
Console.WriteLine($"HTTP {ex.StatusCode} at {ex.Url}: {ex.Body}");
}
```

The exception message follows the same format: `HTTP {status} at {url}: {body}` (e.g. `HTTP 401 at https://pkg.postguard.eu/v2/irma/sign/key: Unauthorized`). The `Url` property lets callers wrapping this SDK distinguish a PKG failure from a Cryptify failure (and which Cryptify phase) without parsing the message.

## `SealException`

Thrown when the native cryptographic library (`libpg_ffi`) fails during encryption. This typically indicates a problem with the input data or the native library itself.
4 changes: 4 additions & 0 deletions docs/sdk/js-encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ The upload is silent by default. Both recipient and sender mails are opt-in. Pas
| `message` | `string` | `undefined` | Optional unencrypted text included in any mail sent |
| `language` | `'EN' \| 'NL'` | `'EN'` | Notification email template language |

The SDK validates the `notify` shape and throws `TypeError` for common misuse like `{ notify: true }`, a top-level `recipients`, or non-boolean values such as `{ recipients: 'yes' }`. Catch this in tests rather than at runtime.

If `notify` is omitted on the first `sealed.upload()` for a given `PostGuard` instance, the SDK logs a one-time `console.info` reminding you that the upload is silent and how to opt in. Pass `{ recipients: false }` to acknowledge the silent intent and suppress the notice — the validator counts both as explicit shapes.

## Encrypt raw data

For email addons, use `data` instead of `files`. The Thunderbird addon's crypto popup encrypts the full MIME message (body + attachments) as raw bytes, then wraps it in an email envelope:
Expand Down
1 change: 1 addition & 0 deletions docs/sdk/js-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ Thrown when:
- The user declines disclosure in the Yivi app
- The Yivi session times out
- The Yivi session is aborted for any other non-success reason
- `pg.sign.yivi(...)` is called from a non-browser runtime (Node, Bun, Deno). The QR widget needs a DOM, so the call fails fast before any session starts. Use `pg.sign.apiKey(...)` or `pg.sign.session(...)` from a server runtime.

Affects both `pg.encrypt(...).upload()` and `pg.encrypt(...).toBytes()` when `pg.sign.yivi(...)` is used, and `pg.open(...).decrypt(...)` when a Yivi session backs the decryption.

Expand Down
Loading