Skip to content
Merged
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

Example applications demonstrating PostGuard integration. Contains reference implementations for developers building on PostGuard. Code snippets in docs.postguard.eu come from this repo.

There are three sub-projects:
There are four sub-projects:

- `pg-sveltekit/`: SvelteKit web app using the `@e4a/pg-js` SDK.
- `pg-node/`: Node.js CLI using the `@e4a/pg-js` SDK from a server runtime.
- `pg-dotnet/`: .NET console app using the `postguard-dotnet` SDK.
- `pg-manual/`: manual encryption/decryption using the `@e4a/pg-wasm` library directly.

Expand All @@ -24,6 +25,19 @@ npm run dev

See [pg-sveltekit/README.md](pg-sveltekit/README.md) for environment variables and build instructions.

### Node.js example

Requires Node.js 20.6+ and a PostGuard API key.
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.

Stale after the engines bump. pg-node/package.json now requires node >= 22, but the root README still advertises 20.6+. Two consistent options: bump this to Node.js 22+ to match the sub-project's engines, or leave it at 20.6 with a sentence clarifying that pg-node specifically needs 22+. Recommend the former — simpler and matches pg-node/README.md:16.


```bash
cd pg-node
npm install
cp .env.example .env # set at minimum PG_API_KEY
npm run send # encrypt + upload + notify recipients
```

See [pg-node/README.md](pg-node/README.md) for the full configuration and modes.

### .NET example

Requires the .NET 10.0+ SDK and a PostGuard API key. The example uses the `E4A.PostGuard` NuGet package, so no Rust toolchain or local build of the native library is needed.
Expand Down
23 changes: 23 additions & 0 deletions pg-node/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# PostGuard for Business API key (PG-…). Get one from
# business.staging.postguard.eu (staging) or business.postguard.eu (production).
PG_API_KEY=

# Endpoints. Defaults are staging.
PG_PKG_URL=https://pkg.staging.postguard.eu
PG_CRYPTIFY_URL=https://storage.staging.postguard.eu

# Optional: PostGuard website used in the printed download link.
# Defaults to https://staging.postguard.eu on staging Cryptify,
# else https://postguard.eu.
# PG_DOWNLOAD_URL=https://staging.postguard.eu

# Recipients. Citizen is an exact email match; Organisation matches by email domain.
PG_CITIZEN_EMAIL=citizen@example.com
PG_ORGANISATION_EMAIL=noreply@example.org

# Optional unencrypted message body included in Cryptify's notification mail.
PG_MESSAGE=

# Optional comma-separated list of file paths to encrypt. If empty the
# example uses two in-memory demo files (report.txt + notes.txt).
# PG_INPUT_FILES=./files/report.pdf,./files/notes.txt
2 changes: 2 additions & 0 deletions pg-node/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.env
75 changes: 75 additions & 0 deletions pg-node/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# pg-node: PostGuard Node.js example

Node.js example demonstrating how to use the [@e4a/pg-js](https://www.npmjs.com/package/@e4a/pg-js) SDK from a server runtime. Mirrors the [pg-sveltekit](../pg-sveltekit) example's "Informatierijk notificeren" flow (citizen + organisation recipients) but as a CLI script — drop-in starting point for backend integrations.

## What it does

Two modes, selected by the script flag:

1. **Send** (`npm run send`) — encrypts the input files for a citizen (exact email) and an organisation (email domain), uploads to Cryptify, and asks Cryptify to email each recipient a download link.
2. **Upload-only** (`npm run upload`) — same encryption + upload, but silent. Cryptify returns a UUID you can distribute through some other channel.

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.

[Rule: writing-rules] Minor: the **Send** (...) — encrypts ... / **Upload-only** (...) — same encryption ... pattern is the "Inline header + colon/dash + sentence" list (writing-rules #48) plus the em-dash-overuse tell (#40). Same idea would read cleaner as plain sentences or a small table. Not a blocker — calling it out so it doesn't get copy-pasted into future example READMEs.

Files come from `PG_INPUT_FILES` (comma-separated paths) or two in-memory demo files if that is unset.

## Prerequisites

- **Node.js 22+** — matches `@e4a/pg-js`'s `engines.node`. The SDK also supports Bun and Deno; the same encryption code in `src/encryption.mjs` works there too.
- 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
```

The `package.json` depends on the published [`@e4a/pg-js`](https://www.npmjs.com/package/@e4a/pg-js) (currently `^1.9.0`).

## Run

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.

[Rule: writing-rules] Em-dash count is on the high side for a short README (~10 across 75 lines). A few replace cleanly with a period or parentheses (e.g. Mirrors the pg-sveltekit example's flow — drop-in starting point... → just two sentences). Stylistic only.

```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.

## Staging Cryptify does not send email

The default `PG_CRYPTIFY_URL` is `storage.staging.postguard.eu` — the staging deployment. It **does not actually deliver notification emails**, so you can exercise the full upload + notify flow without spamming real inboxes while you integrate.

- The upload itself works. You get back a real UUID and the download URL is usable.
- `npm run send` succeeds, but no recipient mail is sent. Open the printed URL yourself to verify the decrypt flow end-to-end.
- Point `PG_CRYPTIFY_URL` at the production Cryptify host to exercise real email delivery.

## 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 |

## How it maps to the SDK

The work happens in [`src/encryption.mjs`](./src/encryption.mjs):

```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 { uuid } = await sealed.upload({ notify: { recipients: true, message, language: 'EN' } });
```

`notify` must be nested under an object — the SDK validates the shape and throws a clear `TypeError` if you pass `{ notify: true }` or forget to nest. See the [SDK README](https://github.com/encryption4all/postguard-js#server-side-usage-node-bun-deno) for the full server-side surface.
107 changes: 107 additions & 0 deletions pg-node/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// CLI entry point. Run with one of:
// npm run send # encrypts and asks Cryptify to mail recipients
// npm run upload # encrypts and uploads silently
// npm start # same as `npm run send`
//
// Configuration via .env or environment variables — see .env.example.

import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
import {
API_KEY,
CITIZEN_EMAIL,
ORGANISATION_EMAIL,
MESSAGE,
INPUT_FILES,
DOWNLOAD_URL,
IS_CRYPTIFY_STAGING,
CRYPTIFY_URL,
PKG_URL,
} from './src/config.mjs';
import { encryptAndSend, encryptAndUpload } from './src/encryption.mjs';

if (!API_KEY) {
console.error('Missing PG_API_KEY. Copy .env.example to .env and set it.');
process.exit(2);
}

const mode = process.argv.includes('--upload-only') ? 'upload-only' : 'send-email';

console.log(`pg-node example — mode: ${mode}`);
console.log(` PKG: ${PKG_URL}`);
console.log(` Cryptify: ${CRYPTIFY_URL}${IS_CRYPTIFY_STAGING ? ' (staging — no mails actually sent)' : ''}`);
console.log(` Citizen: ${CITIZEN_EMAIL}`);
console.log(` Organisation: ${ORGANISATION_EMAIL}`);
console.log('');

const files = await loadFiles();
console.log(`Encrypting ${files.length} file(s):`);
for (const f of files) console.log(` ${f.name} (${f.size} bytes)`);
console.log('');

const abortController = new AbortController();
process.on('SIGINT', () => {
console.log('\nCancelling…');
abortController.abort();
});

const onProgress = (pct) => {
process.stdout.write(`\r upload progress: ${pct}% `);
};

const t0 = performance.now();
const uuid =
mode === 'send-email'
? await encryptAndSend({
files,
citizen: { email: CITIZEN_EMAIL },
organisation: { email: ORGANISATION_EMAIL },
apiKey: API_KEY,
message: MESSAGE,
onProgress,
signal: abortController.signal,
})
: await encryptAndUpload({
files,
citizen: { email: CITIZEN_EMAIL },
organisation: { email: ORGANISATION_EMAIL },
apiKey: API_KEY,
onProgress,
signal: abortController.signal,
});
const t1 = performance.now();

process.stdout.write('\n');
console.log('');
console.log(`Done in ${(t1 - t0).toFixed(0)}ms`);
console.log(`UUID: ${uuid}`);
console.log(`Download: ${DOWNLOAD_URL}/download?uuid=${uuid}`);

if (mode === 'send-email' && IS_CRYPTIFY_STAGING) {
console.log('');
console.log('Note: staging Cryptify does not actually deliver mails. Open the URL above to test decrypt.');
}

/** Read each path in PG_INPUT_FILES as a File. If empty, return two demo files. */
async function loadFiles() {
if (INPUT_FILES.length === 0) {
return [
new File(
[new TextEncoder().encode('This is a sample report for PostGuard encryption testing.\n')],
'report.txt',
{ type: 'text/plain' }
),
new File(
[new TextEncoder().encode('Confidential notes — only the intended recipient can read this.\n')],
'notes.txt',
{ type: 'text/plain' }
),
];
}
return Promise.all(
INPUT_FILES.map(async (path) => {
const bytes = await readFile(path);
return new File([bytes], basename(path));
})
);
}
Loading